Skip to content

Привязка к иерархическим данным

В типичном иерархическом источнике данных бизнес-объект имеет свойство, в котором хранится коллекция дочерних объектов данных. Два подхода позволяют вам предоставлять дочерние данные в контролы TreeList и TreeView в режиме иерархической привязки:

Динамическая загрузка данных

При привязке к иерархическому источнику данных TreeList и TreeView контролируют загрузку узлов по требованию по умолчанию: дочерние узлы динамически загружаются при развертывании родительского узла.

Установите для свойства AllowDynamicDataLoading значение false, чтобы загрузить все узлы одновременно после привязки контрола к источнику данных.

Если вы используете динамическую загрузку узлов, контролы TreeList и TreeView не имеют доступа к узлам (и их нижележащим данным), которые не были загружены. Это накладывает следующие ограничения на функции проверки узлов и фильтрации/поиска:

  • Когда вы проверяете родительский узел в рекурсивном режиме (см. AllowRecursiveNodeChecking), контрол проверяет этот узел вместе с загруженными в данный момент дочерними узлами. Контрол не проверяет дочерние узлы, которые не были загружены.

  • Когда вы выполняете поиск данных во встроенном окне поиска или фильтруете данные с помощью строки автоматической фильтрации, контрол выполняет поиск данных только по загруженным в данный момент узлам.

Укажите путь к дочерним данным

Один из способов предоставить дочерние данные в контрол TreeList/TreeView заключается в указании имени свойства, которое хранит дочерние данные в бизнес-объекте. Используйте свойство ChildrenFieldName для этой цели. Например, если бизнес-объект хранит дочерние данные в коллекции Items, установите для свойства ChildrenFieldName значение "Items".

Вам также необходимо присвоить свойству HasChildrenFieldName имя свойства, которое возвращает true, если бизнес-объект имеет дочерние данные, и false, в противном случае. Эта информация необходима для отображения или скрытия кнопок расширения узла. Если вы не укажете свойство HasChildrenFieldName, контрол отображает кнопки расширения для дочерних узлов до тех пор, пока пользователь не попытается развернуть эти узлы.

В следующем примере демонстрируется, как привязать контрол TreeList к данным. Объект Employee в примере содержит коллекцию Subordinates, которая содержит дочерние элементы. Свойство ChildrenFieldName указывает имя свойства (строка "Subordinates"), в котором хранятся дочерние данные.

xmlns:mxtl="https://schemas.eremexcontrols.net/avalonia/treelist"
...
<mxtl:TreeListControl Grid.Column="0" Width="400" Name="treeList1" >
    <mxtl:TreeListControl.Columns>
        <mxtl:TreeListColumn Name="colName" FieldName="Name" Header="Name" />
        <mxtl:TreeListColumn Name="colBirthdate" FieldName="Birthdate" Header="Birthdate"/>
    </mxtl:TreeListControl.Columns>
</mxtl:TreeListControl>
using CommunityToolkit.Mvvm.ComponentModel;

Employee p1 = new Employee() { Name = "Mark Douglas", Birthdate=new DateTime(1990, 01, 5) };
Employee p2 = new Employee() { Name = "Mary Watson", Birthdate = new DateTime(1985, 12, 17) };
Employee p3 = new Employee() { Name = "Alex Wude", Birthdate = new DateTime(2000, 10, 7) };
Employee p4 = new Employee() { Name = "Sam Louis", Birthdate = new DateTime(1975, 8, 27) };
Employee p5 = new Employee() { Name = "Dan Miller", Birthdate = new DateTime(1981, 3, 6) };
p1.Subordinates.Add(p2);
p1.Subordinates.Add(p3);
p2.Subordinates.Add(p4);
p3.Subordinates.Add(p5);

ObservableCollection<Employee> employees = new ObservableCollection<Employee>() { p1 };

treeList1.ChildrenFieldName = "Subordinates";
treeList1.HasChildrenFieldName = "HasChildren";

treeList1.ItemsSource = employees;

public partial class Employee : ObservableObject
{
    [ObservableProperty]
    public string name="";

    [ObservableProperty]
    public DateTime? birthdate=null;

    public ObservableCollection<Employee> Subordinates { get; } = new();

    public bool HasChildren { get { return Subordinates.Count > 0; } }
}

Укажите выбор дочерних элементов

Другим подходом к предоставляет дочерние данные в режиме иерархической привязки является реализация children selector. Селектор указывает, доступны ли дочерние данные в бизнес-объекте, и возвращает эти дочерние данные по запросу. Контрол TreeList/TreeView использует информацию, предоставленную селектором, для создания дочерних узлов и отображения кнопок распахивания узлов.

Дочерний селектор полезен в следующих сценариях:

  • Когда родительский и дочерний бизнес-объекты принадлежат к разным типам
  • Во всех остальных случаях, когда вы не можете указать путь к дочерним элементам со свойством ChildrenFieldName.

