Подтвердить что ты не робот

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

У меня есть набор объектов базы данных, каждый из которых содержит коллекции объектов Schema и объекты User. Я хочу привязать их к TreeView, но добавив дополнительные статические уровни в иерархии, чтобы получившийся TreeView выглядел более или менее следующим образом:

<TreeView>
    <TreeViewItem Header="All the databases:">
        <TreeViewItem Header="Db1">
            <TreeViewItem Header="Here all the schemas:">
                <TreeViewItem Header="Schema1"/>
                <TreeViewItem Header="Schema2"/>
            </TreeViewItem>
            <TreeViewItem Header="Here all the users:">
                <TreeViewItem Header="User1"/>
                <TreeViewItem Header="User2"/>
            </TreeViewItem>
        </TreeViewItem>
        <TreeViewItem Header="Db2">
            <TreeViewItem Header="Here all the schemas:">
                <TreeViewItem Header="Schema1"/>
                <TreeViewItem Header="Schema2"/>
            </TreeViewItem>
            <TreeViewItem Header="Here 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 all the schemas:" ItemsSource="{Binding Path=Schemas}"/>
            <TreeViewItem Header="Here 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 видит все поддерево, внедренное в базу данных node как отдельный элемент, и оно выбирает только все. Мне нужно иметь возможность выбирать конкретную схему, пользователя или базу данных. Как настроить шаблоны и привязки так, чтобы они работали так, как мне нужно?

4b9b3361

Ответ 1

О, человек, это невероятно трудная задача. Я пробовал делать это сам много раз. У меня было очень похожее требование, когда у меня есть что-то вроде класса Customer, у которого есть коллекция Locations и коллекция Orders. Я хотел бы, чтобы Locations and 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

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

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

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

Ответ 3

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

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

Ответ 4

Вот модификация решения 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, размещенного как чтобы изменить вопрос, а не как ответ