Создание узлов TreeView

85

Элементы управления TreeView часто применяются для размещения больших объемов данных. Объясняется это тем, что TreeView обладает сворачиваемо-разворачиваемой структурой. Даже в случае прокрутки TreeView пользователем сверху донизу, видимой не обязательно будет вся доступная в нем информация. Информация, не являющаяся видимой, может вообще пропускаться в элементе управления TreeView, сокращая накладные расходы (и время, необходимое для его заполнения). Даже еще лучше то, что при открытии элемента TreeViewItem инициируется событие Expanded, а при закрытии — событие Collapsed. Этот момент очень удобно использовать для добавления недостающих узлов или удаления тех, что уже больше не нужны. Такой подход называется оперативным (just-in-time) созданием узлов.

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

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

Дерево каталогов

В применении элемента управления TreeView с возможностью оперативного создания узлов для отображения папок на жестком диске нет ничего нового. Первый шаг заключается в добавлении в TreeView списка дисков при первой загрузке окна. Изначально узел для каждого диска представляется в свернутом виде. Буква диска отображается в заголовке, а объект DriveInfo хранится в свойстве TreeViewItem.Tag для упрощения поиска вложенных каталогов в дальнейшем без воссоздания этого объекта. (Это увеличивает накладные расходы приложения, связанные с памятью, но при этом сокращает количество проверок безопасности доступа к файлам. Общий эффект является незначительным, но зато немного улучшает производительность и упрощает код.) Ниже приведен код, в котором TreeView заполняется списком дисков с помощью класса System.IO.DriveInfo:

foreach (DriveInfo drive in DriveInfo.GetDrives())
{
       TreeViewItem item = new TreeViewItem();
       item.Tag = drive;
       item.Header = drive.ToString();
       item.Items.Add("*");
       trw_Products.Items.Add(item);
}

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

Указатель места заполнения представляет собой удобный инструмент, который можно использовать для определения того, развернул ли уже пользователь данную папку для просмотра ее содержимого. Однако его главной обязанностью является отображение рядом с каждым элементом специального значка, указывающего на возможность разворачивания. Без него пользователь просто не сможет разворачивать каталог и отображать содержащиеся в нем подпапки. Если в каталоге нет никаких подпапок, этот значок будет просто исчезать при попытке пользователя развернуть его, что похоже на поведение проводника Windows при просмотре сетевых папок.

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

<TreeView Name="trw_Products" Margin="5" TreeViewItem.Expanded="trw_Products_Expanded">
private void trw_Products_Expanded(object sender, RoutedEventArgs e)
        {
            TreeViewItem item = (TreeViewItem)e.OriginalSource;
            item.Items.Clear();
            DirectoryInfo dir;
            if (item.Tag is DriveInfo)
            {
                DriveInfo drive = (DriveInfo)item.Tag;
                dir = drive.RootDirectory;
            }
            else dir = (DirectoryInfo)item.Tag;
            try
            {
                foreach (DirectoryInfo subDir in dir.GetDirectories())
                {
                    TreeViewItem newItem = new TreeViewItem();
                    newItem.Tag = subDir;
                    newItem.Header = subDir.ToString();
                    newItem.Items.Add("*");
                    item.Items.Add(newItem);
                }
            }
            catch
            { }
        }

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

В качестве альтернативы можно сделать так, чтобы обновление выполнялось при каждом выборе элемента, за счет обработки события ViewItem.Selected, или применить компонент вроде System.IO.FileSystemWatcher для ожидания уведомлений от операционной системы при добавлении, удалении или переименовании папки. Компонент FileSystemWatcher является единственным способом гарантировать, что обновление дерева каталогов будет осуществляться сразу же при появлении изменения, но чреват наибольшими накладными расходами.

Комбинируя с TreeView мощные возможности шаблонов элементов управления, можно добиться много чего. Например, можно создавать элементы управления, радикально отличающиеся как по внешнему виду, так и по поведению, просто заменяя шаблоны для элементов управления TreeView и TreeViewItem.

Внесение таких корректировок требует более глубокого изучения шаблонов. Начать можно с каких-нибудь интересных примеров. Например, в состав Visual Studio входит образец многостолбцового элемента управления TreeView, сочетающего в себе дерево и сетку. Просмотреть его можно, отыскав в справочной системе Visual Studio раздел TreeListView sample [WPF] (Пример элемента управления TreeListView [WPF]). Еще одним интригующим примером является предложенный Джошем Смитом (Josh Smith) эксперимент по компоновке, преобразующий TreeView в нечто больше похожее на организационную схему. Просмотреть полный код этого образца можно по следующему адресу: http://www.codeproject.com/KB/WPF/CustomTreeViewLayout.aspx.

Пройди тесты
Лучший чат для C# программистов