Рейтинг@Mail.ru

 

 

 

 

 

 

.: Учебник для продвинутых по Delphi 7 :.

<< НазадСодержаниеВперед >>

ГЛАВА 5


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

 
маркированный список Что такое библиотека ComCtl32
маркированный список Многостраничный блокнот — компоненты TTabControl и TPageControl
маркированный список Компонент TToolBar
маркированный список Компонент TImageList
маркированный список Компоненты TTreeView и TListView
маркированный список Календарь
маркированный список Компонент TMonthCalendar
маркированный список Компонент TDateTimePicker
маркированный список Панель состояния TStatusBar
маркированный список Расширенный комбинированный список TComboBoxEx
маркированный список Создание нового компонента на базе элементов управления из библиотеки ComCtl32
маркированный список Резюме

 

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

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

 

Что такое библиотека ComCtl32

Изменив внешний вид "окон" в Windows 95, менеджеры Microsoft задумались о том, чтобы дать независимым разработчикам средства для создания приложений, внешне похожих на системные утилиты и использующих единые типовые элементы управления. Например, все эти элементы автоматически поддерживают установленные в системе цветовое оформление, размер, шрифт и т. п.

Все элементы, проверенные и обкатанные в Windows, объединялись в библиотеке ComCtl32.dll, документировались и публиковались для использования разработчиками. С 1995 года сменилось много версий библиотеки, элементы добавлялись и совершенствовались. Соответственно росла и страница Win32 в Палитре инструментов Delphi. Все компоненты, представленные там, взяты из библиотеки ComCtl32.

Специально отметим, что эти элементы управления не являются ActiveX. Это — обычные специализированные разновидности окон Windows, установка свойств которых происходит через посылку специализированных сообщений. Полная документация по всем сообщениям и применяемым в них константам и структурам есть в MSDN. Для большинства сообщений предусмотрены специальные функции оболочки, которые описаны в модуле CommCtrl.pas. Сами "дельфийские" классы компонентов работают с их использованием; классы компонентов описаны в модуле ComCtrls.pas.

Примечание 

Своими свойствами компоненты Delphi покрывают лишь 70—80% возможностей соответствующих элементов управления. Так что для решения некоторых специфических задач иногда приходится обращаться к функциям модуля ComCtrls.pas. Примеры этого имеются как в настоящей, так и в последующих главах данной книги.

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

Проблема эта решается включением в дистрибутив и запуском на машине клиента приложения 50comupd.exe, которое находится на дистрибутивном диске Delphi 7 в папке \info\extras\comctl\ и обновляет библиотеку, или его можно скачать с сервера Microsoft . Ни в коем случае не пытайтесь просто ("руками") заменить ComCtl32.dll на более новую версию — это запрещено Microsoft и последствия могут вас серьезно огорчить.

Размер 50comupd.exe составляет около 500 Кбайт, так что если оно и "утяжелит" ваш дистрибутив, то ненамного. Есть и более масштабный способ решения этой проблемы — установить последнюю версию Internet Explorer, который включает в себя новую библиотеку ComCtl32. Если у вас дело упирается только в элементы управления, то этот способ избыточен. Но если вы используете в распространяемом приложении другие решения от Microsoft, привязанные к IE (скажем, криптографическую библиотеку CryptoAPI или стек протоколов Internet в динамической библиотеке Winlnet.dll — на нем базируется большинство сетевых технологий в Delphi 7), то он может оказаться необходимым.

Проверка версии библиотеки элементов управления делается с использованием функции из модуля ComCtrls.pas:

function GetComCtlVersion: Integer;

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

const

ComCtlVersionIE3 = $00040046; 

ComCtlVersionIE4 = $00040047; 

ComCtlVersionIE401 = $00040048;

 ComCtlVersionIE5 = $00050050; 

ComCtlVersionlESOl = $00050051;

 ComCtlVersionIE6 = $00060000;

В Windows 2000 и ME библиотека элементов управления имеет версию $00050051 (5.81). Наконец, в следующей ОС от Microsoft — Windows XP (ex-Whistler) — сделаны кардинальные изменения внешних возможностей интерфейса пользователя. И основой их станет 6-я версия библиотеки ComCtl32.

И о совместимости. Не нужно быть мудрецом, чтобы понять, что компоненты со страницы Win32 работают только в среде Win32. Когда вы создаете Kylix-совместимое приложение (CLX Application), вместо страницы Win32 в Палитре компонентов появляется страница Common Controls. Находящиеся там компоненты сделаны на основе библиотеки QT (www.trolltech.com) и лицензированы Borland. Во многом они повторяют методы и свойства элементов управления, соответствующих им в Windows, но далеко не всегда. Поэтому, если вы пишете переносимые приложения, забудьте о библиотеке ComCtl32 и странице Палитры компонентов Win32 и пропустите эту главу.

 

