Как смешать привязанные к базе данных и статические уровни в TreeView?

StackOverflow https://stackoverflow.com/questions/1815056

Вопрос

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

Это было полезно?

Решение

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

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

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

  • В вышеописанной объектной модели я создал классы LocationCollection и OrderCollection.Они оба наследуются от ObservableCollection и переопределяют toString(), чтобы возвращать "Местоположения" и "Заказы" соответственно.
  • Я создаю класс MultiCollectionConverter, который реализует IMultiValueConverter
  • Я создал класс FolderNode, который имеет свойство Name и Items.Это объект-заполнитель, который будет представлять ваши "папки" в виде дерева.
  • Определите hierarchicaldatatemplate, которые используют многозадачность в любом месте, где вы хотите сгруппировать несколько дочерних коллекций в папки.

Результирующий 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

Другие советы

Проблема в том, что TreeView не очень хорошо подходит для того, что вы хотите выполнить: он ожидает, что все подузлы будут одного типа. Поскольку у вашей базы данных есть узел типа Collection <Schemas & Gt; и типа Коллекция <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;
        }
    }
}

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

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

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

Примечание об авторстве: контент, скопированный из окончательного решения ОП , опубликован как < a href = "https://stackoverflow.com/revisions/1815056/3"> изменить вопрос , а не как ответ

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top