C# 动态CDATA:TransformToTree

C# 动态CDATA:TransformToTree,c#,wpf,system.reactive,dynamic-data,reactiveui,C#,Wpf,System.reactive,Dynamic Data,Reactiveui,我有一个应用程序,其中可以使用反斜杠(\)字符在层次结构中构造标记 比如, Country\Canada\Alberta Country\Canada\British Columbia Country\USA\California Country\USA\Texas 将成为用户界面中的一部分 Country Canada Alberta British Columbia USA California Texas

我有一个应用程序,其中可以使用反斜杠(\)字符在层次结构中构造标记

比如,

Country\Canada\Alberta
Country\Canada\British Columbia
Country\USA\California
Country\USA\Texas
将成为用户界面中的一部分

Country
    Canada
        Alberta
        British Columbia
    USA
        California
        Texas
在数据库中,它作为字符串存储,并作为
TagDto
返回给客户端。为了实现这一目标,我尝试了以下几点:

public class TagDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class TagLeaf
{
    public string Id { get; }
    public string ParentId { get; }
    public int TagId { get; }
    public string Name { get; }

    public TagLeaf(string id, string parentId, int tagId, string name)
    {
        Id = id;
        ParentId = parentId;
        TagId = tagId;
        Name = name;
    }

    // IEquatable implemented on Id property.
}

public class TagsViewModel : ReactiveObject
{
    private IDisposable TagsSubscription { get; }

    public SourceCache<TagDto, string> Tags { get } = new SourceCache<TagDto, string>(t => t.Id);

    private readonly ReadOnlyObservableCollection<TagLeafViewModel> _tagTree;
    public ReadOnlyObservableCollection<TagLeafViewModel> TagTree => _tagTree;

    public ReactiveCommand AddBelgium { get; }

    public TagsViewModel()
    {
        AddBelgium = ReactiveCommand.Create(() => 
            Tags.AddOrUpdate(new TagDto {Id = 5, Name = @"Country\Belgium"});

        // this comes from an web service normally. 
        Tags.AddOrUpdate(new[] {
            new TagDto {Id = 1, Name = @"Country\Canada\Alberta"},
            new TagDto {Id = 2, Name = @"Country\Canada\British Columbia"},
            new TagDto {Id = 3, Name = @"Country\USA\California"},
            new TagDto {Id = 4, Name = @"Country\USA\Texas"}
        });

        TagsSubscription = Tags
            .Connect()
            .TransformMany(dto => 
            {
                var names = dto.Name.Split(new[] {'\\'}, StringSplitOptions.RemoveEmptyEntries);
                var results = new TagLeaf[names.Length];
                var parentId = "";
                for (var i = 0; i < names.Length; i++)
                {
                    var name = names[i];
                    var id = $"{parentId}{name}\\";
                    results[i] = new TagLeaf(id, parentId, dto.Id, name);
                    parentId = id;
                }

                return results;
            }, leaf => leaf.Id)
            .TransformToTree(leaf => leaf.ParentId)
            .Transform(leaf => new TagLeafViewModel(leaf))
            .Sort(SortExpressionComparer<TagLeafViewModel>.Ascending(vm => vm.Name))
            .Bind(out _tagTree)
            .Subscribe();
    }
}

public class TagLeafViewModel : ReactiveObject
{
    private readonly ReadOnlyObservableCollection<TagLeafViewModel> _children;
    public ReadOnlyObservableCollection<TagLeafViewModel> Children => _children;

    private string _name;
    public string Name
    {
        get => _name;
        set => this.RaiseAndSetIfChanged(ref _name, value);
    }

    public TagLeafViewModel(Node<TagLeaf, string> node)
    {
        Name = node.Item.Name;
        ChildrenSubscription = node.Children
            .Connect()
            .Transform(n => new TagLeafViewModel(n))
            .Sort(SortExpressionComparer<TagLeafViewModel>.Ascending(vm => vm.Name))
            .Bind(out _children)
            .Subscribe();
    }
}

// TagsView.xaml
<StackPanel>
    <Button x:Name="AddBelgiumButton" Content="Add Belgium"/>
    <telerik:RadTreeView x:Name="TagTreeView">
        <telerik:RadTreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Name}"/>
            </HierarchicalDataTemplate>
        </telerik:RadTreeView.ItemTemplate>
    </telerik:RadtreeView>
