Глава 11

Элементы управления Win32

Элементы управления составляют суть пользовательского интерфейса Windows. Всеми программами нужно управлять более или менее единообразно, поэтому в составе ОС имеется набор типовых кнопок, редактирующих элементов, списков выбора и т. п., которыми вы можете "украсить" свои разработки. Перечень этот постоянно пополняется. Во-первых, не дремлет фирма Microsoft. С новыми версиями ее продуктов (в первую очередь это MS Internet Explorer) поставляются новые элементы управления; содержатся они в библиотеке COMCTL32.DLL. Во-вторых, на ниве их создания подвизаются многочисленные сторонние фирмы, оформляющие свои элементы управления в виде элементов ActiveX (файлов осх). И в-третьих, некоторое количество элементов написано прямо на Delphi — как в фирме Borland (Inprise), так и независимыми разработчиками.

Элементам управления, пришедшим из состава Windows, начиная с Delphi 3, посвящена отдельная страница в Палитре компонентов под названием Win32. Их количество (и возможности!) постоянно растут. В этой главе будут рассмотрены основные и появившиеся в Delphi 5 элементы.

Компонент TToolBar

Возможность создать панель инструментов появилась у разработчика давно, начиная с первых версий Delphi. Тогда она была реализована с помощью сочетания компонентов TPanel и TSpeedButton. Так можно было поступить и сейчас; но панель инструментов получила развитие с появлением стандартного элемента управления TToolBar, который объединяет расположенные на нем кнопки и другие элементы управления и централизованно управляет ими.

Для других элементов управления, помещаемых на TToolBar, создается невидимая кнопка, обеспечивающая взаимодействие между ними и панелью. Но не со всеми из них "все гладко". Так, компонент TSpinEdit масштабируется и позиционируется неправильно. Вместо него следует применять пару TEdit+TUpDown.

Все кнопки (класс TToolButton) на панели инструментов имеют одинаковый размер, задаваемый свойствами:

property ButtonWidth: Integers;

property ButtonHeight: Integer;

На каждой кнопке могут отображаться два ее атрибута — текст (заголовок кнопки, свойство caption) и картинка. Показ текста можно запретить установкой значения False для свойства showCaptions.

Панель инструментов тяжело себе представить без украшающих ее картинок. У TToolBar целых три свойства, ссылающихся на списки картинок:

property Images: TCustomImageList;

property Disabledlmages: TCustomImageList;

property Hotlmages: TCustomImageList;

В обычном состоянии на кнопках отображаются картинки из набора, указанного свойством images. Если кнопка неактивна (свойство Enabled обращено в False), надпись на кнопке отображается серым цветом и на ней показывается картинка из свойства Disabledlmages.

Если значение свойства

property Fiat: Boolean;

установлено равным True, внешний вид панели инструментов становится более "модным" —- плоским. В этом случае границы кнопок не видны, и все они выглядят как набор надписей и рисунков на единой плоской панели. Границы становятся видны, только когда над кнопкой находится указатель мыши. Можно при этом изменить и внешний вид кнопки. Если задано значение свойства Hotlmages, то на текущей кнопке обычная картинка из images меняется на картинку из Hotlmages. Посмотрите, например, на панель Microsoft Internet Explorer 4 и старше — там все картинки на кнопках серые, но кнопка, к которой подведен указатель мыши, становится цветной.

Возможность сделать панель инструментов плоской появилась в версии 4.70 библиотеки COMCTL32.DLL. Распространяя приложение, не забудьте поставить с дистрибутивами эту библиотеку нужной версии. Кстати, это касается не только TToolBar, но и большинства компонентов, описанных в этой главе.

Текст и картинка на кнопке могут располагаться друг относительно друга двумя способами, в зависимости от значения свойства List. Если значение List равно False (установка по умолчанию), то картинка располагается сверху, текст — снизу. В противном случае вы увидите текст справа от картинки.

Панели инструментов часто располагаются на контейнерах — компонентах TCooiBar или TControiBar. Те, как правило, имеют свою фоновую картинку, и располагающийся сверху TToolBar можно сделать прозрачным. За это отвечает свойство:

property Transparent: Boolean;

Особенно удобно использовать его, если установлен режим плоской панели — в этом случае прозрачен не только фон самой панели, но и всех кнопок на ней.

Перейдем к рассмотрению функциональных возможностей кнопок. Вы думаете, "функциональные возможности" — это громко сказано? С одной стороны, да: кнопка — это то, на что нажимает пользователь, и не более того. Главное событие и для кнопки TTooiButton и для панели TToolBar — событие onClick. Кроме него, они могут отреагировать только на перемещение мыши и на процессы перетаскивания/переноса (Drag-and-Drop, Drag-and-Dock, описанные в следующей главе).

С другой стороны, кнопки можно нажимать в разнообразных вариантах и сочетаниях. Ключ к выбору варианта — свойство style объекта TTooiButton:

type TToolButtonStyle = (tbsButton, tbsCheck, tbsDropDown, tbsSeparator, tbsDivider) ;

property Style: TToolButtonStyle;

Стили tbsSeparator и tbsDivider предназначены для оформления панели и представляют собой пустое место и вертикальный разделитель, соответственно. Обычная кнопка — это, понятное дело, стиль tbsButton.

Если вы хотите создать одну или несколько кнопок, "залипающих" после нажатия, выберите стиль tbsCheck. После щелчка такая кнопка остается в нажатом состоянии до следующего нажатия. Об ее состоянии говорит свойство:

