3.6.3 Компоненты отображения иерархических данных — TreeView, Outline, ListView

Компоненты TreeView и Outline служат для отображения иерархических данных в виде дерева (см. пример на рис. 3.18), в котором пользователь может выбрать нужный ему узел или узлы. Иерархическая информация может быть самой разной: структура некоторого предприятия, структура документации учреждения, структура отчета и т.п. С каждым узлом дерева могут быть связаны некоторые данные.

Возможности компонента TreeView несколько шире, чем компонента Outline. К тому же TreeView — 32-разрядный компонент, a Outline — 16-разрядный. Поэтому Outline целесообразно использовать только в приложениях, создаваемых с помощью Delphi 1.

Основным свойством TreeView, содержащим информацию об узлах дерева, является Items. Доступ к информации об отдельных узлах осуществляется через этот индексный список узлов. Например, TreeView1.Items[1] — это узел дерева с индексом 1 (второй узел дерева). Каждый узел является объектом типа TTreeNodes, обладающим своими свойствами и методами.

Во время проектирования формирование дерева осуществляется в окне редактора узлов дерева, представленном на рис. 3.20. Это окно вызывается двойным щелчком на компоненте TreeView или нажатием кнопки с многоточием около свойства Items в окне Инспектора Объектов.

Рис. 3.20
Окно редактора узлов дерева компонента TreeView

Кнопка New Item (новый узел) позволяет добавить в дерево новый узел. Он будет расположен на том же уровне, на котором расположен узел, выделенный курсором в момент щелчка на кнопке New Item.

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

Кнопка Delete (удалить) удаляет выделенный узел дерева. Кнопка Load позволяет загрузить структуру дерева из файла. Файл, хранящий структуру дерева — это обычный текстовый файл, содержащий тексты узлов. Уровни узлов обозначаются отступами. Например, файл дерева, изображенного на рис. 3.18 и 3.20, может иметь вид:

производство
цех 1
цех 2
цех 3
управление
администрация
бухгалтерия

Для каждого нового узла дерева можно указать ряд свойств в панели Item Properties окна на рис. 3.20. Это прежде всего свойство Text — надпись, появляющаяся в дереве около данного узла. Свойства Image Index и Selected Index определяют индекс пиктограммы, отображаемой для узла, который соответственно не выделен и выделен пользователем в данный момент. Эти индексы соответствуют списку изображений, хранящихся в отдельном компоненте ImageList (см. раздел 9.3). Указание на этот компонент вы можете задать в свойстве Images компонента TreeView. Индексы начинаются с 0. Если вы укажете индекс -1 (значение по умолчанию), пиктограммы изображаться не будут. Последнее свойство — State Index в панели Item Properties позволяет добавить вторую пиктограмму в данный узел, не зависящую от состояния узла. Подобная пиктограмма может просто служить дополнителъной характеристикой узла. Индекс, указываемый как State Index, соответствует списку изображений, хранящихся в отдельном компоненте ImageList, указанном в свойстве StateImages компонента TreeView.

Мы рассмотрели формирование дерева в процессе проектирования. Однако, дерево можно формировать или перестраивать и во время выполнения приложения. Для этого служит ряд методов объектов типа TTreeNodes. Следующие методы позволяют вставлять в дерево новые узлы:


function Add(Node: TTreeNode; const S: string): TTreeNode;Добавляет новый узел с текстом S как последний узел уровня, на котором расположен Node.
function AddFirst(Node: TTreeNode; const S: string): TTreeNode;Вставляет новый узел с текстом S как первый из узлов уровня, на котором находится Node. Индексы последующих узлов увеличиваются на 1.
function Insert (Node: TTreeNode; const S: string): TTreeNode;Вставляет новый узел с текстом S сразу после узла Node на тот же уровень. Индексы последующих узлов увеличиваются на 1.
function AddChild(Node: TTreeNode; const S: string): TTreeNode;Добавляет узел с текстом S как последний дочерний узла Node.
function AddChildFirst(Node: TTreeNode; const S: string); TTreeNode;Вставляет новый узел с текстом S как первый из дочерних узлов узла Node. Индексы последующих узлов увеличиваются на 1.