Многостраничный блокнот — компоненты TTabControl и TPageControl

В Палитре компонентов имеется два элемента управления, обеспечивающих создание многостраничных блокнотов. Это компоненты TTabControl и TPageControl. Переключение между страницами осуществляется при помощи закладок. Закладки могут выглядеть как "настоящие" в бумажном блокноте, а могут быть похожи на стандартные кнопки Windows. Кстати, сама Палитра компонентов Delphi является примером использования такого элемента управления.

Компонент TTabControl отличается тем, что представляет собой как бы "виртуальный" блокнот. Это — единый объект с одной фактической страницей. При переключении закладок осуществляется вызов метода-обработчика события

property OnChange: TNotifyEvent;

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

Компонент TFageControl является контейнером для объектов TTabSheet, соответствующих отдельным страницам блокнота. Страницы в нем могут нести каждая свой набор дочерних компонентов; их можно переключать уже во время разработки.

Первый подход удобен, если на разных страницах у вас должны располагаться одни и те же компоненты, "начиненные" различными данными. Идеальный пример приводится самими разработчиками Delphi (папка Help\Samples\TabCntrl — обязательно посмотрите пример!). Здесь TTabControl используется для редактирования базы данных. Закладки для страниц создаются по одной для каждой записи в таблице. А на одной-единственной странице располагаются компоненты для отображения данных. При переключении закладок происходит навигация по таблице, содержимое полей меняется, и создается впечатление перехода на другую страницу.

Второй подход необходим, если у вас действительно разные страницы с различными наборами компонентов на них. Компонент TPageControl используют для создания редакторов свойств и настроек программы, а также для разного рода мастеров (Wizards).

Оба компонента в своей основе имеют общий элемент управления из библиотеки ComCtl32 (в документации Microsoft он называется Tab Control). Соответственно, в иерархии классов Delphi они оба произошли от класса TCustomTabcontrol, от которого унаследовачи значительную часть свойств и методов. А вот механизмы работы отдельных страниц у каждого компонента свои. Поэтому сначала мы рассмотрим общие для двух компонентов свойства, а затем особенности использования страниц. Свойства и методы-обработчики класса-предка TCustomTabcontrol представлены в табл. 5.1 и 5.2 соответственно. Обратите внимание, что перечисленные свойства и методы в потомках объявляются как опубликованные (published).

Таблица 5.1. Основные свойства, общие для TTabControl и TPageContrli

Объявление
Описание
property Tablndex: Integer;
Задает номер текущей страницы, начиная с 0
property TabHeight: Smallint;
Задает высоту закладок в пикселах. При значении 0 высота определяется автоматически так, чтобы вместить текст
property TabWidth: Smallint;
Задает ширину закладок. При значении 0 ширина определяется автоматически так, чтобы вместить текст
type TTabStyle = (tsTabs, tsButtons, tsFlatButtons);

property Style: TtabStyle;

Определяет стиль закладок компонента: 
  •  tsTabs — стандартные закладки; 
  • tsButtons — объемные кнопки; 
  •  tsFlatButtons — плоские кнопки
type TTabPosition = (tpTop, tpBottom, tpLeft, tpRight);

property TabPosition: TTabPosition;

Определяет расположение закладок на компоненте. Расположение, отличное от tpTop, возможно только для стиля tsTabs
property HotTrack: Boolean;
При значении True названия страниц выделяются цветом при перемещении над ними указателя мыши
property Images : TCustomlmageList;
Указывает на список картинок, появляющихся на закладках страниц
property RaggedRight: Boolean;
При значении True ширина закладок изменяется таким образом, чтобы они не занимали всю сторону блокнота
property MultiLine: Boolean;
При значении True закладки страниц могут располагаться в несколько рядов (если они не помещаются в один). При значении False в верхнем правом углу появляются кнопки, организующие прокрутку невидимых заголовков
property ScrollOpposite: Boolean;
При значении True, если закладки расположены в несколько рядов, при переходе к закладке следующего ряда все остальные ряды перемещаются на противоположную сторону блокнота. Действительно только при MultiLine=True

 

Таблица 5.2. Основные методы-обработчики, общие для TTabControl и TPageControl

Объявление
Описание
type TTabChangingEvent = procedure (Sender: TObject; var AllowChange: Boolean) of object; property OnChanging: TTabChangingEvent;
Вызывается непосредственно перед открытием новой страницы. Параметр AllowChange, установленный в значение False, запрещает открытие
property OnChange: TNotifyEvent;
Вызывается при открытии новой страницы
property OnDrawTab: TDrawTabEvent;
Вызывается при перерисовке страницы, только если свойство

