如何在TreeView中混合数据绑定和静态级别?

时间:2009-11-29 08:54:08

标签: wpf data-binding treeview hierarchicaldatatemplate

我有一组 Database 对象,每个对象都包含 Schema 对象和 User 对象的集合。我想将它们绑定到TreeView,但在层次结构中添加其他静态级别,以便生成的TreeView看起来或多或少像这样:

<TreeView>
    <TreeViewItem Header="All the databases:">
        <TreeViewItem Header="Db1">
            <TreeViewItem Header="Here's all the schemas:">
                <TreeViewItem Header="Schema1"/>
                <TreeViewItem Header="Schema2"/>
            </TreeViewItem>
            <TreeViewItem Header="Here's all the users:">
                <TreeViewItem Header="User1"/>
                <TreeViewItem Header="User2"/>
            </TreeViewItem>
        </TreeViewItem>
        <TreeViewItem Header="Db2">
            <TreeViewItem Header="Here's all the schemas:">
                <TreeViewItem Header="Schema1"/>
                <TreeViewItem Header="Schema2"/>
            </TreeViewItem>
            <TreeViewItem Header="Here's all the users:">
                <TreeViewItem Header="User1"/>
                <TreeViewItem Header="User2"/>
            </TreeViewItem>
        </TreeViewItem>
    </TreeViewItem>
</TreeView>

通过使用以下模板,我能够非常接近我想要的东西:

<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type smo:Database}">
        <TreeViewItem Header="{Binding Path=Name}">
            <TreeViewItem Header="Here's all the schemas:" ItemsSource="{Binding Path=Schemas}"/>
            <TreeViewItem Header="Here's all the users:" ItemsSource="{Binding Path=Users}"/>
        </TreeViewItem>
    </HierarchicalDataTemplate>
    <DataTemplate DataType="{x:Type smo:Schema}">
        <TextBlock Text="{Binding Path=Name}"/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type smo:User}">
        <TextBlock Text="{Binding Path=Name}"/>
    </DataTemplate>
</Window.Resources>

然后在代码中我设置了这样的绑定:

TreeViewItem treeViewItem = new TreeViewItem();
treeViewItem.Header = "All the databases:";
treeViewItem.ItemsSource = server.Databases;
treeView.Items.Add(treeViewItem);

生成的TreeView看起来像我想要的那样,但是不可能选择特定的模式或用户。显然,WPF将以数据库节点为根的整个子树视为单个项目,并且它只选择整个事物。我需要能够选择特定的架构,用户或数据库。如何设置模板和绑定,使其按照我需要的方式工作?

4 个答案:

答案 0 :(得分:12)

哦,男人,这是一项令人难以置信的令人沮丧的任务。我已经尝试过很多次了。我有一个非常相似的要求,我有一个类似于具有Locations集合和Orders集合的Customer类。我希望Locations和Orders在树视图中成为“文件夹”。正如您所发现的,所有向您展示如何绑定到自引用类型的TreeView示例都是无用的。

首先,我尝试手动构建一个我将在ViewModel中生成的FolderItemNode和ItemNode对象的树,但这违背了绑定的目的,因为它不会响应底层的集合更改。

然后我提出了一种似乎运作良好的方法。

  • 在上面描述的对象模型中,我创建了类LocationCollection和OrderCollection。它们都从ObservableCollection继承并覆盖ToString()以分别返回“Locations”和“Orders”。
  • 我创建了一个实现IMultiValueConverter的MultiCollectionConverter类
  • 我创建了一个具有Name和Items属性的FolderNode类。这是占位符对象,它将代表树视图中的“文件夹”。
  • 定义在任何想要将多个子集合分组到文件夹中的任何地方使用MultiBinding的hierarchicaldatatemplate。

生成的XAML看起来与下面的代码类似,您可以grab a zip file which has all the classes and XAML in a working example

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Local="clr-namespace:WpfApplication2"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">

    <Window.Resources>

        <!-- THIS IS YOUR FOLDER NODE -->
        <HierarchicalDataTemplate DataType="{x:Type Local:FolderNode}" ItemsSource="{Binding Items}">
            <Label FontWeight="Bold" Content="{Binding Name}" />
        </HierarchicalDataTemplate>

        <!-- THIS CUSTOMER HAS TWO FOLDERS, LOCATIONS AND ORDERS -->
        <HierarchicalDataTemplate DataType="{x:Type Local:Customer}">
            <HierarchicalDataTemplate.ItemsSource>
                <MultiBinding>
                    <MultiBinding.Converter>
                        <Local:MultiCollectionConverter />
                    </MultiBinding.Converter>
                    <Binding Path="Locations" />
                    <Binding Path="Orders" />
                </MultiBinding>
            </HierarchicalDataTemplate.ItemsSource>
            <Label Content="{Binding Name}" />
        </HierarchicalDataTemplate>

        <!-- OPTIONAL, YOU DON'T NEED SPECIFIC DATA TEMPLATES FOR THESE CLASSES -->
        <DataTemplate DataType="{x:Type Local:Location}">
            <Label Content="{Binding Title}" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type Local:Order}">
            <Label Content="{Binding Title}" />
        </DataTemplate>

    </Window.Resources>

    <DockPanel>
        <TreeView Name="tree" Width="200" DockPanel.Dock="Left" />
        <Grid />
    </DockPanel>