Каждый из этих методов возвращает вставленный узел. Ниже в качестве примера приведен код, формирующий то же дерево, которое вы можете видеть на рис. 3.18 и 3.20.

TreeView1.Items.Clear; // очистка списка
// добавление корневого узла 'производство' (индекс 0)
TreeView1.Items.Add(nil, 'производство');
{добавление дочерних узлов 'цех 1' — 'цех 3' (индексы 1 — 3)}
TreeView1.Items.AddChild(TreeView1.Items.Item[0], 'цех 1');
TreeView1.Items.AddChild(TreeViewl.Items.Item[0], 'цех 2');
TreeView1.Items.AddChild(TreeView1.Items.Item[0], 'цех 3');
{добавление корневого узла 'управление' после узла
'производство' (индекс 4) }
TreeView1.Items.Add(TreeView1.Items.Item[0], 'управление');
{добавление дочерних узлов 'администрация' и
'бухгалтерия' узла 'управление' }
TreeView1.Items.AddChild(TreeView1.Items.Item[4], 'администрация');
TreeView1.Items.AddChild(TreeView1.Items.Item[4], 'бухгалтерия');

Дерево может быть сколь угодно разветвленным. Например, следующие операторы добавляют дочерние узлы «бригада 1» и «бригада 2» в сформированный ранее узел «цех 1»:

TreeView1.Items.AddChild(TreeView1.Items.Item[1], 'бригада 1');
TreeView1.Items.AddChild(TreeView1.Items.Item[l], 'бригада 2');

Текст, связанный с некоторым узлом, можно найти с помощью его свойства Text. Например, TreeView1.Items.Item[1].Text — это надпись «цех 1».

С каждым узлом может быть связан некоторый объект. Добавление таких узлов осуществляется методами AddObject, AddObjectFirst, InsertObject, AddChildObject, AddChildObjectFirst, аналогичными приведенным выше, но содержащими в качестве параметра еще указатель на объект:


function AddObject(Node: TTreeNode; const S: string; Ptr: Pointer): TreeNode;Добавляет новый узел с текстом S и объектом Ptr как последний узел уровня, на котором расположен Node.
function AddObjectFirst(Node: TTreeNode; const S: string; Ptr: Pointer): TTreeNode;Вставляет новый узел с текстом S и объектом Ptr как первый из узлов уровня, на котором находится Node. Индексы последующих узлов увеличиваются на 1.
function InsertObject(Node: TTreeNode; const S: string; Ptr: Pointer): TTreeNode; Вставляет новый узел с текстом S и объектом Ptr сразу после узла Node на тот же уровень. Индексы последующих узлов увеличиваются на 1.
function AddChildObject(Node: TTreeNode; const S: string; Ptr: Pointer): TTreeNode;Добавляет узел с текстом S и объектом Ptr как последний дочерний узла Node.
function AddChildObjectFirst(Node: TTreeNode; const S: siring; Ptr: Pointer): TTreeNode;Вставляет новый узел с текстом S и объектом Ptr как первый из дочерних узлов узла Node. Индексы последующих узлов увеличиваются на 1.

Объект, связанный с некоторым узлом, можно найти с помощью его свойства Data. Например, TreeView1.Items.Item[1].Data.

Для удаления узлов имеется два метода: Clear, очищающий все дерево, и Delete(Node: TTreeNode), удаляющий указанный узел Node и все его узлы — потомки. Например, оператор

TreeView1.Items.Clear;
удалит в нашем примере все узлы, а оператор
TreeView1.Items.Delete(TreeView1.Items.Item[1]);
удалит узел «цех 1» и его дочерние узлы (если они имеются).

При удалении узлов, связанных с объектами, сами эти объекты не удаляются.

Реорганизация дерева, связанная с созданием или удалением многих узлов, может вызывать неприятное мерцание изображения. Избежать этого можно с помощью методов BeginUpdate и EndUpdate. Первый из них запрещает перерисовку дерева, а второй — разрешает. Таким образом, изменение структуры дерева может осуществляться по следующей схеме:

TreeView1.Items.BeginUpdate;
<операторы изменения дерева>
TreeView1.Items.EndUpdate;