OwnerDraw = True

property OnGetlmagelndex: TTabGetlmageEvent;
Вызывается при отображении на закладке картинки

Как видно из таблицы, большинство свойств обеспечивают различные стили представления многостраничного блокнота. При настройке стиля обратите внимание, что свойство RaggedRight может не работать, т. к. вступает в противоречие со свойством Tabwidth. При Tabwidth = 0 компонент изменяет ширину закладок в соответствии с длиной текста, в противном случае ширина закладок всегда равна значению свойства Tabwidth.

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

Свойство Tabindex, задающее номер текущей страницы, позволяет переключать страницы программно. Для компонента TTabControl это единственный способ изменить текущую страницу на этапе разработки. При смене страниц сначала происходит событие onchanging — в этот момент Tabindex еще содержит индекс старой страницы (и смену можно запретить), а затем OnChange — это свойство уже указывает на новую страницу.

В компоненте TTabControl число и заголовки страниц полностью зависят от свойства

property Tabs: TStrings;

В списке перечисляются заголовки страниц, для которых автоматически создаются закладки. Порядок следования страниц зависит от расположения текстов заголовков в свойстве Tabs.

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

procedure TForml.TabControllChange(Sender: TObject);

 begin 

with TabControll do

begin

Editl.Visible := Tablndex = 0;

 Edit2.Visible := Tablndex = 1;

 Edit3.Visible := Tablndex = 2; 

end; 

end;

Компонент TPageControl, в отличие от TTabControl, для обеспечения работы создает "настоящую" страницу — экземпляр класса TTabSheet. Список указателей на все созданные экземпляры страниц хранится в свойстве Pages, доступном только для чтения:

property Pages[Index: Integer]: TTabSheet;

Номер индекса соответствует порядковому номеру страницы. Для создания новой страницы используется команда New Page из всплывающего меню компонента, перенесенного на форму. Если же вы хотите создать страницу на этапе выполнения, создайте экземпляр TTabSheet самостоятельно и в свойстве Pagecontrol укажите на родительский блокнот:

 pcMain: TPageControl;

 ts : TTabSheet;

...

ts := TTabSheet.Create(pcMain);

 with ts do 

begin

PageControl := pcMain;

 ts.Caption :='New page' ; 

end;

Общее число страниц хранится в свойстве

property PageCount: Integer;

доступном только для чтения. Текущую страницу можно задать свойством:

property ActivePage: TTabSheet;

Если во время разработки (этой возможностью компонент TPageControl отличается от своего собрата) или во время выполнения переключиться на другую страницу, значение свойства ActivePage изменится.

Также для перехода на соседнюю страницу программными средствами можно использовать метод

procedure SelectNextPage(GoForward: Boolean);

в котором параметр GoForward при значении True задает переход на следующую страницу, иначе — на предыдущую.

Рассмотрев свойства блокнота, обратимся к его страницам и остановимся подробнее на возможностях класса TTabSheet. На владельца страницы указывает значение свойства

property PageControl: TPageControl;

Расположение страницы в блокноте задает свойство Pageindex:

property Pageindex: Integer;

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

property Tablndex: Integer;

Страница может временно "исчезнуть" из блокнота, а затем опять появиться. Для этого применяется свойство

property TabVisible: Boolean;

 

Компонент TToolBar

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

Примечание 

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

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

property ButtonWidth: Integer; 

property ButtonHeight: Integer;

Но эти свойства срабатывают только тогда, когда свойство

property ShowCaptions: Boolean;

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

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

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

property Images: TCustomlmageList; 

property Disabledlmages: TCustomlmageList; 

property Hotlmages: TCustomlmageList;

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

Если свойство

property Flat: Boolean;

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

 Примечание

Возможность сделать панель инструментов плоской появилась в версии 4.70 библиотеки ComCtl32.dll. Распространяя приложение, не забудьте поставить с дистрибутивами эту библиотеку нужной версии.

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

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

property Transparent: Boolean;

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

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

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

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

property Style: TToolButtonStyle;

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

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

property Down: Boolean;

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

property Grouped: Boolean;

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

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

property AllowAllUp: Boolean;

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

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

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

property Indeterminate: Boolean;

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

Свойство

property Marked: Boolean;

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

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

var StartingPoint : TPoint; 

Selecting : boolean;

procedure TForml.ToolBarlMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer); 

begin

StartingPoint := (Sender as TControl).ClientToScreen(Point(X,Y));