Используйте свойство TreeListControlBase.ChildrenSelector, чтобы назначить селектор вашему контролу TreeList/TreeView. Селектор - это объект, который реализует интерфейс ITreeListChildrenSelector:

public interface ITreeListChildrenSelector
{
    // The method should return whether a business object has child data. 
    // The control uses this information to display or hide node expand buttons.
    bool HasChildren(object item) => true;
    // The method should return child objects for a business object.
    IEnumerable? SelectChildren(object item);
}

Пример - Реализация простого выбора дочерних элементов

В следующем примере создается селектор (MyTreeListChildrenSelector), который предоставляет информацию о дочерних элементах для контрола TreeList.

xmlns:mxtl="https://schemas.eremexcontrols.net/avalonia/treelist"
...
<Grid RowDefinitions="Auto, Auto, Auto, Auto" ColumnDefinitions="400, *">
    <Grid.Resources>
        <local:MyTreeListChildrenSelector x:Key="mySelector" />
    </Grid.Resources>

    <mxtl:TreeListControl Grid.Column="0" Name="treeList2"
     ChildrenSelector="{StaticResource mySelector}">
        <mxtl:TreeListControl.Columns>
            <mxtl:TreeListColumn Name="colName1" FieldName="Name" />
            <mxtl:TreeListColumn Name="colBirthdate1" FieldName="Birthdate"/>
        </mxtl:TreeListControl.Columns>
    </mxtl:TreeListControl>
</Grid>
using CommunityToolkit.Mvvm.ComponentModel;

Employee p1 = new Employee() { Name = "Mark Douglas", Birthdate = new DateTime(1990, 01, 5)};
Employee p2 = new Employee() { Name = "Mary Watson", Birthdate = new DateTime(1985, 12, 17)};
Employee p3 = new Employee() { Name = "Alex Wude", Birthdate = new DateTime(2000, 10, 7)};
Employee p4 = new Employee() { Name = "Sam Louis", Birthdate = new DateTime(1975, 8, 27)};
Employee p5 = new Employee() { Name = "Dan Miller", Birthdate = new DateTime(1981, 3, 6)};
p1.Subordinates.Add(p2);
p1.Subordinates.Add(p3);
p2.Subordinates.Add(p4);
p3.Subordinates.Add(p5);

ObservableCollection<Employee> employees = new ObservableCollection<Employee>() { p1 };

treeList2.ItemsSource = employees;

public class MyTreeListChildrenSelector : ITreeListChildrenSelector
{
    public bool HasChildren(object? item) => (item as Employee).HasChildren;
    public IEnumerable? SelectChildren(object? item) => (item as Employee).Subordinates;
}

public partial class Employee : ObservableObject
{
    [ObservableProperty]
    public string name="";

    [ObservableProperty]
    public DateTime? birthdate=null;

    public ObservableCollection<Employee> Subordinates = new();

    public bool HasChildren { get { return Subordinates.Count > 0; } }
}

Используйте дочерний селектор, когда родительский и дочерний бизнес-объекты различны

Следующие соображения применимы, когда родительские и дочерние бизнес-объекты относятся к разным типам.

  • TreeList отображает значения из полей, к которым привязаны столбцы TreeList (см. TreeListColumn.FieldName). Родительский и дочерний бизнес-объекты должны предоставлять доступ к этим полям.

  • Контрол TreeView отображает значения из бизнес-объектов из поля, имя которого задано свойством DataFieldName. Убедитесь, что родительский и дочерний объекты данных содержат это поле.

Пример - Реализация дочернего селектора для различных бизнес-объектов

Предположим, что контрол TreeView привязан к коллекции объектов City. Каждый объект City владеет коллекцией объектов Street, которые, в свою очередь, владеют коллекциями объектов Building. TreeView должен отображать города на корневом уровне, улицы на втором уровне и номера зданий на третьем уровне.

treeview-hierarchical-different-bus-objects-example

Приведенный ниже код показывает определения бизнес-объектов City, Street и Building. Обратите внимание, что все эти объекты имеют свойство Name. Имя этого свойства будет присвоено свойству TreeViewControl.DataFieldName в XAML.

public class City
{
    public City(string name) { Name = name; }
    public string Name { get; set; }
    public ObservableCollection<Street> Streets = new();
}

public class Street 
{
    public Street(string name, string[] buidingNumbers) { 
        Name = name;
        foreach (string number in buidingNumbers)
        {
            Buildings.Add(new Building(number));
        }
    }
    public string Name { get; set; }
    public ObservableCollection<Building> Buildings = new();
}

public class Building  
{
    public Building(string name) { Name = name; }
    public string Name { get; set; }
}

Контрол TreeView привязан к коллекции Cities, определенной в модели представления. Свойству TreeViewControl.DataFieldName присвоено значение "Name". Это свойство определяет поле бизнес-объектов, значения которых должны отображаться в контроле TreeView.

