.NET数据绑定 - 用于文件夹和项目的递归树的自定义数据源

时间:2009-08-12 15:25:14

标签: c# .net data-binding tree

改写为:

我可以使用其他一些想法中的一些输入,建议和样本来构建数据表示集合。

该集合需要提供一个databindable,可编辑项目树,但有一点点扭曲:项目需要是两种类型之一,每种类型提供略有不同的特征。这两种类型的项目是Folder和TreeItem。文件夹包含它自己的项目列表(同样,Folder或TreeItem类型)和TreeItem不包含列表。

我目前的方法相当接近,但感觉很狡猾。基本上我有一个抽象的基类,TreeItemBase,它(以循环方式)继承自BindableList。然后我有两个具体的派生类型,Folder和TreeItem,它们都继承自抽象基类。明显的缺陷是TreeItem,它不能包含子项,仍然继承自BindingList;因此,假装它不是一个集合,这取决于一些丑陋的hackery。

是BindingList<>这个系列的基础不佳?一些DataBinding interfaces听起来像是对数据绑定提供了更高程度的控制,但我没有找到一个非常正确的。我的理想虽然提供了一个自定义实现,让我可以控制数据绑定如何遍历集合,并可以检查每个元素的具体类型以确定它是否包含集合,或者它是否是树中的终点

这是XML的快速剪切,以帮助可视化我想要表达的内容;我很容易在XSD中干净地编写结构和规则 - 但我只是很难将它转换为.NET并支持数据绑定。

<Items>
  <TreeItem xsi:type="Folder" name="Root">
    <TreeItem xsi:type="Folder" name="Sub1">
      <TreeItem xsi:type="TreeItem" name="Humm"/>
    </TreeItem>
    <TreeItem xsi:type="TreeItem" name="Bleh"/>
    <TreeItem xsi:type="Folder" name="Sub2">
      <TreeItem xsi:type="TreeItem" name="Boo!"/>
    </TreeItem>
  </TreeItem>
</Items>

更新:我一直在更多地研究我的方法,接近我想要做的事情,使用接口而不是基础类的项目,但已经打了一个障碍。我遇到的问题包含在seperate question

理想情况下,我想使用抽象基类方法,以便生成的XML将FolderTreeItem视为complexTypes(无需手动控制序列化),但它可以忽略不计要求。

4 个答案:

答案 0 :(得分:3)

也许我对这个问题的知识深度还不够大,但你不能这样做:

有一个接口,以及两个实现该接口的类。

interface ITreeItem
{
    IEnumerable<ITreeItem> GetChildren();
}

class MyFolder : ITreeItem
{
    public IEnumerable<ITreeItem> GetChildren()
    {
        // TODO: Return the list of children
    }
}

class MyITreeItem : ITreeItem
{
    public IEnumerable<ITreeItem> GetChildren()
    {
        // TODO: Return the list of children
    }
}

然后,如果您的目标是将集合数据绑定到某个列表,那么您应该能够使用IEnumerable集合。在每次调用数据集集合时,您应该能够检查项目的类型:

foreach (var node in root.GetChildren())
{
    if (node is MyFolder)
    {
        var folder = (MyFolder)node;

        // Bind fields from the folder object
    }
    else if(node is MyTreeItem)
    {
        var folder = (MyTreeItem)node;

        // Bind fields from the tree item object
    }
}

当我在另一个列表中嵌套列表时,我做了类似的事情(我认为)。为了显示数据,我设置了嵌套的ListView控件。

很抱歉,如果这不是你想要的,但希望它有所帮助!

答案 1 :(得分:2)

SkippyFire的解决方案似乎比我的更优雅,但我想我会告诉你我是如何解决这个问题的。以下解决方案显示了我为构建可绑定到树视图的集合所做的操作,并且您可以确定已选择了哪些项目。它没有实现任何可绑定列表或任何东西。但是,从您的帖子中不清楚这是否是您想要的。