Selecting := True; 

end;

procedure TForml.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 TControl).ClientToScreen(Point(X,Y)); 

with ToolBarl do for i := 0 to ButtonCount-1 do

 begin

r0 :=Buttons[i].ClientRect;

OffsetRect(r0,Buttons[i].

ClientOrigin.X,Buttons[i]. ClientOrigin.Y);

 if IntersectRect(r0,r,r0) 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.

 

Компонент TlmageList

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

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

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

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

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

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

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

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

inout BITMAP "inout.bmp"

 tools BITMAP "tools.bmp"

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

2. Скомпилировать ресурсы при помощи утилиты brcc32.exe, поставляемой с Delphi:

C:\Program Files\Borland\Delphi7\bin\brcc32 bitmap.rc

3. Появившийся файл bitmap.res нужно включить в состав проекта. Для этого используется директива $R:

{$R bitmap.res}

4. Теперь картинка содержится в ресурсах и будет включена в состав исполняемого файла. Осталось загрузить ее в компонент TimageList. Для этого используется метод ResourceLoad:

ImageListl.ResourceLoad(rtBitmap, 'bitmaps',TColor(0));

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

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

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

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

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

Var bmp: TBitmap;

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

bmp.Transparent := True;

ImageListl.AddMasked(bmp, bmp.TransparentColor);

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

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

property Handle: HImageList;

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

 

Компоненты 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 Count: Integer;
Счетчик числа дочерних узлов данного узла
property Item [Index: Integer] : TTreeNode;
Список дочерних узлов
property Parent: TTreeNode;
Ссылка на объект — родительский узел (верхнего уровня)
property Level: Integer;
Уровень, на котором находится узел. Для корневого узла это свойство равно 0; его потомки имеют значение Level=l и т. д.
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 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

Значение
Внешний вид
vslcon
Элементы списка появляются в виде больших значков с надписью под ними. Картинки для больших значков хранятся в свойстве Largelmages. Возможно их перетаскивание
vsSmalllcon
Элементы списка появляются в виде маленьких значков с надписью справа. Картинки для маленьких значков хранятся в свойстве Smallimages. Возможно их перетаскивание
vsList
Элементы списка появляются в колонке один под другим с надписью справа. Перетаскивание невозможно
vsReport
Элементы списка появляются в нескольких колонках один под другим. В первой содержится маленький значок и надпись, в остальных — определенная программистом информация. Если свойство 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.

 

Календарь

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

 

Компонент TMonthCalendar

Этот элемент управления представляет собой панель с календарем на один месяц (рис. 5.7). Он обладает богатыми возможностями по настройке. Основные свойства компонента, отвечающие за внешний вид и управление календарем, представлены в табл. 5.6. Их назначение достаточно прозрачно и не требует особенных комментариев.

Рис. 5.7. Компонент TMonthCalendar

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

Таблица 5.6. Основные свойства компонента TMonthCalendar

Объявление
Описание
property CalColors : TMonthCalColors;
Определяет цвета основных элементов календаря
property Date: TDate;
Содержит выбранную дату
property EndDate: TDate;
Содержит последнюю из выбранных дат при MultiSelect = True. Иначе совпадает со свойством Date
type TCalDayOfWeek = (dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday, dowSunday, dowLocaleDefault) ; property FirstDayOfWeek: TCalDayOfWeek;
Определяет день, с которого начинается неделя. Значение по умолчанию dowLocaleDefault соответствует установкам ОС
property MaxDate: TDate;
Максимальная доступная для просмотра дата
property MaxSelectRange: Integer;
Максимальная доступная для выбора дата
property MinDate: TDate;
Минимальная доступная для просмотра дата
property MultiSelect: Boolean;
При значении True позволяет выбирать несколько дат одновременно
property ShowToday: Boolean;
Включает или отключает показ текущей даты в нижней части календаря
property ShowTodayCircle: Boolean;
Включает или отключает выделение текущей даты красным кругом
property WeekNumbers : Boolean;
Включает или отключает показ номеров недель в левой части календаря

Результат выбора даты в календаре сохраняется в свойстве Date. При использовании возможности выбора нескольких значений одновременно в свойстве EndDate содержится последняя дата, а в свойстве Date — самая ранняя из выбранных.

Метод-обработчик

property OnGetMonthlnfo: TOnGetMonthlnfoEvent;

вызывается при смене месяца.

 

Компонент TDateTimePicker