</StackPanel>

// TagsView.xaml.cs constructor
public TagsView()
{
    ...
    this.WhenActivated(d => 
    {
        d(this.AddBelgiumButton.Events().Click.Select(x => Unit.Default).InvokeCommand(ViewModel, vm => vm.AddBelgium));
        d(this.OneWayBind(ViewModel, vm => vm.TagTree, v => v.TagTreeView.ItemsSource));
    });
}
公共类标记到
{
公共int Id{get;set;}
公共字符串名称{get;set;}
}
公共类标签页
{
公共字符串Id{get;}
公共字符串ParentId{get;}
public int TagId{get;}
公共字符串名称{get;}
公共标记叶(字符串id、字符串parentId、int标记id、字符串名称)
{
Id=Id;
ParentId=ParentId;
TagId=TagId;
名称=名称;
}
//在Id属性上实现了IEquatable。
}
公共类TagsViewModel:ReactiveObject
{
私有IDisposable标记订阅{get;}
publicsourcecache标记{get}=newsourcecache(t=>t.Id);
私有只读只读可观察集合_tagTree;
public ReadOnlyObservableCollection标记树=>\u标记树;
public ReactiveCommand add{get;}
公共标记viewmodel()
{
addBelgile=ReactiveCommand.Create(()=>
Tags.AddOrUpdate(新的TagDto{Id=5,Name=@“Country\belligen”});
//这通常来自web服务。
Tags.AddOrUpdate(新[]{
新标记为{Id=1,Name=@“Country\Canada\Alberta”},
新标记为{Id=2,Name=@“Country\Canada\British Columbia”},
新标记为{Id=3,Name=@“Country\USA\California”},
新标记为{Id=4,Name=@“Country\USA\Texas”}
});
TagsSubscription=标记
.Connect()
.TransformMany(dto=>
{
var Name=dto.Name.Split(新[]{'\\'},StringSplitOptions.RemoveEmptyEntries);
var results=新标记叶[names.Length];
var parentId=“”;
for(var i=0;ileaf.Id)
.TransformToTree(叶=>leaf.ParentId)
.Transform(叶=>新标记LeafViewModel(叶))
.Sort(SortExpressionComparer.Ascending(vm=>vm.Name))
.Bind(out_tagTree)
.Subscribe();
}
}
公共类TagLeafViewModel:ReactiveObject
{
私有只读只读可观察集合\u子对象;
public ReadOnlyObservableCollection子项=>\u子项;
私有字符串\u名称;
公共字符串名
{
get=>\u name;
set=>this.RaiseAndSetIfChanged(ref\u name,value);
}
公共标记LeafViewModel(节点)
{
Name=node.Item.Name;
ChildrenSubscription=node.Children
.Connect()
.Transform(n=>新的标记叶视图模型(n))
.Sort(SortExpressionComparer.Ascending(vm=>vm.Name))
.捆绑(儿童)
.Subscribe();
}
}
//TagsView.xaml
//TagsView.xaml.cs构造函数
公共TagsView()
{
...
当激活时(d=>
{
d(this.AddBelgiumButton.Events().Click.Select(x=>Unit.Default).InvokeCommand(ViewModel,vm=>vm.AddBelgium));
d(this.OneWayBind(ViewModel,vm=>vm.TagTree,v=>v.TagTreeView.ItemsSource));
});
}
这将生成一个我所期望的树,但是如果我展开
Country
并单击Add belling,而不是将此插入到树中作为Country下的新节点,它将折叠整个Country节点


添加新标记会导致两个新的
标记叶
流到
TramsformToTree
。一个用于国家,一个用于比利时,因此我理解为什么它会更新国家节点,但我不确定如何克服这一点-任何建议都将不胜感激。

我相信我已经取得了突破,但是,建议我们仍然欢迎

意识到
TransformMany
是我之前尝试的问题,我决定需要维护两个独立的缓存来实现我想要的

我现在有了一个TagService,它公开了两个缓存。每当底层
TagDto
缓存中的一个项目被更改时,我都会用这些更改手动更新
TagLeaf
缓存。在我的示例应用程序中,这将插入新节点,而不会折叠根节点

这是不完整的,我仍然需要处理删除父<代码>标签> <代码>,当他们在代码<标签叶>代码>缓存中没有子节点时,但我相信我能做到这一点,所以我认为问题已经解决了。

public class TagService : ITagService
{
    private readonly SourceCache<TagDto, int> _tagDtos = new SourceCache<TagDto, int>(t => t.Id);
    public IObservableCache<TagDto, int> TagDtos => _tagDtos;

    private readonly SourceCache<TagLeaf, string> _tagLeafs = new SourceCache<TagLeaf, string>(t => t.Id);
    public IObservableCache<TagLeaf, string> TagLeafs => _tagLeafs;

    public TagService()
    {
        _tagDtos.AddOrUpdate(new[]
        {
            new TagDto {Id = 1, Name = @"Country\Canada\Alberta"},
            new TagDto {Id = 2, Name = @"Country\Canada\British Columbia"},
            new TagDto {Id = 3, Name = @"Country\USA\California"},
            new TagDto {Id = 4, Name = @"Country\USA\Texas"}
        });

        _tagDtos
            .Connect()
            .Transform(dto =>
            {
                var names = dto.Name.Split(new[] {'\\'}, StringSplitOptions.RemoveEmptyEntries);
                var results = new TagLeaf[names.Length];

                var parentId = "";
                for (var i = 0; i < names.Length; i++)
                {
                    var name = names[i];
                    var id = $"{parentId}{name}\\";
                    results[i] = new TagLeaf(id, parentId, dto.Id, name);
                    parentId = id;
                }

                return new TagBranch(dto.Id, results);
            })
            .ForEachChange(change =>
            {
                var branch = change.Current;
                switch (change.Reason)
                {
                    case ChangeReason.Remove:
                        var lastLeaf = branch.Leaves.Last();
                        _tagLeafs.RemoveKey(lastLeaf.Id);
                        break;

                    case ChangeReason.Add:
                        foreach (var leaf in branch.Leaves)
                        {
                            if (_tagLeafs.Keys.Contains(leaf.Id))
                                continue;

                            _tagLeafs.AddOrUpdate(leaf);
                        }
                        break;
                }
            })
            .Subscribe();
    }

    public void AddOrUpdate(TagDto dto)
    {
        _tagDtos.AddOrUpdate(dto);
    }
}
公共类标记服务:ITagService
{
private readonly SourceCache _tagDtos=new SourceCache(t=>t.Id);
public IObservableCache TagDtos=>\u TagDtos;
private readonly SourceCache _tagLeafs=new SourceCache(t=>t.Id);
公共IObservableCache标记叶=>\u标记叶;
公共服务()
{
_tagDtos.AddOrUpdate(新[]
{
新标记为{Id=1,Name=@“Country\Canada\Alberta”},
新标记为{Id=2,Name=@“Country\Canada\British Columbia”},
新标记为{Id=3,Name=@“Country\USA\California”},
新标记为{Id=4,Name=@“Country\USA\Texas”}
});
_塔格多斯
.Connect()
.Transform(dto=>
{
var Name=dto.Name.Split(新[]{'\\'},StringSplitOptions.RemoveEmptyEntries);
var results=新标记叶[names.Length];
var parentId=“”;
for(var i=0;ipublic TagsViewModel(ITagService tagService)
{
    AddBelgium = ReactiveCommand.Create(() =>
        tagService.AddOrUpdate(new TagDto {Id = 5, Name = @"Country\Belgium"}));

    TagsSubscription = tagService.TagLeafs
        .Connect()
        .TransformToTree(leaf => leaf.ParentId)
        .Transform(node => new TagLeafViewModel(node))
        .Sort(SortExpressionComparer<TagLeafViewModel>.Ascending(vm => vm.Name))
        .Bind(out _tagTree)
        .Subscribe();
}