这是我的XML文件的一个示例:

 <controls name="Parent" attribute="element">
  <control name="Files" attribute="element">
    <control name="Cashflow" attribute="element">
      <control name="Upload" attribute="leaf"/>
      <control name="Download" attribute="leaf"/>
    </control>
  </control>
  <control name="Quotes" attribute="element">
     <control name="Client Quotes" attribute="leaf"/>
  </control>
</controls>    

然后我有一个代表每个项目的类。它包含一个名称,一个子节点列表(1级向下),对其父级的引用,以及记录该元素属性的字符串。

 public class Items
    {
        public string Name { get; set; }
        public List<Items> SubCategories { get; set; }
        public string IsLeaf { get; set; }
        public string Parent { get; set; }
    }  

从那里,我按如下方式填写项目列表:

List<Items> categories = new List<Items>();

XDocument categoriesXML = XDocument.Load("TreeviewControls.xml");

categories = this.GetCategories(categoriesXML.Element("controls"));

这会调用GetCategories()方法

private List<Items> GetCategories(XElement element)
        {

            return (from category in element.Elements("control")
                    select new Items()
                    {
                        Parent = element.Attribute("name").Value,
                        Name = category.Attribute("name").Value,
                        SubCategories = this.GetCategories(category),
                        IsLeaf = category.Attribute("attribute").Value

                    }).ToList();
        }

填充了类别变量后,我只是将列表指定为树视图的ItemSource。

controltree.ItemsSource = categories;

从那里开始,如果树中的选择发生变化,我会检查选择是否为叶节点,如果是,则引发事件。

private void Selection_Changed(object sender, RoutedEventArgs e)
        {
            Items x = controltree.SelectedItem as Items;

            if (x.IsLeaf.Equals("leaf"))
                _parent.RaiseChange(x.Parent+","+x.Name);
        }

此解决方案也适用于树中的任何深度。

答案 2 :(得分:1)

我使用了来自source forge的treeviewadv。它有一个非常好的MVC方式来处理树视图类型建模。它是一个Windows窗体控件,它将模型绑定到支持列的树视图样式控件。他们还提供了一些不错的示例代码。

答案 3 :(得分:0)

ObservableCollection可能是最好的选择,加上两个DataTemplates。

public class TreeItem : INotifyPropertyChanged
{
    public string Name { get; set; }
    // ...
}

public class Folder : TreeItem
{
    public ObservableCollection<TreeItem> Items { get; private set; }

    public Folder()
    {
        this.Items = new ObservableCollection<TreeItem>();
    }
    // ...
}

你的DataTemplate(包括秘密酱HierarchicalDataTemplate):

<HierarchicalDataTemplate DataType="{x:Type local:Folder}"
                          ItemsSource="{Binding Path=Items}">
    <TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:TreeItem}">
    <TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>

全部放在一起(代码背后):

public class FolderList : ObservableCollection<TreeItem>
{
    public FolderList()
    {
        this.Add(new TreeItem { Name = "Hello" });
        this.Add(new TreeItem { Name = "World" });

        var folder = new Folder { Name = "Hello World" };
        folder.Items.Add(new TreeItem { Name = "Testing" });
        folder.Items.Add(new TreeItem { Name = "1" });
        folder.Items.Add(new TreeItem { Name = "2" });
        folder.Items.Add(new TreeItem { Name = "3" });
        this.Add(folder);
    }
}

XAML:

<Grid>
    <Grid.Resources>
        <local:FolderList x:Key="MyItems" />
        <HierarchicalDataTemplate DataType="{x:Type local:Folder}"
                                  ItemsSource="{Binding Path=Items}">
            <TextBlock Text="{Binding Path=Name}"/>
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type local:TreeItem}">
            <TextBlock Text="{Binding Path=Name}"/>
        </DataTemplate>
    </Grid.Resources>
    <TreeView>
      <TreeViewItem ItemsSource="{Binding Source={StaticResource MyItems}}"
                    Header="Root" />
    </TreeView>
</Grid>