Безусловно, календарь будет очень полезен пользователям. Однако было бы желательно не только выбирать даты, но и вводить их в элементы управления. Компонент TDateTimePicker совмещает календарь и однострочный текстовый редактор, причем календарь полностью совпадает с рассмотренным выше (оба компонента являются наследниками класса TCommonCaiendar). Свойства и методы компонента представлены в табл. 5.7.

Таблица 5.7. Основные свойства и методы компонента TDateTimePicker

Объявление
Описание
type TDTCalAlignment = (dtaLeft, dtaRight) ; property CalAlignment : TDTCalAlignment;
Выравнивает панель календаря по левой или правой стороне компонента
property Checked: Boolean;
Возвращает True, если флажок в редакторе включен
type TDTDateFormat = (df Short, dfLong) ; property DateFormat: TDTDateFormat;
Определяет формат представления даты
type TDTDateMode = (dmComboBox, dmUpDown) ; property DateMode: TDTDateMode;
Задает стиль компонента
property DroppedDown: Boolean;
Возвращает True, если панель календаря включена
type TDateTimeKind = (dtkDate, dtkTime) ; property Kind: TDateTimeKind;
Определяет возвращаемый результат — дату или время. Время можно вводить только в стиле dmUpDown
property Parselnput: Boolean;
Включает или отключает метод-обработчик OnUserlnput
property ShowCheckbox: Boolean;
Управляет видимостью флажка
type TTime = type TDateTime; property Time: TTime;
Содержит установленное время
property OnChange: TNotifyEvent;
Вызывается при вводе даты или времени
property OnCloseUp: TNotifyEvent;
Вызывается при сворачивании панели календаря
property OnDropDown: TNotifyEvent;
Вызывается при разворачивании панели календаря
type TDTParselnputEvent = procedure (Sender : TObject; const UserString: string; var DateAndTime: TDateTime; var AllowChange: Boolean) of object;

property OnUser Input: TDTParselnputEvent;

Вызывается при прямом вводе значения в редактор.

Параметр UserString содержит вводимое значение.

Параметр DateAndTime содержит значение даты или времени.

Параметр AllowChange управляет изменением значения

Компонент TDateTimePicker может обеспечивать ввод даты или времени.

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

В зависимости от значения свойства Kind элемент управления настраивается на ввод даты или времени. Результат ввода даты сохраняется в свойстве Date. Дату можно выбирать из всплывающего календаря или путем перебора. Результат ввода времени сохраняется в свойстве Time.

Свойство Parseinput при значении True разрешает ручной ввод значения. В этом случае разработчик может использовать метод-обработчик

type TDTParselnputEvent = procedure(Sender: TObject; const UserString: string; var DateAndTime: TDateTime; var AllowChange: Boolean) of object; property OnUserlnput: TDTParselnputEvent;

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

procedure TForml.DateTimePicker2UserInput(Sender: TObject;

 const UserString: String; var DateAndTime: TDateTime; var AllowChange: Boolean); 

begin

 try

DateAndTime := StrToDateTime(UserString);

 except

on E: EConvertError do ShowMessage('Неверное значение');

 end;

  end;

Обратите внимание, что здесь обязательно должно присутствовать присвоение результата ввода параметру DateAndTime, иначе элемент управления не получит новое значение.

 

Панель состояния TStatusBar

Этот вид элементов управления применяется уже достаточно давно. Его роль заключается в отображении различного рода справочной информации. Панель состояния инкапсулирована в компоненте TStatusBar.

Обычно панель состояния размещается в нижней части окна. Поэтому при переносе на форму свойство Align всегда имеет значение alBottom. Панель состояния можно разделить на произвольное число самостоятельных частей. Каждая часть описывается объектом TStatusPanel. Коллекция всех таких объектов находится в свойстве

property Panels: TStatusPanels;

Например, для того чтобы показать на панели состояния текущую дату и время, в методе-обработчике OnTimer компонента TTimer достаточно предусмотреть следующий код:

procedure TForml.TimerITimer(Sender: TObject); 

begin

StatusBar1.Panels[0].Text := DateToStr(Now);

StatusBarl.Panels[1].Text := TimeToStr(Now); 

end;

Впрочем, панель состояния можно сделать сплошной. Для этого свойство SimplePanel должно иметь значение True. В данном случае текст панели должен содержаться в свойстве SimpleText.

 

Расширенный комбинированный список TComboBoxEx

Такой выпадающий список знаком пользователям со времен Windows 95 (например, список всех элементов оболочки Shell: папки My Computer, My Documents и т. п.) Соответствующий элемент управления появился в библиотеке ComCtl32 несколько позже, а в компонент он превратился только в Delphi 7.

Что отличает этот "продвинутый" выпадающий список от обычного TCоmbоВох? С функциональной точки зрения основных отличий два: возможность добавлять картинки к элементам и выравнивать последние с разным отступом, имитируя иерархию.

