Как смешать данные и статические уровни в TreeView? - PullRequest
12 голосов
/ 29 ноября 2009

У меня есть коллекция База данных объектов, каждый из которых содержит коллекции Схема объектов и Пользователь объектов. Я хочу связать их с 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 ]

12 голосов
/ 03 декабря 2009

О боже, это невероятно неприятная задача. Я пытался сделать это сам много раз. У меня было очень похожее требование, когда у меня было что-то вроде класса Customer, в котором есть коллекция Locations и коллекция Orders. Я хотел, чтобы Locations и Orders были "папками" в древовидной структуре. Как вы обнаружили, все примеры TreeView, которые показывают вам, как связываться с самообращающимися типами, практически бесполезны.

Сначала я прибегнул к ручному построению дерева объектов FolderItemNode и ItemNode, которые я сгенерировал бы в ViewModel, но это побеждало цель связывания, поскольку оно не реагировало на изменения базовой коллекции.

Затем я предложил подход, который, кажется, работает очень хорошо.

  • В описанной выше объектной модели я создал классы LocationCollection и OrderCollection. Они оба наследуются от ObservableCollection и переопределяют ToString (), чтобы возвращать «Locations» и «Orders» соответственно.
  • Я создаю класс MultiCollectionConverter, который реализует IMultiValueConverter
  • Я создал класс FolderNode, который имеет свойство Name и Items. Это объект-заполнитель, который будет представлять ваши «папки» в древовидном представлении.
  • Определите иерархические таблицы данных, которые используют MultiBinding везде, где вы хотите сгруппировать несколько дочерних коллекций в папки.

Полученный XAML выглядит аналогично приведенному ниже коду, и вы можете получить zip-файл, содержащий все классы и XAML в рабочем примере .

<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

2 голосов
/ 29 ноября 2009

Проблема в том, что TreeView не очень хорошо подходит для того, что вы хотите выполнить: он ожидает, что все подузлы будут одного типа. Поскольку у узла вашей базы данных есть узел типа Collection <Schemas> и типа Collection <Users>, вы не можете использовать HierarchicalDataTemplate. Лучший подход - использовать вложенные расширители, содержащие списки.

Приведенный ниже код делает то, что вы хотите, я думаю, при этом максимально приближаясь к вашему первоначальному замыслу:

<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 голосов
/ 29 декабря 2014

Вот модификация решения Джоша для работы с 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, опубликовано как редактирование вопроса , а не как ответ

0 голосов
/ 29 ноября 2009

Вам необходимо заполнить свойства, которые вы используете в привязке, данными из вашей базы данных. В настоящее время вы используете новый TreeViewItem и используете его в качестве источника данных, поэтому то, что вы говорите об этом, рассматривает все как один узел, так как имеет смысл поместить его в один узел.

Вам необходимо загрузить данные базы данных и прикрепить их к свойствам, которые вы использовали в своем шаблоне WPF в качестве элементов привязки.

...