</Window>

Folders in TreeView

答案 1 :(得分:2)

问题是TreeView不太适合你想要的东西:它希望所有的子节点都是相同的类型。由于您的数据库节点具有Collection <Schemas&gt;类型的节点和类型集合<Users&gt;您不能使用HierarchicalDataTemplate。更好的方法是使用包含ListBox的嵌套扩展器。

下面的代码可以实现您的想法,同时尽可能接近原始意图:

<Window x:Class="TreeViewSelection.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:smo="clr-namespace:TreeViewSelection"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <Style TargetType="ListBox">
            <Setter Property="BorderThickness" Value="0"/>
        </Style>
        <DataTemplate DataType="{x:Type smo:Database}">
                <TreeViewItem Header="{Binding Name}">
                    <TreeViewItem Header="Schemas">
                        <ListBox ItemsSource="{Binding Schemas}"/>
                    </TreeViewItem>
                    <TreeViewItem Header="Users">
                    <ListBox ItemsSource="{Binding Users}"/>
                </TreeViewItem>
                </TreeViewItem> 
        </DataTemplate>
        <DataTemplate DataType="{x:Type smo:User}" >
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type smo:Schema}">
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
    </Window.Resources>
    <StackPanel>
        <TreeViewItem ItemsSource="{Binding DataBases}" Header="All DataBases">
        </TreeViewItem>
    </StackPanel>
</Window>

using System.Collections.ObjectModel;
using System.Windows;

namespace TreeViewSelection
{
    public partial class Window1 : Window
    {
        public ObservableCollection<Database> DataBases { get; set; }
        public Window1()
        {
            InitializeComponent();
            DataBases = new ObservableCollection<Database>
                            {
                                new Database("Db1"),
                                new Database("Db2")
                            };
            DataContext = this;
        }
    }

    public class Database:DependencyObject
    {
        public string Name { get; set; }
        public ObservableCollection<Schema> Schemas { get; set; }
        public ObservableCollection<User> Users { get; set; }

        public Database(string name)
        {
            Name = name;
            Schemas=new ObservableCollection<Schema>
                        {
                            new Schema("Schema1"),
                            new Schema("Schema2")
                        };
            Users=new ObservableCollection<User>
                      {
                          new User("User1"),
                          new User("User2")
                      };
        }
    }

    public class Schema:DependencyObject
    {
        public string Name { get; set; }
        public Schema(string name)
        {
            Name = name;   
        }
    }

    public class User:DependencyObject
    {
        public string Name { get; set; }
        public User(string name)
        {
            Name = name;
        }
    }
}

答案 2 :(得分:0)

您需要在绑定中使用数据库中的数据填充您正在使用的属性。目前您正在使用新的TreeViewItem,并将其用作数据源,所以您所说的将所有内容视为单个节点都是有意义的,因为您已将其放置在单个节点中。

您需要加载数据库数据并将其作为绑定项附加到您在WPF模板中使用的属性。

答案 3 :(得分:0)

这是对Josh使用SMO的解决方案的修改(我原来的问题陈述):

<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type local:FolderNode}" ItemsSource="{Binding Items}">
        <TextBlock Text="{Binding Name}"/>
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type smo:Database}">
        <HierarchicalDataTemplate.ItemsSource>
            <MultiBinding>
                <MultiBinding.Converter>
                    <local:MultiCollectionConverter />
                </MultiBinding.Converter>
                <Binding Path="Schemas" />
                <Binding Path="Users" />
            </MultiBinding>
        </HierarchicalDataTemplate.ItemsSource>
        <TextBlock Text="{Binding Name}"/>
    </HierarchicalDataTemplate>
    <DataTemplate DataType="{x:Type smo:User}" >
        <TextBlock Text="{Binding Name}"/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type smo:Schema}">
        <TextBlock Text="{Binding Name}"/>
    </DataTemplate>
</Window.Resources>

和修改过的转换器:

public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    FolderNode[] result = new FolderNode[values.Length];
    for (int i = 0; i < values.Length; ++i)
    {
        result[i].Items = (IEnumerable)values[i];
        result[i].Name = values[i] is UserCollection ? "Users" : "Schemas";
    }
    return result;
}
  

归因注意:OP's最终解决方案复制的内容,以edit to the question发布,而不是作为答案