Реализовано это следующим образом.

У компонента TComboBoxEx, помимо свойства items, есть свойство

property ItemsEx: TComboExItems;

которое представляет собой коллекцию элементов типа TComboExitem. Щелкнув на этом свойстве в Инспекторе объектов, увидим типичный редактор коллекций, где каждый элемент обладает такими опубликованными свойствами:

  •  свойство Caption отвечает за заголовок элемента, каким он буден виден в списке;
  •  свойство Data — это нетипизированный указатель на прикрепляемые к элементу данные;
  •  отступ от левого края списка задается свойством indent. В документации написано, что оно задается в пикселах. Это почти так: на самом деле одна единица значения свойства соответствует десятку пикселов;
  •  три номера картинок: обычный imageindex, номер для выбранного элемента Selected Imageindex И Overlaylmagelndex. Последнее свойство задает номер картинки, используемой как накладываемая маска для первых двух. Она должна быть черно-белой: белые области прозрачны для исходной картинки, черные — нет. Все три индекса указывают на один и тот же список картинок, задаваемый свойством images родительского компонента.

Дополнительные опции в расширенном выпадающем списке задаются свойством styleEx. Это — множество из четырех флагов, установка которых сводится к разрешению или запрету перечисленных выше новых свойств.

 

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

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

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

Рис. 5.8. Мастер создания новых компонентов Delphi 7

Мастер создания новых компонентов (рис. 5.8) создаст для нас шаблон. Поскольку элементы из состава библиотеки ComCtl32 есть не что иное, как окна со специфическими свойствами, наш компонент мы породим от TWinControl. IP-редактор представляет собой окно класса WC_IPADDRESS.

Название нового компонента выбрано TCustomiPEdit. Такая схема принята разработчиками Delphi для большинства компонентов VCL. Непосредственным предком, допустим, TEdit является компонент TCustomEdit.

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

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

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

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

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

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

Сообщение
Назначение
IPM CLEARADDRESS
Очистить поле адреса
IPM GETADDRESS
Считать адрес
IPM_ISBLANK
Проверить, не пустое ли поле адреса
IPM SETADDRESS
Установить адрес
IPM_SETFOCUS
Передать фокус заданному полю элемента управления
IPM_SETRANGE
Установить ограничения на значения в заданном поле

Кроме перечисленных, IP-редактор извещает приложение об изменениях, произведенных пользователем, путем посылки ему сообщения WM_NOTIFY.

Следует иметь в виду, что IP-редактор не является потомком обычного редактора (TCustomEdit) и не обрабатывает характерные для того сообщения ЕМ_ХХХХ, так что название TCustomipEdit отражает только внешнее сходство.

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

constructor TIPEditor.Create(AOwner: TComponent);

 begin

inherited Create(AOwner);

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

Color := clBtnFace;

Width := 160;

Height := 25;

Align := alNone; 

end;

procedure TIPEditor.CreateParams(var Params: TCreateParams);

 begin

InitCommonControl(ICC_INTERNET_CLASSES);

inherited CreateParams(Params);

CreateSubClass(Params, WC_IPADDRESS); 

end;

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

Реализовано это следующим образом:

 Листинг 5.2. Исходный код компонента TCustomlPEdit 

unit uIPEdit; 

interface

uses

Windows, Messages, SysUtils, Classes, Controls;

type

TCustomlPEdit = class(TWinControl)

 private

{ Private declarations }

 FIPAddress: DWORD;

 FIPLimits: array [0..3] of word;

 FCurrentField : Integer;

//procedure CMWantSpecialKey(var Msg: TCMWantSpecialKey);

 message CM_WANTSPECIALKEY;

procedure WMGetDlgCode(var Message: TWMGetDlgCode);

message WM_GETDLGCODE;

procedure CMDialogChar(var Message: TCMDialogChar);

 message CM_DIALOGCHAR;

//procedure CMDialogKey(var Message: TCMDialogKey);

 message CM_DIALOGKEY;

procedure CNNotify(var Message: TWMNotify); 

message CN_NOTIFY; 

protected

{ Protected declarations }

function GetIP(Index: Integer): Byte;

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

function GetMinIP(Index: Integer): Byte;

procedure SetMinIP(Index: Integer; Value: Byte);

function GetMaxIP(Index: Integer): Byte;

procedure SetMaxIP(Index: Integer; Value: Byte);

function GetlPString: string;

procedure SetlPString(Value: string);

function IsBlank: boolean;

procedure SetCurrentFieldfIndex: Integer);

//