public partial class MainWindowViewModel : ViewModelBase
{
    [ObservableProperty]
    ObservableCollection<City> cities = new();
    //...
}
<mxtl:TreeViewControl 
    Name="treeView"
    ItemsSource="{Binding Cities}"
    DataFieldName="Name"
    ...
/>

Поскольку бизнес-объекты в иерархическом источнике данных относятся к разным типам, необходимо реализовать селектор для извлечения дочерних элементов для узлов Tree View.

public class MyTreeListChildrenSelector : ITreeListChildrenSelector
{
    public bool HasChildren(object item)
    {
        if (item is City city)
            return city.Streets.Count > 0;
        if (item is Street street)
            return street.Buildings.Count > 0;
        return false;
    }
    public IEnumerable? SelectChildren(object item)
    {
        if (item is City city)
            return city.Streets;
        if (item is Street street)
            return street.Buildings;
        return null;
    }
}

Чтобы назначить селектор MyTreeListChildrenSelector контролу TreeView, используйте свойство TreeListControlBase.ChildrenSelector.

xmlns:local="clr-namespace:EremexAvaloniaApplication10.Views"

<mx:MxWindow.Resources>
    <local:MyTreeListChildrenSelector x:Key="mySelector" />
</mx:MxWindow.Resources>

<mxtl:TreeViewControl ...
    ChildrenSelector="{StaticResource mySelector}"
/>

Полный код

Полный код этого примера приведен ниже.

<!-- MainWindow.axaml -->

<mx:MxWindow xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:EremexAvaloniaApplication10.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:mx="https://schemas.eremexcontrols.net/avalonia"
        xmlns:mxtl="https://schemas.eremexcontrols.net/avalonia/treelist"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="EremexAvaloniaApplication10.Views.MainWindow"
        x:DataType="vm:MainWindowViewModel"
        Icon="/Assets/EMXControls.ico"
        Title="EremexAvaloniaApplication10"
        xmlns:local="clr-namespace:EremexAvaloniaApplication10.Views"
        >

    <Design.DataContext>
        <vm:MainWindowViewModel/>
    </Design.DataContext>

    <mx:MxWindow.Resources>
        <local:MyTreeListChildrenSelector x:Key="mySelector" />
    </mx:MxWindow.Resources>

    <mxtl:TreeViewControl Name="treeView"
                          ItemsSource="{Binding Cities}"
                          DataFieldName="Name"
                          ChildrenSelector="{StaticResource mySelector}"
                          />
</mx:MxWindow>
//MainWindow.axaml.cs

using Avalonia.Controls;
using Eremex.AvaloniaUI.Controls.Common;
using Eremex.AvaloniaUI.Controls.TreeList;
using EremexAvaloniaApplication10.ViewModels;
using System.Collections;

namespace EremexAvaloniaApplication10.Views
{
    public partial class MainWindow : MxWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class MyTreeListChildrenSelector : ITreeListChildrenSelector
    {
        public bool HasChildren(object item)
        {
            if (item is City city)
                return city.Streets.Count > 0;
            if (item is Street street)
                return street.Buildings.Count > 0;
            return false;
        }
        public IEnumerable? SelectChildren(object item)
        {
            if (item is City city)
                return city.Streets;
            if (item is Street street)
                return street.Buildings;
            return null;
        }
    }
}
//MainWindowViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using System.Collections.ObjectModel;
using System.Security.Cryptography;

namespace EremexAvaloniaApplication10.ViewModels
{
    public partial class MainWindowViewModel : ViewModelBase
    {
        [ObservableProperty]
        ObservableCollection<City> cities = new();

        public MainWindowViewModel()
        {
            Street s1 = new Street("Khaosan Rd", new string[] { "234", "278", "299" });
            Street s2 = new Street("Nonsi Rd", new string[] { "678", "639" });
            Street s3 = new Street("Airport Rd", new string[] { "324", "373" } );
            Street s4 = new Street("Pipeline Rd", new string[] { "111", "185" });

            City c1 = new City("Bangkok");
            City c2 = new City("Mumbai");

            c1.Streets.Add(s1);
            c1.Streets.Add(s2);
            c2.Streets.Add(s3);
            c2.Streets.Add(s4);

            cities.AddRange(new[] { c1, c2 });
        }
    }

    public class City
    {
        public City(string name) { Name = name; }
        public string Name { get; set; }
        public ObservableCollection<Street> Streets = new();
    }

    public class Street 
    {
        public Street(string name, string[] buildingNumbers) { 
            Name = name;
            foreach (string number in buildingNumbers)
            {
                Buildings.Add(new Building(number));
            }
        }
        public string Name { get; set; }
        public ObservableCollection<Building> Buildings = new();
    }

    public class Building  
    {
        public Building(string name) { Name = name; }
        public string Name { get; set; }
    }
}

Смотрите также



* Эта страница была создана автоматически с помощью сервиса машинного перевода Яндекс Переводчик.