property Down: Boolean;

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

property Grouped: Boolean;

Такая группа называется группой с зависимым нажатием. Если на панели инструментов есть ряд расположенных подряд кнопок с Styie=tbscheck и Grouped=True, то он будет обладать свойствами группы с зависимым нажатием. Если групп зависимых кнопок должно быть две и более, разделить их между собой можно кнопкой другого стиля (например, tbsSeparator или tbsDivider) или любым другим элементом управления (рис. 11.1).

В такой группе всегда должна быть нажата хотя бы одна кнопка; на этапе разработки установите значение True для ее свойства Down. Но если это вам не подходит, можно установить значение свойства

property AllowAlIUp: Boolean;

равным True — и можно отжимать все кнопки. Значение этого свойства всегда одинаково для всех кнопок в группе.

Рис. 11.1. Несколько групп кнопок с зависимым нажатием на панели инструментов

Если в какой-то ситуации одна или несколько кнопок должны стать недоступными, для этого можно установить значение False для свойства Enabled. Но у кнопок в группе есть еще и третье состояние — неопределенное:

property Indeterminate: Boolean;

Такие кнопки выделяются серым цветом, чтобы показать пользователю, что их выбирать не следует. Переход в состояние indeterminate=True все еще позволяет кнопке обрабатывать событие onciick, но при этом она переходит в отжатое состояние (Down=False). Но — только до следующего нажатия. После него кнопка выходит из состояния Indeterminate.

Свойство

property Marked: Boolean;

отображает поверхность кнопки синим цветом (точнее, цветом clHighlight), как у выделенных объектов. В отличие от предыдущего случая с indeterminate, кнопка остается в состоянии Marked независимо от нажатий, вплоть до присвоения этому свойству значения False.

Ниже приведен фрагмент программы, с помощью которого можно выделить кнопки на панели при помощи мыши. Приведенные ниже обработчики событий нужно присвоить всем кнопкам панели, и самой панели TToolBar:

var StartingPoint : TPoint;

Selecting : boolean;

procedure TForml.ToolBarlMouseDownfSender: TObject; Button: TMouseButton;

Shift: TShiftState;

X, Y: Integers); begin

StartingPoint := (Sender аз TControi).ClientToScreen(Point(X,Y));

Selecting := True;

end;

procedure TFormI.ToolBarlMouseUp(Sender: TObject;

Button: TMouseButton;

Shift: TShiftState;

X, Y: Integer);

var i: Integer;r,r0 : TRect;

begin if Selecting then begin r.TopLeft := StartingPoint;

r.BottomRight := (Sender as TControi).ClientToScreen(Point(X,Y));

with ToolBarl do for i :=- 0 to ButtonCount-1 do begin rO :^Buttons[i].ClientRect;

OffsetRect(rO,Buttons[i].ClientOrigin.X,

Buttons[i].ClientOrigin.Y);

if IntersectRect(rO,r,rO) then Buttons[i].Marked := True;

end;

end;

Selecting := False;

end;

Наличие обработчиков событий onMouseDown/onMouseUp не мешает нажатию кнопок — нажатие все равно вызывает событие onClick.

Компонент TToolBar может стать полноценной заменой меню (взгляните хотя бы на приложения из состава MS Office 97 или 2000). К каждой из кнопок можно присоединить меню — и не одно, а целых два:

property DropdownMenu: TPopupMenu;

property PopupMenu: TPopupMenu;

Для того чтобы по нажатию левой кнопки мыши выпадало меню

DropdownMenu, Нужно установить один из стилей кнопок — tbsButton или tbsDropdown. В первом случае меню возникнет при нажатии в любой части кнопки. При этом событие OnClick не возникает; кнопка из-за этого становится "неполноценной" — она пригодна только для показа меню. Второй случай — стиль tbsDropdown — специально предназначен для удобства работы с выпадающими меню. Меню возникает при щелчке на специальной области с изображением треугольника в правой части кнопки. А вот щелчок на остальной части кнопки, как обычно, вызовет событие OnClick.

Можно сделать панель инструментов плавающей — как том же MS Office. О реализации механизма переноса (Docking), призванного помочь в этом, говорится в следующей главе.

Компонент TImageUst

С ростом возможностей пользовательского интерфейса Windows все больше и больше элементов управления стали оснащаться картинками. И вот, для централизованного управления этими картинками появился элемент управления TimageList. Он представляет собой оболочку для создания и использования коллекции одинаковых по размеру и свойствам изображений. На этапе разработки ее "начиняют" картинками (с Delphi для этих целей поставляется целая подборка, находящаяся в каталоге \ images). Компонент TimageList обладает двумя свойствами — images и imageindex. Первое указывает на список (компонент TimageList), второе — на конкретную картинку в этом списке.

Проще всего заполнить список при помощи встроенного редактора (рис. 11.2), выполнив двойной щелчок на компоненте или выбрав пункт Image List Editor в его контекстном меню.

Рис. 11.2. Редактор списка изображений TimageList

