【问题标题】:Recursively Add Items to a TreeView递归地将项目添加到 TreeView
【发布时间】:2016-08-12 11:33:57
【问题描述】:

我有一个从 WinRT.XamlToolKit 导入的 TreeView,我正试图在通用应用程序中使用它。重要的是要注意,我知道有类似的问题,但有些问题因为它们早于通用应用程序而不起作用,或者它们不适应 MVVM 的使用。按照教程了解如何填充树,我遇到了这种递归方法:

private ObservableCollection<TreeViewItemModel> BuildTree(int depth, int branches)
    {
        var tree = new ObservableCollection<TreeViewItemModel>();

        if (depth <= 0) return tree;
        var depthIndices = Enumerable.Range(0, branches).Shuffle();

        for (var i = 0; i < branches; i++)
        {
            var d = depthIndices[i] % depth;
            var b = _rand.Next(branches / 2, branches);
            tree.Add(
                new TreeViewItemModel
                {
                    Branch = b,
                    Depth = d,
                    Text = "Item " + _itemId++,
                    Children = BuildTree(d, b)
                });
        }
        return tree;
    }

它被称为 TreeItems = BuildTree(5,5); 然后我将我的 TreeView ItemSource 绑定到 TreeItems 并附加到 TreeViewItemModel 的 Text 属性。

这适用于预先确定的集合,然后如方法中所见,使用随机数来计算树将如何显示。 (在什么深度,有多少分支等)

我不确定的是如何实现类似的递归函数,以使用具有不确定数量的子项的不同 ItemModel 填充树。在我的设置中,我有一个空间,它可以有子空间、设备和/或传感器。一个空间可以没有设备和传感器,但有一个或多个子空间,或者它可以没有子空间,但它可以有设备和/或传感器。在我的 Sensor 类中,它有一个 Parent Id,与它绑定到的 Space 的 Id 和它报告的 Device 相关。

所以,简而言之,我需要从空间列表中递归地填充我的ObservableCollection&lt;TreeViewSpaceModel&gt;

TreeViewSpaceModel 类:

public class TreeViewSpaceModel : NotifyObject
{
    private string _name;
    private Guid _iD;
    private string _parentName;
    private ObservableCollection<TreeViewSpaceModel> _children = new ObservableCollection<TreeViewSpaceModel>();
    private ObservableCollection<TreeViewDeviceModel> _devices = new ObservableCollection<TreeViewDeviceModel>();
    private ObservableCollection<TreeViewSensorModel> _sensors = new ObservableCollection<TreeViewSensorModel>();

    public string Name
    {
        get { return _name; }
        set { SetProperty(ref _name, value); }
    }

    public string ParentName
    {
        get { return _parentName; }
        set { SetProperty(ref _parentName, value); }
    }

    public Guid Id
    {
        get { return _iD; }
        set { SetProperty(ref _iD, value); }
    }

    public ObservableCollection<TreeViewSpaceModel> Children
    {
        get { return _children; }
        set { SetProperty(ref _children, value); }
    }

    public ObservableCollection<TreeViewDeviceModel> Devices
    {
        get { return _devices; }
        set { SetProperty(ref _devices, value); }
    }

    public ObservableCollection<TreeViewSensorModel> Sensors
    {
        get { return _sensors; }
        set { SetProperty(ref _sensors, value); }
    }
}

DeviceModel 没有子级,但它有传感器,并且 SensorModel 没有子级或设备或传感器,因为它是层次结构的末端。 空间类:

public class Space
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int SpaceTypeId { get; set; }
    public virtual Space Parent { get; set; }
    public virtual ICollection<Sensor> Sensors { get; set; }
    public virtual ICollection<Device> Devices { get; set; }
    public virtual ICollection<Space> Children { get; set; }
}

理想情况下,最终结果应该是这样的:

Space1
---- ChildSpace1
--------Device1
-------------Sensor1
-------------Sensor2 (Sensors report for Spaces, but are bound to Devices)
--------SubChildSpace1
-------------Sensor1
-------------Sensor2
--------SubChildSpace2
-------------Sensor1
------------Sensor2
--------Device2
---- ChildSpace2
Space2

如果有一种更简单的方法来代替递归方法,我很乐意接受它。在研究通用应用程序的 TreeViews 时,我确实遇到过其他人说用户应该能够执行类似 items.Node(0).Add(stuffhere) 的操作;但是,我无法弄清楚如何实现这一点,因为 ObservableCollection 没有提供 .Node 扩展。

