Компоненты TTreeView w TListView
Эти компоненты известны каждому, кто хоть раз видел Windows 98 или Windows 2000. Именно на их базе создано ядро пользовательского интерфейса — оболочка Explorer, да и большинство других утилит Windows. Они включены в библиотеку ComCtl32.dll и доступны программистам.
Компонент TTreeView называют деревом
(рис. 5.3).
Компонент TTreeView — правопреемник компонента
TOutiine, разработанного Borland еще для Delphi 1 и предназначен для отображения иерархической информации. Его "сердцем" является свойство
property Items: TTreeNodes;
Рис. 5.3. Внешний вид
компонента TTreeView
Данное свойство — это список всех вершин дерева,
причем список, обладающий дополнительными полезными свойствами. Каждый из элементов
списка — это объект типа TTreeNode. Свойства
его сведены в табл. 5.3.
Таблица 5.3. Список свойств
объекта TTreeNode
|
|
property
HasChildren: Boolean;
|
Равно True,
если узел имеет дочерние узлы
|
|
Счетчик числа дочерних узлов данного
узла
|
property
Item [Index: Integer] : TTreeNode;
|
|
property
Parent: TTreeNode;
|
Ссылка на объект — родительский узел
(верхнего уровня)
|
|
Уровень, на котором находится узел.
Для корневого узла это свойство равно 0; его потомки имеют значение
Level=l и т. д.
|
|
|
|
Данные, связанные с узлом
|
property
TreeView: TCustomTreeView;
|
Ссылка на компонент TTreeView,
в котором отображается данный узел
|
|
Дескриптор окна компонента TTreeView,
в котором отображается данный узел
|
property
Owner: TTreeNodes;
|
Ссылка на компонент TTreeNodes,
которому принадлежит данный узел
|
|
Индекс узла в списке своего родителя
|
property
IsVisible: Boolean;
|
Равно True,
если узел видим (все его родительские узлы развернуты)
|
property
Itemld: HTreeltem;
|
Дескриптор узла (применяется при вызове
некоторых методов)
|
property
Absolutelndex: Integer;
|
Абсолютный индекс узла в списке корневого
узла
|
property
Imagelndex: Integer;
|
Индекс картинки, соответствующей невыбранному
узлу в нормальном состоянии
|
property
Selectedlndex: Integer;
|
Индекс картинки, соответствующей выбранному
узлу
|
property
Overlaylndex: Integer;
|
Индекс картинки, которая может накладываться
поверх основной
|
property
Statelndex: Integer;
|
Индекс дополнительной картинки, отражающей
состояние узла
|
property
Selected: Boolean;
|
Равно True,
если данный узел выбран пользователем
|
property
Focused: Boolean;
|
Равно True,
если данный узел выбран пользователем для редактирования текста узла
|
property
Expanded: Boolean;
|
Равно True,
если данный узел развернут (показываются его дочерние узлы)
|
Очень важным является свойство Data.
Вместе с каждым узлом можно хранить не только текст, но и любые данные. Необходимо
только помнить, что при удалении узла они автоматически не освобождаются, и
это придется сделать вручную.
Для добавления узлов в дерево используются десять
методов объекта TTreeNode (табл. 5.4).
Таблица 5.4. Методы, позволяющие
добавлять узлы в объект TTreeNode
|
|
function
Add (Node: TTreeNode; const S: string) : TTreeNode;
|
Узел добавляется последним в тот же
список, что и узел Node
|
function
AddObject (Node: TTreeNode; const S: string; Ptr: Pointer) : TTreeNode
;
|
To же, что и метод Add,
но с узлом связываются данные из параметра
Ptr
|
function
AddFirst (Node: TTreeNode; const S: string): TTreeNode;
|
Узел добавляется первым в тот же список,
что и узел Node
|
function
AddObjectFirst (Node: TTreeNode; const S: string; Ptr: Pointer) : TTreeNode;
|
То же, что и метод AddFirst,
но с узлом связываются данные из параметра Ptr
|
function
AddChildfNode: TTreeNode; const S: string): TTreeNode;
|
Узел добавляется последним в список
дочерних узлов узла Node
|
function
AddChildObject (Node: TTreeNode; const S: string; Ptr: Pointer) : TTreeNode;
|
То же, что и метод AddChild,
но с узлом связываются данные из параметра Ptr
|
function
AddChildFirst (Node: TTreeNode; const S: string): TTreeNode;
|
Узел добавляется первым в список дочерних
узлов узла Node
|
function
AddChildObjectFirst (Node: TTreeNode; const S: string; Ptr: Pointer)
: TTreeNode;
|
То же, что и метод AddChildFirst,
но с узлом связываются данные из параметра
Ptr
|
function
Insert (Node: TTreeNode; const S: string): TTreeNode;
|
Узел добавляется непосредственно перед
узлом Node
|
function
InsertObject (Node: TTreeNode; const S: string; Ptr: Pointer) : TTreeNode;
|
То же, что и метод insert,
но с узлом связываются данные из параметра
Ptr
|
Во всех этих методах параметр
s — это текст создаваемого узла. Место появления узла (первый или последний)
также зависит от состояния свойства TTreeView.SortType:
type TSortType
= (stNone, stData, stText, stBoth); property SortType: TSortType;
Если узлы дерева как-либо сортируются, то новые
узлы появляются сразу в соответствии с правилом сортировки. По умолчанию значение
этого свойства равно stNone.
Добавляя к дереву сразу несколько узлов, следует
воспользоваться парой методов BeginUpdate
И EndUpdate:
TreeViewl.Items.BeginUpdate;
ShowSubKeys(Root,1);
TreeViewl.Items.EndUpdate;
Они позволяют отключать и снова включать перерисовку
дерева на экране на момент добавления (удаления, перемещения) узлов и тем самым
сэкономить подчас очень много времени.
Помимо добавления узлов в дерево программным
способом можно сделать это и вручную во время разработки. При щелчке в Инспекторе
объектов на свойстве items запускается
специальный редактор (рис. 5.4).
Рис. 5.4. Внешний вид
редактора узлов компонента TTreeView
Внешний вид компонента TTreeview
может быть весьма основательно настроен под нужды пользователя. Свойство showButtor.s
отвечает за то, будут ли отображаться кнопки со знаком "+" и "—"
перед узлами, имеющими "потомство" (дочерние узлы). Щелчок на этих
кнопках позволяет сворачивать/разворачивать дерево дочерних узлов. В противном
случае делать это нужно двойным щелчком на узле или установить свойство
AutoExpand в значение True — тогда
сворачивание и разворачивание будет происходить автоматически при выделении
узлов. Свойство showLines определяет,
будут ли родительские и дочерние узлы соединяться видимыми линиями. Аналогично,
свойство showRoot определяет, будут ли
на рисунке соединяться между собой линиями корневые узлы (если их несколько).
При помощи свойства HotTrack можно динамически
отслеживать положение текущего узла: если оно установлено в значение True,
то текущий узел (не выделенный, а именно текущий — тот, над которым находится
курсор мыши) подчеркивается синей линией.
Наконец, для оформления дерева можно использовать
наборы картинок. Наборов два — в свойствах images
и stateimages. Напомним, что у каждого
узла-объекта TTreeNode есть свойства
Imagelndex И Statelndex,
а вдобавок еще и seiectedindex. Первая
картинка предназначена для отображения типа узла, а вторая — его состояния.
Можно сразу (при добавлении) указать номера изображений для того или иного случая,
а можно делать это динамически. Для этого существуют события:
type TTVExpandedEvent
= procedure(Sender: TObject; Node: TTreeNode)of object;
property OnGetlmagelndex:
TTVExpandedEvent;
property OnGetSelectedlndex:
TTVExpandedEvent;
Пример их использования дан в листинге 5.1 ниже
— в момент возникновения этих событий следует переопределить свойство
imageindex или Seiectedindex передаваемого
в обработчик события объекта TTreeNode.
Свойства stateindex
и stateimages можно порекомендовать для
имитации множественного выбора. Дело в том, что в отличие от TListview,
в TTreeView невозможно одновременно выбрать
несколько узлов. Вы можете отслеживать щелчки на узлах, и для выбранных устанавливать
значение stateindex в 1; под этим номером
в stateimages поместите, например, галочку.
Изложенная информация будет неполной, если не
рассказать, на какие события реагирует компонент TTreeView.
Большинство из них происходит парами — до наступления какого-то изменения и
после него. К примеру, возникновение события
onChanging означает начало перехода фокуса от одного узла к другому,
a Onchange — его завершение.
Четверка событий
type TTVCollapsingEvent
= procedure(Sender: TObject; Node: TTreeNode;
var AllowCollapse:
Boolean) of object;
type TTVExpandingEvent
= procedure(Sender: TObject; Node: TTreeNode;
var AllowExpansion:
Boolean) of object;
property OnExpanding:
TTVExpandingEvent;
property OnExpanded:
TTVExpandedEvent;
property OnCollapsing:
TTVCollapsingEvent;
property OnCollapsed:
TTVExpandedEvent;
сопровождает процесс свертывания/развертывания
узла, а пара
type TTVEditingEvent
= procedure(Sender: TObject; Node: TTreeNode;
var AllowEdit:
Boolean) of object;
property OnEditing:
TTVEditingEvent;
type TTVEditedEvent
= procedure(Sender: TObject; Node: TTreeNode;
var S: string)
of object;
property OnEdited:
TTVEditedEvent;
сопровождает редактирование его текста. Событие
onDeletion происходит при удалении узла.
Иногда нужно сравнивать узлы между собой — если вы хотите сделать это по своим
правилам, используйте событие oncompare.
Наконец, те, кому и приведенных возможностей
мало, могут сами рисовать на компоненте TTreeView.
У него есть свойство Canvas и четыре события
— OnCustomDraw, OnCustomDrawItem, OnAdvancedCustomDraw,
OnAdvancedCustomDrawItem.
Перейдем теперь к компоненту TListview.
Его еще называют расширенным списком. Действительно, в этот компонент заложено
столько, что он перекрывает все мыслимые и немыслимые задачи по отображению
упорядоченной однородной информации.
Начнем со свойства viewstyie:
type TViewStyle
= (vslcon, vsSmalllcon, vsList, vsReport);
property
ViewStyle: TViewStyle;
В зависимости от значения этого свойства кардинально
меняется внешний вид компонента. Описание значений приведено в табл. 5.5.
Таблица 5.5. Режимы отображения
компонента TListview
|
|
|
Элементы списка появляются в виде больших
значков с надписью под ними. Картинки для больших значков хранятся в
свойстве Largelmages. Возможно
их перетаскивание
|
|
Элементы списка появляются в виде маленьких
значков с надписью справа. Картинки для маленьких значков хранятся в
свойстве Smallimages. Возможно
их перетаскивание
|
|
Элементы списка появляются в колонке
один под другим с надписью справа. Перетаскивание невозможно
|
|
Элементы списка появляются в нескольких
колонках один под другим. В первой содержится маленький значок и надпись,
в остальных — определенная программистом информация. Если свойство ShowColumnHeaders
установлено в значение True, колонки
снабжаются заголовками
|
Как и для предыдущего компонента, элементы списка
содержатся в свойстве items. Это и есть
собственно список; ничего необычного, кроме методов добавления/удаления, там
нет. Каждый элемент списка (объект TListitem)
в свою очередь похож на компонент TTreeNode.
Но у него есть и важное отличие — он может стать носителем большого количества
дополнительной информации. Помимо свойства Data у него есть и свойство
property Subltems:
TStrings;
При помощи этого свойства с каждым элементом
списка может быть связан целый набор строк и объектов. Но как эти строки показать
пользователю?
Именно они должны, по замыслу разработчиков
этого элемента управления, отображаться в режиме отображения vsReport.
Сначала следует создать необходимое количество заголовков колонок (заполнив
свойство columns), учитывая, что первая
из них будет отведена под сам текст элемента списка (свойство caption).
Последующие же колонки будут отображать текст строк ИЗ свойства
Items . Subltems (рис. 5.5).
Рис. 5.5. Так будет располагаться
информация компонента TListView в режиме
vsReport
Элементы в списке могут быть отсортированы —
за это отвечает свойство SortType. Можно
отсортировать элементы не только по названию (это возможно при значении SortType,
равном stText), но и по данным (значения
stData и stBoth),
как это сделано в утилите Explorer. Для
реализации такой сортировки нужно обработать события OnColumnClick
И OnComparel
var ColNum
: Integer;
procedure TMainForm.ListViewlColumnClick(Sender:
TObject; Column:
TListColumn);
begin
ColNum := Column.Index;
ListViewl.AlphaSort;
end;
procedure TMainForm.ListViewlCompare(Sender:
TObject; Iteml, Item2:
TListltem;
Data: Integer; var Compare: Integer);
begin
if ColNum =
0 then // Заголовок
Compare :=
CompareStr(Iteml.Caption, Item2 .Caption);
else
Compare :=
CompareStr(Iteml.Subltems[ColNum-1],
Item2 .Subltems[ColNum-1]);
end;
Рассмотрим пример использования компонентов
TTreeview и TListview.
Где их совместное применение будет полезным? Выберем для этого отображение данных
системного реестра. С одной стороны, ключи в реестре упорядочены иерархически.
С другой, каждый из них может иметь несколько
разнотипных значений. Таким образом, мы почти
пришли к тому же решению, что и разработчики из Microsoft, создавшие утилиту
Registry Editor — слева дерево ключей,
справа — расширенный список их содержимого.
Конечно, нет смысла дублировать их труд. Здесь
мы ограничимся только просмотром реестра, затратив на это совсем немного усилий
— четыре компонента и пару десятков строк кода. Так выглядит главная (и единственная)
форма приложения Mini-Registry browser
(рис. 5.6).
Рис. 5.6.
Приложение Mini-Registry browser А вот и весь его исходный код:
Листинг 5.1. Приложение Mini-Registry-browser,
главный модуль
unit main;
interface
uses
Windows, Messages,
SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Grids,
Outline, ComCtrls, ImgList, ExtCtrls;
type
TForml = class(TForm)
TreeViewl: TTreeView;
ListViewl: TListView;
ImageListl:
TImageList;
Splitterl: TSplitter;
procedure FormCreate(Sender:
TObject);
procedure TreeViewlChange(Sender:
TObject; Node: TTreeNode);
procedure FormDestroy(Sender:
TObject);
procedure TreeViewlExpanded(Sender:
TObject; Node: TTreeNode);
procedure TreeViewlGetlmagelndex(Sender:
TObject; Node: TTreeNode);
private
{ Private declarations
}
public
{ Public declarations
}
procedure ShowSubKeys(ParentNode:
TTreeNode;depth: Integer);
function
GetFullNodeName(Node: TTreeNode):string;
end;
var
Forml: TForml;
implementation
uses registry;
{$R *.DFM}
var reg : TRegistry;
procedure TForml.FormCreate(Sender:
TObject);
var root
: TTreeNode;
begin
Reg := TRegistry.Create;
ListViewl.ViewStyle
:= vsReport;
with ListViewl
do
begin
with Columns.Add
do
begin
Width := ListViewl.Width
div 3-2;
Caption :=
'Name';
end;
with Columns.Add
do
begin
Width := ListViewl.Width
div 3*2-2;
Caption :=
'Value';
end;
end;
TreeViewl.Items.Clear;
Reg.RootKey
:= HKEY_LOCAL_MACHINE;
Root := TreeViewl.Items.Add(nil,'HKEY_LOCAL_MACHINE');
TreeViewl.Items.AddChildtroot,'');
end;
procedure TForml.FormDestroy(Sender:
TObject);
begin
Reg.Free;
end;
function TForml.GetFullNodeName(Node:
TTreeNode):string;
var CurNode
: TTreeNode;
begin
Result:='';
CurNode := Node;
while CurNode.Parentonil
do
begin
Result:= '\'+CurNode.Text
+ Result;
CurNode :=
CurNode.Parent;
end;
end;
procedure TForml.TreeViewlChange(Sender:
TObject; Node: TTreeNode);
var s:
string;
Keylnfo : TRegKeylnfo;
ValueNames
: TStringList;
i : Integer;
DataType :
TRegDataType;
begin
ListViewl.Items.Clear;
s:= GetFullNodeName(Node);
if not
Reg.OpenKeyReadOnly(s) then Exit;
Reg.GetKeylnfo(Keylnfo);
if Keylnfo.NumValues<=0
then Exit;
ValueNames
:= TStringList.Create;
Reg.GetValueNames(ValueNames);
for i
:= 0 to ValueNames.Count-1 do
with ListViewl.Items.Add
do
begin
Caption :=
ValueNames[i];
DataType :=
Reg.GetDataType(ValueNames[i]);
Case DataType
of
rdString: s
:= Reg.ReadString(ValueNames[i]);
rdlnteger: s:=
'0x'+IntToHex(Reg.Readlnteger(ValueNames[i]),8);
rdBinary: s:='Binary';
else s:= '???';
end;
Subltems.Add(s);
Imagelndex
:=1;
end;
ValueNames.Free;
end;
procedure TForml.ShowSubKeys(ParentNode:
TTreeNode;depth: Integer);
var ParentKey:
string;
KeyNames :
TStringList;
KeyInfo
: TRegKeylnfo;
CurNode : TTreeNode;
i : Integer;
begin
Cursor := crHourglass;
TreeViewl.Items.BeginUpdate;
ParentKey :=
GetFullNodeName(ParentNode);
if ParentKeyO1'
then
Reg.OpenKeyReadOnly(ParentKey)
else
Reg.OpenKeyReadOnly('\')
;
Reg.GetKeylnfo(Keylnfo)
;
if KeyInfo.NumSubKeys<=0
then Exit;
KeyNames
:= TStringList.Create;
Reg.GetKeyNames(KeyNames);
While ParentNode.GetFirstChildonil
do ParentNode.GetFirstChild.Delete;
if (KeyNames.Count>0)
then for i:=0 to KeyNames.Count-1 do
begin
Reg.OpenKeyReadOnly(ParentKey+'\'-t-KeyNames[
i ]) ;
Reg.GetKeylnfo(Keylnfo);
CurNode :=
TreeViewl.Items.AddChild(ParentNode,KeyNames[i];
if KeyInfo.NumSubKeys>0
then
begin
TreeViewl.Items.AddChild(CurNode,
'');
end;
end;
KeyNames.Free;
TreeViewl.Items.EndUpdate;
Cursor := crDefault;
end;
procedure TForml.TreeViewlExpanded(Sender:
TObject; Node: TTreeNode);
begin
ShowSubKeys(Node,1);
end;
procedure TForml.TreeViewlGetlmagelndex(Sender:
TObject; Node: TTreeNode);
begin
with Node do
begin
if Expanded
then Imagelndex := 2
else Imagelndex
:= 3;
end;
end;
end.
Для работы с системным реестром используется
объект VCL TRegistry, удачно инкапсулирующий
все предназначенные для этого функции Windows API. В обработчике события
OnCreate главной формы создается объект
Reg, а также к списку Listview1
добавляются два заголовка (свойство Columns).
Пояснений требует принцип построения дерева
ключей. Во-первых, это приложение отображает только один из системных ключей
(а именно HKEY_LOCAL_MACHINE); при желании
его можно заменить или добавить остальные. Во-вторых, попытка построить все
"развесистое" дерево ключей сразу займет слишком много времени и наверняка
не понравится пользователям. Вспомним, ведь утилита
Registry Editor работает довольно
быстро. Значит, придется строить дерево динамически — создавать и показывать
дочерние узлы в момент развертывания родительского узла. Для этого используется
событие OnExpand компонента
TreeView1.
Остановимся на секунду. А какие узлы помечать
кнопкой разворачивания (с пометкой "+"), ведь у родительского узла
еще нет потомков? Выход из положения такой — в момент построения ключа проверить,
есть ли у него дочерние. Если да, то к нему добавляется один (фиктивный) пустой
ключ. Его единственная роль — дать системе поставить "+" против родительского
узла.
Когда же пользователь щелкнул на кнопке, отмеченной
знаком "+", и родительский узел разворачивается, фиктивный дочерний
узел удаляется и вместо него создаются узлы настоящие, полученные путем сканирования
реестра (см. метод ShowSubKeys).
Снабдим узлы картинками. Для этого в компонент
imageList1 поместим картинки, соответствующие
открытой и закрытой папкам. Напомним, что для отрисовки и смены картинок есть
специальные события — OnGetlmageIndex И
OnGetSelectedIndex. В данном примере у
двух ЭТИХ событий один обработчик: развернутому узлу он сопоставляет картинку
раскрытой папки, а свернутому — закрытой.
В заключение нужно сказать об очень важной особенности
компонента TListview. Когда он отображает
большой объем информации, обработка данных может затянуться очень и очень надолго
и занять слишком много памяти. Выход — перевести список в так называемый виртуальный
режим. Он применяется для тех случаев, когда элементов в списке слишком много
и хранить их там невозможно из соображений экономии времени или памяти. Выход
из положения прост:
1. Переводим компонент в виртуальный режим установкой
свойства OwnerData в значение True.
2. Сообщаем списку сколько в нем должно быть
элементов установкой нужного значения items.Count.
3. Чтобы предоставить нужные данные, программист
должен предусмотреть обработку событий OnData,
OnDataFind, OnDataHint и OnDataStateChange.
Как минимум нужно описать обработчик события OnData.
TLVOwnerDataEvent
= procedure(Sender: TCbject; Item: TListltem) of object;
Вам передается объект TListitem,
и внутри обработчика события OnData
необходимо динамически "оформить" его — полностью, от заголовка
до картинок.
Возникает это событие перед каждой перерисовкой
списка. Так что, если сбор данных для вашего списка занимает более или менее
продолжительное время, лучше не связывать его с событием
OnData — перерисовка сильно затянется. К тому же в виртуальном режиме
сортировать список невозможно.
Borland прилагает к Delphi 7 прекрасный пример
к вышесказанному — Virtual Listview.
К нему и отсылаем заинтересованного читателя.
Примечание
Ответы на вопросы по компоненту
TListview можно найти сразу в двух местах: "родном" файле
справки d7vcl.hlp и файле справки Windows Win32.hip. Во втором из них информация
содержится в виде описания сообщений, посылаемых окну класса Listview,
и соответствующих им макросов. Некоторые из них позволят вам расширить функциональные
возможности компонента TListview. Эти
макросы содержатся в файле CommCtrl.pas.
Содержание раздела