Если метод BeginUpdate применен подряд несколько раз, то перерисовка дерева произойдет только после того, как столько же раз будет применен метод EndUpdate.

Среди свойств узлов следует отметить Count — число узлов, управляемых данным, т.е. дочерних узлов, их дочерних узлов и т.п. Если значение Count узла равно нулю, значит у узла нет дочерних узлов, т.е. он является листом дерева.

Вернемся к свойствам компонента TreeView. Важным свойством компонента TreeView является Selected. Это свойство указывает узел, который выделен пользователем. Пользуясь этим свойством можно запрограммировать операции, которые надо выполнить для выбранного пользователем узла. Если ни один узел не выбран, значение Selected равно nil. При выделении пользователем нового узла происходят события OnChanging (перед изменением выделения). В обработчик события передаются параметры Node: TTreeNode — узел, который выделен в данный момент, и var AllowChange: Boolean — разрешение на перенос выделения. Если в обработчике задать AllowChange = false, то переключение выделения не произойдет.

У компонента TreeView имеется свойство RightClickSelect, разрешающее (при значении равном true) выделение узла щелчком как левой, так и правой кнопкой мыши.

Ряд событий компонента TreeView связан с развертыванием и свертыванием узлов. При развертывании узла происходят события OnExpanding (перед развертыванием) и OnExpanded (после развертывания). В обработчики обоих событий передается параметр Node: TTreeNode — развертываемый узел. Кроме того в обработчик OnExpanding передается параметр var AllowExpansion: Boolean, который можно задать равным false, если желательно запретить развертывание. При свертывании узла происходят события OnCollapsing (перед свертыванием) и OnCollapsed (после свертывания). Так же, как и в событиях, связанных с развертыванием, в обработчики передается параметр Node: TTreeNode — свертываемый узел, а в обработчик OnCollapsing дополнительно передается параметр var AllowCollapse: Boolean, разрешающий или запрещающий свертывание.

Свойство ReadOnly компонента TreeView позволяет запретить пользователю редактировать отображаемые данные. Если редактирование разрешено, то при редактировании возникают события OnEditing и OnEdited, аналогичные рассмотренным ранее (в обработчике OnEditing параметр var AllowEdit: Boolean позволяет запретить редактирование).

Ряд свойств компонента TreeView: ShowButtons, ShowLines, ShowRoot отвечают за изображение дерева и позволяют отображать или убирать из него кнопки, показывающие раскрытия узла, линии, связывающие узлы, и корневой узел. Поэкспериментируйте с этими свойствами и увидите, как они влияют на изображение.

Свойство SortType позволяет автоматически сортировать ветви и узлы дерева. По умолчанию это свойство равно stNone, что означает, что дерево не сортируется. Если установить SortType равным stText, то узлы будут автоматически сортироваться по алфавиту. Возможно также проводить сортировку по связанным с узлами объектам Data (значение SortType равно stData), одновременно по тексту и объектам Data (значение SortType равно stBoth) или любым иным способом. Для использования этих возможностей сортировки надо написать обработчик события OnСompare, в который передаются, в частности, параметры Node1 и Node2 — сравниваемые узлы, и по ссылке передается целый параметр Compare, в который надо заносить результат сравнения: отрицательное число, если узел Node1 должен располагаться ранее Node2, 0, если эти узлы считаются эквивалентными, и положительное число, если узел Node1 должен располагаться в дереве после Node2. Например, следующий оператор в обработчике события OnCompare обеспечивает обратный алфавитный порядок расположения узлов:

Compare := — AnsiCompareText(Node1.Text, Node2.Text);

События OnCompare наступают после задания любого значения SortType, отличного от stNone, и при изменении пользователем свойств узла (например, при редактировании им надписи узла), если значение SortType не равно stNone. После сортировки первоначальная последовательность узлов в дереве теряется. Поэтому последующее задание SortType = stNone не восстанавливает начальное расположение узлов, но исключает дальнейшую генерацию событий OnCompare, т.е. автоматическую перестановку узлов, например, при редактировании их надписей. Если же требуется изменить характер сортировки или провести сортировку с учетом новых созданных узлов, то надо сначала задать значение SortType = stNone, а затем задать любое значение SortType, отличное от stNone. При этом будут сгенерированы новые обращения к обработчику событий OnCompare.