Пользоваться им очень просто, но нужно обратить внимание на одну тонкость. Только что выбранное изображение можно отредактировать, изменив его положение относительно отведенного ему прямоугольника: Crop (размещение начиная с точки (0,0)), Stretch (масштабирование) или Center (центровка). Кроме того, можно изменить прозрачный цвет (Transparent Color). Точки с этим цветом при отрисовке не будут видны (прозрачны). Его можно выбрать либо из списка либо мышью, щелкнув в нужном месте на увеличенной картинке в верхнем левом углу редактора. Если редактор уже записал изображение в список, редактирование этих свойств становится невозможным. Запись происходит, например, при закрытии редактора. Кроме того, размер картинок (свойства Height и width) нужно установить заранее. Если компонент настроен на размер 16х16, а вы пытаетесь наполнить его картинками 32х32, они будут сжаты и потеряют во внешнем виде.

Можно сильно упростить подбор картинок для TimageList. Если просмотреть ресурсы приложений из состава MS Office, да и многих других пакетов, то можно обнаружить, что картинки, которые встречаются на панелях инструментов, "склеены" между собой (рис. 11.3). Для просмотра ресурсов можно использовать, к примеру, приложение Resxplor, поставляемое в качестве примера с Delphi 5.

Рис. 11.3. Такие картинки часто используют для панелей инструментов

Их удобно использовать и в собственных программах. Кроме того, со времен Delphi 3 известна следующая ошибка разработчиков Microsoft: в разньк версиях библиотеки COMCTL32.DLL запись и чтение картинок при сохранении осуществлялась по-разному; если вы заполнили список во время разработки, скомпилировали приложение и запустили его на машине с другой версией библиотеки COMCTL32, вполне вероятно, что список окажется пустым.

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

  1. Создать исходный файл ресурсов, куда нужно включить и поименовать требуемые файлы с расширением bmp, к примеру:
  2. inout BITMAP "inout.bmp"

    tools BITMAP "tools.bmp"

    Сохранить этот файл с расширением rc, скажем, bitmap, rс.

  3. Скомпилировать ресурсы при помощи утилиты Ьгсс32.ехе, поставляемой с Delphi:
  4. C:\Program Files\Borland\Delphi5\bin\brcc32 bitmap.re

  5. Появившийся файл bitmap, res нужно включить в состав проекта. Для этого используется директива $R: {$R bitmap.res}
  6. Теперь картинка содержится в ресурсах и будет включена в состав исполняемого файла. Осталось загрузить ее в компонент TimageList. Для этого используется метод ResourceLoad: ImageListl.ResourceLoadfrtBitmap,'bitmaps',TCoior (0));

При этом произойдет автоматическая "нарезка" картинок в соответствии со свойствами width и Height. Если размер большой картинки, к примеру, 256х16 пикселов, а ширина, заданная свойством TimageList, равна 16 пикселов, то в список будут включены 16 элементов размером 16х16. Поэтому еще во время разработки нужно правильно настроить размеры в компоненте TimageList, иначе после загрузки ресурса картинки будут разрезаны как попало.

Есть и другой метод загрузки — FileLoad:

function FileLoad(ResType: TResType; Name: string; MaskColor: TColor):

Boolean;

Аналогичным путем он позволяет загружать картинки из любого пригодного файла. Но загрузка из файла менее надежна — нет гарантии, что у пользователя вашего приложения нужный файл всегда находится на месте и он не изменен.

Описанный выше редактор списка картинок "умеет" делать их прозрачными еще во время разработки. Часто бывает необходимо сделать прозрачными картинки, загружаемые из файлов во время исполнения. Для этого нужно использовать их свойство Transparent:

Var bmp: TBitmap;

bmp.LoadFromFile('с:\test.bmp');

bmp.Transparent := True;

ImageListl.AddMaskedfbmp,bmp.TransparentColor);

В методе AddMasked нужно вторым параметром указать "прозрачный" (фоновый) цвет, который в данном случае равен bmp. TransparentColor.

Как элемент управления Win32, TimageList имеет собственный дескриптор:

property Handle: HImageList;

Не следует путать этот дескриптор с дескрипторами растровых картинок, входящих в состав списка. В файле commctrl . раз приведены прототипы всех функций для работы с этим элементом управления, и для их вызова необходимо значение свойства Handle. Обратитесь к ним, если опубликованных свойств TimageList вам недостаточно.

Компоненты TTreeView и TListView

Эти компоненты известны каждому, кто хоть раз видел Windows 95 или NT 4.0. Именно на их базе создано ядро пользовательского интерфейса — оболочка Explorer, да и большинство других утилит Windows. Они включены в COMCTL32.DLL и доступны программистам.

Он — правопреемник компонента TOutline, разработанного Borland еще для Delphi 1 и предназначен для отображения иерархической информации. Его "сердцем" является свойство

property Items: TTreeNodes;

Это — список всех вершин дерева, причем список, обладающий дополнительными полезными свойствами. Каждый из элементов списка — это объект типа TTreeNode. Свойства его сведены в табл. 11.1.

Таблица 11.1. Список свойств объекта TTreeNode

Название

Описание

property HasChildren: Boolean;

Равно True, если узел имеет дочерние узлы

property Count: Integer;

Счетчик числа дочерних узлов данного узла

property Item[Index: Integer]: TTreeNode;

Список дочерних узлов

property Parent: TTreeNode;

Ссылка на объект-родительский узел (верхнего уровня)

property Level: Integer;