procedure CreateParams(var Params: TCreateParams); override;

procedure CreateWnd; override;

//procedure KeyDown(var Key: Word; Shift: TShiftState);override;

function IPDwordToString(dw: DWORD): string;

function IPStringToDword(s: string): DWORD; 

public

{ Public declarations }

constructor Create(AOwner: TComponent); 

override;

property IP[Index: Integer]: byte read GetIP write SetIP;

property MinIP[Index: Integer]: byte read GetMinIP write SetMinIP;

property MaxIP[Index: Integer]: byte read GetMaxIP write SetMaxIP;

property IPString : string read GetlPString write SetlPString;

property CurrentField : Integer read FCurrentField write SetCurrentField;

 procedure Clear;

end;

TIPEdit = class(TCustomlPEdit)

published

property Align;

property Anchors;

property BorderWidth;

property DragCursor;

property DragKind;

property DragMode;

property Enabled;

property Font;

property Hint;

property Constraints;

property ParentShowHint;

property PopupMenu;

property ShowHint;

property TabOrder;

property TabStop;

property Visible;

property OnContextPopup;

property OnDragDrop;

property OnDragOver;

property OnEndDock;

property OnEndDrag;

property OnEnter;

property OnExit;

property OnMouseDown;

property OnMouseMove;

property OnMouseUp;

property OnStartDock;

property OnStartDrag;

{ Published declarations }

property IPString; 

end;

procedure Register;

implementation

uses Graphics, commctrl, comctrls;

constructor TCustomlPEdit.Create(AOwner: TComponent); 

begin

inherited Create(AOwner);

FIPAddress := 0;

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

Color := clBtnFace;

Width := 160;

Height := 25;

Align := alNone;

TabStop := True; end;

procedure TCustomlPEdit.CreateParams(var Params: TCreateParams);

 begin

InitCommonControl(ICC_INTERNET_CLASSES); 

inherited CreateParams(Params); CreateSubClass(Params, WC_IPADDRESS); 

with Params do 

begin

Style := WS_VISIBLE or WS_BORDER or WS_CHILD;

if NewStyleControls and CtlSD then

begin

Style := Style and not WS_BORDER; ExStyle := ExStyle or WS_EX_CLIENTEDGE; 

end; 

end;

 end;

procedure TCustomlPEdit.CreateWnd;

 var i: Integer; 

begin

inherited CreateWnd; Clear;

{ for i := 0 to 3 do

 begin

MinIP[i] := 0; MaxIP[i] := $FF; end; }

CurrentField := 0;

 end;

procedure TCustomlPEdit.WMGetDlgCode(var Message: TWMGetDlgCode);

 begin

inherited;

Message.Result := {Message.Result or} DLGC_WANTTAB; 

end;

procedure TCustomlPEdit.CNNotify(var Message: TWMNotify);

 begin

with Message.NMHdr" do

 begin case Code of

IPN_FIELDCHANGED : begin

FCurrentField := PNMIPAddress(Message.NMHdr)~.iField; {if Assigned(OnlpFieldChange) then

with PNMIPAdress(Message.NMHdr)^ do begin 

OnIPFieldChange(Self, iField, iValue);}

 end; 

end; 

end; 

end;

(procedure TCustomlPEdit.KeyDown(var Key: Word; Shift: TShiftState); 

begin

inherited KeyDown(Key, Shift);

 if Key = VKJTAB then if ssShift in Shift then

CurrentField := (CurrentField -1+4) mod 4

 else

CurrentField := (CurrentField + I) mod 4; end; }