Имеются и другие возможности сортировки. Например, метод AlphaSort обеспечивает алфавитную последовательность узлов независимо от значения SortType, но при отсутствии обработчика событий OnCompare (если обработчик есть, то при выполнении метода AlphaSort происходит обращение к этому обработчику). Отличие метода AlphaSort от задания значения SortType = stText заключается в том, что изменение надписей узлов приводит к автоматической пересортировке дерева только при SortType = stText.

Мы рассмотрели основные возможности компонента TreeView. Компонент Outline похож на него. Структура дерева тоже содержится в свойстве Items и доступ к отдельным узлам также осуществляется через этот индексный список узлов. Но индексы начинаются с 1. Например, Outline1.Items[1] — это узел дерева с индексом 1 (первый узел дерева). Свойство Items имеет тип TOutlineNode. Его свойства и методы отличаются от свойств и методов типа узлов в TreeView. И заполняется структура дерева иначе: через свойство Lines типа TStrings. Редактор этого свойства можно вызвать, щелкнув на кнопке с многоточием около свойства Lines в окне Инспектора Объектов. Вы попадете в окно, в котором можете записать тексты всех узлов, делая отступы, чтобы выделить уровни узлов. Текст должен выглядеть так, как выше описывалось представление структуры в текстовом файле.

Пиктограммы, сопровождающие изображения узлов, задаются параметрами PictureOpen (пиктограмма развернутого узла), PictureClosed (пиктограмма свернутого узла), PictureMinus (пиктограмма символа «-» около развернутого узла, имеющего наследников), PicturePlus (пиктограмма символа «+» узла, имеющего наследников, но не развернутого), PictureLeaf (пиктограмма узла, не имеющего наследников — листа дерева). Основное отличие пиктограмм компонента Outline заключается в том, что они одинаковы для всех узлов одного типа, тогда как в TreeView можно задавать пиктограммы индивидуально для каждого узла.

Программно изменять структуру дерева можно с помощью методов Add, AddObject (добавление узла в дерево), Insert, InsertObject (вставка узла в заданную позицию), AddChild, AddChildObject (вставка дочернего узла), Delete (удаление узла).

Индекс выделенного пользователем узла можно определить через свойство SelectedItem. Если SelectedItem = 0, значит ни один узел не выделен. Текст выделенного узла определяется свойством Text: например,

OutLine1.Items[Outline1.SelectedItem].Text

Впрочем, тот же самый текст даст и выражение

OutLine1.Lines[Outline1.SelectedItem-1]

В заключение коротко рассмотрим компонент ListView. Он позволяет отображать в стиле Windows 95/98 данные в виде списков, таблиц, крупных и мелких пиктограмм. С подобным отображением все вы сталкиваетесь, раскрывая папки Windows.

Стиль отображения информации определяется свойством ViewStyle, которое может устанавливаться в процессе проектирования или программно во время выполнения. Свойство может принимать значения: vsIcon — крупные значки, vsSmallIcon — мелкие значки, vsList — список, vsReport — таблица. Что означает каждое из этих значений вы можете посмотреть в любой папке Windows на рабочем столе.

Рис. 3.21
Окно редактора списка объектов компонента ListView

Если вы хотите предоставить пользователю возможность изменять вид списка, сделайте следующее:

Основное свойство компонента, описывающее состав отображаемой информации — Items. Во время проектирования оно может быть установлено специальным редактором (рис. 3.21), вызываемом щелчком на кнопке с многоточием рядом с этим свойством в окне Инспектора Объектов. Оно похоже на окно, описанное для компонента TreeView (рис. 3.20). Точно так же в нем задаются новые узлы кнопкой New Item и дочерние узлы — кнопкой New SubItem. Только смысл дочерних узлов другой: это информация, которая появляется только в режиме vsReport — в виде таблицы.

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