【问题讨论】:

    标签: c# recursion treeview win-universal-app winrt-xaml-toolkit


    【解决方案1】:

    由于控件不支持虚拟化 - 它假定项目数量有限(它最适合远低于 1000 个项目),因此递归应该没问题。 TreeViewSpaceModel 的一个小问题是它有多个子模型列表。要让它与TreeView 一起使用,您需要一个子级列表,但它们可以是不同的类型。如果您想以不同的方式显示它们 - 您需要使用模板选择器。至于生成列表,如果没有编译检查,它可能是这样的:

    static ObservableCollection<TreeViewItemModel> GetSpaceTree(
        IEnumerable<Space> spaces)
    {
        return new ObservableCollection<TreeViewItemModel>(
            spaces
                .Where(space => space.Parent == null)
                .Select(space => new TreeViewSpaceModel(space))
        );
    }
    

    那么在TreeViewSpaceModel 中你就会有这样的构造函数

    TreeViewSpaceModel(Space space)
    {
        // TODO: Set name, ID etc.
        this.Children = new ObservableCollection<TreeViewItemModel>(
            space.Children.Select(childSpace => new TreeViewSpaceModel(childSpace))
            .Union(space.Devices.Select(device => new TreeViewDeviceModel(device)))
            .Union(space.Sensors.Select(sensor => new TreeViewSensorModel(sensor)))
        );
    }
    

    【讨论】:

    • 为什么是space.Parent == null
    • 我猜想找到根源?但这是您的代码,请随时更改。
    • 哦,好的。此外,在这种情况下 TreeViewItemModel 类的外观如何。我问这个的原因是因为在我当前的迭代中,我没有使用 TreeViewItemModel 类,这是我在开头展示的示例递归方法中使用的类。
    • 它可能只是一个几乎类,或者你甚至可以使用一个接口。它唯一需要的是一个 ObservableCollection&lt;TreeViewItemModel&gt; 的 Children 属性。
    • 我在这里看到的一个问题是,传感器可以是空间的子节点,但同一个传感器可以是设备的子节点。这不适合这个实现吗?
    【解决方案2】:

    我不太确定为什么我无法得到 Filip Skakun 提供的答案,但他仍然值得称赞,因为他的示例确实帮助我思考如何解决问题。我只是使用了一个类模型 -> TreeViewItemModel 并像这样构建它来构建我的 Observable Collection。

    TreeViewItemModel 类:

    public class TreeViewItemModel : NotifyObject
    {
    
        private string _name;
        private Guid _iD;
        private string _parentName;
        private Guid _parentId;
        private string _parentDeviceName;
        private ObservableCollection<TreeViewItemModel> _children = new ObservableCollection<TreeViewItemModel>();
    
    
        public string Name
        {
            get { return _name; }
            set { SetProperty(ref _name, value); }
        }
    
        public Guid ParentId
        {
            get { return _parentId; }
            set { SetProperty(ref _parentId, value); }
        }
    
        public string ParentDeviceName
        {
            get { return _parentDeviceName; }
            set { SetProperty(ref _parentDeviceName, value); }
        }
        public string ParentName
        {
            get { return _parentName; }
            set { SetProperty(ref _parentName, value); }
        }
    
        public Guid Id
        {
            get { return _iD; }
            set { SetProperty(ref _iD, value); }
        }
    
        public ObservableCollection<TreeViewItemModel> Children
        {
            get { return _children; }
            set { SetProperty(ref _children, value); }
        }
    
    
        public TreeViewItemModel(object thing)
        {
            //GetDevices();
            if (thing.GetType() == typeof (Space))
            {
                var space = (Space)thing;
                var parentName = string.Empty;
                if (space.Parent != null)
                {
                    parentName = space.Parent.Name;
                }
                Name = space.Name;
                ParentName = parentName;
                Id = space.Id;
                Children = new ObservableCollection<TreeViewItemModel>(space.Children.Select(s => new TreeViewItemModel(s)).Union(space.Devices.Select(d => new TreeViewItemModel(d)).Union(space.Sensors.Select(sensor => new TreeViewItemModel(sensor)))));
            }
    
            else if (thing.GetType() == typeof (Device))
            {
                var device = (Device) thing;
                var parentName = device.Space.Name;
                Name = device.Name;
                ParentName = parentName;
                Id = device.Id;
                Children = new ObservableCollection<TreeViewItemModel>(device.Sensors.Select(s => new TreeViewItemModel(s)));
            }
    
            else if (thing.GetType() == typeof(Sensor))
            {
                var sensor = (Sensor) thing;
                var space = sensor.Space.Name ?? string.Empty;
                //var device = Devices.First(d => d.Id == sensor.DeviceId);
                var device = sensor.Device;
                ParentName = device == null ? "No Matching Device" : device.Name;
                Name = sensor.Id.ToString();
                Id = sensor.Id;
                ParentName = space;
    
                Children = null;
            }
    
        }
    

    我这样称呼它:TreeSpaces = BuildSpaceTree(AllSpaces); BuildSpaceTree方法如下:

    private ObservableCollection<TreeViewItemModel> BuildSpaceTree(IEnumerable<Space> spaces)
        {
           return new ObservableCollection<TreeViewItemModel>(spaces.Where(space => space.Parent == null).Select(space => new TreeViewItemModel(space)));
        }
    

    Filip 的回答最大的问题是我不喜欢尝试联合多个不同类类型的列表。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-30
      • 2010-10-14
      • 2019-03-12
      相关资源
      最近更新 更多