How to mix databound and static levels in a TreeView?

后端 未结 4 1562
感动是毒
感动是毒 2021-02-03 10:23

I have a collection of Database objects, each containing collections of Schema objects and User objects. I want to bind them to a TreeView, but adding

相关标签:
4条回答
  • 2021-02-03 10:44

    Here's a modification of Josh's solution to work with SMO (my original problem statement):

    <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>
    

    and the modified converter:

    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;
    }
    

    Attribution Note: Content copied from OP's final solution, posted as an edit to the question, rather than as an answer

    0 讨论(0)
  • 2021-02-03 10:46

    The problem is that a TreeView is not very well suited to what you want to acomplish: It expects all the subnodes to be of the same type. As your database node has a node of type Collection<Schemas> and of type Collection<Users> you cannot use a HierarchicalDataTemplate. A Better approach is to use nested expanders that contain ListBoxes.

    The code below does what you want I think,while being as close as possible to your original intent:

    <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;
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-03 10:46

    You need to fill the properties you're using in your binding with data from your database. Currently you're using a new TreeViewItem, and using it as a datasource, so what you're saying about it seeing everything as a single node makes sense, as you've placed it in a single node.

    You need to load your database data and attach it to the properties you've used in your WPF template as binding items.

    0 讨论(0)
  • 2021-02-03 10:59

    Oh man this is an incredibly frustrating task. I've tried doing it myself many times. I had a very similar requirement where I've got something like a Customer class that has both a Locations collection and a Orders collection. I wanted Locations and Orders to be "folders" in the tree view. As you've discovered, all the TreeView examples that show you how to bind to self-referencing types are pretty much useless.

    First I resorted to manually building a tree of FolderItemNode and ItemNode objects that I would generate in the ViewModel but this defeated the purpose of binding because it would not respond to underlying collection changes.

    Then I came up with an approach which seems to work pretty well.

    • In the above described object model, I created classes LocationCollection and OrderCollection. They both inherit from ObservableCollection and override ToString() to return "Locations" and "Orders" respectively.
    • I create a MultiCollectionConverter class that implements IMultiValueConverter
    • I created a FolderNode class that has a Name and Items property. This is the placeholder object that will represent your "folders" in the tree view.
    • Define hierarchicaldatatemplate's that use MultiBinding anywhere that you want to group multiple child collections into folders.

    The resulting XAML looks similar to the code below and you can 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

    0 讨论(0)
提交回复
热议问题