{procedure TCustomlPEdit.CMWantSpecialKey(var Msg: TCMWantSpecialKey); 

begin

inherited;

//Msg.Result := Ord(Char(Msg.CharCode) = #9) ; end;}

procedure TCustomlPEdit.CMDialogChar(var Message: TCMDialogChar); 

begin with Message do

if CharCode = VKJTAB then 

begin

Message.Result := 0; if GetKeyState(VK_SHIFT)<>0 then 

begin

if (CurrentField=0) then Exit; CurrentField := CurrentField — 1;

 end

else 

begin

if (CurrentField=3) then Exit; CurrentField := CurrentField + 1; 

end;

Message.Result := 1; end //VK_TAB 

else

inherited; end;

{procedure TCustomlPEdit.CMDialogKey(var Message: TCMDialogKey); 

begin

if (Focused or Windows.IsChild(Handle, Windows.GetFocus)) 

and

(Message.CharCode = VK_TAB) and (GetKeyState(VK_CONTROL) < 0) then 

begin 

if GetKeyState (VK_SHIFT) 00 then

CurrentField := (CurrentField -1+4) mod 4 

else

CurrentField := (CurrentField + 1) ir.oci 4; Message.Result := 1; 

end else

inherited; end; }

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

 begin

SendMessage

(Handle,IPM_GETADDRESS,0,longint(@FipAddress));

case Index of

1 : Result := FIRST_IPADDRESS(FipAddress);

2 : Result := SECOND_IPADDRESS(FipAddress) ;

3 : Result := THIRD_IPADDRESS(FipAddress);

4 : Result := FOURTH_IPADDRESS(FipAddress); else Result := 0; 

end;

 end;

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

 begin

 case Index of

1: FIPAddress := FIPAddress AND $FFFFFF or DWORD(Value) shl 24;

2: FIPAddress := FIPAddress AND $FFOOFFFF or DWORD(Value) shl 16;

3: FIPAddress := FIPAddress AND $FFFFOOFF or DWORD(Value) shl 8;

4: FIPAddress := FIPAddress AND $FFFFFFOO or DWORD(Value);

else Exit;

 end;

SendMessage(Handle, IPM_SETADDRESS, 0, FIPAddress); 

end;

function TCustomlPEdit.GetMinIP(Index: Integer): Byte; begin if (Index<0) or (Index>3) then

Result := 0

else

Result := LoByte(FIPLimits[Index]);

 end;

procedure TCustomlPEdit.SetMinIP(Index: Integer; Value: Byte); 

begin

if (Index<0) or (Index>3)

 then Exit;

FIPLimits[Index] := MAKEIPRANGE(HiByte(FIPLimits[Index]), Value);

SendMessage(Handle, IPM_SETRANGE, Index, FIPLimits[Index]);

 end;

function TCustomlPEdit.GetMaxIP(Index: Integer): Byte; begin if (Index<0) or (Index>3) 

then

Result := 0 

else

Result := HiByte(FIPLimits[Index]);

 end;

procedure TCustomlPEdit.SetMaxIP(Index: Integer; Value: Byte);

 begin

if (Index<0) or (Index>3) then Exit;

FIPLimits[Index] := MAKEIPRANGE(Value, LoByte(FIPLimits[Index]));

SendMessage(Handle, IPM_SETRANGE, Index, FIPLimits[Index]);

 end;

procedure TCustomlPEdit.Clear,

 begin

SendMessage(Handle, IPM_CLEARADDRESS, 0, 0); 

end;

function TCustomlPEdit.IsBlank: boolean;

 begin

Result:= SendMessage(Handle, IPM_ISBLANK, 0, 0) = 0; 

end;

procedure TCustomlPEdit.SetCurrentField(Index: Integer); 

begin

if (Index<0) or (Index>3)

 then Exit;

FCurrentField := Index;

SendMessage(Handle, IPM_SETFOCUS, wParam(FCurrentField), 0) ;

 end;

function TCustomlPEdit.IPDwordToString(dw: DWORD): string;

 begin

Result := Format('%d.%d.%d.%d',

[FIRST_IPADDRESS(dw),

SECOND_IPADDRESS(dw),

THIRD_IPADDRESS(dw),

FOURTH_IPADDRESS(dw)]);

 end;

function TCustomlPEdit.IPStringToDword(s: string): DWORD; 

var i,j : Integer;

NewAddr, Part : DWORD;

begin

NewAddr := 0; 

try

i := 0; repeat

j := PosC. ', s); if j<=l then if i<3 then

Abort else

Part := StrToInt(s) else

Part := StrToInt(Copy(s, I, j-1));

 if Part>255 then Abort; Delete(s, 1, j);

NewAddr := (NewAddr shl 8) or Part; 

Inc(i); 

until i>3; 

Result := NewAddr;

//Windows.MessageBox(0, pChar(IntToHex(FIPAddress, 8)), '', MB_Ok); 

except end;

end;

function TCustomlPEdit.GetlPString: string;

 begin

SendMessage(Handle,IPM_GETADDRESS, 0, longint(SFIPAddress));

Result := IpDwordToString(FIPAddress); 

end;

procedure TCustomlPEdit.SetlPString(Value: string);

 begin

FIPAddress := IPStringToDword(Value);

SendMessage(Handle, IPM_SETADDRESS, 0, FIPAddress); 

end;

procedure Register; 

begin

RegisterComponents('Samples', [TIPEdit]); 

end;

end.

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

 

Резюме

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

С элементами управления вам так или иначе придется сталкиваться во всех последующих главах.

 

<< НазадСодержаниеВперед >>

 

®Сайт разработал: Nek по вопросам пишите сюда NekSuper@yandex.ru
 
Hosted by uCoz