TOC

This article is currently in the process of being translated into Russian (~97% done).

Элемент управления TreeView:

Отложенная загрузка элементов TreeView

Обычно при использовании элемента TreeView либо задаётся его связывание с коллекцией элементов, либо элементы каждого уровня добавляются одновременно вручную. Однако в некоторых ситуациях вам может понадобится отложить загрузку дочерних элементов какого-либо узла до того момента пока они фактически не понадобятся. Это особенно полезно в случае работы со сложно устроенными древовидными структурами с большим количеством уровней и дочерних узлов. Типичным примером является структура папок в вашем компьютере.

Каждый диск в вашем компьютере содержит большое количество папок, которые в свою очередь содержат множество подпапок и т.д. Перебор всех дисков и папок может занят очень много времени, и в результате ваш элемент TreeView будет содержать огромное количество узлов, с высокой долей вероятности того, что они никогда не будут использоваться. Это идеальный сценарий для отложенной загрузки данных в элемент TreeView, когда инофрмация о подпапках будет загружаться по требованию.

Для получения этого эффекта мы добавляем папку-заглушку в каждый диск или папку, а затем, когда пользователь раскроет её, удаляем эту папку-заглушку и заменяем её на фактические папки. Вот как выглядит наше приложение сразу после запуска. К этому моменту мы получили только список имеющихся на компьютере дисков:

Вы можете начать разворачивать каждый узел, и приложение будет автоматически подгружать соответствующие подпапки. Если папка пуста, она будет отображаться как пустая, только после того как вы попытаетесь её раскрыть, как это показано на рисунке ниже:

Как же можно это сделать? Давайте рассмотрим этот код:

<Window x:Class="WpfTutorialSamples.TreeView_control.LazyLoadingSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="LazyLoadingSample" Height="300" Width="300">
    <Grid>
        <TreeView Name="trvStructure" TreeViewItem.Expanded="TreeViewItem_Expanded" Margin="10" />
    </Grid>
</Window>
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;

namespace WpfTutorialSamples.TreeView_control
{
	public partial class LazyLoadingSample : Window
	{
		public LazyLoadingSample()
		{
			InitializeComponent();
			DriveInfo[] drives = DriveInfo.GetDrives();
			foreach(DriveInfo driveInfo in drives)
				trvStructure.Items.Add(CreateTreeItem(driveInfo));
		}

		public void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
		{
			TreeViewItem item = e.Source as TreeViewItem;
			if((item.Items.Count == 1) && (item.Items[0] is string))
			{
				item.Items.Clear();

				DirectoryInfo expandedDir = null;
				if(item.Tag is DriveInfo)
					expandedDir = (item.Tag as DriveInfo).RootDirectory;
				if(item.Tag is DirectoryInfo)
					expandedDir = (item.Tag as DirectoryInfo);
				try
				{
					foreach(DirectoryInfo subDir in expandedDir.GetDirectories())
						item.Items.Add(CreateTreeItem(subDir));
				}
				catch { }
			}
		}

		private TreeViewItem CreateTreeItem(object o)
		{
			TreeViewItem item = new TreeViewItem();
			item.Header = o.ToString();
			item.Tag = o;
			item.Items.Add("Loading...");
			return item;
		}
	}
}

Код XAML очень простой, интерес представляет только то, как мы подписываемся на событие Expanded элемента TreeViewItem. Обратите внимание, что это именно TreeViewItem, а не сам TreeView, но поскольку событие передаётся вверх по цепочке, мы можем перехватывать его в одном месте для всего элемента TreeView, вместо того чтобы отслеживать его для каждого узла дерева. Это событие вызывается каждый раз, когда разворачивается узел, о чём мы должны знать, чтобы подгрузить дочерние узлы по требованию.

В коде формы (Code-behind) сначала мы добавляем в элемент TreeView все диски, имеющиеся в системе. Мы присваиваем свойству Tag экземпляр класса DriveInfo, чтобы можно было его получить в последующем. Обратите внимание, что мы добавили собственный метод для создания элементов TreeViewItem, который называется CreateTreeItem(), так как мы будем использовать тот же самый метод для последующего динамического создания дочерних папок. В этом методе мы добавляем дочерние элементы в коллекцию Items в виде строки с текстом "Loading...".

Следующим на очереди идёт событие TreeViewItem_Expanded. Как уже обсуждалось, это событие возникает каждый раз, когда мы разворачиваем узел элемента TreeView, поэтому вначале мы проверяем, был ли уже загружен этот узел, путём контроля, содержит ли он единственный дочерний узел в виде строки, и если это так, и мы обнаружили дочерний узел "Loading...", это означает, что сейчас мы должны загрузить фактические данные и заменить ими этот подстановочный узел.

Дальше мы используем свойство Tag для получения ссылки на экземпляры классов DriveInfo или DirectoryInfo, которые представляет собой текущий узел, и получаем список папок, которые мы добавляем к развёрнутому элементу, также вызывая метод CreateTreeItem(). Обратите внимание, что цикл, в котором мы добавляем дочерние папки обёрнут в конструкцию try...catch - это важно, поскольку некоторые папки могут оказаться недоступными из-за ограничений системы безопасности. Вы можете перехватывать такие исключения и отображать эти ситуации в интерфейсе тем или иным способом.

Заключение

Таким образом, подписываясь на событие Expanded, мы легко можем создать элемент TreeView с отложенной загрузкой данных, что является лучшим решением во многих ситуациях, по сравнению со статической загрузкой.

This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!