Уровень, на котором находится узел. Для корневого уз(па это свойство равно 0; его потомки имеют значение Level=1 и т. д.

property Text: string;

Текст узла

property Data: Pointer;

Данные,связанные с узлом

property TreeView: TCustomTreeView;

Ссылка на компонент TTreeview, в котором отображается данный узел

property Handle: HWND;

Дескриптор окна компонента Ttreeview, в котором отображается данный узел

property Owner: TTreeNodes;

Ссылка на компонент TTreeNodes, которому принадлежит данный узел

property Index: Longint;

Индекс узла в списке своего родителя

property IsVisible: Boolean;

Равно True, если узел видим (все его родительские узлы развернуты)

property Itemid: HTreeItem;

Дескриптор узла (применяется при вызове некоторых методов)

property Absolutelndex: Integer;

Абсолютный индекс узла в списке корневого узла

property Imagelndex: Integer;

Индекс картинки, соответствующей невыбранному узлу в нормальном состоянии

property Selectedlndex: Integer;

Индекс картинки, соответствующей выбранному узлу

property Overlaylndex: Integer;

Индекс картинки, которая может накладываться поверх основной

property Statelndex: Integers-

Индекс дополнительной картинки, отражающей состояние узла

property Selected: Boolean;

Равно True, если данный узел выбран пользователем

property Focused: Boolean;

Равно True, если данный узел выбран пользователем для редактирования текста узла

property Expanded: Boolean;

Равно True, если данный узел развернут (показываются его дочерние узлы)

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

Для добавления узлов в дерево используются десять методов объекта TTreeNode (табл. 11.2).

Таблица 11.2. Методы, позволяющие добавлять узлы В объект TTreeNode

Метод

Описание

function Add(Node: TTreeNode;

const S:string): TTreeNode;

Узел добавляется последним в тот же список, что и узел Node

function AddObject(Node: TTreeNode; const S: string; Ptr: Pointer): TTreeNode;

To же, что и function Add, но с узлом связываются данные из параметра Ptr

function AddFirst(Node: TTreeNode; const S: string): TTreeNode;

Узел добавляется первым в тот же список, что и узел Node

function AddObjectFirst(Node: TTreeNode; const S: string; Ptr: Pointer); TTreeNode;

То же, что и function AddFirst, но с узлом связываются данные из параметра Ptr

function AddChild(Node: TTreeNode; const S: string): TTreeNode;

Узел добавляется последним в список дочерних узлов узла Node

function AddChildObject(Node: TTreeNode; const S: string; Ptr: Pointer): TTreeNode;

То же, что и function Addchild, но с узлом связываются данные из параметра Ptr

function AddChildFirst(Node: TTreeNode; const S: string): TTreeNode;

Узел добавляется первым в список дочерних узлов узла Node

function AddChildObject-First(Node: TTreeNode; const S: string; Ptr: Pointer): TTreeNode;

То же, что и function AddChildFirst,но с узлом связываются данные из параметра Ptr

function Insert(Node: TTreeNode; const S: string): TTreeNode;

Узел добавляется непосредственно перед узлом Node

function InsertObject(Node: TTreeNode; const S: string; Ptr: Pointer): TTreeNode;

То же, что и function 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 запускается специальный редактор (рис. 11.4).

Рис. 11.4. Внешний вид редактора узлов компонента TTreeView

Внешний вид компонента TTreeView может быть весьма основательно настроен под нужды пользователя. Свойство showButtons отвечает за то, будут ли показываться кнопки со значками "+" и "-" перед узлами, имеющими "потомство" (дочерние узлы). Щелчок на этих кнопках позволяет сворачивать/ разворачивать дерево дочерних узлов. В противном случае делать это нужно двойным щелчком на узле или установить значение свойства AutoExpand равным True — тогда сворачивание и разворачивание будет происходить автоматически при выделении узлов. Свойство showLines определяет, будут ли родительские и дочерние узлы соединяться видимыми линиями. Аналогично, showRoot определяет, будут ли на рисунке соединяться между собой линиями корневые узлы (если их несколько). При помощи свойства HotTrack можно динамически отслеживать положение текущего узла: если оно установлено в True, то текущий узел (не выделенный, а именно текущий — тот, над которым находится курсор мыши) подчеркивается синей линией.

Наконец, для оформления дерева можно использовать наборы картинок. Их два — в свойствах Images и Statelmages. Напомним, что у каждого узла объекта TTreeNode есть свойства imageindex и stateindex, а вдобавок еще и selectedindex. Первая картинка предназначена для отображения типа узла, а вторая — его состояния. Можно сразу (при добавлении) указать номера изображений для того или иного случая, а можно делать это динамически. Для этого существуют события:

type TTVExpandedEvent = procedure(Sender: TObject; Node: TTreeNode) of

object;

property OnGetImageIndex: TTVExpandedEvent;

property OnGetSelectedIndex: TTVExpandedEvent;

Пример их использования дан в листинге 11.1 ниже — в момент возникновения этих событий следует переопределить свойство image index или 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 OnCollaps ing: 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 objects; property OnEdited: TTVEdiledEvent;

— редактирование его текста. Событие onDeietion происходит при удалении узла. Иногда нужно сравнивать узлы между собой — если вы хотите сделать это по своим правилам, используйте событие OnCompare.

Наконец, те, кому и приведенных возможностей мало, могут сами рисовать на компоненте TTreeview. У него есть свойство canvas и четыре

события — OnCustomDraw, OnCustomDrawItem, OnAdvancedCustomDraw, OnAdvancedCustomDrawItcm.

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

Начнем со свойства viewStyle:

type TViewStyle = (vslcon, vsSmallIcon, vsLisL, vsReport);

property ViewStyle: TViewStyle;

В зависимости от значения этого свойства, кардинально меняется внешний вид компонента. Описание значений приведено в табл. 11.3.

Таблица 11.3. Режимы отображения компонента TListview

Значение

Описание

vslcon

Элементы списка появляются в виде больших значков с надписью под ними. Картинки для больших значков хранятся в свойстве Largelmages. Возможно их перетаскивание

VsSmallIc

on

Элементы списка появляются в виде маленьких значков с надписью справа. Картинки для маленьких значков хранятся в свойстве Smalllmages. Возможно их перетаскивание.

vsList

Элементы списка появляются в колонке один под другим с надписью справа. Перетаскивание невозможно.

vsReport

Элементы списка появляются в нескольких колонках один под другим. В первой содержится маленький значок и надпись, в остальных — определенная программистом информация. Если значение свойства ShowColumnHeaders установлено равным True, колонки снабжаются заголовками.

Как и для предыдущего компонента, элементы списка содержатся в свойстве items. Это и есть собственно список; ничего необычного, кроме методов добавления/удаления, там нет. Каждый элемент списка, объект TListitem, в свою очередь, похож на TTreeNode. Но у него есть и важное отличие — он может стать носителем большого количества дополнительной информации. Помимо свойства Data, у него есть и свойство

property Subltems: TStrings;

При помощи этого свойства с каждым элементом списка может быть связан целый набор строк и объектов. Но как эти строки показать пользователю? Именно они должны, по замыслу разработчиков этого элемента управления, отображаться в режиме отображения vsReport. Сначала следует создать необходимое количество заголовков колонок (заполнив свойство columns), учитывая, что первая из них будет отведена под сам текст элемента списка (свойство caption). Последующие же колонки будут отображать текст строк из свойства Items. Subltems (рис. 11.6).

Элементы в списке могут быть отсортированы — за это отвечает свойство sortType. Можно отсортировать элементы не только по названию (это возможно при значении SortType, равном stText), но и по данным (значения stData и stBoth), как это сделано в утилите Explorer. Для реализации такой сортировки нужно обработать события OnColumnciick и oncompare:

var ColNum : Integer;

procedure TMainForm.ListViewColumnClick(Sender: TObject;

Column:

TListColumn);

begin

ColNum := Column.Index;

ListViewl.AlphaSort;

end;

procedure TMainForm-ListViewlCompare(Sender: TObject;

Iteml, Item2:TListItem;

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 [ColNurci-1]);

end;

Таблица. 11.4. Так будет располагаться информация компонента TListView в режиме vsReport

Columns[0]

Columns[1]

 …

 

Columns[m+1]

Items[0].Caption

Items[0].Subltems [0 ]

 

 

Items[0].Subltems [m]

Items[1].Caption

Items[1].Subltems [ 0 ]

 

 

Items[1].Subltems[m]

 

 

  

 

 

 

 

Items[n].Caption

Items[n].Subltems [0 ]

 

 

Items[n].Subltems[m]

 Рассмотрим пример использования компонентов TTreeView и TListView. Где их совместное использование будет полезным? Выберем для этого отображение данных системного реестра. С одной стороны, ключи в реестре упорядочены иерархически. С другой, каждый из них может иметь несколько разнотипных значении. Таким образом, мы почти пришли к тому же решению, что и разработчики из Microsoft, создавшие утилиту Registry Editor — слева дерево ключей, справа — расширенный список их содержимого.

Конечно, нет смысла дублировать их труд. Здесь мы ограничимся только просмотром реестра, затратив на это совсем немного усилий — четыре компонента и пару десятков строк кода. Так выглядит главная (и единственная) форма приложения Mini — Registry Browser.

А вот и весь его исходный код:

 

Листинг 11.1 Приложение Mini Registry Browser, главный модуль

unit main;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, Grids, Outline, ComCtrls, ImqList, ExtCtrls;

type

TFormI = class(TForm) TreeViewl: TTreeView;

ListViewl: TListView;

ImageListI: TImageList;

Splitteri: TSplitter;

procedure FormCreate(Sender: TObject);

procedure TreeViewlChange(Sender: TObject; Node: TTreeNode);

procedure FormDestroy(Sender: TObject);

procedure TreeViewlExpanded(Sender: TObject; Node: TTreeNode);

procedure TreeViewlGetImageIndex(Sender: TObject; Node: TTreeNode);

private

{ Private declarations } public

( Public declarations } procedure ShowSubKeys(ParentNode: TTreeNode;depth: Integer);

function GetFullNodeName(Node: TTreeNode):string;

end;

var

Formi: TFonnl;

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 Colunms.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.AddChild(root,' ');

end;

procedure TFormI.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 TFormI.TreeViewlChange(Sender: TObject; Node: TTreeNode) ;

var s: string;

Keylnfo : TRegKeyInfo;

ValueNames : TStringList;

i : Integer;

DataType : TRegDataType;

begin

ListViewl.Items.Clear;

s:= GetFullNodeName(Node);

if not Reg.OpenKeyReadOnly(s) then Exit;

Reg.GetKeyInfo(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:= 'Ox'+IntToHex(Reg.Readlnteger

(ValueNames[i]),8);

rdBinary: s:='Binary';

else s:= '???';

end;

SubItems.Add(s) ;

Imagelndex :=1;

end;

ValueNames.Free ;

end;

procedure TFomI.ShowSubKeys(ParentNode: TTreeNode;depth: Integer);

var ParentKey: string;

KeyNames : TStringList;

Keylnfo : TRegKeyInfo;

CurNode : TTreeNode;

i : Integer;

begin Cursor := crHourglass;

TreeViewl.Items.BeginUpdate ;

ParentKey := GetFullNodeName(ParentNode) ;

if ParentKeyo'' then

Reg.OpenKeyReadOnly(ParentKey) else Reg.OpenKeyReadOnly('\');

Reg.GetKeyInfo(Keylnfo) ;

if KeyInfo.NuinSubKeys<=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+'\'

+KeyNames[i]);

Reg.GetKeyInfo(Keylnfo);

CurNode := TreeViewl.Items.AddChild

(ParentNode,KeyNames[i]);

if Keylnfo.Num3ubKeys>0 then

begin TreeViewl.Items.AddChild(CurNode,'');

end;

end;

KeyNames.Free;

TreeViewl.Items.EndUpdate;

Cursor := crDefault;

end;

procedure TFormI.TreeViewlExpanded(Sender: TObject;

Node: TTreeNode);

begin

ShowSubKeys(Node,1);

end;

procedure TFormI.TreeViewlGotImageIndex(Sender: TObject;

Node:TTreeNode);

begin

with Node do

begin

if Expanded then Image Index := 2

else Imagelndex := 3;

end;

end;

end.

Для работы с системным реестром используется объект VCL TRegistry, удачно инкапсулирующий все предназначенные для этого функции Windows API. В обработчике события OnCreate главной формы создается объект Reg, а также к списку Listviewl добавляются два заголовка (свойство Columns).

Пояснений требует принцип построения дерева ключей. Во-первых, это приложение отображает только один из системных ключей (а именно hkey_local_machine); при желании его можно заменить или добавить остальные. Во-вторых, попытка построить все "развесистое" дерево ключей сразу займет слишком много времени и наверняка не понравится пользователям. Вспомним, ведь утилита Registry Editor работает довольно быстро. Значит, придется строить дерево динамически — создавать и показывать дочерние узлы в момент развертывания родительского узла. Для этого используется событие OnExpand компонента TreeViewl.

Остановимся на секунду. А какие узлы помечать кнопкой разворачивания (с пометкой "+"), ведь у родительского узла еще нет потомков? Выход из положения такой — в момент построения ключа проверить, есть ли у него до-

черние. Если да, то к нему добавляется один (фиктивный) пустой ключ. Его единственная роль — дать системе поставить "+" против родительского узла.

Когда же пользователь щелкнул на кнопке, отмеченной знаком "+", и родительский узел разворачивается, фиктивный дочерний узел удаляется и вместо него создаются узлы настоящие, полученные путем сканирования реестра (СМ. Метод ShowSubKeys).

Снабдим узлы картинками. Для этого в компонент imagebisti поместим картинки, соответствующие открытой и закрытой папкам. Напомним, что для отрисовки и смены картинок есть специальные события — OnGetImageIndex И OnGetSelectedIndex. В данном Примере у двух этих событий один обработчик: развернутому узлу он сопоставляет картинку раскрытой папки, а свернутому — закрытой.

В заключение нужно сказать об очень важной особенности компонента TListview. Когда он отображает большой объем информации, обработка данных может затянуться очень и очень надолго, и занять слишком много памяти. Выход — перевести список в так называемый виртуальный режим. Он применяется для тех случаев, когда элементов в списке слишком много и хранить их там невозможно из соображений экономии времени или памяти. Выход из положения прост:

  1. Переводим компонент в виртуальный режим установкой значения свойства OwnerData равным True;
  2. Сообщаем списку, сколько в нем должно быть элементов, установкой нужного значения items. Count.
  3. Чтобы предоставить нужные данные, программист должен предусмотреть обработку событий OnData, OnDataFind, OnDataHint И OnDataStateChange.

Как минимум, нужно описать обработчик события OnData.

TLVOwnerDataEvent = procedure(Sender: TObject;

Item: TListItem) of object;

Вам передается объект TListitem, и внутри обработчика события OnData необходимо динамически "оформить" его — полностью, от заголовка до картинок.

Возникает это событие перед каждой перерисовкой списка. Так что, если сбор данных для вашего списка занимает более или менее продолжительное время, лучше не связывать его с событие OnData — перерисовка сильно затянется. К тому же, в виртуальном режиме сортировать список невозможно.

Inprise прилагает к Delphi 5 прекрасный пример" к вышесказанному— Virtual ListView. К нему и отсылаем заинтересованного читателя.

Ответы на вопросы по компоненту TListview можно найти сразу в двух местах: "родном" файле справки deibvcl.hip и файле справки Windows win32.hlp.

Во втором из них информация содержится в виде описания сообщений, посылаемых окну класса Listview, и соответствующих им макросов. Некоторые из них позволят вам расширить функциональные возможности TListView. Эти макросы содержатся в файле Commctrl .раs.

 Компонент TCheckListBox

Интересным новшеством для многих будет компонент TCheckListBox. Это — гибрид обычного списка выбора (TListBox) и переключателя (rcheckBox). Удобство заключается в том, что все пункты списка снабжаются индивидуальным переключателем (рис. 11.5).

Рис. 11.5. Компонент TCheckListBox

Если сам текст пункта, как и ранее в TListBox, содержится в свойстве items, то состояние его переключателя — в свойстве checked. Более того, пункт может находиться в трех состояниях — отмеченном, неотмеченном и неактивном (grayed). Перевести его в нужное состояние можно посредством установки свойства state:

CheckListBoxl.checked[0] := false;

CheckListBoxl.State[1] := cbGrayed;

CheckListBoxl.State[2] := cbChecked;

Компоненты TCoolBar и TControlBar

Компонент TCoolBar впервые появился в Delphi 3. Придуман он в Microsoft, и "дельфийский" компонент — только оболочка для элемента управления, содержащегося в библиотеке COMCTL32.DLL. Этот элемент управления отличается способом решения проблемы "свободного места" — здесь каждая панель снабжена вертикальной полосой слева, за которую ее можно передвигать, освобождая место для самой нужной на данный момент панели.

По сути, TCoolBar — это разновидность контейнера для панелей инструментов. Основу его составляет коллекция панелей (свойство Bands), которые сами по себе не появляются в Инспекторе объектов, а доступны только через соответствующий редактор свойства. Чтобы "скомплектовать" новую панель, следует добавить элемент свойства Bands (класс rcooiband) в редакторе этого свойства и затем связать его с одним из оконных элементов управления на форме (свойство control). После этого данный элемент перемещается на отведенную ему панель.

Рис. 11.6. Внешний вид компонента TCoolBar

Элемент управления CoolBar разрабатывался для применения массовым непрофессиональным пользователем в Internet Explorer, и в нем предусмотрено много красивых (и подчас бесполезных!) вещиц. Весь компонент и каждую панель в отдельности можно украсить отдельным фоном (рис. 11.6).

Фон всего компонента задается свойством Bitmap. Чтобы он был виден везде, нужно следующее: во-первых, у отдельных панелей в их свойствах Bitmap не должны содержаться другие картинки, во-вторых, значение их свойства parentBitmap должно быть установлено равным True, и в-третьих, на панелях должны находиться прозрачные компоненты (скажем, прозрачный компонент TToolBar).

Однако, помимо очевидной пользы, TCoolBar принес с собой и очевидные проблемы. Так вышло у разработчиков Microsoft, что с разными версиями Internet Explorer стали поставляться разные версии этого элемента управления, частично несовместимые между собой и влекущие ошибки в программах, которые его используют (в том числе, написанных на Delphi). Теперь можно перейти от проблемного TCooiBar к его аналогу TControlBar, разработанному целиком и полностью в Inprise.

Внешне этот компонент выглядит как обычная панель (рис. 11.10). Однако каждый помещенный на него элемент управления сопровождается собственным контейнером для перетаскивания, причем не только в пределах самого TControlBar, но и за его пределами. Такая возможность называется переносом (Docking); о ней будет рассказано ниже.

В силу сказанного нет смысла помещать на эту панель отдельные кнопки — лучше сразу поместить панель инструментов (ТТооl.ваr). Кстати, главная форма среды Delphi реализована именно с помощью TControlBar.

Вы никогда не задумывались, каким способом в приложениях из состава MS Office реализовано плавающее меню? Теперь и в Delphi можно реализовать такую возможность, используя компонент TControlBar.

Конечно, настоящее меню (компонент TMainMenu) нельзя поместить на панель инструментов. Но есть возможность имитации этого действия. Выполним следующую последовательность действий:

  1. Поместим на форму компонент TControlBar и на него — компонент TToolBar (Панель инструментов).
  2. Добавим в панель инструментов нужное число кнопок — по числу пунктов меню верхнего уровня. Для них нужно установить значения свойств showCaptions (чтобы был виден текст) и Flat (чтобы создавалась иллюзия не отдельных кнопок, а общей полосы меню) равным True.
  3. Для компонента TTooiBar установим свойства: DragKind — в значении dkDock и DragMode — в dmAutomatic (о значении этих свойств рассказано в разделе "Перенос компонентов ('docking)" следующей главы).
  4. Добавим на форму нужное число выпадающих меню (компонентов трорирмепи) и свяжем их с кнопками посредством свойства кнопок

DropdownMenu.

Получившийся результат виден на рис. 11.7.

Рис. 11.7. Плавающее меню, созданное при помощи компонентов TControlBar и TtoolBar

Создание нового компонента на базе элементов управления из COMCTL32

С каждой следующей версией Internet Explorer Microsoft поставляет новую библиотеку COMCTL32 с новыми элементами управления. Программисты Inprise пытаются поспеть за ними, но получается это не всегда. Так что полезно было бы и самому научиться создавать оболочку для новых и необходимых элементов управления, тем более, что это несложно. Рассмотрим это на примере.

Подходящей кандидатурой может служить редактор IP-адресов, появившийся в версии библиотеки 4.71 (Internet Explorer 4.0): Это элемент, упрощающий редактирование.

Мастер создания новых компонентов (рис. 11.8) создаст для нас шаблон. Поскольку элементы из состава COMCTL32 есть ни что иное, как окна со специфическими свойствами, наш компонент мы породим от TWincontrol. IP-редактор представляет собой окно класса WC IPADDRESS.

Первым делом при создании компонента, особо не раздумывая, следует опубликовать типовые свойства и события, которые есть у большинства визуальных компонентов. Чтобы не занимать место в книге, позаимствуем их список у любого другого компонента из comctrls . раз.

Далее приступим к описанию свойств, которыми будет отличаться наш компонент от других. Возможности, предоставляемые IP-редактором, описаны в справочной системе MSDN. Визуально он состоит из четырех полей, разделенных точками (рис. 11.9).

Рис. 11.8. Мастер создания компонентов

Рис. 11.9. Тестовое приложение, содержащее IP-редактор (внизу)

Для каждого из полей можно задать отдельно верхнюю и нижнюю границы допустимых значений. Это удобно, если вы планируете работать с адресами какой-либо конкретной IP-сети. По умолчанию границы равны О...255.

Элемент управления обрабатывает шесть сообщений (см. документацию MSDN), которые сведены в таблицу 11.5.

Таблица 11.5. Сообщения, обрабатываемые элементом управления IP Address Control

Сообщение

Описание

IPM_CLEARADDRESS

Очистить поле адреса

IPM_GETADDRESS

Считать адрес

IPM_ISBLANK

Проверить, не пустое ли поле адреса

IPM_SETADDRESS

Установить адрес

IPM_SETFOCUS

Передать фокус заданному полю элемента управления

IPM_SETRANGE

Установить ограничения на значения в заданном поле

Следует иметь в виду, что IP-редактор не является потомком обычного редактора (TEdit) и не обрабатывает характерные для того сообщения ем_хххх.

В создаваемом коде компонента первым делом нужно переписать конструктор create и метод createparams. Последний метод вызывается перед созданием окна для установки его будущих параметров. Именно здесь нужно инициализировать библиотеку общих элементов управления COMCTL32 и породить новый класс окна.

constructor TIpEditor.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

ControlStyle := [csCaptureMouse, csClickEvents, csDoubleClicks, csOpaque];

Color := clBtnFace;

Width := 160;

Height := 25;

Align := aINone;

end;

procedure TIPEditor.CreateParams(var Pa rams: TCreateParains);

begin

InitConroonControl(ICC_INTERNET_CLAS3E3);

inherited CreateParam5(Params);

CreateSubClass(Params, WC_IPADDRESS);

end;

После создания свое значение получает дескриптор окна Handle (это свойство унаследовано от TWinControl). Весь информационный обмен с элементом идет через обмен сообщениями с использованием этого дескриптора. Минимально необходимыми для работы являются свойства ip (задает IP-адрес в редакторе), IPString (отображает его в виде текстовой строки) и процедура clear (очищает редактор). Реализовано это следующим образом:

interface Type

TIPEditor = class(TWinControl) private

( Private declarations } protected

{ Protected declarations } ipAddress: Integer;

procedure CreateParams(var Params: TCreateParams); overrider-function GetIP(Index: Integer): Byte;

procedure SetIPfIndex: Integer; Value: Byte);

function GetIPString: string;

public

{ Public declarations }

constructor Create(AOwner: TComponent);overrider-property IP[Index: Integer]: byte read GetIP write SetIP;

property IPString : string read GetIPString;

procedure Clear;

implementation

function TIPEditor.GetIP(Index: Integer): Byte;

begin SendMessage( Handle,IPM_GETADDRESS,0,longint(@ipAddress)) ;

case Index of

1 : Result := FIRST_IPADDRESS(ipAddress);

2 : Result := SECOND_IPADDRESS(ipAddress);

3 : Result := THIRD_IPADDRESS.(ipAddress);

4 : Result := FOURTH_IPADDRESS(ipAddress);

else ;

end;

end;

procedure TIPEditor.SetIP(Index: Integer; Value: Byte);

begin case Index of

1: ipAddress := ipAddress AND $FFFFFF or integer(Value) shl24;

2: ipAddress := ipAddress AND $FFOOFFFF or integer(Value) shl16;

3: ipAddress := ipAddress AND $FFFFOOFF or integer(Value) shl8;

4: ipAddress := ipAddress AND $FFFFFFOO or integer(Value);

else Exit;

end;

SendMessage( Handle,IPM_SETADDRESS,0,ipAddress) ;

end;

procedure TIPEditor.Clear;

begin

SendMessage( Handle,IPM_CLEARADDRESS,0,0) ;

end;

function TIPEditor.GetIPString: string;

begin SendMessage( Handle,IPM_GETADDRESS,0,longint(@ipAddress)) ;

Result := Format('%d. %d. %d. %d', [FIRST_IPADDRESS(ipAddress), SECOND_IPADDRESS(ipAddress), THIRD_IPADDRESS(ipAddress), FOURTH_IPADDRESS(ipAddress)]) ;

end;

Не праздным вопросом является проверка версии библиотеки элементов управления. В модуле COMCTRLS.PAS описана полезная функция:

function GetComCtlVersion: Integer;

Она возвращает номер установленной версии библиотеки в виде пары цифр. Каждая версия выходит обычно вместе со следующей версией обозревателя Интернет. Вот константы, описанные также в cumctrls.pas:

ComCtlVersionIE3 = $00040046; //4.70

ComCtlVersionIE4 = $00040047; //4.71

ComCtlVersionIE401 = $00040048; //4.72

ComCtlVersionIES = $00050050; //5.80

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

Резюме

Элементы управления — поистине неисчерпаемая тема. Надеемся, что читатель воспримет данную главу как руководство к последующему углубленному анализу того или иного элемента в свете стоящих перед ним задач.

С элементами управления вам так или иначе придется сталкиваться во всех последующих главах. Особо выделим только главу 30, в которой рассказано об элементах управления ActiveX.