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