Создание иерархии и отображение в TreeView WPF
Есть класс, который описывает товар:
using System.Collections.ObjectModel;
namespace Test
{
public class Product
{
public ObservableCollection<Product> productName { get; set; }
public string nameOfProduct { get; set; }
public Product()
{
productName = new ObservableCollection<Product>();
}
}
}
Есть привязка к этому коду:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TreeView Name="TrvView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Product}" ItemsSource="{Binding productName}">
<TextBlock Text="{Binding nameOfProduct}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Есть код, который создаёт и отображает элементы коллекции в иерархическом виде:
private void Create()
{
for (int i = 0; i < 10; i++)
{
bread = new Product(){nameOfProduct = "Хлеб"};
for (int j = 0; j < 10; j++)
{
breadVarieties = new Product(){nameOfProduct = "Сорт хлеба " + j.ToString()};
bread.productName.Add(breadVarieties);
}
TrvView.Items.Add(bread);
}
}
На выходе получаю ожидаемый и красивый результат:
До чего я не могу додуматься: Как я могу реализовать код таким образом, чтобы скажем, после каждого 10го сорта хлеба создавался не следующий корневой элемент, который расположен выше, я данные опускались ниже. Примерно вот так:
У меня получается, если я сам заранее предопределяю глубину вложения (пишу ещё несколько циклов for и добавляю элементы). Как я могу сделать такое программно?
Ответы (1 шт):
В WPF не работают с контролами напрямую, а вместо этого используют привязки данных. Есть шаблон проектирования MVVM, который помогает организовать WPF приложение правильно, но давайте для начала по-простому.
Назначим DataContext и реализуем INotifyPropertyChanged
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get => _products;
set
{
_products = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// здесь будем писать код
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Для модели данных тоже реализуем INPC.
Я переименовал свойства, чтобы они выглядели понятнее и соответствовали общепринятым правилам именования в C#.
public class Product : INotifyPropertyChanged
{
private string _name;
private ObservableCollection<Product> _items = new ObservableCollection<Product>();
public ObservableCollection<Product> Items
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
public string Name;
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
И вот такой XAML
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
<Grid>
<TreeView ItemsSource="{Binding Products}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Product}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Структура проекта готова. Теперь, как бы вы не меняли содержимое коллекций, как бы вы не переименовывали элементы, TreeView будет автоматически следить за всеми изменениями в дереве и немедленно их отображать в интерфейсе.
Обратите внимание, у TreeView больше нет имени, и оно больше не нужно.
Давайте теперь заполним дерево
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Products = new ObservableCollection<Product>();
FillProducts(Products, 10, 2, true);
}
private void FillProducts(ObservableCollection<Product> items, int count, int depth, bool isRoot)
{
for (int i = 0; i < count; i++)
{
Product product = new Product() { Name = $"{isRoot ? "Хлеб" : "Сорт хлеба"} {i + 1}" };
items.Add(product);
if (i == count - 1 && depth > 1)
FillProducts(product.Items, count, depth - 1, false);
}
}
Готово.
P.S. Можно еще чисто ради развлечения посмотреть как этот код работает визуально. Для этого добавим немного асинхронности и паузы.
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
Products = new ObservableCollection<Product>();
await FillProductsAsync(Products, 10, 2, true);
}
private async Task FillProductsAsync(ObservableCollection<Product> items, int count, int depth, bool isRoot)
{
for (int i = 0; i < count; i++)
{
Product product = new Product() { Name = $"{isRoot ? "Хлеб" : "Сорт хлеба"} {i + 1}" };
items.Add(product);
await Task.Delay(500);
if (i == count - 1 && depth > 1)
FillProducts(product.Items, count, depth - 1, false);
}
}
Теперь видно, как элементы добавляются в дерево один за другим визуально.