Свойство Image Index определяет индекс пиктограммы. Индекс соответствует спискам изображений, хранящимся в отдельных компонентах ImageList (см. раздел 9.3). Указания на эти компоненты вы можете задать в свойствах LargeImages для режима vsIcon и SmallImages для режимов vsSmallIcon, vsList и vsReport. Индексы начинаются с 0. Если вы укажете индекс -1 (значение по умолчанию), пиктограммы изображаться не будут. Свойство State Index в панели Item Properties позволяет добавить вторую пиктограмму в данный объект. Подобная пиктограмма может просто служить дополнительной характеристикой объекта. Индекс, указываемый как State Index, соответствует списку изображений, хранящихся в отдельном компоненте ImageList, указанном в свойстве StateImages компонента ListView.

Свойство Columns определяет список заголовков таблицы в режиме vsReport при свойстве ShowColumnHeaders (показать заголовки), установленном в true. Свойство Columns можно задать в процессе проектирования специальным редактором заголовков, вызываемом двойным щелчком на компоненте ListView или щелчком на кнопке с многоточием рядом со свойством Columns в окне Инспектора Объектов. Редактор заголовков уже был нами рассмотрен (см. рис. 3.19).

Свойство Checkboxes определяет отображение индикатора с флажком около каждого элемента списка. Индикаторы можно устанавливать программно или их может изменять пользователь во время выполнения. Тогда узнать программно, установлен ли индикатор в некотором элементе Items[i], можно проверкой его свойства Checked. Например:

for i:=0 to ListView1.Items.Count-1 do
if ListView1.Items[i].Checked then
...;

Свойства HotTrack и HotTrackStyles определяют появление выделения при перемещении курсора над элементом списка и стиль этого выделения. Свойство HoverTime (только в Delphi 5) задает в миллисекундах задержку появления такого выделения.

Свойство списка Selected определяет выделенный пользователем элемент списка.

Свойство DragMode определяет режим перетаскивания элементов списка. Если вы хотите разрешить пользователю перетаскивать пиктограммы по площади списка в режимах vsIcon и vsSmallIcon, вам надо сделать следующее:

Метод Arrange:

procedure Arrange(Code: TListArrangement);
позволяет упорядочить пиктограммы в режимах vsIcon и vsSmallIcon. Параметр Code определяет способ упорядочивания:
arAlignBottomвыравнивание вдоль нижнего края области
arAlignLeftвыравнивание вдоль левого края области
arAlignRightвыравнивание вдоль правого края области
arAlignTopвыравнивание вдоль верхнего края области
arDefaultвыравнивание по умолчанию (вдоль верхнего края области)
arSnapToGridразмещение каждой пиктограммы в ближайшем узле сетки

В Delphi 5 введено еще одно свойство ListViewWorkAreas.

Это свойство определяет рабочую область (прямоугольную рамку), в которой осуществляется выравнивание пиктограмм в режимах vsIcon и vsSmallIcon. Свойство WorkAreas представляет собой индексированный список, аналогичный Items, но совершенно независимый от него. Если WorkAreas — пустой список (ни одна область в него не добавлена), то упорядочивание пиктограмм производится в пределах всей клиентской области ListView. Добавление новой рабочей области осуществляется методом Add. Свойство рабочей области Rect типа TRect (см. раздел 3.2) определяет границы области. Свойство Color определяет цвет рамки, обрамляющей область. Свойство DisplayName определяет подпись под рамкой. И рамка, и подпись отображаются только в случае, если свойство списка ShowWorkAreas установлено в true.

Пример операторов, создающих в площади списка две рабочие области:

ListView1.WorkAreas.Add;
ListView1.WorkAreas[0].Rect := Rect(0,0,
ListView1.ClientWidth div 4, ListView1.ClientHeight div 2);
ListView1.WorkAreas.Items[0].DispiayName := 'Область 0';
ListView1.WorkAreas.Items[0].Color := clRed;
ListView1.WorkAreas.Add;
ListView1.WorkAreas[1].Rect := Rect(ListView1.ClientWidth div 2,
0, (ListView1.ClientWidth div 4)*3, ListView1.ClientHeight div 2);
ListView1.WorkAreas.Items[1].DisplayName := 'Область 1';

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

Способ упорядочивания определяется соответствующим заданием свойства SortType, которое уже рассматривалось нами для компонента TreeView.


к началу страницы