    ГЛАВА 7 

    Общие элементы управления Win32 

    Ноэль Раис 

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

    Простые общие элементы управления 

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

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

    Быстро выбрать (или установить) целое внутри диапазона можно с помощью 
компонента TTrackBar, который позволяет изменять целочисленные значения, 
используя указатель полосы значений. Свойство Position устанавливает 
расположение указателя TTrackBar или бегунка* 

    Простые общие элементы управления 

    В данном разделе рассматриваются простые элементы управления, которые не 
содержат дочерних объектов и не работают со списками, а отображают значения 
отдельных свойств или управляют ими. Такими элементами являются Track Bar, 
Up-Down, Hot key И Progress Bar. 

    Новые элементы в Delphi 3 

    Здесь описываются элементы управления, добавленные в последнее время в 
палитру компонентов: TToolBar, TCoolBar, TDateTimePicker И TAnimate. 

    Сложные общие элементы управления 

    Большая часть этой главы посвящена использованию общих элементов 
управления, которые управляют списками строк или содержат множественные 
дочерние объекты. Этими элементами управления являются status Bar, Header, 
Image List, Tab, Page, Rich Edit, List View И Tree View. 

    Резюме 

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


145


    (thumb) во время разработки или выполнения. Штрихи, размещенные вдоль 
полосы, отмечают интервалы внутри диапазона, управляемого свойствами Min, Max 
и Frequency. 
    Чтобы программно задать точную позицию каждого штриха, установите свойство 
TickStyle равным tsManual и вызовите метод SetTick. Вот как расставить штрихи вручную. 
    1.   Создайте проект, выбрав команду 
File^New Application. 
    2.   Поместите в форму компоненты TTrackBar и TUpDown со страницы Win95 
палитры компонентов. 
    3.   Поместите в форму компоненты TEdit и TButton со страницы Standard 
палитры компонентов. 
    4.   Установите в инспекторе объектов для свойства TickStyle компонента 
TrackBarl значение tsManual. 
    5.   Установите в окне Object Inspector для свойства Associate компонента 
TUpDown значение Editl (о компоненте TUpDown более подробно рассказывается в 
следующих разделах этой главы). 
    6.   Во вкладке Events окна инспектора объектов создайте обработчика 
события OnClick для компонента TButton. Введите следующий код в обработчик 
.события OnClick. 

    procedure  TForml.ButtonlClick(Sender:   TObject); begin 
    TrackBarl.SetTick(StrToInt(Editl.Text) ) ; end; 

    Чтобы применить элемент управления TTrackBar, можете использовать метод 
SetTick для графического отображения частоты распределения некоторых видов 
данных; элемент управления TTrackBar позволяет перемещаться по данным. Можно 
также перехватить событие OnShowHint компонента TApplication, чтобы увидеть 
текстовое описание для текущей позиции на TTrackB ar. 
    В следующем примере демонстрационная программа Holdings в сетке отображает 
количество, биржевой код и стоимость ценных бумаг. Компонент TTrackBar 
позволяет пользователю перемещаться по записям, в которых количество ценных 
бумаг соответствует свойству Position компонента TTrackBar. 
    На рис. 7.1 штрихи вдоль левой границы TTrackBar отмечают каждое 
дискретное значение поля Shares (ценные бумаги). Кроме того, интервал между 
штрихами отображает распределение количества ценных бумаг. По мере перемещения 
бегунка компонента TTrackBar по вертикали курсор таблицы перескакивает к 
самому близкому соответствующему значению поля Shares. 

    Рис. 7.1. Пример использования компонента TTrackBar 

    Прежде чем запускать файл, приведенный в листинге?. 1, создайте индекс по 
полю Shares с именем By-Shares в программе Database Desktop и установите 
свойство IndexName таблицы Tablel равным новому индексу. Файл SETTKC2. PAS 
находится на дополнительно распространяемой дискете. 

    Листинг 7.1. \UDELPHI3\CHP7 \TRACRBAR\sETTCK2. PAS. Файл исходного текста 
приложения TrackBar Shares Selector 

    unit settck2; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
DB, DBTables, Grids, DBGrids, ComCtrls, StdCtrls; 
    type 
    TForml = class(TForm) DBGridl: TDBGrid; TrackBarl: TTrackBar; Tablel: 
TTable; DataSourcel: TDataSource; TablelSYMBOL: TStringField; 


    146 Часть II. Разработка приложений 


    TablelSHARES: TFloatField; 
    TablelPUR_PRICE: TFloatField; 
    procedure FormCreate(Sender: TObject); 
    procedure FormShow(Sender: TObject); 
    procedure TrackBarlChange(Sender: TObject); private 
    procedure AppShowHint(var HintStr: string; var CanShow: Boolean; 
    var Hintlnfo: THintlnfo); end; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} uses CommCtrl; 
    (* 
    TBM_GETTHUMBRECT 
    wParam = 0; 
    IParam = (LPARAM) (LPRECT) Iprc; 
    Сообщение TBM_GETTHUMBRECT получает размер и положение 
    привязанного к сетке компонента TTrackBar. *) 
    // Показывать подсказку рядом с бегунком. 
    procedure TForml.AppShowHint(var HintStr: string; var CanShow: Boolean; 
    var Hintlnfo: THintlnfo); var 
    TrackBarThumbRect: TRect; begin 
    SendMessage(TrackBarl.Handle, TBM_GETTHUMBRECT, 0, Longlnt(@TrackBarThumbRect)); 
    Hintlnfo.HintPos := 
TrackBarl.ClientToScreen(TrackBarThumbRect.BottomRight) ; end; 
    // Внимание: убедимся, что таблица закрыта 
    // в режиме разработки, 
    // чтобы избежать ужасной ошибки 
    // "table is busy" ("таблица недоступна"). 
    procedure TForml.FormCreate(Sender: TObject); 
    begin 
    with Application do begin 
    OnShowHint := AppShowHint; HintPause := 0; HintShortPause := 0; end; end; 
    procedure TForml.FormShow(Sender: TObject); begin 
    with Tablel do begin Open; Last; 
    TrackBarl.Max := Tablel['Shares']; First; 
    TrackBarl.Min := Tablel['Shares ']; TrackBarl.LineSize := TrackBarl.Max 
div 20; TrackBarl.PageSize := TrackBarl.Max div 4; while not EOF do begin 
    TrackBarl.SetTick(Tablel['Shares']); 

    Глава 7. Общие элементы управления Win32 147 


    Next; end; end; end; 
    procedure TForml.TrackBarlChange(Sender: TObject); begin 
    if Tablel.State in [dsBrowse] then begin 
    Tablel.FindNearest([TrackBarl.Position]); 
    TrackBarl.Hint := Tablel.FieldByName('Shares').AsString + ' shares'; end; end; 
    end. 


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

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

    На заметку 
    На самом деле вместо компонента TEdit можно применять ТМето или TRichEdit. 
Компонент TUpDown все еще будет функционировать правильно, хотя, вероятно, вам 
никогда не представится случай такого использования ресурсов. 
    Чтобы привязать элемент управления UpDown к компоненту для редактирования 
текста, выберите для первого имя соответствующего компонента в раскрывающемся 
списке свойства Associate. Элемент управления TUpDown сам расположится справа 
или слева от компонента, к которому привязан, в зависимости от свойства 
AlignButton, принимающего значения udLeft или u dRight. Ориентация (свойство 
Orientation) может принимать значение udVertical или udHorizontal. 
    Чтобы использовать TUpDown, необязательно ассоциировать элемент управления 
для редактирования. Событие Оп-Click уведомляет о воздействии пользователя на 
кнопки приращения и уменьшения, что позволяет программе гибко реагировать на 
эти события. Рассмотрим в качестве примера форму-календарь с элементом 
управления TUpDown, привязанным к текущему месяц у (текущий месяц отображается 
в поле ввода TEdit, в сетке отображаются числа текущего месяца). Оставив 
свойство Associate пустым (равным nil), с помощью обработчика события Onclick 
можно отобразить полное название месяца (рис. 7.2). 
    Перед вызовом события OnClick событие OnChanging позволяет предотвратить 
изменение (для этого нужно установить параметр Allow-Change). К сожалению, 
событие OnChanging не позволяет узнать, нажал ли пользователь клавишу btPrev 
(уменьшение) или btNext (приращение). 
    Отсутствие этой информации препятствует выполнению таких задач, как 
предотвращение появления будущих дат внутри обработчика. Файл UDCAL1. PAS, 
показанный в листинге 7.2, находится на дополнительно распространяемой дискете. 

    Рис. 7.2. Использование элемента управления TUpDown вместе с компонентом 
TCalendar 

    Листинг 7.2. \UDELPHI3\CH7\UPDOWN\UDCAL1. PAS. Элемент управления TUpDown 
с TCalendar 

    unit UDCall; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls,•Forms, Dialogs, 
ExtCtrls, ComCtrls, StdCtrls, Grids, Calendar, Buttons, CommCtrl; 


    148 Часть Л. Разработка приложений 


    type 
    TfrmDate = class(TForm) 
    Calendar!: TCalendar; 
    StatusBarl: TStatusBar; 
    Panel2: TPanel; 
    Panel!: TPanel; 
    Editl: TEdit; 
    Edit2: TEdit; 
    UpDownl: TUpDown; 
    UpDown2: TUpDown; 
    cbAllowChange: TCheckBox; 
    BitBtnl: TBitBtn; 
    procedure FormCreate(Sender: TObject); 
    procedure UpDownlClick(Sender: TObject; Button: TUDBtnType); 
    procedure UpDown2Click(Sender: TObject; Button: TUDBtnType); 
    procedure CalendarIChange(Sender: TObject); 
    procedure UpDownlChanging(Sender: TObject; var AllowChange: Boolean); end; 
    const 
    Months: array [1..12] of ShortString = ( 
    'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 
    'October', 'November', 'December' ); 
    var 
    frmDate: TfrmDate; 
    implementation {$R *.DFM} 
    procedure TfrmDate.FormCreate(Sender: TObject); begin 
    Editl.Readonly := True; 
    Editl.Text := Months[Calendarl.Month]; 
    Edit2.Readonly := True; 
    Edit2.Text := IntToStr(Calendarl.Year); 
    UpDownl.Position := Calendarl.Month; 
    with UpDown2 do 
    begin 
    Min := 1900; 
    Max := 3000; // Этих размеров должно быть достаточно. 
    Position := Calendarl.Year; 
    Thousands := False; end; end; 
    procedure TfrmDate.UpDownlClick(Sender: TObject; Button: TUDBtnType); begin 
    with Calendarl do begin 
    if (Button = btNext) then        ,  ,,  ,v , . . 
    Month := Month + 1 
    else // Button = btPrev 
    Month := Month - 1; Editl.Text := Months[Month]; end; end; 
    procedure TfrmDate.UpDown2Click(Sender: TObject; Button: TUDBtnType); begin 
    Calendarl.Year := UpDown2.Position end; 


    Глава 7. Общие элементы управления Win32  149 


    procedure TfrmDate.CalendarlChange(Sender: TObject}; begin 
    with Calendarl do 
    StatusBarl.SimpleText := Months[Month] + ' ' + 
    IntToStr(Day) + ', ' + IntToStr(Year); end; 
    procedure TfrmDate.UpDownlChanging(Sender: TObject; 
    var AllowChange: Boolean); begin 
    AllowChange := cbAllowChange.Checked; end; 
    end. 


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

    Если вы хотите разрешить пользователю назначать собственные комбинации 
клавиш пунктам меню во время выполнения, можете использовать элемент 
управления HotKey. Назначение THotKey, как вы уже догадались, — горячая 
клавиша типа TShortCut, которым можно управлять с помощью функций 
ShortcutToText и TextToShortCut элемента управления для создания меню. 
    Чтобы указать, какая клавиша-модификатор (hkShift, hkCtrl, hkAlt или 
hkExt) принимается по умолчанию, когда пользователь нажимает клавишу символа 
или функциональную клавишу, нужно определить набор Modifiers. Например, если 
свойство Modifiers читается как [hkCtrl, hkShif t] и пользователь нажимает 
клавишу <А>, элемент управления THotKey читается ка к <Ctr!+Shift+A>. 
Модификаторы отображаются в порядке, определенном в наборе Modifiers. 
    Вы можете написать пример программы, которая позволяет определять 
комбинации клавиш для команд меню во время выполнения. Для этого потребуется 
две формы. В листинге 7.3 показана первая форма, содержащая компоненты TBitBtn 
и TMainMenu (хотя следовало бы поместить в форму и компонент TPopupMenu). 
Вторая форма (листинг 7.4) используется для определен ия пунктов меню и 
выполнения фактического назначения новых комбинаций клавиш. В этой второй 
форме будут содержаться элементы управления TListView и THotKey. Об элементах 
управления TListVie подробнее будет рассказано далее в этой главе, а пока 
примите на веру то, что касается элемента управления TListViev, 

    Рис. 7.3. Определение новой комбинации клавиш во время выполнения 

    Рис. 7.4. Назначенная комбинация клавиш в главном меню 

    Код первой формы для щелчка на кнопке TBitBtn создает вторую форму и 
загружает элементы TListView из списка пунктов меню и связанные с ними 
комбинации клавиш. Пользователь может выбирать пункты меню, представляемые в 
TListView, и назначать новые комбинации клавиш элементам управления THotKey 
(рис. 7.3), Когда вторая форма закрывается, главное меню модифицируется с 
помощью содержимого элемента управления List View (рис. 7.4). Файлы HKEY1. PAS 
(листинг 7.3) и HKEY2 . PAS (листинг 7.4) находятся на дополнительно 
распространяемой дискете. 


    150 Часть II. Разработка приложений 


    Листинг 7.3. \UDELPHI3\CH7 \HOTKEYI . PAS. Форма, содержащая Main Menu 
(Главное меню) 
    unit hkeyl; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
Menus, StdCtrls, Buttons; 
    type 
    TForml = class(TForm) 
    MainMenul: TMainMenu; 
    BitBtnl: TBitBtn; 
    Filel: TMenuItem; 
    Exit2: TMenuItem; 
    N3: TMenuItem; 
    PrintSetup2: TMenuItem; 
    Print2: TMenuItem; 
    N4: TMenuItem; 
    SaveAs2: TMenuItem; 
    Save2: TMenuItem; 
    Open2: TMenuItem; 
    New2: TMenuItem; 
    procedure BitBtnlClick(Sender: TObject); 
    procedure ExitlClick(Sender: TObject); end; 
    var 
    Forml: TForml; 
    implementation 
    {$R *.DFM} 
    uses ComCtrls, HKey2; 
    procedure TForml.BitBtnlClick(Sender: TObject); var 
    i: integer; Newltem : TListltem; ShortCutStr: string; begin 
    with TfrmDefineMenu.Create(self) do try 
    for i := 0 to Filel.Count - 1 do begin 
    Newltem := ListViewl.Items.Add; Newltem.Caption := Filel.Items[i].Caption; 
ShortCutStr := ShortCutToText(Filel.Items[i].Shortcut); 
Newltem.Subltems.Add(ShortCutStr); end; 
    ListViewl.Items[0].Selected := True; //По умолчанию фокус принадлежит 
первому элементу. ShowModal; for i := 0 to ListViewl.Items.Count - 1 do 
    Filel.Items[i].Shortcut := TextToShortCut(ListViewl.Items[i].Subltems[0]); 
finally Free; end; end; 


    Глава 7. Общие элементы управления Win32 151 


    procedure TForml.ExitlClick(Sender: TObject); begin 
    Close; end; 
    end. 

    Листинг 7.4. HKEY2. PAS. Форма, содержащая THotKey unit  hkey2; 

    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
StdCtrls, Buttons, ComCtrls; 
    type 
    TfrmDefineMenu = class(TForm) HotKeyl: THotKey; BitBtnl: TBitBtn; Labell: 
TLabel; ListViewl: TListView; 
    procedure ListViewlClick(Sender: TObject); procedure FormKeyPress(Sender: 
TObject; var Key: Char); procedure FormCreate(Sender: TObject); end; 
    var 
    frmDefineMenu: TfrmDefineMenu; 
    implementation {$R *.DFM} uses Menus; 
    procedure TfrmDefineMenu.ListViewlClick(Sender: TObject); begin 
    with ListViewl do 
    if (Assigned(Selected) and (Selected.Caption <> '-')) then 
    HotKeyl.HotKey := TextToShortCut (Listviewl.Selected.Subltems[0]); 
HotKeyl.SetFocus; end; 
    procedure TfrmDefineMenu.FormKeyPress(Sender: TObject; var Key: Char); begin 
    with ListViewl do begin 
    Selected.Subltems[0] := ShortCutToText(HotKeyl.HotKey); 
Updateltems(Selected.Index, Selected.Index); end; end; 
    procedure TfrmDefineMenu.FormCreate(Sender: TObject); var 
    NewColumn: TListColumn; begin 
    // Эти свойства можно установить в инспекторе объектов, 
    // но для ясности они включены сюда. 
    KeyPreview := True; 
    with ListViewl do 
    begin 
    Readonly := True; 


    152 Часть II. Разработка приложений 

    ViewStyle := vsReport; NewColumn := ListViewl.Columns.Add; 
NewColumn.Caption := 'Item1; NewColumn.Width := 100; NewColumn := 
ListViewl.Columns.Add; NewColumn.Caption := 'HotKey1; NewColumn.Width := 200; 
end; end; 
    end. 

    Элемент управления Progress Bar 

    Элемент управления Progress Bar (индикатор хода выполнения процесса) 
облегчает жизнь пользователей, позволяя им созерцать текущее состояние длинных 
операций, таких как загрузка форм и выполнение процедур, интенсивно 
использующих диск (например, копирование файла). Чтобы использовать элемент 
управления TProgressBar, определите свойства Min и Мах, а затем обновите 
значение свойства Position по ходу выполнения операции. 
    Ниже показано использование индикатора хода выполнения загрузки форм в 
память. Чтобы создать этот проект, выполните следующие действия. 
    1.    В меню File выберите команду New Application. 
    2.    В меню File выберите команду NewOForms1^About Box. 
    3.    Удалите кнопку OK из формы AboutBox. 
    4.    Добавьте компонент TProgressBar в форму AboutBox и установите его 
свойство Align равным alBottom. 
    5.    В меню File выберите команду New Form. Повторите этот шаг еще два 
раза. В вашем проекте теперь должно быть 1-5 модулей. 
    6.    Выберите команду View=> Project Source и отредактируйте исходный 
текст так, как показано в листинге 7.5 (этот текст записан на дополнительно 
распространяемой дискете). 

    !   ЛИСТИНГ 7.5. \UDELPHI3\CH7\PROGRESSBAR\PRGBAR1\PROJECT1.DPR. Загрузка форм 

    program Project!; 
    uses 
    Forms, Windows, 
    Forms, windows, 
    Unitl in 'Unitl.pas'                                       {Forml}, 
    Unit2 in 'Unit2.pas'                                       {AboutBox}, 
    Unit3 in 'Unit3.pas'                                       {Form3}, 
    Unit4 in 'Unit4.pas'                                      {Form4}, 
    Units in 'Units.pas'                                       {FormS}; 
    {$R *.RES} 
    const 
    // Используется в функции Sleep API. NumberOfMilliseconds: Longlnt = 100; 
    begin 
    Application.Initialize; 
    with TAboutbox.Create(Application) do 
    try 
    Show; 
    Update; 
    Application.CreateFormfTForml, Forml); 
    ProgressBarl.Position := 25; 
    // Имитировать время, необходимое для инициализации формы 
    // при разработке проекта, с помощью функции Sleep API. 
    // Параметр, передаваемый функции Sleep, указывает, на сколько 
    // миллисекунд задерживается выполнение. 


    Глава 7. Общие элементы управления Win32 153 


    Sleep(NumberOfMilliseconds) ; 
    Application.CreateForm(TForm3, Form3); 
    ProgressBarl.Position := 50; 
    Sleep (NumberOfMilliseconds); 
    Application.CreateForm(TForm4, Form4); 
    ProgressBarl.Position := 75; 
    Sleep (NumberOfMilliseconds) ; 
    Application.CreateForm(TForm5, FormS); 
    ProgressBarl.Position := 100; finally 
    Free; end; 
    Application.Run; end. 

    Можно устанавливать не только свойства Min, Max и Position, но и свойство 
Step и вызывать метод Steplt. чтобы сместить позицию на указанное количество 
единиц. Если вы хотите перемещать позицию индикатора на определенное 
количество единиц за одно обращение к методу, используйте метод StepBy. 
    На рис. 7.5 и в листинге 7.6 используется файловый поток для создания 
двоичной копии файла. Свойства Max, Min и Step установлены равными 
соответствующим размерам исходного файла. Число байтов, скопированных за 
каждое обращение к методу TFileStream. CopyFrom, мало для этого примера. В 
реальных проектах для повышения эффективности рекомендуется копировать большие 
фрагменты файла. Кроме того. в этом примере элемент управления TStatusBar 
появляется немного раньше срока. Строка состояния используется здесь для того, 
чтобы в ней поместился элемент управления TProgressBar. Файл STSPRG1.PAS 
находится на дополнительно распространяемой дискете. 

    Рис. 7.5. Процесс копирования файла отображается с помощью индикатора выполнения 

    Листинг 7.6. \UDELPHI3\CHP7\PROGRESSBAR\PRGBAR2\STSPRG1. PAS. Копирование файла 

    unit StsPrgl; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
StdCtrls, ComCtrls; 
    type 
    TForml = class(TForm) Buttonl: TButton; Editl: TEdit; Edit2: TEdit; 
Label1: TLabel; Label2: TLabel; StatusBarl: TStatusBar; IblStep: TLabel; 
IblMin: TLabel; IblMax: TLabel; IblSize: TLabel; 
    procedure ButtonlClick(Sender: TObject); end; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} 


    154 Часть II. Разработка приложений 


    procedure TForml.ButtonlClick(Sender: TObject); var 
    FromStream, ToStream: TFileStream; BytesToCopy: Longlnt; begin 
    FromStream := TFileStream.Create(Editl.Text, fmOpenRead) ; 
    ToStream := TFileStream.Create(Edit2.Text, fmCreate or fmOpenWrite); 
    { Инициализация элемента управления Statusbar. } 
    StatusBarl.SimplePanel := True; 
    StatusBarl.SizeGrip := False; 
    StatusBarl.SimpleText := 'Saving to ' + Edit2.Text; 
    StatusBarl.Refresh; // Убедиться, что текст выведен на экран. 
    try 
    with TProgressBar.Create(Self) do try 
    { Расположить индикатор в правой части 
    строки состояния. } Тор := Тор + 3; 
    Left := StatusBarl.Width - Width; Height := Height - 3; Parent := StatusBarl; 
    { Установить малые приращения для шагов копирования. } Step := 
FromStream.Size div 1000; Max := FromStream.Size; BytesToCopy := Step; { 
Вывести статистику. } 
    IblStep.Caption := 'Step: ' + InttoStr (Step); 
    IblSize.Caption := 'From Stream size: ' + InttoStr(FromStream.Size); 
IblMin.Caption := 'Min: ' + InttoStr(Min); IblMax.Caption := 'Max: ' + 
InttoStr(Max); { Выполнить потоковое копирование. } 
    while (ToStream.Position + BytesToCopy <= FromStream.Size) do begin 
    ToStream.CopyFrom(FromStream, BytesToCopy); Steplt; 
    Application.ProcessMessages; end; 
    { Скопировать оставшиеся байты. } 
    ToStream.CopyFrom(FromStream, FromStream.Size - ToStream.Size); Position 
:= ToStream.Size; finally 
    StatusBarl.SimpleText := ''; Free; end; finally ToStream.Free; 
FromStream.Free; end; end; 
    end. 


    Новые элементы управления Delphi 3 

    В Delphi 3 предлагаются новейшие элементы управления из COMCTL32^;j^Ji В 
частности— панель инструментов (Tool Bar) и элементы управления Cool Bar, 
которые ускоряют создание прикладных программ с единообразным интерфейсом пользователя. 

    Глава 7. Общие элементы управления Win32 155 


    Панель инструментов 

    Теперь, когда в моей палитре находится TToolBar, я не представляю, как 
программисты обходились без этого элемента управления. Новый TToolBar— оконный 
элемент управления, который ускоряет разработку прикладной программы, позволяя 
быстро создавать кнопки с одинаковыми размерами и манипулировать ими. Кнопки 
панели инструментов называются TToolButtons — легкие потомки TControl, которые 
не используют программ обработки Windows. TToolBar может содержать и такие 
оконные элементы управления, как TEdit и TComboBox. 

    Особенности использования TToolBar во время разработки 

    Чтобы создать TToolButtons во время разработки, щелкните правой кнопкой 
мыши на панели инструментов и выберите команду New Button или New Separator из 
контекстного меню. Эти кнопки можно перемешать на панели инструментов и 
снабжать заголовками. Кнопки панели инструментов имеют идентичные размеры, так 
что если вы измените размеры одной кнопки, соот ветствующим образом изменятся 
и все остальные кнопки. 
    Используйте компонент TImageList, чтобы снабдить элементы управления 
TToolButtons пиктограммами с помощью свойства Images. Поместите компонент 
TImageList в форму и расположите в ней несколько растров из каталога 
\Images\Buttons. Теперь, щелкнув правой кнопкой на панели инструментов, можете 
манипулировать существующими кнопками, а также добавить не сколько новых. 
Наконец, установите свойство Images элемента управления панели инструментов и 
посмотрите на результат (рис. 7.6). Каждому компоненту TToolButton 
соответствует графический символ из ImageList. TToolButton имеет свойство 
Imagelndex, которое автоматически растягивается вдоль панели и может быть 
изменено во время проектирования или выпо лнения. 

    Рис. 7.6. Элемент управления TToolBar во время разработки 

    Чтобы получить плоскую кнопку в стиле кнопок броузера MS Internet Explorer 
3, установите свойство Flat равным True. Если вы хотите отображать второй 
набор изображений при перемещении указателя мыши над каждой кнопкой, добавьте 
другой компонент TImageList и назначьте его свойству Hotlmages. 
    Если размер формы изменен, причем ее размер меньше области, в которой 
содержится TToolBar, и свойству Wra-pable присвоено значение True, кнопки 
панели инструментов перераспределятся автоматически. Кроме того, каждый 
компонент TToolButton имеет свойство Wrap, которое позволяет переносить кнопки 
в следующую строку. 
    I 

    Работа с TToolBar в программах 

    Чтобы добавить кнопку TToolButton на панель инструментов во время 
выполнения, сначала нужно создать экземпляр TToolButton, а затем установить 
свойство Parent. 

    with  TToolButton.Create(Self)   do Parent   :=  ToolBarl; 

    Чтобы удалить все кнопки TToolButtons панели инструментов, используйте 
метод ClearButtons. Он действует только на TToolButtons и не удаляет 
каких-либо оконных элементов управления, помещенных на панель инструментов. 
Чтобы удалить конкретную кнопку панели инструментов, следует ^отослать 
сообщение Windows TB_DELETEBUTTON. В следующем примере удаляет ся вторая 
кнопка с ToolBarl (отсчет индексов начинается с нуля).

    procedure TForml.ButtonlClick(Sender:   TObject); var 
    Buttonlndex:   Longlnt; begin 
    Buttonlndex   :=   1; 
    SendMessage(ToolBarl.Handle,   TB_DELETEBUTTON,   Buttonlndex,   0); end; 

    Вы можете создать Tool Box (набор инструментов), который позволяет 
настраивать панели инструментов во время выполнения прикладной программы. 
    Чтобы создать пример прикладной программы, которая настраивала бы панель 
инструментов, понадобятся две формы. В первую форму (набор инструментов) 
необходимо поместить компонент TToolBar, несколько предопределенных кнопок 
TToolButtcns и элемент управления Image List. Поместите на панель инструментов 
несколько новых кнопок, а в набор изображений Ima 
    ge List — достаточное количество растров из каталога \Delphi 3. 
0\Images\Buttons, чтобы поставить их в соответствие доступным кнопкам. 
Установите свойство DragMode кнопок равным dmAutomatic и свойство BorderStyle 
элемента управления TToolBar равным bsSingle. 


    156 Часть II. Разработка приложений 

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

    procedure  TfrmToolBar.CameraClick(Sender:   TObject); begin 
    MessageDlg(You  clicked on the  +   (Sender  as  TToolButton).Name  + '   
Button.',   mtlnformation,    [mbOk],    0);end; 

    Во второй форме также будет содержаться компонент TToolBar для того, 
чтобы- пользователь мог опустить на нее новые кнопки. Установите свойство 
Visible формы равным True, и, когда вы запустите проект, появятся сразу обе 
формы. Модуль содержит также код для реализации процесса перетащить и 
опустить, копирования кнопок в форму пользователя и назначен ия для них новых 
обработчиков события Onclick. 
    Файлы USRDEF1. PAS и USRDEF2 . PAS (листинг 7.7) находятся на 
дополнительно распространяемой дискете. 

    Листинг 7.7. \UDELPHI3\CH7\TOOLBAR\USRDEF\USRDEF2. PAS. Создание 
пользовательских панелей инструментов 

    unit UsrDef2; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
ToolWin, ComCtrls, StdCtrls; 
    type 
    TfrmUserForm = class(TForm) tbUserDefined: TToolBar; Label1: TLabel; 
procedure tbUserDefinedDragOver(Sender, Source: TObject; X, Y: Integer; 
    State: TDragState; var Accept: Й'о'оГёапТ;" 
    procedure tbUserDefinedDragDrop(Sender, Source: TObject; X, Y: Integer); end; 
    var 
    frmUserForm: TfrmUserForm; 
    implementation uses UsrDefl; {$R *.DFM} 
    procedure TfrmUserForm.tbUserDefinedDragOver(Sender, %>Source: TObject; X, 
Y: Integer; 
    State: TDragState; var Accept: Boolean); begin 
    Accept := ((Sender <> Source) and (Source is TToolButton)); end; 
    procedure TfrmUserForm.tbUserDefinedDragDrop(Sender, 
    4>Source: TObject; X, Y: Integer); 
    const 
    Count: Smalllnt = 0; begin 
    with TToolButton.Create(Self) do begin 
    // Расположить кнопку на панели инструментов. 
    Parent := tbUserDefined; 
    // Присвоить кнопкам изображения. 
    // Идти в обратном направлении от исходной кнопки, 
    // найти ее родителя, привести к TToolBar, a 
    // затем использовать свойство Images из TToolBar. 
    tbUserDefined.Images := TToolBar((Source as TToolButton).Parent).Images; 
    Imagelndex := (Source as TToolButton).Imagelndex; 


    Глава 7. Общие элементы управления Win32 157 


    // Присвоить кнопке уникальное имя. 
    Name := 'New' + (Source as TToolButton).Name + 
    IntToStr(Count); 
    // Назначить обработчика OnClick. OnClick := (Source as 
TToolButton).OnClick; Inc(Count); end; end; 
    end. 


    "Крутая" панель, и что в ней крутого 

    Чтобы объяснить, что такое Cool Bar, мы скажем просто: это компонент, 
который сделан по типу панели инструментов MS Internet ExplorerS. В компоненте 
TCoolBar есть несколько компонентов TCoolBands, содержащих элемент управления, 
который можно динамически устанавливать в нужную позицию и размер которого 
можно изменять. Компонент TCoolBar и каждая из его панелей могут содержать 
растровое изображение, которое "воспроизводится" только на панели или на всей 
области элемента управления TCoolBar. Кроме того, свойство Images, связанное с 
компонентом TImage, слева от каждой панели TCoolBand отображает маленький 
графический символ. 

    Панели Cool Band во время разработки 

    Первая задача в работе с TCoolBar состоит в том, чтобы создать ряд 
панелей, по одной для каждого элемента управления, которым нужно управлять с 
помощью Cool Bar. Существует два способа создания Bands (полосы). Можно 
щелкнуть на кнопке с многоточием свойства Bands, добавить элемент управления в 
форму и выбрать свойство Control панели, чтобы связать тот элемент управления 
с этой панелью. То же самое можно сделать и более простым способом: опустите 
элемент управления непосредственно на компонент Cool Bar — и панель будет 
создана и связана со свойством панели Control автоматически. 

    Добавление панелей во время выполнения 

    Чтобы добавлять компонент TCoolBand во время выполнения, объявите 
переменную типа TCollectionltem. Используйте метод Add для панели TCoolBar, и 
он возвратит экземпляр TCollectionltem. Чтобы использовать экземпляр 
TCollectionltem, его нужно привести к типу TCoolBand. Этот метод создания не 
совсем похож на методы, которые мы рассмотрели раньше, он н епонятен 
интуитивно и может быть изменен (или заменен) ко времени поставки программного продукта. 
    procedure TForml.ButtonlCl.ick (sender:   TObject); var 
    CoolBand:   TCollectionltem; begin 
    CoolBarl.Images   :=  ImageListl; CoolBand   := CoolBarl.Bands.Add; with 
CoolBand as  TCoolBand do begin 
    Control   :=  Editl; Text   := Editl.Text; Bitmap   :=  Imagel. Picture 
.Bitmap/end ; end; 


    Компонент TDateTimePicker 

    TDateTimePicker предназначен для выбора дат и времени. Первоначально 
TDateTimePicker напоминает комбинированный список с датой, но когда на нем 
щелкнуть, раскрывается календарь. Это позволяет просмотреть календарь любого 
месяца и года, узнать текущую дату и в заключение выбрать число. Если изменить 
значение свойства Kind с dtDate на dtTime, то TDa teTimePicker станет 
элементом управления для редактирования значения времени. Этот элемент 
управления выглядит почти как элемент управления TUpDown. Фактическое 
значение, выбранное пользователем, элемент управления сохраняет в свойствах 
Date и Time типа TDate и TTime соответственно. 

    158 Часть II. Разработка приложений 

    Хотя TDateTimePicker автоматизирует утомительную проверку правильности 
даты и времени, вы можете интер-фетировать данные, которые пользователь вводит 
непосредственно в элемент управления. Чтобы сделать это, установите войство 
Parselnput равным True и проверьте вводимые данные в обработчике события 
OnUserlnput. Обработчик юбытия OnUserlnput передае т необработанную строку в 
том виде, в каком она была введена пользователем, и ожидает PDateTime обратно. 
Кроме того, можно предотвратить любое изменение, установив AllowChange равным 
False. Ес-1И вы хотите предотвратить выбор даты пользователем с помощью 
раскрывающейся части элемента управления, установите для свойства DateMode 
значение dmUpDown вместо dmComboBox. 

    Компонент TAnimate 

    TAnimate проигрывает видеоклипы (AVI-файлы) в фоновом режиме, в то время 
как основная подзадача (thread) продолжает работать. Элемент управления может 
загружать AVI-файлы с помощью свойства FileName или ResourcelD, но проигрывает 
только несжатые файлы без звука. Он разработан для обработки простых 
последовательностей BMP-файлов, неупакованных в AV I-файл. Чтобы проиграть 
файл AVI без звука во время разработки, установите свойство FileName и уста-iT 
новите свойство Active равным True. Если появляется сообщение "Cannot Open 
AVI" ("Нельзя открыть AVI"), вероятно, '• AVI-файл сжат или содержит звук. В 
таком случае нужно использовать TMediaPlayer для обработки этих файлов. Если а 
щелкнуть правой кнопкой мыши на компоненте TAnimate во время разработки, то 
откроется контекстное меню с команда-** ми Next Frame и Previous Frame. 
Свойство Repetitions устанавливает количество воспроизведений клипа. По 
умолчанию значение для Repetitions равно -1, что заставляет клип повторяться в 
бесконечном цикле. 
    Методы времени выполнения — Open, Close, Play, Seek и Stop. Метод Play 
принимает параметры для индекса (отсчет индексов начинается с нуля) 
начального, конечного и отображаемого кадров. Если задать нуль в качестве 
индекса начального кадра, то клип будет проигрываться с первого кадра. Если 
задать -1 в качестве индекса конечного кадра, то клип будет воспроизводиться 
до последнего кадра. Метод Seek устанавливает клип на заданный кадр. 

    Сложные общие элементы управления 

    Рассмотрим функциональные возможности следующей группы элементов 
управления. Обратите внимание на такой факт: у каждого из этих элементов 
управления есть некоторый способ добавления некоторых возможностей. Одни 
элементы управления используют массивы или списки, другие создают порожденные 
объекты или имеют методы Add. С помощью TTabControl и TPageC ontrol один и тот 
же основной элемент управления Windows может приобрести свойства, которые 
удобно разделить на две категории. TTabControl управляет собой, используя 
массив Tabs [ ]; TPageControl использует отдельные объекты TTabSheet в 
качестве потомков. 

    Элемент управления "панель состояния" 

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

    Свойство SimplePanel 

    Чтобы использовать панель состояния в ее наиболее простой форме, 
установите свойство SimplePanel равным True и присвойте строку свойству 
SimpleText следующим образом. 

    StatusBarl.SimplePanel   := True; 
    StatusBarl.SimpleText   :=   'a  simple  example'; 

    Если вы поняли, что такое панель состояния, можете так назначить в 
прикладной программе обработчик события Оп-Hint для модификации TStatusBar, 
чтобы в панели состояния появлялись текстовые'подсказки, после того как 
пользователь установит указатель мыши на каком-либо элементе управления. 
Например, присвойте строки свойству Hint для нескольких элеме нтов управления 
вашей формы, установите обработчик события OnHint и назначьте обработчик 
события OnCreate формы следующим образом. 

    type TForml = class (TForm) private 


    Глава 7. Общие элементы управления Win32 159 


    procedure StatusHint(Sender: TObject); end; 
    procedure TForml.StatusHint(Sender: TObject); begin 
    StatusBarl.SimpleText := Application.Hint; 
    end; 
    procedure TForml.FormCreate(Sender: TObject); begin 
    StatusBarl.SimplePanel := True; 
    Application. OnHint := StatusHint; end; 


    Множественные панели 

    Из следующих разделов вы узнаете, как создать в программе несколько 
панелей и управлять каждой из них. Такие панели здесь рассматриваются как 
поверхности для рисунка и как контейнеры — для оконных элементов управления. 

    Динамическое создание панелей 

    Чтобы создать панель в строке состояния (компонент TStatusBar, в котором 
SimplePanel установлено равным False), используйте метод Add "панельного" 
объекта TStatusBar. А затем, когда у вас будет ссылка на объект TSta-tusPanel, 
возвращенная методом Add, можете установить свойства панели. 

    Procedure MyProc; var 
    Panel: TStatusPanel; begin Panel := StatusBarl.Panels.Add; 
    with РапеГ""сГо         """............" 
    begin 
    Text   :=   'New  Panel'; Bevel   :=  pbLowered; Alignment   :=  taCenter; 
Width   :=   100; end; end; 

    В листинге 7.8 имитируются функциональные возможности редактора свойства 
Panels. Для создания проекта, показанного на рис. 7.7, нужен компонент 
редактирования, чтобы обеспечить текст на панели, второй компонент 
редактирования, связанный с элементом управления TUpDown, для задания ширины 
панели, два элемента управления TRa-dioGroup для управления с 
    войствами Alignment и Bevel и TButton, чтобы начать создание панели. Файл 
исходного текста, приведенного в листинге 7.8, STSBAR1. PAS, находится на 
дополнительно распространяемой дискете. 

    Рис. 7.7. Динамическое создание панелей 

    Рис. 7.8. Создание трехмерного текста на панели состояния 


    160 Часть II. Разработка приложений 


    Листинг 7.8. \UDELPHi3\CHP7\STATUSBAR\STSBARi\STSi. PAS. Исследование 
свойств панелей 

    unit Stsl; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
ComCtrls, StdCtrls, ExtCtrls, Buttons; 
    type 
    TForml = class(TForm) StatusBarl: TStatusBar; Buttonl: TButton; GroupBoxl: 
TGroupBox; rgAlignment: TRadioGroup; edPanelText: TEdit; Label!: TLabel; 
rgBevel: TRadioGroup; edPanelWidth: TEdit; Label2: TLabel; UpDownl: TUpDown; 
    procedure ButtonlClick(Sender: TObject); end; 
    var 
    Form!: TForml; 
    implementation 
    {$R *.DFM} 
    //________________________________________________________________________ 
    // Добавить новую панель и установить ее атрибуты. 
    //-----------------------------------------------------------------------
    procedure TForml.ButtonlClick(Sender: TObject); const 
    BevelSettings: array[0..2] of TStatusPanelBevel = (pbNone, pbLowered, 
pbRaised); AligranentSettings: array[0..2] of TAlignment = (taCenter, 
taLeftJustify, taRightJustify); var 
    Panel: TStatusPanel; begin 
    Panel := StatusBarl.Panels.Add; 
    with Panel do 
    begin 
    Text := edPanelText.Text; 
    Bevel := BevelSettings[rgBevel.Itemlndex]; Alignment := 
AlignmentSettings[rgAlignment.Itemlndex]; Width := 
StrToInt(edPanelWidth.Text); end; end; 
    end. 


    Рисование на панели 

    Возможно, вы заметили, что свойство Style не было использовано в 
предыдущем примере. Оно позволяет управлять рисованием на любой панели. По 
умолчанию задан стиль psText. Если стиль панели — psOwnerDraw, можете 
использовать событие OnDrawPanel из компонента TStatusBar, Оно обеспечивает 
ссылки на весь компонент TStatusBar, конкретную выводимую панел ь и на 
прямоугольную область рисования. 


    Глава 7. Общие элементы управления Win32 161 
    6 Использование Delphi 3. Специальное издание 


    Например, в листинге 7.9 показано, как можно раскрасить трехмерный текст, 
выступающий или вдавленный, непосредственно на канве панели состояния (рис. 
7.8). Исходный текст листинга 7.9 находится в файле STS2 . PAS на 
дополнительно распространяемой дискете. 

    I Листинг 7.9. \UDELPHI3\CHPT7\STATUSBAR\STSBAR2\STS2. PAS. Создание 
трехмерного текста в строке состояния  j A 

    unit sts2; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ComCtrls, 
ExtCtrls, StdCtrls, Buttons; 
    type 
    TTextStyle = (spTextRaised, spTextLowered); 
    TForml = class(TForm) 
    StatusBarl: TStatusBar; 
    edPanelText; TEdit; 
    Labell: TLabel; 
    rgTextStyle: TRadioGroup; 
    procedure StatusBarlDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; 
const Rect: TRect); 
    procedure FormCreate(Sender: TObject); 
    procedure edPanelTextChange(Sender: TObject); 
    procedure Draw3DText(Canvas: TCanvas; PanelRect: TRect; Text: String; 
    TextStyle: TTextStyle); end; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} 
    procedure TForml. Draw3DText(Canvas: TCanvas; PanelRect: TRect; Text: String; 
    TextStyle: TTextStyle); begin 
    with Canvas do begin 
    Brush.Style := bsClear;        // Закрасить текст, а не фон. Font.Color := clHighlightText; 
    DrawText(Handle, PChar(Text), -1, PanelRect, DT_VCENTER  and DT_SINGLELINE ); 
    if (TextStyle = spTextRaised) then begin 
    Inc(PanelRect.Left); Inc(PanelRect.Top); end else begin 
    Dec(PanelRect.Left); Dec(PanelRect.Top); end; 
    Font.Color := clWindowText; 
    DrawText(Handle, PChar(Text), -I, PanelRect, DT_VCENTER  and 
DT_SINGLELINE); end; end; 


    162 Часть //. Разработка приложений 


    procedure TForml.StatusBarlDrawPanel(StatusBar: TStatusBar; 
    Panel: TStatusPanel; const Rect: TRect); var 
    PanelRect: TRect; begin 
    Panel.Text := edPanelText.Text; PanelRect := Rect; with PanelRect do 
    Left := Left + Canvas.TextWidth('W); if rgTextStyle.Itemlndex = 0 then 
    Draw3DText(StatusBar.Canvas, PanelRect, Panel.Text, spTextRaised) else 
    Draw3DText(StatusBar.Canvas, PanelRect, Panel.Text, spTextLowered) end; 
    procedure TForml.FormCreate(Sender: TObject); var 
    Panel: TStatusPanel; begin 
    Panel := StatusBarl.Panels.Add; 
    Panel.Style := psOwnerDraw; end; 
    procedure TForml.edPanelTextChange(Sender: TObject); begin 
    StatusBarl.Refresh; end; 
    end. 

    Используя метод OnDrawPanel, необязательно внутри элемента управления 
TStatusBar (или в области вывода) выводить только текст. При наличии доступа к 
канве Canvas элемента управления TStatusBar в строке состояния можно 
отобразить индикатор выполнения процесса или растр рядом с текстом. 
    Поскольку панель состояния — это оконный элемент управления, на ней можно 
разместить другие элементы управления. Например, на панели состояния можно 
поместить компоненты TImage, загрузить свойство Picture изображения 
представлением пиктограммы программы и создать действующую панель состояния, 
во многом подобную панели задач Windows 95. 
    Проект, показанный на рис. 7.9, содержит строку состояния с миниатюрными 
пиктограммами приложений, которые при щелчке на них выполнят соответствующую программу. 

    Рис. 7.9. Активная строка состояния 


    Глава 7. Общие элементы управления Win32 163 

    Создание этого приложения происходит во время проектирования формы 
(листинг 7.10). Список имен выполняемых программ в прикладной программе 
загружается в TStringList, и в том же самом цикле ImageList заполняется рядом 
изображений, использующих обращение к функции API Imagelist_AddIcon (COMMCTRL. 
PAS). Для функции Im-agelist_Add!con требуется обрабо тчик ImageList и 
пиктограмма, полученная с помощью другого обращения к API, функции Extractlcon 
(SHELLAPI. PAS). Функция Extractlcon принимает дескриптор обработчика, имя 
файла, содержащего пиктограмму, и индекс пиктограммы: 

    Imagelist_AddIcon(Imagelist.Handle,   Extractlcon(Handle,   
PChar(ExeList[i]),   0}); 

    Загрузите TImageList и TStringList и передайте их процедуре 
Displaylconlmages вместе с заготовкой оконного элемента управления, 
используемого для порождения ряда TImages. Внутри Displaylconlmages в цикле 
пройдите через ImageList, создавая по ходу компоненты TImage в строке 
состояния. Каждая канва TImage окрашена в цвет панели состояния, а на ее по 
нижении представлены пиктограммы программы, причем свойство Hint из 
TStringList содержит имя выполняемой программы и перехватывает обработчик 
события Onclick, который и заботится о выполнении связанной с пиктограммой 
прикладной программы. 
    Понятие "активной панели состояния" могло бы быть расширено, чтобы 
включить настраиваемый пользователем список прикладных программ. Или такая 
панель могла бы отображать пиктограммы для выполнения действий, принимаемых 
внутри вашей прикладной программы, например перемещения по таблице, проверки 
правильности написания документа или передачи электрон ной почты. Файл 
ACTBAR1. PAS находится на дополнительно распространяемой дискете. 

    •   ЛИСТИНГ 7.10. \UDELPHI3\CH7\STATUSBAR\ACTBAR1. PAS. СоЗДЗНИе ЗКТИВНОЙ 
СТрОКИ СОСТОЯНИЯ 

    unit ActBarl; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
ComCtrls, ExtCtrls, StdCtrls, Buttons; 
    type 
    TForml = class(TForm) 
    StatusBarl: TStatusBar; 
    procedure FormCreate(Sender: TObject); private 
    procedure StatusIconClick(Sender: TObject); 
    procedure Displaylconlmages(ImageList: TImageList; ExeNameList: TStringList; 
    WinControl: TWinControl); end; 
    var 
    Forml: TForml; 
    implementation 
    {$R *.DFM} 
    uses ShellAPI, CommCtrl; 
    procedure TForml.StatusIconClick(Sender: TObject); begin 
    WinExec(PChar((Sender as TImage).Hint), SW_ShowNormal); end; 
    procedure TForml.Displaylconlmages(ImageList: TImageList; ExeNameList: TStringList; 
    WinControl: TWinControl); var 
    IconLeft: integer; 
    i: integer; begin 
    IconLeft := ImageList.Width div 2; 
    for i := 0 to ImageList.Count - 1 do with TImage.Create(Self) do 


    164 Часть II. Разработка приложений 


    begin 
    Parent : = ^[inCon^trol.^ 
    ffint~T= ExeNameList.Strings[i]; 
    ShowHint := True; 
    OnClick := StatusIconClick; 
    Top := (WinControl.Height - ImageList.Height) div 2; 
    Left := IconLeft; 
    Width := ImageList.Width; 
    Height := ImageList.Height; 
    IconLeft := IconLeft + Width + 5; 
    Canvas.Brush.color := StatusBarl.Brush.Color; 
    Canvas.FillRect(Canvas.ClipRect); 
    ImageList.Draw(Canvas, 0, 0, i) ; end; end; 
    procedure TForml.FormCreate(Sender: TObject); const 
    ExeList: array[0..9] of String = ( 
    1CALC.EXE', 
    'CLIPBRD.EXE1 , 
    'NOTEPAD.EXE', 
    'REGEDIT.EXE', 
    'PBRUSH.EXE', 
    1SYSMON.EXE', 
    'DEFRAG.EXE', 
    'EXPLORER.EXE', 
    1 TELNET.EXE', 
    1 TERMINAL.EXE' 
    ); 
    var 
    StringList: TStringList; ImageList: TImageList; i: integer; begin 
    StringList := TStringList.Create; ImageList := TImageList.Create(Self); try 
    for i := 0 to 9 do begin 
    Imagelist_AddIcon(ImageList.Handle, Extractlcon(Handle, PChar(ExeList[i]), 
0) StringList.Add(ExeList[i]); end; 
    Displaylconlmages(ImageList, StringList, StatusBarl); finally 
    StringList.Free; ImageList.Free; end; end; 
    end. 


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

    Элемент управления заголовка— панель общего назначения, выравниваемая по 
верхней границе формы, которую можно обрабатывать примерно так же, как и 
TStatusBar. Подобные методы могут использоваться, чтобы добавлять разделы и 
обрабатывать события вывода владельца. В отличие от TStatusBar, в 
THeaderControl включены свойства Sections вместо Panels. Чтоб ы выяснять, что 
происходит в Sections, используйте события OnSectionClick, OnSectionResize и 
OnSectionTrack компонента THeaderControl. 
    Чтобы создать новые разделы во время разработки, воспользуйтесь кнопкой с 
изображением многоточия для свойства Sections и щелкните на кнопке Add 
редактора свойств. Определив свойства THeaderSection в редакторе свойств, 
можно присвоить текст или определить ширину строки заголовка. 
    Чтобы создать разделы программно, используйте метод Sections.Add и 
присвойте его результат THeaderSection (листинг 7.11). Файл SECCRT1. PAS 
находится на дополнительно распространяемой дискете. 


    Глава 7. Общие элементы управления Win32 165 


    ЛИСТИНГ 7.11. \UDELPHI3\CH7\HEADERCONTROL\SECTIONCREATE\SECCRT1.PAS. 
    Программное создание разделов панели заголовка 

    unit' SecCrtl;................    ............................................................................................................" 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls; 
    type 
    TForml = class(TForm) 
    HeaderControll: THeaderControl; 
    procedure FormCreate(Sender: TObject); private 
    { Закрытые объявления. } public 
    { Открытые объявления. } end; 
    var 
    Forml: TForml; 
    implementation ($R *.DFM} 
    procedure CreateSections(HeaderControl: THeaderControl; SectionText: array 
of string); var 
    I: integer; 
    HS: THeaderSection; begin 
    for I := Low(SectionText) to High(SectionText) do 
    begin 
    HS := HeaderControl.Sections.Add; HS.Text := SectionText[I]; 
    end; end; 
    procedure TForml.FormCreate(Sender: TObject); begin 
    CreateSections(HeaderControll, ['one', 'two', 'three', 'four']); end; 
    end. 

    Каждый раздел может иметь свой метод прорисовки. Чтобы присвоить свойству 
Style значение hsOwnerDraw для раздела, напишите код для обработчика события 
OnDrawSection. Пример, приведенный на рис. 7.10, показывает, как нарисовать 
графический символ из ImageList в ответ на щелчок на разделе (листинг 7.12). 
Цвет шрифта также изменяется, чтобы высветить текущий текст. Файл SECDRW1. PAS 
находится на дополнительно распространяемой дискете. 

    Рис. 7.10. Окно Owner Draw Header Control с несколькими разделами 


    Листинг 7.12. \UDELPHi3\CHP7\HEADERCONTROL\ONSECTiONDRAW\ SECDRW1.PAS. 
Управление отображением разделов заголовка 

    unit SecDrwl; 
    interface 
    uses 


    166 Часть П. Разработка приложений 


    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls; 
    type 
    TForml = class(TForm) 
    HeaderControll: THeaderControl; 
    ImageListl: TImageList; 
    procedure HeaderControllDrawSection(HeaderControl: THeaderControl; 
    Section: THeaderSection; const Rect: TRect; Pressed: Boolean); end; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} 
    procedure TForml.HeaderControllDrawSection(HeaderControl: THeaderControl; 
    Section: THeaderSection; const Rect: TRect; Pressed: Boolean); begin 
    with HeaderControl.Canvas do if Pressed then begin 
    Font.Color := clRed; 
    ImageListl.Draw(HeaderControl.Canvas, Rect.Left + 2, Rect.Top + 2, 0); 
TextOut(Rect.Left + ImageListl.Width + 4, Rect.Top + 2, Section.Text); end 
else begin 
    Font.Color := clBlue; 
    TextOut(Rect.Left + ImageListl.Width + 4, Rect.Top + 2, Section.Text); 
end; end; 
    end. 

    Компонент THeaderControl имеет два уникальных события: OnSectionTrack и 
OnSectionResize. Событие OnSectionTrack вызывается, когда пользователь 
перемещает разделитель. Когда перемещение разделителя завершается, вызывается 
событие OnSectionResize. Можно было бы использовать обработчики для этих двух 
событий, чтобы изменять размеры других элементов у правления, например 
TListBoxes, в соответствии с размерами разделов. 

    Элемент управления Image List 

    Image List — невизуальный элемент управления, косвенно являющийся потомком 
TComponent, назначение которого состоит в том, чтобы хранить ряд изображений 
одинакового размера. Многие компоненты Win95 используют TImageList для 
визуального различия. 
    Набор изображений можно легко определить во время разработки в редакторе 
компонентов TImageList и затем присвоить различным свойствам изображений 
элементов управления, таким как TTreeView, TListView, TToolBar и TCoolBar. 
Например, TListView имеет как свойство Smalllmages, так и свойство 
Largelmages, указывающие на отдельные наборы TImageList, кото рые хранят 
графические образы для маленьких и больших пиктограмм. Компонент TImageList 
также имеет собственные методы рисования, методы для рисования изображений во 
время перемещения оконного элемента управления и методы поддержки масок для 
перекрывающихся изображений. 
    Самые новые библиотеки общих элементов управления (Common Controls DLL 
(4.0)), которые распространяются с Internet Explorer 3.0, расширяют 
возможности Image List. Набор изображений, например, использу-™    ется вместе 
с усовершенствованным элементом управления Header, который автоматизирует 
процесс вывода изображений в заголовке, устраняя необходимость 
непосредственного получения канвы и рисунка. 

    Глава 7. Общие элементы управления Win32 167 


    ImageList в режиме проектирования 

    Чтобы ознакомиться с основными чертами ImageList, можно обойтись и без 
программирования. В новом проекте добавьте в форму компонент TTreeView. 
Установите свойство Images компонента TTreeView равным ImageListl. Затем 
найдите свойство Items и щелкните на кнопке с многоточием, чтобы вызвать 
редактор элементов TTreeView. 
    Щелкните на кнопке New Item и введите любой текст (рис. 7.11). Проделайте 
эту операцию несколько раз, вводя любой текст. Когда вы дойдете до последнего 
элемента, щелкните на кнопке New Subltem и введите текст. Для вложенного 
элемента установите значение Image Index равным 1. Чтобы закончить операцию, 
щелкните на кнопке ОК для выхода из редактора э лементов. 
    Теперь дважды щелкните на компоненте TlimageList, чтобы отобразить 
редактор ImageList. Редактор отображает четыре ячейки в нижней части 
диалогового окна; эти ячейки предназначены для хранения ряда изображений. 
Горизонтальные полосы прокрутки появляются, когда к набору добавляется пятое 
изображение. Щелкните на кнопке Add, укажите каталог \DELPHI3\ 
    IMAGES\BUTTONS и дважды щелкните на первом файле в списке ABORT.BMP. 
Повторите эту операцию для файла ALARM. BMP. В ответ на запрос в диалоговом 
окне ("separate into 2 separate bitmaps?" ("разделить на 2 отдельных 
растра?")) щелкните на кнопке No. В группе Options выберите опцию Crop. 
Щелкните на кнопке ОК редактора набора изображений, чтобы увидеть элемент 
управления TTreeView, и запустите проект на выполнение (рис. 7.12). Первый 
уровень элементов отображает изображение ABORT. BMP, второй — 
ALARM. BMP. Изменение индекса изображений приводит к выводу соответствующего 
элемента в наборе изображений. 

    Рис. 7.11. Редактор элементов TTreeView 

    Рис. 7.12. TTreeView и связанные изображения 

    Компонент TImageList обеспечивает и другие возможности. Вы их изучите по 
мере исследования элементов управления Win95, которые зависят от Image List. 
Ну а теперь посмотрим, на что способен компонент TImageList сам по себе. 

    Перехват экрана с помощью TImageList 

    По умолчанию размер изображения равен 16x16 пикселей, но в 
действительности он может быть значительно большим, даже такого размера, как 
весь экран. Вызовите конструктор CreateSize с новыми параметрами ширины и 
высоты, чтобы зарезервировать необходимый объем памяти для хранения 
изображения. С помощью CreateSize и метода Get-Bitmap можно реализовать 
    простейшую утилиту, которая перехватывает весь экран целиком, сохраняет 
его и загружает растровое изображение из файла (рис. 7.13). 

    Рис. 7.13. Утилита перехвата экрана 


    168 Часть II. Разработка приложений 

    Рисование и сохранение всего рабочего стола как одной гигантской канвы не 
связанно с Image List напрямую, но является основой для сбора данных с экрана, 
так что эта функция покрывает предыдущую. Вы должны получить программу 
обработки контекста устройства для всего рабочего стола и назначить ее в 
качестве обработчика для TCanvas. В разделе закрытых объявлений типа формы 
поместите переменную, которая будет содержать канву рабочего стола. 

    type TForml   =  class    (TForm) 
    • private 
    DeskTopCanvas:   TCanvas; end; 

    Кроме того, в обработчике события OnCreate формы поместите оператор, 
присваивающий контекст устройства рабочего стола программе обработки новой канвы. 

    // Получить  контекст устройства окна рабочего стола, procedure  
TForml.FormCreate(Sender:   TObject); begin 
    DeskTopCanvas   :=  TCanvas.Create; 
    DeskTopCanvas.Handle   :=  GetDC(Hwnd_Desktop); end; 

    Hwnd_Desktop — специальная константа, определенная в файле WINDOWS . PAS, 
которая идентифицирует рабочий стол. Константа Hwnd_Desktop в настоящее время 
равна нулю, но она включена для совместимости и удобочитаемости. 
    Теперь, когда вы научились пользоваться методами и свойствами TCanvas, 
можете сохранять рабочий стол как растр, а позже вставлять его в TImageList. 
Чтобы сейчас испытать правильность последовательности шагов, поместите кнопку 
на форму с кодом для вывода текста в верхнем левом углу рабочего стола. 
    procedure  TForml.ButtonlClick(Sender:   TObject); begin 
    DeskTopCanvas.TextOut(0,0,'        howdy world'); end; 
    Убедившись в том, что это работает, избавьтесь от кода теста и удалите 
кнопку из формы. В оставшейся части этого раздела вы научитесь: 
    •    перехватывать изображение рабочего стола и помещать его в Image List; 
    *    отслеживать и отображать растр Image List; 
    в   перемещать изображение в пределах области отображения. 
    Перемещение изображения внутри области отображения занимает 
непропорциональное количество кода, так что это отсрочено на более поздний срок. 
    Для проекта требуется панель, которая покрывает почти всю форму, кроме 
полосы в нижней части, причем свойство панели Align установлено равным alTop и 
свойство BevelOuter установлено равным bvLowered. Опустите элемент управления 
TImage на панель и установите свойство Align равным alClient. Внизу формы 
поместите кнопку Speed Button и элементы управления Image List, TEdit и TUpDown. 
Установите свойство Associate компонента TUpDown равным Editl, а свойство 
Мах— равным нулю. Загрузите VIDEO.BMP в качестве пиктограммы для кнопки Speed 
Button. 
    Теперь позаботимся о хорошем разрешении интерфейса пользователя. Поскольку 
по умолчанию величина изображения в Image List равна всего 16x16 пикселей, 
выполним конструктор CreateSize, чтобы в Image List можно было сохранять 
большие растры с размером экрана. Чтобы сделать это, нужно передать ширину и 
высоту экрана в качестве параметров; обработчик с обытия OnCreate формы теперь 
примет такой вид. 

    procedure  TForml.FormCreate(Sender:   TObject); begin 
    //  Получить   контекст  устройства  окна рабочего  стола. 
    DeskTopCanvas := TCanvas.Create; 


    Глава 7. Общие элементы управления Win32 169 


    //  Восстановить   ImageList  с  большими размерами. 
ImageListl.CreateSize(Screen.Width,   Screen.Height); end; 

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

    procedure  TForml.Refreshlmage(X,   Y:   Smalllnt); begin 
    Image1.Canvas.FiliRect(Image1.Canvas.ClipRect); 
    ImageListl.Draw(Imagel.Canvas,   X,   У,   UpDownl.Position); 
    Imagel.Refresh-end; 

    Обработчик щелчка на кнопке создает временный растр для кнопки, что 
позволяет выполнять метод CopyRect из DeskTopCanvas. Затем для добавления к 
элементу управления Image List используется временный растр и обновляется 
содержимое компонента TUpDown в соответствии с количеством изображений, 
содержащихся в Image List. В заключение, изображение снова перерисовывается с 
помощью Ref reshlmage. 

    procedure TForml.SpeedButtonlClick(Sender: TObject); begin 
    BitMap := TBitMap.Create; 
    DeskTopCanvas.Handle := GetDC(Hwnd_Desktop); 
    try 
    // Установить размер растра равным размеру экрана 
    // и нарисовать канву экрана в соответствии с этим. 
    BitMap.Width := Screen.Width; 
    BitMap.Height := Screen.Height; 
    Bitmap.Canvas.CopyRect(Bitmap.Canvas.ClipRect, 
    DeskTopCanvas, DeskTopCanvas.ClipRect ) ; 
    // Добавить новый растр Image List. 
    ImageListl.Add(BitMap, nil); 
    // Модифицировать содержимое элемента управления TUpDown. 
    UpDownl.Max := ImageListl. Count - 1; 
    UpDownl.Position := UpDownl.Max; 
    UpDownl.Invalidate; 
    // Повторно перерисовать текущий растр Image List на элементе управления Image. 
    Refreshlmage(0,0) ; 
    finally 
    ReleaseDC(Handle, Hwnd_Desktop); 
    BitMap.Free; 
    end; end; 

    Есть еще две подпрограммы в одну строку: одна— для события OnClick 
элемента управления TUpDown, которая вызывает Ref reshlmage, вторая —для 
освобождения объекта DeskTopCanvas, когда форма уничтожается. 
    Чтобы перерисовывание растров происходило только тогда, когда нажата 
кнопка мыши и мышь перемещается, нужно добавить булево свойство Mouse Is Down 
к объявлению типа формы для проверки этого свойства в обработчике события 
OnMouseMove. Необходимо также добавить переменные ImgX и ImgY, чтобы хранить в 
них координаты левого верхнего угла растра во вре мя его перемещения внутри 
элемента управления TImage. 
    Элементу управления TImage можно присвоить изображение курсора в виде 
руки, чтобы указать на возможность перемещения. 

    type TForml  =  class   (TForm) 
    private 
    DeskTopCanvas: TCanvas; 
    BitMap: TBitMap; 
    ImgX, ImgY: Smalllnt; 
    MouselsDown: Boolean; 
    procedure Refreshlmage(X, Y: Smalllnt); 


    170 Часть II. Разработка приложений 

    end; 
    implementation   {$R  *.DFM} const crHandOpen =  1   ; 

    Добавьте код к обработчику события OnCreate формы, чтобы инициализировать 
курсор в виде руки, следующим образом. 

    // Используется  курсор в  виде  руки, 
    //  чтобы указать,   что  растр можно  перемещать. 
    Screen.Cursorstc[HandOpen]    :=  LoadCursorFromFile(   'HandFlat.cur'   ) 
  ; 
    Imagel.Cursor   :=  crHandOpen; 

    Остальной код в листинге 7.13 вычисляет расстояние и направление, в 
котором перемещается изображение, и повторно выводит изображение в новом 
положении. Файл SCRCAP1. PAS находится на дополнительно распространяемой дискете. 

    | Листинг 7.13. \UDELPHI3\CHP7\IMAGELIST\SCRCAP\SCRCAP1.PAS. Перехват 
экрана с помощью imageList 

    unit ScrCapl; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
ExtCtrls, StdCtrls, Buttons, ComCtrls; 
    type 
    TForml = class(TForm) 
    ImageListl: TImageList; 
    SpeedButtonl: TSpeedButton; 
    Editl: TEdit; 
    UpDownl: TUpDown; 
    Panell: TPanel; 
    Imagel: ТImage; 
    Labell: TLabel; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure SpeedButtonlClick(Sender: TObject); 
    procedure UpDownlClick(Sender: TObject; Button: TUDBtnType); 
    procedure ImagelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); 
    procedure ImagelMouseUp(Sender: TObject; Button: TMouseButton; Shift: 
TShiftState; X, Y: Integer); 
    procedure ImagelMouseDown(Sender: TObject; Button: TMouseButton; 
    Shift: TShiftState; X, Y: Integer); private 
    DeskTopCanvas: TCanvas; BitMap: TBitMap; 
    ImgX, ImgY: Smalllnt; 
    MouselsDown: Boolean; 
    procedure Refreshlmage(X, Y: Smalllnt); end; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} 
    const 
    crHandOpen = 1; 
    { 

    Очистить старый растр, нарисовать новый растр на элементе управления Image 


    Глава 7. Общие элементы управления Win32 171 

    и принудительно перерисовать. } 

    procedure TForml.Refreshlmage(X, Y: Smalllnt); begin 
    Imagel.Canvas.FillRect(Imagel.Canvas.ClipRect); 
    ImageListl.Draw(Imagel.Canvas, X, Y, UpDownl.Position); 
    Imagel.Refresh; end; 
    procedure TForml.FormCreate(Sender: TObject); begin 
    // Получить контекст устройства окна рабочего стола. DeskTopCanvas := TCanvas.Create; 
    // Снова вывести ImageList с большими размерами. 
ImageListl.CreateSize(Screen.Width, Screen.Height); 
    Screen.Cursors[crHandOpen] := LoadCursorFromFile('HandFlat.cur'); 
Imagel.Cursor := crHandOpen; end; 
    procedure TForml.FormDestroy(Sender: TObject); begin 
    DeskTopCanvas.Free; end; 
    procedure TForml.SpeedButtonlClick(Sender: TObject); begin 
    Visible : = False; BitMap := TBitMap.Create; 
    DeskTopCanvas.Handle := GetDC(Hwnd_Desktop); try 
    // Установить размеры растра в соответствии с размерами экрана 
    // и нарисовать канву на экране. 
    BitMap.Width := Screen.Width; 
    BitMap.Height := Screen.Height; 
    Bitmap.Canvas.CopyRect(Bitmap.Canvas.ClipRect, 
    DeskTopCanvas, DeskTopCanvas.ClipRect); // Добавить новый растр к Image 
List. ImageListl.Add(BitMap, nil); 
    // Обновить элемент управления Update, чтобы он соответствовал количеству изображений. 
    UpDownl.Max := ImageListl.Count - 1; UpDownl.Position := UpDownl.Max; UpDownl.Invalidate; 
    // Перерисовать текущий растр из Image List на элементе управления Image. 
ImgX := 0; ImgY := 0; Refreshlmage(0, 0); finally 
    ReleaseDC(Handle, Hwnd_Desktop); BitMap.Free; Visible := True; end; end; 
    procedure TForml.UpDownlClick(Sender: TObject; Button: TUDBtnType); begin 
    Refreshlmage(0, 0); end; 
    procedure TForml.ImagelMouseMove(Sender: TObject; Shift: TShiftState; X, 
    Y: Integer); const 
    LastX: Smalllnt = 0; 
    LastY: Smalllnt = 0; 



    172 Часть II. Разработка приложений 


    begin 
    if MouselsDown and (ImageListl.Count > 0) then begin 
    if X > LastX then 
    ImgX := ImgX + (X - LastX) else if X < LastX then 
    ImgX := ImgX - (LastX - X); if Y > LastY then 
    ImgY := ImgY + (Y - LastY) else if Y < LastY then 
    ImgY := ImgY - (LastY - Y); Refreshlmage(ImgX, ImgY); end; 
    LastX := X; LastY := Y; end; 
    procedure TForml.ImagelMouseUp(Sender: TObject; Button: TMouseButton; 
    Shift: TShiftState; X, Y: Integer); begin 
    MouselsDown := False; end; 
    procedure TForml.ImagelMouseDown(Sender: TObject; Button: TMouseButton; 
    Shift: TShiftState; X, Y: Integer); begin 
    MouselsDown := True; end; 
    end. 

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

    Элемент управления Tab (Вкладка), например TPageControl, является потомком 
TCustomTabControl. TTab-Control отображает вкладки вдоль верхней границы 
элемента управления, но не имеет никаких встроенных страниц. Используйте 
элемент управления Tab, если хотите иметь нечто похожее на записную книжку, 
без накладных расходов на дополнительные оконные эле менты управления, как в 
случае с TPageControl. Хотя TTabControl проще, чем TPageControl, для 
TTabControl также нужна программа, реагирующая на новый выбор вкладки пользователем. 

    Управление вкладками 

    Новые вкладки создаются с помощью свойства Tabs. Чтобы установить новые 
вкладки во время разработки, используйте редактор свойства Tabs. Поскольку 
свойство Tabs имеет тип TStrings, можно добавлять, удалять и вставлять вкладки 
во время выполнения. Свойство Tablndex содержит номер активной выбранной вкладки. 

    ПРЕДОСТЕРЕЖЕНИЕ 

    Установка свойства Tabindex равным -1 указывает, что никакая вкладка не 
была выбрана; поэтому проверяйте это, чтобы избежать сообщения "EList error... 
tab control access error." ("Ошибка EList... ошибка доступа для элемента 
управления вкладки."). Вы можете думать, что это сообщение никогда не 
возникнет, но сразу после программного удаления вкладки ни одна вкладка не 
будет выбрана. 
    Можно протестировать программное управление свойством Tabs, используя 
минимум кода (рис. 7.14). Поместите в новый проект TTabControl, элемент 
управления TEdit и три кнопки Bit Buttons. Присвойте кнопкам заголовки "Add", 
"Insert" и "Delete" и назовите их bbAdd, bblnsert и bbDelete. Создайте 
обработчики события Onclick для всех трех кнопок и вставьт е код, который 
приведен в листинге 7.14. Файл TCI. PAS размещен на дополнительно 
распространяемой дискете. 

    Глава 7. Общие элементы управления Win32 173 


    Размеры и размещение вкладок 

    Свойства TabWidth и TabHeight указывают величину соответствующих измерений 
в пикселях. Элемент управления устанавливает ширину и высоту вкладки, когда 
TabHeight и TabWidth имеют значение по умолчанию, равное нулю. После того как 
TabWidth или TabHeight получило значение, отличное от нуля, эти размеры станут фиксированными. 
    MultiLine— булево свойство, которые управляет поведением вкладок, когда 
общая ширина вкладок превышает ширину элемента управления Tab. Если MultiLine 
равно False, появляется набор стрелок, которые позволяют пользователю 
прокручивать окно, чтобы увидеть вкладки, невидимые на элементе управления. 
Если MultiLine равно True, вкладки располагаются по в ертикали одна над другой 
внутри видимой области элемента управления Tab (рис. 7.15). 

    Рис. 7.14. Добавление, вставка и удаление вкладок во время выполнения 

    Рис. 7.15. Элемент управления Tab с установками свойств MultiLine, 
TabWidth и TabHeight 

    Листинг 7.14. \UDELPHi3\CHP7\TABCONTROL\ADDiNSRT\TCl. PAS. Добавление, 
вставка и удаление вкладок во время выполнения 

    unit TCI; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
StdCtrls, Buttons, ComCtrls; 
    type 
    TForml = class(TForm) 
    Editl: TEdit; 
    bbAdd: TBitBtn; 
    bblnsert: TBitBtn; 
    bbDelete: TBitBtn; 
    TabControll: TTabControl; 
    procedure bbAddClick(Sender: TObject); 
    procedure bblnsertClick(Sender: TObject); 
    procedure bbDeleteClick(Sender: TObject}; end; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} 
    procedure TForml.bbAddClick(Sender: TObject); begin 
    TabControll.Tabs.Add(Editl.Text); end; 


    174 Часть II. Разработка приложений 

    procedure TForml.bblnsertClick(Sender: TObject); begin if 
(TabControll.Tablndex <> -1) then 
    TabControll.Tabs.Insert(TabControll.Tablndex, Editl.Text); end; 
    procedure TForml.bbDeleteClick(Sender: TObject); begin if 
(TabControll.Tablndex <> -1) then 
    TabControll.Tabs.Delete(TabControll.Tablndex) ; end; 
    end. 


    События элемента управления Tab 

    Элемент управления Tab имеет два основных события. Первым является событие 
OnChanging. Параметр Allow-Change события OnChanging можно использовать, чтобы 
пользователь не смог перейти к другой вкладке до проверки критерия 
правильности. Например, иногда нужно, чтобы пользователи заполнили поле ввода 
перед переходом к новой вкладке. 

    procedure  TForml.TabControllChanging(Sender:   TObject; 
    var AllowChange:   Boolean); begin 
    AllowChange   :=   (Editl.Text   <>   '    '); end; 

    Затем, после изменения вкладки, происходит событие OnChange. Используйте 
обработчик этого события, чтобы поместить код, который обновляет данные или 
отображает элементы управления, основанные на текущем значении свойства 
Tablndex. 

    Вывод информации на элемент управления Tab 

    Выводить информацию можно непосредственно в клиентскую область или в 
область элемента управления Tab. Как это сделать, рассказано ниже. 

    Вывод информации в клиентскую область элемента управления Tab 

    Элемент управления Tab имеет набор свойств, которые только воспламеняют 
ваше воображение несбыточными мечтами, например свойство DisplayRect, которое, 
казалось бы, указывает, что вы можете рисовать на нем, если у вас есть канва. 
С другой стороны, вы не можете выводить на канву формы, потому что элемент 
управления Tab затеняет вид. Вы могли бы, кон ечно, поместить элемент 
управления TImage на элемент управления Tab и рисовать на канве Canvas 
элемента управления TImage. 
    К счастью, дополнительный элемент управления не нужен. TTabControl имеет 
свойство Handle, которое может использоваться в вызове GetDC. Все, что нужно 
сделать, — это создать TCanvas и назначить обработчик, возвращенный функцией 
GetDC свойству Handle канвы Canvas (листинг 7.15). Файл TCDRAW1. PAS находится 
на дополнительно распространяемой дискете. 

    ЛИСТИНГ 7.15. \UDELPHI3\CHPT7\TABCONTROL\PAINT\TCDRAW1. PAS. Рисование на TabControl 

    unit  TCDrawl; 
    interface 
    uses 
    Windows,   Messages,   SysUtils,   Classes,   Graphics,   Controls,   
Forms,   Dialogs, ComCtrls,   ExtCtrls,   StdCtrls; 
    type 
    TForml = class(TForm) 
    TabControll: TTabControl; 
    procedure TabControllChange(Sender: TObject); private 
    { Закрытые объявления. } 


    Глава 7. Общие элементы управления Win32 175 


    public 
    { Открытые объявления. } end; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} 
    procedure TForml.TabControllChange(Sender: TObject); var 
    Canvas: TCanvas; begin 
    Canvas := TCanvas.Create; try 
    Canvas.Handle := GetDC((Sender as TTabControl).Handle) ; try 
    Canvas.Font.Color := clRed; 
    with (Sender as TTabControl), DisplayRect do 
    begin 
    Canvas.Brush.Color := clWhite; 
    Canvas.FillRect(DisplayRect); 
    Canvas.TextOut((Right - Canvas.TextWidth(Tabs[Tablndex])) 
    div 2, Bottom div 2, Tabs[Tablndex]) ; end; finally 
    ReleaseDC((Sender as TTabControl).Handle, Canvas.Handle) ; end; finally 
    Canvas.Free; end; end. 

    Методика рисования в области пользователя элемента управления Tab может 
использоваться в тех случаях, когда специфический обработчик события OnDraw 
или свойство Canvas не поддерживается. Не забудьте, ^    что, если у вас есть 
программа обработки окна, можете получить контекст устройства через GetDC и 
назначить его свойству Handle создаваемой канвы 

    Вывод информации в область вкладки 

    Что можно сказать о выводе информации в область вкладки TTabControl? 
Возможно, вы хотите вывести графический символ на одну из вкладок или изменить 
цвет текста на вкладке. Та же самая общая методика работает и здесь, но нужен 
прямоугольник вкладки, выбранной в текущий момент. Можно получить значения 
свойств BoundRect, Clien-tRect и DisplayRect. Эт и свойства TRect не очень 
полезны, так как относятся к TTabControl в целом. 
    Delphi не всегда предоставляет все желаемые свойства, но часто позволяет 
получить доступ к инструментальным средствам, которые вы можете предусмотреть 
для себя. В этом случае можно отослать сообщение Windows API TCM_GETITEMRECT 
непосредственно элементу управления и получить прямоугольник, ограничивающий 
конкретную вкладку. Основные синтаксические элементы для такого обращения 
показаны ниже: 

    SendMessage(TabControll.Handle,   TCM_GETITEMRECT,   Tablndex,   Longlnt(@TCRect)); 

    Для SendMessage требуется программа обработки данного элемента управления, 
константа сообщения TCM_GETITEMRECT (определенная в COMMCTRL. PAS), индекс 
рассматриваемой вкладки и адрес переменной TRect. После того как в переменной 
Тгее?~будут"содержаться размеры вкладки, для рисования в области вкладки можно 
использовать тот же набор методов. 
    Рассмотрим усовершенствованный элемент управления Tab, называемый 
THilightTabControl, который высвечивает текущий индекс вкладки красным 
шрифтом, переписывая первоначальный текст (листинг 7.17). Обратите внимание, 
что здесь перехватывается сообщение Windows WM_PAINT, а не какое-нибудь 
событие, например OnChange. Перехват на этом уровне дает правил ьную 
перерисовку даже в том случае, когда элемент управления выводится впервые или 
когда он закрыт, и только затем выводится в другое окно. Исходный текст 
программы, приведенной в листинге 7.16 (HLTABC1. PAS), находится на 
дополнительно распространяемой дискете. 

    176 Часть IL Разработка приложений 


    ЛИСТИНГ 7.16. \UDELPHI3\CHPT7\TABCONTROL\HILITE\HLTABC1.PAS. Исходный 
текст программы для компонента THilightTabControl 

    unit HLTabCl; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls; 
    type 
    THilightTabControl = class(TTabControl) 
    private 
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;   { end; 
    procedure Register; implementation uses CommCtrl; 
    procedure THilightTabControl.WMPaint(var Message: TWMPaint); var 
    InnerRect, TCRect: TRect; Canvas: TCanvas; begin 
    Inherited; 
    SendMe~ssage (Handle, TCM_GETITEMRECT, Tablndex, Longlnt(@TCRect)); 
    Canvas := TCanvas.Create; 
    try 
    with TCRect do 
    InnerRect := RectfLeft + 6, Top + 2, Right - 6, Bottom - 2); Canvas.Handle 
:= GetDC(Handle); Canvas.Font.Color := clRed; Canvas.Brush.Color := clBtnFace; 
    Canvas.TextOut(InnerRect.Left, InnerRect.Top, Tabs[Tablndex]); finally 
    Canvas.Free; end; end; 
    procedure Register; begin 
    RegisterComponents('UDelphi3', [THilightTabControl]); end; 
    end. 


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

    Компоненты TPageControl и TTabControl являются потомками общего предка— 
TCustomTabControl — и совместно используют похожие свойства и события. 
MultiLine, TabHeight, TabWidth, OnChange и OnChanging идентичны в этих двух 
элементах управления. TPageControl, однако, содержит элемент управления Tab 
Sheets, который может, в свою очередь, содержать други е визуальные элементы 
управления и методы управления вкладками, отличные от обработки в элементе 
управления Tab. 

    Глава 7. Общие элементы управления Win32 177 


    Создание страниц вкладок во время разработки 

    В компоненте TPageControl нет редактора, который позволял бы редактировать 
строковый список вкладок, хотя такой редактор есть в случае с TTabControl. 
Дело в том, что Tab Sheets (или Pages — страницы)— это, скорее, отдельные 
элементы управления, созданные и связанные с элементом управления Page с 
помощью свойства PageControl элемента Tab Sheets. До 
    бавление Tab Sheets к элементу управления Page фактически сводится к 
добавлению в массив TTabSheet, известный как свойство Pages. Каждый раз, когда 
к элементу управления Page добавляется новый Tab Sheet (лист вкладки), 
свойство PageCount увеличивается. 
    Чтобы добавить Tab Sheet во время разработки, щелкните правой кнопкой на 
элементе управления Page и выберите New Page из контекстного меню. Контекстное 
меню также предусматривает переход к предыдущим и следующим страницам во время разработки. 

    Создание страниц вкладок во время выполнения 

    Чтобы во время выполнения добавить Tab Sheet к элементу управления Page, 
необходимо сначала создать элемент управления TTabSheet, а затем выполнить 
присваивание свойству PageControl. 

    //  Создать   5  Tab  Sheets  на  PageControll. procedure 
TForml.FormCreate(Sender:   TObject); var 
    i:   integer; begin 
    for  i   :=   0   to   4   do 
    with  TTabSheet.Create(Self)   do begin 
    PageControl   :=  PageControll; Caption   :=   'TabSheet'   +  
IntToStr(i); end; end; 

    В листинге 7.17 содержится пример создания вкладки для каждой таблицы, 
находящейся в модуле данных (рис. 7.16). Создайте новый проект, поместите 
элемент управления Page на Forml и установите свойство Align равным alClient. 
Выполните команду File^New^Data Modules и добавьте модуль CustomerData в 
проект. Дважды щелкните на форме Forml и добавьте код , приведенный в 
листинге, к обработчику события OnCreate. Убедитесь, что вы не забыли добавить 
DB, DBTables и DBGrids в раздел uses, а также имя модуля данных — в раздел unit. 

    Рис. 7.16. Создание Tab Sheet для таблиц в модуле данных 

    Выполните команду Project^Options и сделайте CustomerData первым модулем^ 
в списке Auto-Create, выше Forml. Если здесь неправильно установить порядок, 
будет нарушен доступ гГри попытке сослаться~на~объекты в модуле" данных, 
который не был создан. 

    178 Часть II. Разработка приложений 


    I Листинг 7.17. \UDELPHI3\CHP7\PAGECONTROL\PC1\PCART1. PAS. Создание Tab 
Sheet для каждой таблицы 

    unit PCCrtl; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls; 
    type 
    TForml = class(TForm) 
    PageControll: TPageControl; 
    procedure FormCreate(Sender: TObject); private 
    { Закрытые объявления. } public 
    { Открытые объявления. } end; 
    var 
    Forml: TForml; 
    implementation ($R *.DFM} 
    uses 
    PCCrt2, // Модуль данных CustomerData. DB, DBTables, DBGrids, DBCtrls, ExtCtrls; 
    procedure TForml.FormCreate(Sender: TObject); var 
    i: integer; 
    CurrentDataSrc: TDataSource; CurrentTab: TTabSheet; CurrentPanel: TPanel; 
CurrentCount: Longlnt; begin 
    // Получить число доступных компонентов перед добавлением новых. 
    CurrentCount := CustomerData.ComponentCount - 1; 
    // Поиск источников данных в компонентах - модулях данных. 
    for i := 0 to CurrentCount do 
    // Если найден источник данных, создаем вкладку. 
    if (CustomerData.Components[i] is TDataSource) then 
    begin 
    // Присвоить источник данных вспомогательной переменной. CurrentDataSrc := 
TDataSource(CustomerData.Components[i]); with TTabSheet.Create(Self) do 
    // Добавить вкладку в массив Pages PageControl. 
    PageControl := PageControll; 
    // Присвоить вкладку вспомогательной переменной. 
    CurrentTab := PageControll.Pages[PageControll.PageCount - 1] ; 
    // Если данные поступают от TTable, использовать имя таблицы для вкладки. 
    if (CurrentDataSrc.DataSet is TTable) then 
    CurrentTab.Caption := TTable(CurrentDataSrc.DataSet).TableName; with 
TPanel.Create(Self) do begin 
    Parent := CurrentTab; Align := alTop; Caption := ' ' ; 
    CurrentPanel := Forml.Components[Forml.ComponentCount - 1] as TPanel; with 
TDBNavigator.Create(Self) do begin 


    Глава 7. Общие элементы управления Win32 179 


    Parent := CurrentPanel; DataSource := CurrentDataSrc; end; end; 
    // Вывести данные в новую сетку, with TDBGrid.Create(Self) do begin 
    Parent := CurrentTab; Align := alClient; DataSource := CurrentDataSrc; 
CurrentDataSrc.DataSet.Open; end; 
    end; end; 
    end. 

    Предупреждение ошибок 

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

    CurrentPanel   :=  Forml.Components[Forml.ComponentCount   -   1]   as   TPanel; 
    with TDBNavigator.Create(Self)   do 
    begin 
    Parent   := CurrentPanel; 
    DataSource   :=  CurrentDataSrc; end; 

    Если компонент был добавлен не сразу, возможно, придется в цикле пройтись 
по всему массиву компонентов, разыскивая экземпляр. В других случаях можно 
иметь специальный массив, который содержит экземпляры объектов, как при 
использовании CurrentTab, которому присваивается последний элемент массива 
Pages [ ] в PageControl. 

    Как листать страницы 

    Свойство ActivePage типа TTabSheet устанавливает выбранную страницу как во 
время разработки, так и во время выполнения. ActivePage — объект, относящийся 
к типу TTabSheet. Если в программе нужно только перейти на следующую или 
предшествующую страницу, используйте метод SelectNextPage. Чтобы получить 
ссылку на конкретную страницу относительно текущей страницы без передачи ей 
фокуса, используется метод FindNextPage. 
    В листинге 7.18 показано несколько способов листания страниц. Вы также 
увидите, как работают свойства Pagelndex, Tablndex и Visible (рис. 7.17). Файл 
ACTPAG1. PAS находится на дополнительно распространяемой дискете. 

    Рис. 7.17. Листание страниц 


    180 Часть II. Разработка приложений 

    Pagelndex указывает, где находится TTabSheet в массиве Pages [ ]. 
    Можно сойти с ума, пытаясь заставить свойство Visible работать так, как 
вам нужно! Вы можете даже подумать, что оно вообще неисправно. Чтобы 
сэкономить время, я обращаю ваше внимание на то, что есть два свойства — 
Visible и TabVisible. Visible управляет видимостью элементов внутри области 
пользователя в Tab Sheet, TabVisible позволяет отображать в есь лист Tab 
Sheet. Tablndex — значение индекса в массиве видимых элементов Tab Sheets. 

    \   ЛИСТИНГ 7.18. \UDELPHI3\CHP7\PAGECONTROL\ACTPAGE\ACTPAG1. PAS. 
Листание СТрЭНИЦ 

    unit ActPagl; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
StdCtrls, Buttons, ComCtrls; 
    type 
    TForml = class(TForm) 
    Editl: TEdit; 
    UpDownl: TUpDown; 
    Label1: TLabel; 
    ListBoxl: TListBox; 
    Label2: TLabel; 
    BitBtnl: TBitBtn; 
    BitBtn2: TBitBtn; 
    Label4: TLabel; 
    PageControll: TPageControl; 
    CheckBoxl: TCheckBox; 
    CheckBox2: TCheckBox; 
    LabelS: TLabel; 
    IblTablndex: TLabel; 
    procedure PageControllChange(Sender: TObject); 
    procedure UpDownlClick(Sender: TObject; Button: TUDBtnType); 
    procedure FormCreate(Sender: TObject); 
    procedure ListBoxlClick(Sender: TObject); 
    procedure BitBtnlClick(Sender: TObject); 
    procedure BitBtn2Click(Sender: TObject); 
    procedure CheckBoxlClick(Sender: TObject); 
    procedure CheckBox2Click(Sender: TObject); end; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} Uses ExtCtrls; 
    procedure TForml.PageControllChange(Sender: TObject); begin 
    UpDownl.Position := PageControll.ActivePage.Pagelndex; 
    IblTablndex.Caption := IntToStr(PageControll.ActivePage.Tablndex); 
    CheckBoxl.Checked := PageControll.ActivePage.TabVisible; 
    CheckBox2.Checked := PageControll.ActivePage.Visible; end; 
    procedure TForml.UpDownlClick(Sender: TObject; Button: TUDBtnType); begin 
    PageControll.ActivePage.Pagelndex := UpDownl.Position; end; 
    procedure TForml.FormCreate(Sender: TObject); 


    Глава 7. Общие элементы управления Win32 181 


    var 
    i: integer; begin 
    for i := 0 to 9 do 
    with TTabSheet.Create(Self) do begin 
    PageControl := PageControll; 
    TabVisible := i mod 2 = 0; 
    Name := 'TabSheet' + IntToStr(i); 
    Caption := 'Pagelndex = ' + IntToStr(Pagelndex) + 
    '  Tablndex := ' + IntToStr(Tablndex); with TPanel.Create(Self) do begin 
    Parent := PageControl.Pages[PageControl.PageCount - 1]; Align := alClient; 
    Caption := 'Setting Visible Off Hides This Panel1; end; end; 
    with PageControll do begin 
    UpDownl.Max := PageCount - 1; for i := 0 to PageCount - 1 do 
    ListBoxl.Items.AddObject(Pages[i].Name, TObject(Pages[i])); end; end; 
    procedure TForml.ListBoxlClick(Sender: TObject); begin 
    with ListBoxl do 
    if Itemlndex <> -1 then 
    PageControll.ActivePage := Items.Objects[Itemlndex] as TTabSheet; 
PageControllChange(Sender); end; 
    procedure TForml.BitBtnlClick(Sender: TObject); begin 
    PageControll.SelectNextPage(False); end; 
    procedure TForml.BitBtn2Click(Sender: TObject); begin 
    PageControll.SelectNextPage(True) end; 
    procedure TForml.CheckBoxlClick(Sender: TObject); begin 
    PageControll.ActivePage.TabVisible := CheckBoxl.Checked; 
    PageControllChange(Sender); end; 
    procedure TForml.CheckBox2Click(Sender: TObject); begin 
    PageControll.ActivePage.Visible := CheckBox2.Checked; 
    PageControllChange(Sender); end; 
    end. 


    Применение операций drag-and-drop к Tab Sheet 

    Листы можно перемещать и опускать на элементы управления Page, хотя для 
этого нужно тщательно выбрать прикладную программу. Приведенные ниже примеры 
хорошо работают т^ш.к^^маленькими^габш^идми^ 
    Дополнительная морока — это то, что PageControl не подхватывает объекты 
при событиях DragOver, когда на элементе управления нет никакого листа 
TTabSheet. Так что на PageControl нужно оставить, по крайней мере, одну страницу. 

    182 Часть II. Разработка приложений 

    Чтобы создать ряд Tab Sheets, каждый из которых имеет возможность 
перетаскивания, для каждого листа нужно назначить обработчики событий 
OnMouseDown, OnDragOver и OnDragDrop. Фактически для всех объектов на 
поверхности листа Tab Sheet также должны быть назначены обработчики, и во 
внутренней логике обработчиков нужно учитывать тип класса этих объект ов. 
    В примере показана загрузка ряда Tab Sheets, каждый из которых основан на 
записи из PARTS. DB (рис. 7.18). Чтобы создать прикладную программу, нужен 
компонент ТТаЫе, причем DataBaseName следует установить равным DBDemos, имя 
таблицы Table — PARTS . DB, a Active — True. Кроме того, поместите на форме 
рядом два компонента TPageControl. Внутри объявл ения форм нужны три 
обработчика события и функция CreateDragTabSheet (они показаны ниже). 

    procedure  TabSheetMouseDown   (Sender:   TObject;   Button:   TMouseButton; 
    Shift:   TShiftState;   X,   Y:   Integer); procedure 
TabSheetDragOver(Sender,   Source:   TObject;   X,   Y:   Integer; 
    State:   TDragState;   var Accept:   Boolean); 
    procedure  TabSheetDragDrop(Sender,   Source:   TObject;   X,   Y:   
Integer); function  CreateDragTabSheet(APageControl:   TPageControl):   TTabSheet; 

    Обработчик события OnCreate формы заботится о листе с вкладками, создании 
метки и присвоении обработчиков события (листинг 7.19). Файл DRAG1. PAS 
находится на дополнительно распространяемой дискете. 

    Рис. 7.18. Перемещение листов с вкладками 

    Листинг 7.19. \UDELPHI3\CHP7\PAGECONTROL\PC2\DRAG1. PAS. Перемещение Tab 
Sheets между элементами управления Page 
    unit Dragl; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
ComCtrls, StdCtrls, Mask, DBCtrls, Db, DBTables; 
    type 
    TForml = class(TForm) 
    PageControll: TPageControl; PageControl2: TPageControl; Tablel: TTable; 
TablelPartNo: TFloatField; TablelVendorNo: TFloatField; TableIDescription: 
TStringField; TablelOnHand: TFloatField; TablelOnOrder: TFloatField; 
TablelCost: TCurrencyField; TablelListPrice: TCurrencyField; procedure 
FormCreate(Sender: TObject); 


    Глава 7. Общие элементы управления Win32 183 


    public 
    procedure TabSheetMouseDown(Sender: TObject; Button: TMouseButton; 
    Shift: TShiftState; X, У: Integer); procedure TabSheetDragOver(Sender, 
Source: TObject; X, Y: Integer; 
    State: TDragState; var Accept: Boolean); 
    procedure TabSheetDragDrop(Sender, Source: TObject; X, Y: Integer); 
function CreateDragTabSheet(APageControl: TPageControl): TTabSheet; end; 
    var 
    Forml: TForml; 
    implementation ($R *.DFM} 
    // Определить, можно ли перемещать объект. 
    // Если объект является потомком TTabSheet, 
    // то перемещаем объект-родитель. 
    procedure TForml.TabSheetMouseDown(Sender: TObject; Button: TMouseButton; 
    Shift: TShiftState; X, Y: Integer); var 
    CurrentTab: TTabSheet; begin 
    if Button = mbLeft then begin 
    if (Sender is TTabSheet) then 
    CurrentTab := (Sender as TTabSheet) else 
    CurrentTab := (Sender as TLabel).Parent as TTabSheet; if 
CurrentTab.PageControl.PageCount > 1 then 
    CurrentTab.BeginDrag(True); end; end; 
    // Здесь перемещается только TTabSheet, поэтому 
    // нужно проверить, не перемещается ли 
    // объект на исходный объект. 
    procedure TForml.TabSheetDragOver(Sender, Source: TObject; X, Y: Integer; 
    State: TDragState; var Accept: Boolean); begin 
    Caption := 'Source: ' + (Source as TComponent).Name + 
    '  Drag Target: ' + (Sender as TComponent).Name; 
    if Sender <> Source then 
    Accept := true; end; 
    procedure TForml.TabSheetDragDrop(Sender, Source: TObject; X, Y: Integer); var 
    PCSource, PCTarget: TPageControl; 
    DraggedPage: TTabSheet; begin 
    // Вводим несколько вспомогательных фиктивных указателей на объекты 
    // для улучшения читабельности кода. 
    if (Sender is TTabSheet) then 
    PCTarget := (Sender as TTabSheet).PageControl 
    else 
    PCTarget := ((Sender as TLabel).Parent as TTabSheet).PageControl; 
    DraggedPage := (Source as TTabSheet); 
    PCSource := DraggedPage.PageControl; 
    // Снова присвоить родителя Tab Sheet. 
    DraggedPage.PageControl := PCTarget; 
    // Установить активную страницу для источника и приемника. 
    PCTarget.ActivePage := DraggedPage; 


    184 Часть II. Разработка приложений 


    PCSource.ActivePage := PCSource.Pages[0]; end; 
    function TForml.CreateDragTabSheet(APageControl: TPageControl): TTabSheet; begin 
    Result := TTabSheet.Create(Self); 
    with Result do 
    begin 
    PageControl := APageControl; OnMouseDown := TabSheetMouseDown; OnDragOver 
:= TabSheetDragOver; OnDragDrop := TabSheetDragDrop; end; end; 
    procedure TForml.FormCreate(Sender: TObject); var 
    Fids, Indent, i: integer; CurrentTab: TTabSheet; begin i := 0; 
    Indent := PageControll.Font.Size * 2; with Tablel do begin First; 
    while not EOF do begin 
    CurrentTab := CreateDragTabSheet(PageControll); CurrentTab.Name := 
'TabSheet1 + IntToStr(i); Inc(i); 
    CurrentTab.Caption := FieldByName('Description').AsString; for Fids := 0 
to FieldCount - 1 do with TLabel.Create(CurrentTab) do begin 
    Parent := CurrentTab; 
    Name := 'Label' + IntToStr(i) + IntToStr(Fids); 
    Caption := Fields[Fids].FieldName + ':  ' + Fields[Fids].AsString; Left := Indent; 
    Top := Indent + (Fids * Height * 2); OnMouseDown := TabSheetMouseDown; 
OnDragOver := TabSheetDragOver; OnDragDrop := TabSheetDragDrop; end; Next; 
end; end; 
    CurrentTab := CreateDragTabSheet(PageControl2); CurrentTab.Caption := 
'Drag Parts here...'; end; 
    end. 


    Rich Edit 

    '   Rich Text Format (RTF) — спецификация, которая определяет 
форматирование текста, набранного символами в коде ASCII. Если вы хотите 
больше узнать о внутренней организации Rich Text Format, посетите 
www.microsoft.com или www.wotsit.demon.co.uk, которые во время работы над этой 
книгой были наполне-W W W     ны миРиаДами спецификаций форматов файл ов, 
включая Rich Text Format. Компонент TRichEdit предназначен для реализации 
множества сложных элементов RTF, но сам по себе несколько привередлив. 

    Глава 7. Общие элементы управления Win32 185 

    На    « заметку 
    По сравнению с Delphi 2.0, в компоненте TRichEdit были сделаны некоторые 
усовершенствования. При печати в среде NT 4.0 в Delphi 2.0 обычно происходила 
ошибка divide-by-zero (деление на нуль); теперь она устранена. Элемент 
управления Rich Edit имел ограничение на размер текста (64 КБайт) в Delphi 2.0 
из-за способа обработки передачи сообщений. Чтоб ы снять это ограничение, 
элемент управления был существенно переработан. 

    Полная демонстрационная версия элемента управления Rich Edit находится в 
файле \Delphi  3 . 0\Demos\RichText, который поставляется вместе с Delphi. He 
будем дублировать эту прикладную программу, а рассмотрим некоторые функцио-, 
нальные возможности элемента управления Rich Edit. 

    Атрибуты текста 

    TRichEdit имеет свойства атрибутов текста, управляющие такими свойствами, 
как имя шрифта, цвет, стиль, размер и интервал, которые могут быть установлены 
по умолчанию или для выбранного текста. Свойство SelAttributes управляет 
атрибутами текста внутри интервала символов от SelStart до SelLength. 
Управление атрибутами остального текста осуществляетс я свойством 
DefAttributes. И SelAttributes, и DefAttributes относятся к типу TTextAttributes. 
    Проект в листинге 7.20 преследует двойную цель. Во-первых, он выводит в 
поток текст RTF и отображает те же самь данные на втором элементе управления 
Rich Edit, у которого истинно свойство PlainText; этот элемент управления по-1 
зволяет увидеть "сырой" текст. Во-вторых, проект демонстрирует некоторые 
свойства TTextAttributes для выбранного текста. 
    Когда вы будете запускать этот проект на выполнение, попробуйте выделить 
некоторый текст, а затем изменить шрифт, цвет и размер шрифта. Поскольку 
результат отображается на втором элементе управления Rich Edit, вы увидите 
работу, которая нужна для поддержки формата Rich Text, но которая обычно 
выполняется "за кулисами" (рис. 7.19). Файл SELATR1. РА З находится на 
дополнительно распространяемой дискете. 

    Рис. 7.19. Исследование атрибутов текста и формата RTF 

    Листинг 7.20. \UDELPHI3\CHPT7\RICHEDIT\SELATR\SELATR1. PAS. Исследование 
SelAttributes и текста     I rich text 

    unit SelAtrl; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
StdCtrls, ComCtrls, ExtCtrls, ToolWin; 
    type 
    TForml = class(TForm) RichEditl: TRichEdit; RichEdit2: TRichEdit; 
ToolBarl: TToolBar; ComboBoxl: TComboBox; 


    186 Часть II. Разработка приложению 


    StatusBarl: TStatusBar; Panell: TPanel; Panel2: TPanel; TrackBarl: 
TTrackBar; ToolButtonl: TToolButton; ColorDialogl: TColorDialog; ImageListl: TImageList; 
    procedure RichEditlChange(Sender: TObject); procedure FormCreate(Sender: 
TObject); procedure ComboBoxlClick(Sender: TObject); procedure 
RichEditlSelectionChange(Sender: TObject); procedure TrackBarlChange(Sender: 
TObject); procedure ToolButtonlClick(Sender: TObject); end; 
    var 
    Forml: TForml; 
    InStream: TMemoryStream; 
    implementation {$R *.DFM} 
    procedure TForml.RichEditlChange(Sender: TObject); begin 
    InStream.Clear; 
    RichEditl.Lines.SaveToStream(InStream); 
    InStream.Position := 0; 
    RichEdit2.Lines.LoadFromStream(InStream); end; 
    procedure TForml.FormCreate(Sender: TObject); var 
    I: integer; begin 
    RichEdit2.PlainText := True; Comboboxl.Items.Clear; with Screen do 
    for I := 0 to Fonts.Count - 1 do 
    if (Comboboxl.Items.IndexOf(Screen.Fonts[I]) = -1) then 
    ComboBoxl.Items.Add(Screen.Fonts[I]) ; ComboBoxl.Itemlndex := 0; end; 
    procedure TForml.ComboBoxlClick(Sender: TObject); begin 
    RichEditl.SelAttributes.Name := ComboBoxl.Items[ComboBoxl.Itemlndex]; end; 
    procedure TForml.RichEditlSelectionChange(Sender: TObject); begin 
    TrackBarl.Position := RichEditl.SelAttributes.Size; end; 
    procedure TForml.TrackBarlChange(Sender: TObject); begin 
    RichEditl.SelAttributes.Size := TrackBarl.Position; end; 
    procedure TForml.ToolButtonlClick(Sender: TObject); begin 
    ColorDialogl.Options := [cdSolidColor, cdPreventFullOpen]; 
    if ColorDialogl.Execute then 
    RichEditl.SelAttributes.Color := ColorDialogl.Color; end; 


    Глава 7. Общие элементы управления Win32 187 

    initialization 
    InStream := TMemoryStream.Create; finalization 
    InStream.Free; end. 


    Форматирование абзацев 

    Свойство Paragraph управляет отступом, табуляцией и выравниванием. 
Свойства Firstlndent, Leftlndent и Rightlndent определяют выравнивание первой 
строки, левого и правого полей абзаца. Для выделенного абзаца можно установить 
ряд позиций табуляции с помощью свойства TabCount. Для этого необходимо задать 
значения элементов массива Tab [ ]. Вот пример процедуры, которая программно 
устанавливает позиции табуляции. 

    procedure  SetTabStops(RichEdit:   TRichEdit;   TabStops:   array  of   
integer); var 
    I:   integer; begin 
    with RichEdit.Paragraph do begin 
    TabCount   := High(TabStops)   +  1; for   I   :=   0   to  TabCount   do 
Tab[I]    :=  TabStops[I]; end; end; 

    Передавая элементу управления Rich Edit массив целых чисел, можно 
установить все позиции табуляции в одном операторе, как показано ниже: 

    SetTabStops(RichEditl,    [20,   120,   200]); 

    Вы могли бы выровнять элемент управления Track Bar поперек верхней части 
формы и использовать метод SetTick, чтобы проиллюстрировать размещение позиций 
табуляции. Элемент управления Header, который использует владельца для 
рисования, также может быть хорошим кандидатом на горизонтальную линейку, 
используемую, чтобы управлять атрибутами Paragraph. 

    Рис. 7.20. Структуры полей таблицы 

    Хотя это несколько старомодно, но я иногда люблю печатать структуру 
таблицы со списком полей таблицы, их типами и размерами. Ни DataBase DeskTop, 
ни Explorer DB, кажется, не в состоянии решить эту задачу. Чтобы восполнить 
пробел, можно вывести список полей в элементе управления Rich Edit со 
свойством Tab для выравнивания столбцов 

    188 Часть II. Разработка приложений 

    (рис. 7.20). В листинге 7.21 показано, как использовать свойство 
SelAttributes, чтобы назначить заголовкам столбцов полужирный шрифт и 
применить атрибуты подчеркивания, и свойство Paragraph, чтобы выровнять 
заголовок раздела индексов ("Indexes"). Файл TBLPRT1. PAS находится на 
дополнительно распространяемой дискете. 

    ПРЕДОСТЕРЕЖЕНИЕ 

    Как я уже упоминал, основной элемент управления, от которого происходит 
TRichEdit, имеет дефекты. Где это возможно, группа разработки Delphi либо 
обошла их, либо не использовала наиболее уязвимых частей. В некоторых случаях, 
особенно с TRichEdit, дефекты были непреодолимы. Вы найдете, например, что 
свойство Paragraph компонента TRichEdit, обрабатываемое в программе, не работает 
корректно, когда текст простирается за видимую область элемента управления. 
Аналогично атрибуты TTextAt-tributes при обработке в программе не всегда ведут 
себя предсказуемо. Протестируйте этот элемент управления перед использованием 
его специфических функциональных возможностей. 

    ; Листинг 7.21. \UDELPHI3\CHPT7\RICHEDIT\TABLEPRINT\TBLPRT1. РАЗ. 
Распечатка списка полей 

    "unit'Tblprtl;"'.............................." ........... 
.......................................      " 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
StdCtrls, ComCtrls, ToolWin, DBTables, Db; 
    type 
    TForml = class(TForm) 
    StatusBarl: TStatusBar; 
    ToolBarl: TToolBar; 
    RichEditl: TRichEdit; 
    ToolButtonl: TToolButton; 
    ImageListl: TImageList; 
    cbDatabaseName: TCoraboBox; 
    cbTableName: TComboBox; 
    Tablel: TTable; 
    procedure FormCreate(Sender: TObject); 
    procedure cbDatabaseNameChange(Sender: TObject); 
    procedure cbTableNameChange(Sender: TObject); 
    procedure ToolButtonlClick(Sender: TObject); end; 
    const 
    FldTyps: array[ftUnknown..ftTypedBinary] of string = ( 
    'ftUnknown', 'ftString', 'ftSmallint', 'ftlnteger', 'ftWord', 'ftBoolean', 'ftFloat', 
    'ftCurrency', 'ftBCD', 'ftDate', 'ftTime', 'ftDateTime', 'ftBytes', 
'ftVarBytes', 'ftAutoInc', 'ftBlob', 'ftMemo', 'ftGraphic', 'ftFmtMemo', 
'ftParadoxOle', 'ftDBaseOle', 
    1ftTypedBinary1 ); TABCHAR = #9; 
    var 
    Forml: TForml; 
    implementation {$R *.DFM} 
    procedure SetTabStops(RichEdit: TRichEdit; TabStops: array of integer); var 
    I: integer; begin 
    with RichEdit.Paragraph do 
    begin 


    Глава 7. Общие элементы управления Win32 189 


    TabCount := High(TabStops) + 1; for I := 0 to TabCount - 1 do 
    Tab[I] := TabStops[I]; end; end; 
    procedure WritelndexDescription(Table: TTable; RichEdit: TRichEdit) var 
    I: integer; begin 
    with Table do begin 
    SetTabStops(RichEdit, [80]); 
    IndexDefs.Update; 
    if IndexDefs.Count > 0 then 
    begin 
    with RichEdit, SelAttributes do begin 
    Paragraph.Alignment := taCenter; 
    Style := Style + [fsBold, fsltalic]; 
    Lines.Add(''); 
    Lines.Add('Indexes ' ); 
    Paragraph.Alignment := taLeftJustify; 
    Style := Style - [fsltalic] + [fsUnderline]; 
    Lines.Add('Index Name' + TABCHAR + 'Fields'); 
    Style := Style - [fsBold, fsUnderline]; 
    for I := 0 to IndexDefs.Count - 1 do 
    if ixPrimary in IndexDefs[I].Options then 
    Lines.Add('(Primary)' + TABCHAR + IndexDefs[I].Fields) else 
    Lines.Add(IndexDefs[I].Name + TABCHAR + IndexDefs[I].Fields) ; end; end; 
    end; 
    ( 
    end; 
    procedure WriteTableDescription(Table: TTable; RichEdit: TRichEdit); var 
    I: integer; begin 
    with Table, RichEdit, SelAttributes do begin 
    SetTabStops(RichEdit, [20, 120, 200]); Style := Style + [fsUnderLine, fsBold]; 
    Lines.Add('#' + TABCHAR + 'Label' + TABCHAR + 'Type' + TABCHAR + 'Size'); 
Style := Style - [fsUnderLine, fsBold]; for I := 0 to FieldCount - 1 do wi,th 
Fields [I] do 
    'Lines. AdddntToStr (FieldNo) + TABCHAR + DisplayLabel + TABCHAR + 1  
FldTyps[DataType] + TABCHAR + IntToStr(DataSize)); end; end; 
    procedure TForml.FormCreate(Sender: TObject); begin 
    Session.GetDatabaseNames(cbDatabaseName.Items); 
    cbDatabaseName.Itemlndex := cbDatabaseName.Items.IndexOf('DBDemos'); 
    cbDatabaseNameChange(Sender); end; 
    procedure TForml.cbDatabaseNameChange(Sender: TObject); begin 
    with cbDatabaseName do 
    Session.GetTableNames(Items[Itemlndex], '', True, False, cbTableName.Items); 
    cbTableName.Itemlndex := 0; 


    190 Часть II. Разработка приложений 


    cbTableNameChange(Sender) ; end; 
    procedure TForml.cbTableNameChange(Sender: TObject); begin 
    if cbTableName.Itemlndex <> -1 then begin 
    with Tablel do try 
    DatabaseName := cbDatabaseName.Items[cbDatabaseName.Itemlndex]; 
    TableName := cbTableName.Items[cbTableName.Itemlndex]; 
    Open; 
    RichEditl.Lines.Clear; 
    WriteTableDescription(Tablel, RichEditl); 
    WritelndexDescription(Tablel, RichEditl); 
    StatusBarl.Panels[0].Text := 'Field Listing for ' + TableName + 
    ' in ' + DatabaseName + '.'; 
    StatusBarl.Panels[1].Text := 'Fields: ' + IntToStr(FieldCount); 
StatusBarl.Panels[2].Text := 'Indexes: ' + IntToStr(IndexDefs.Count); finally 
Close; end; end; end; 
    procedure TForml.ToolButtonlClick(Sender: TObject); begin 
    RichEditl.Print(StatusBarl.Panels[0].Text); end; 
    end. 


    Отображение строки и столбца 

    Элемент управления Rich Edit, подобно TEdit и TMemo, является потомком 
TCustomMemo, и поэтому ему можно отослать те же сообщения элементов управления 
редактированием "ЕМ_", которые позволяют преодолеть ограничения VCL. Эти 
сообщения можно использовать, чтобы обеспечить статистику в текущих столбце и строке. 
    Используйте событие OnSelectionChange элемента управления Rich Edit, чтобы 
проверить переход на новую строку и столбец (рис. 7.21). 

    Рис. 7.21. Слово "edit" найдено в строке 4, в столбце 10 

    procedure GetRTRowCol (RichEdit: TRichEdit;  var Row, Col: Longlnt; begin 
    with RichEdit do 
    begin 


    Глава 7. Общие элементы управления Win32 191 

    Row := SendMessage(Handle, EM_LINEFROMCHAR, SelStart, 0); Col := SelStart 
- SendMessage(Handle, EM_LINEINDEX, Row, Ob-end; end; 
    procedure TForml.RichEditlSelectiOnChange(Sender: TObject); var 
    RTRow, RTCol: Longlnt; begin 
    GetRTRowAndColumn(RichEditl, RTRow, RTCol);  StatusBarl.Panels[0] .Text := 
'Ln IntToStr(RTRow) + 
    1 Col ' + IntToStr(RTCol); end; 
    Поиск текста 
    Поиск текста в элементе управления RichEdit предусматривает выполнение 
двух операций — вызов компонента TFindDialog и обработку события OnFind. Но 
сначала установите HideSelection на RichEdit равным False так, чтобы вы могли 
видеть найденный текст, даже когда фокус не принадлежит элементу управления 
Rich Edit. 
    procedure  TForml.FormCreate(Sender:   TObject); begin 
    RichEditl.HideSelection   :=   False; end; 
    Если вы хотите, чтобы диалог поиска появился возле курсора в тексте, 
установите свойство Position из TFindDialog, используя сообщение EM_POS 
FROMCHAR для получения положения текстового курсора. Кроме того, перед 
выполнением диалога нужно предварительно загрузить в свойство FindText любой 
выбранный пользователем текст. Это делается следующим образо м. 

    procedure  TForml.tbFindClick(Sender:   TObject); var 
    TempPoint:   TPoint; begin                                                 
    with  RichEditl,   FindDialogl   do begin 
    Perform(EM_POSFROMCHAR, Longlnt(@TempPoint), SelStart  +  SelLength +  2); 
    Position   :=  ClientToScreen   (TempPoint); 
    FindText   := Copy(Text,   SelStart  +  1,   SelLength); 
    Execute; end; end; 


    ПРЕДОСТЕРЕЖЕНИЕ 

    Документация Microsoft, используемая в DELPHI 2.0, содержит неправильное 
определение для сообщения EM_POSFROMCHAR. Во-первых, константа EM_POSFROMCHAR 
определена неправильно в RICHEDIT.PAS. ComCtrls имеет правильное определение, 
а также вызывает другие определения из RICHEDIT.PAS по мере необходимости, так 
что лучше избегать явного включения RICHEDIT.РАЗ. 
    Кроме того, синтаксисы EM_POSFROMCHAR и EM_CHARFROMPOS определены 
неправильно. По этому (неправильному!) определению wparam принимает индекс 
символа и возвращает координаты символа. На самом деле сообщение должно 
выглядеть примерно так: 

    SendMessage (Handle, EM_POS FROMCHAR, Longlnt (@RTPos), SelStart); где 
RTPos передан КЭК адрес TPoint. 

    Когда пользователь щелкает на кнопке Find Next, вызывается обработчик 
события OnFind. Функция Сору повторно выделяет текст, который нужно найти, 
основываясь на текущем положении текстового курсора. Затем функция Pos 
определяет искомый текст внутри заданного диапазона поиска. Если Роз находит 
текст, удовлетворяющий критерию поиска, устанавливаются свойства SelStart и 
SelLength, чтобы выделить найденный текст. Это делается следующим образом. 


    procedure TForml.FindDialoglFind(Sender: TObject); var 
    SearchToken: string; 
    FoundAt: Longlnt; begin 
    if FindDialogl.FindText <> '' then 
    with RichEditl do 


    192 Часть II. Разработка приложений 


    begin 
    SearchToken   := Copy(Text,   SelStart  +  SelLength  +  1, 
    Length   (Text)   -  SelStart); FoundAt   :=  Pos(Uppercase(FindDialogl.FindText), 
    Uppercase(SearchToken)); if   (FoundAt  <>   0)   then begin 
    SelStart   :=   SelStart   +   SelLength  +   FoundAt   -   1; SelLength   
:= Length(FindDialogl.FindText); end; end; end; 

    Кроме установки SelStart и SelLength, можно изменять SelAttributes, чтобы 
выделить найденный текст подчеркиванием или другим цветом. Вы можете 
использовать эту же идею внутри новой программы просмотра, чтобы высветить 
строки, начинающиеся с "http//", для указания ссылки на Web-адеса. 

    Элемент управления List View 

    Элемент управления List View отображает список в удобном для пользователя 
стиле. Список элементов, например, может отображаться в виде больших 
пиктограмм или в виде отчета, по колонкам. Элемент управления List View 
присутствует почти всюду в Windows 95. Например, клиентская область окна 
программы Explorer — элемент управления List View. 

    Характеристики List View во время разработки 

    Принципиальные свойства, которые определяют, как TListView отображает 
информацию для просмотра, следующие. 
    К Свойство Items, относящееся к типу TListltems, является коллекцией 
элементов TListltem, из которых состоит список. Элементы имеют редактор 
свойства, который можно использовать для создания и установки каждого свойства 
элемента: Caption, Imagelndex и Statelndex. Свойства Imagelndex и Statelndex 
используются в качестве указателей на элементы списк 
    а Image List. 
    •    TListltem также имеет свойство TStrings, именуемое Subltems. При 
просмотре List View в режиме Details элементы Subltems формируют текст, 
перечисленный под столбцами справа от свойства Caption. 
    •    Стиль ViewStyle, который соответствует четырем крайним справа кнопкам 
Windows Explorer— Large Icons, Small Icons, List и Details. Они определяются 
константами vslcon, vsSmalllcon, vsList и vsReport. 
    •    Если значение стиля ViewStyle равно vsReport (Details), то нужно 
создать элемент TListColumn внутри свойства Columns. 

    Работа с List View во время выполнения 

    Создать элементы в List View можно так же, как в общих элементах 
управления: вызовите метод Add родителя объекта, получите ссылку и присвойте 
свойствам значения, используя эту ссьПГк'уГ"""" 

    var 
    Listltem:   TListltem; begin 
    Listltem   :=  ListViewl.Items.Add; 
    Listltem.Caption   := Editl.Text; 

    Когда свойство ViewStyle равно vsReport, элемент управления List View 
становится рядом столбцов, причем элементы Items занимают крайний столбец 
слева (рис. 7.22). Каждый элемент Item имеет вложенные элементы Subltems, 
перечисленные справа. 
    Прежде чем можно будет увидеть Subltems, нужно создать Columns следующим образом. 

    var 
    ListColumn: TListColumn; begin 
    ListColumn := ListViewl.Columns-Add; 
    ListColumn.Caption := Editl .Text; 
    ListColumn.Width := Length(Editl.Text) * Font.Size; 


    Глава 7. Общие элементы управления Win32 193 
    7 Использование Delphi 3. Специальное издание 

    Затем можно загружать свойство Subltems данного элемента Items. Элементы 
Subltems— это элементы типа TStrings, причем в них каждый элемент массива 
принадлежит столбцу, как в следующем примере. 

    ListViewl.Selected.Subltems.Add('This   sub  item displays  to  the  right 
of  the  selecteditem text.'); 


    Рис. 7.22. Компонент равно vsReport) 


    TListView   (СВОЙСТВО  ViewStyle 


    Пример программы в листинге 7.22 позволяет проверить возможности List View 
по динамическому созданию элементов Items, Subltems и Columns. Во время 
создания формы поместите в нее один элемент Image List с растрами размером 
16x16 пикселей, второй элемент Image List-—с пиктограммами изображения 
размером 32x32; элементам комбинированного списка присво йте названия каждой 
пиктограммы (обратите внимание на обращение к методу CreateSize по поводу 
отмены заданных по умолчанию размеров 16x16). Когда пользователь щелкает на 
кнопке Add List Item панели инструментов, текущий индекс комбинированного 
списка используется, чтобы обновить свойство Image Index. Файл LVADD1. PAS 
находится на дополнительно рас пространяемой дискете. 

    Листинг 7.22. \UDELPHI3\CHPT7\LISTVIEW\ADDITEMS\LVADD1. PAS. Работа с List 
View во время выполнения      ' 

    unitLVAddl; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
StdCtrls, ComCtrls, ToolWin; 
    type 
    TfrmMain = class(TForm) ListViewl: TListView; ImageListl: TImageList; 
ToolBarl: TToolBar; ToolButtonl: TToolButton; ToolButton2: TToolButton; 
ToolButtonS: TToolButton; ToolButton4: TToolButton; tbAddListltem: 
TToolButton; ToolButton6: TToolButton; Editl: TEdit; ToolButton7: TToolButton; 
il!6: TImageList; i!32: TImageList; ComboBoxl: TComboBox; Too 
    lButtonS: TToolButton; tbAddColumn: TToolButton; tbAddSubltem: TToolButton; 
    procedure tbAddListltemClick(Sender: TObject); procedure 
ToolButtonlClick(Sender: TObject); procedure ToolButton2Click(Sender: 
TObject); procedure ToolButtonSClick(Sender: TObject); 


    194 Часть II. Разработка приложений 


    procedure ToolButton4Click(Sender: TObject); procedure FormCreate(Sender: 
TObject); procedure tbAddColumnClick(Sender: TObject); procedure 
tbAddSubltemClick(Sender: TObject); end; 
    var 
    frmMain: TfrmMain; 
    implementation 
    Uses CommCtrl, ShellAPI, LVAdd2; 
    {$R *.DFM} 
    procedure TfrmMain.tbAddListltemClick(Sender: TObject); var 
    Listltem: TListltem; begin 
    Listltem := ListViewl.Items.Add; 
    Listltem.Caption := Editl.Text; 
    Listltem.Imagelndex := Comboboxl.Itemlndex; end; 
    procedure TfrmMain.ToolButtonlClick(Sender: TObject); begin 
    ListViewl.ViewStyle := vslcon; end; 
    procedure TfrmMain.ToolButton2Click(Sender: TObject); begin 
    ListViewl.ViewStyle := vsSmalllcon; end; 
    procedure TfrmMain.ToolButtonSClick(Sender: TObject); begin 
    ListViewl.ViewStyle := vsList; end; 
    procedure TfrmMain.ToolButton4Click(Sender: TObject); begin 
    ListViewl.ViewStyle := vsReport; end; 
    procedure TfrmMain.FormCreate(Sender: TObject); const 
    DirPath = 'C:\Program Files\Borland\Delphi 3.0\Images\Icons\'; var 
    SearchRec: TSearchRec; begin      "—*  '—™——• i!32.CreateSize(32, 32); 
    if (FindFirst(DirPath + '*.BMP', faAnyFile, SearchRec) = 0) then begin try 
    while (FindNext(SearchRec) = 0) do 
    1116.FileLoad(rtBitmap, DirPath + SearchRec.Name, clNone); finally 
    FindClose(SearchRec); end; end; 
    if (FindFirst(DirPath + '*.ICO', faAnyFile, SearchRec) = 0) then begin try 
    while (FindNext(SearchRec) = 0) do 


    Глава 7. Общие элементы управления Win32 195 

    begin 
    Imagelist_AddIcon(1132.Handle, Extractlcon(Handle, PChar(DirPath + 
SearchRec.Name) , 0)); 
    ComboBoxl.Items.Add(Copy(SearchRec.Name, 0, Pos ( '. ', SearchRec.Name) - 
1)); end; finally 
    FindClose(SearchRec); end; end; 
    ComboBoxl.Itemlndex := 0; end; 
    procedure TfrmMain,tbAddColumnClick(Sender: TObject); var 
    ListColumn: TListColumn; begin 
    ListColumn := ListViewl.Columns.Add; 
    ListColumn.Caption := Editl.Text; 
    ListColumn.Width := Length(Editl.Text) * Font.Size; 
    tbAddSubltem.Enabled := True; end; 
    procedure TfrmMain.tbAddSubltemClick(Sender: TObject); begin 
    if Assigned(ListViewl.Selected) then begin 
    frmAddSubltems.ShowModal; 
    ListViewl.Selected.Subltems.Assign(frmAddSubltems.Memol.Lines) ; end else 
    ShowMessage('To add Subltems, first select an item.'); end; 
    end. 


    Элемент управления Tree View 

    Элемент управления TTreeView содержит текст, графику и данные в выделенном 
списке. Каждый элемент в списке — объект TTreeNode. Список в целом — объект 
TTreeNodes, представляемый свойством Items. Чтобы управлять списком элементов, 
например добавить узел, нужно использовать свойство Items. 
    Едва ли TTreeView следует безоговорочно относить к неукротимым драконам, 
но названия^ его свойств выбраны так, что их легко перепутать. 

    заметку    -."__...................,„,_„________,„„,.„1,„ 
    Например, свойство Item относится к типу TTreeNode, часто эти 
идентификаторы используются один вместо другого. Аналогично Items, свойство, 
которое представляет массив элементов Item, относится к типу TTreeNodes. 
Термин Items имеет тенденцию использоваться вместо TTreeNodes. 

    Присвоение значений элементам во время разработки 

    Рассмотрим, как загрузить элементы в TTreeView во время разработки, а 
также как обеспечить изображение для каждого элемента. Для этого в форме нужны 
два набора изображений Image Lists, а также TTreeView. Добавьте любые два 
изображения из каталога \Images\Buttons к ImageListl, чтобы соотнести их со 
свойствами Itemlndex и Se-lectedlndex. К ImageList2 добавьте только одно 
изображение из \Images\Buttons. Этот второй TImageList обеспечивает Statelmages 
(они отображаются как дополнительный графический символ слева от элемента). 
Графические символы Statelmages полезны для маркировки элемента как части 
группы. Например, в распечатке счета можно поставить метки рядом с 
оплаченными счетами. 
    В окне Object Inspector выберите ImageListl для свойства Images элемента 
управления Tree View. Для свойства Statelmages выберите ImageList2. 

    196 Часть II. Разработка приложений 

    Чтобы создать список элементов для TTreeView во время разработки, щелкните 
на кнопке с многоточием свойства Items компонента TTreeView, и будет вызван 
редактор элементов TTreeView. Щелкните на кнопке New Item и наберите некоторый 
текст в поле свойств диалога. Оставьте значение Image Index равным нулю, но 
измените значение поля Selected Index, прис воив ему 1. 
    Может понадобиться установить свойство Statelndex для элемента. 
Пиктограмма состояния— дополнительный графический символ, который отображается 
в крайней слева позиции от элемента. Его можно использовать для обозначения 
просмотренной почты, оплаченных счетов и т.д. Значение свойства Statelndex, по 
умолчанию равное -1, указывает, что не выбрана ни одна пиктограмма состояния. 
Если изменить это значение и установить его равным нулю, то отобразится первое 
изображение в ImageList2. Добавьте еще несколько элементов и подэлементов. 
Перед запуском примера добавьте следующие две строки кода к обработчику 
OnDblClick TTreeView. 

    procedure TForml .TreeViewlDblClick (Sender: TObject) ;                    
    begin 
    TreeViewl.Selected.Statelndex   :=  1; 
    TreeViewl. Invalidate; 
    end; 

    Запустите проект на выполнение и убедитесь, что пиктограмма состояния из 
Im-ageList2 расположена в крайней слева позиции от элемента (рис. 7.23). 
Пиктограмма, отображаемая между пиктограммой состояния и текстом, 
соответствует графическому символу в ImageListl, указанному в Imagelndex. 
Щелкните на любом элементе, чтобы отобразить второй графический 
символ в ImageListl, на который указывает Selectedlndex. 

    Рис. 7.23. Элемент TTreeView с пиктограммами 


    Присвоение значений элементам во время выполнения 

    Код для добавления нового элемента к TTreeView может показаться вам 
знакомым. Используйте метод Add объекта 

    TTreeNodes. 
    var 
    Node: TTreeNode; begin 
    Node := TreeViewl.Items.Add(TreeViewl.Selected, Editl.Text); 

    Новый узел будет добавлен к концу того же уровня, что и узел, определенный 
в первом параметре. Если параметр равен nil (нуль), добавление происходит на 
уровне корня. Второй параметр — отображаемый текст. 
    Имеется несколько разновидностей методов, которые добавляют и вставляют 
узлы TTreeNodes. Эти методы иногда удобно разделить на следующие две категории. 
    •   Методы, которые добавляют узлы TTreeNodes на том же уровне, что и узел 
TTreeNode, переданный в качестве параметра. К этим методам относятся Add, 
AddFirst, Insert, InsertObject, AddObject и AddObject-First. 
    •    Методы, которые добавляют узлы TTreeNodes, относящиеся к следующему 
уровню вниз по иерархии. Это методы AddChild, AddChildFirst, AddChildObject и AddChildObjectFirst. 
    Методы, ссылающиеся на объекты, включают параметр-указатель, который может 
использоваться для запоминания любого 4-байтового значения. Там можно хранить 
число Longlnt, указатель на запись или объект-заготовку (который является 
только 4-байтовым указателем). Любую память, кроме этих 4 байт для указателя, 
нужно выделять и освобождать. 

    Пример: загрузка элементов из таблицы базы данных 

    Для загрузки значений элементов управления Tree View из таблицы нужен цикл 
While, чтобы в цикле просматривать записи таблицы; внутрь цикла нужно 
поместить метод Items .Add следующим образом. 

    Customers.Open; 
    while not Customers.EOF do 
    begin 


    Ha заметку 


    Глава 7. Общие элементы управления Win32 197 


    CustomerNode   :=  Items.Add(Nil,   Customers['Company']); Next; end; 

    Вы также можете захотеть представить связь "главный—подчиненный", где 
подчиненные записи отображаются как потомки главных. Общая последовательность 
приведена ниже. 
    Ж Сохраните ссылку, возвращенную из вызова метода It ems. Add. Например, в 
предшествующем фрагменте кода CustomerNode содержит код возврата из Items 
.Add. CustomerNode понадобится позже для добавления порожденных элементов. 
    • После обращения к Items .Add, но перед обращением к методу Next 
поместите код, чтобы выбрать подмножество подчиненных записей. (Можно 
отфильтровать записи или использовать TQuery для подчиненных записей.) 
    В Повторите цикл While для уровня подчиненных записей и поместите этот код 
перед обращением к оператору Next для главных записей. Замените все 
специфические для таблицы идентификаторы от имени главной таблицы до имени 
подчиненной таблицы. Замените Items. Add на Items .AddChild и передайте ранее 
сохраненный объект TTreeNode как первый параметр. 
    В следующем примере используется модуль данных CustomerData, чтобы 
обеспечить данные для таблицы. Только для одной главной и одной подчиненной 
записей минимальный код выглядел бы примерно так, как в следующем примере. 

    procedure TForml.ToolButtonlClick(Sender: TObject); var 
    CustomerNode: TTreeNode; begin 
    with Treeviewl, CustomerData do begin 
    Orders.Filtered := True; 
    // Добавить все имена покупателей в TreeNodes. 
    while not Customers.EOF do 
    begin 
    CustomerNode   :=  Items .Add (Nil,   Customers ['Company']); 
    . .  '        //  Добавить   все   заказы для  последней  записи  
заказчика  в  TreeNodes. with Orders  do begin 
    Filter   :=   'CustNo  =   '   + 
    Customers.FieldByName('CustNo').Asstring; Refresh; 
    while  not  EOF  do begin 
    Items.AddChild(CustomerNode,    '#'   + 
    FieldByNamne('OrderNo').Asstring  +   '      '   + FieldByNamne(    
'SaleDate'    ).AsString); Next ; end; 
    end;   //  Заказы. Customers.Next; end; 
    //  Передать  фокус  первому элементу в  Tree View. Items .GetFirstNode. 
Selected   :=  True/end;   //  TreeViewl,   CustomerData end; 

    В следующем примере проекта к этому процессу добавится еще три уровня: от 
Customers и Orders до Line Items (рис. 7.24). Дополнительно, если заказчик 
имеет ItemTotal, большее, чем заданное количество, за именем заказчика 
помещается метка, определенная в Image List в свойстве Statelmages, и, если 
условие выполняется, устанавливается Statelndex этого узла, равный индексу 
графического символа в Image List. Линейка Progress Bar постоянно информирует 
пользователя о проценте загруженных записей. Также обратите внимание на методы 
BeginUpdate и EndUpdate компонента TTreeNodes. Свойство BeginUpdate 
предотвращает рисование с помощью TTreeView, пока не вызовется EndUpdate 
(листинг 7.23). Файл LOADTBL1 
    . PAS находится на дополнительно распространяемой дискете. 


    198 Часть II. Разработка приложений 


    Рис. 7.24. Загрузка элементов TreeView из таблицы базы данных 

    Листинг 7.24. \UDELPHI3\CHP7 \TREEVIEW\LOADTABLE\LOADTBLI . PAS. Загрузка 
элементов TreeView из таблицы базы данных 

    unit loadtbll; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
ComCtrls, ToolWin, StdCtrls; 
    type 
    TForml = class(TForm) 
    StatusBarl: TStatusBar; 
    ToolBarl: TToolBar; 
    TreeViewl: TTreeView; 
    ToolButtonl: TToolButton; 
    ImageListl: TImageList; 
    ProgressBarl: TProgressBar; 
    ImageList2: TImageList; 
    Editl: TEdit; 
    UpDownl: TUpDown; 
    Labell: TLabel; 
    procedure ToolButtonlClick(Sender: TObject); end; 
    var 
    Forml: TForml; 
    implementation uses OrderExp2; {$R *.DFM} 
    procedure TForml.ToolButtonlClick(Sender: TObject); var 
    LineltemsNode, OrdersNode, CustomerNode: TTreeNode; begin 
    with Treeviewl, CustomerData do 
    begin 


    Глава 7. Общие элементы управления Win32 199 


    // Инициализировать таблицы. 
    Lineltems.Filtered := True; 
    Lineltems.Open; 
    Orders.Filtered := True; 
    Orders.Open; 
    Customers.Close; 
    Customers.IndexName := 'ByCompany'; 
    Customers.Open; 
    // Установить Progress Bar на поверхности StatusBarl. 
    with ProgressBarl do 
    begin 
    Max := Customers.RecordCount; Parent := StatusBarl; StatusBarl.SimplePanel 
:= True; Left := StatusBarl.Canvas.TextWidth('W) * 4; Top := 4; 
    Height : = StatusBarl.Height - (StatusBarl.Height div 3); Visible := True; end; 
    Items.Clear; 
    // Задержать рисование TreeView до события EndUpdate. Items.BeginUpdate; 
    // Добавить все имена заказчиков в TreeNodes. while not Customers.EOF do begin 
    CustomerNode := Items.Add(Nil, Customers['Company']); 
    // Добавить все заказы для последней записи заказчика в TreeNodes. 
    with Orders do 
    begin 
    Filter := 'CustNo = ' + Customers.FieldByName('CustNo').AsString; 
    Refresh; 
    while not EOF do 
    begin 
    OrdersNode := Items.AddChild(CustomerNode, '#' + 
FieldByName('OrderNo'}.AsString + 
    1  ' + FieldByName('SaleDate').AsString); // Пометить заказчиков с большим 
общим заказом, if (FieldbyName('ItemsTotal').Aslnteger > (UpDownl.Position * 
1000)) then 
    CustomerNode.Statelndex := 1; 
    // Добавить все элементы для последней записи заказа в TreeNodes. with 
Lineltems do begin 
    Filter := 'OrderNo = ' + Orders.FieldByName('OrderNo').AsString; 
    Refresh; 
    while not EOF do 
    begin 
    LineltemsNode := Items.AddChild(OrdersNode, 
FieldByName('PartName').AsString + 
    1 (#' + FieldByName('PartNo').AsString + '), Qty ' + 
FieldByName('Qty').AsString 4 ', $' + FieldByName('Price').AsString) ; Next; end; 
    end; // Lineltems Next; end; 
    end; // Orders Customers.Next; // Обновить состояние. 
    ProgressBarl.Position := Customers.RecNo; 
    StatusBarl.SimpleText :='%'+ IntToStr((Customers.RecNo * 100) div 
Customers.RecordCount) ; 
    // Разрешить перерисовку текста панели состояния и Progress Bar. 
Application.ProcessMessages; end; 


    200          Часть П. Разработка приложений 


    // Выполнить метод Draw, // чтобы перерисовать TTreeView, // так как 
теперь все элементы загружены. Items.EndUpdate; 
    // Передать фокус первому элементу в Tree View. 
Items.GetFirstNode.Selected := True; ProgressBarl.Visible := False; 
StatusBarl.SimpleText := ''; end; // TreeViewl, CustomerData end; 
    end. 


    Забираемся на дерево наследований 

    А что если вы хотите загружать элементы в обратном порядке? Предположим, 
вы хотите отобразить в списке объектную иерархию наследования, начинающуюся со 
специфического объекта, например с TForml, и перечислить всех предков, идущих 
в обратном направлении к TObject. Как можно отобразить этот же список в 
TTreeView, начинающемся с TObject (рис. 7.25)? 
    Разделим эту задачу на подзадачи и пока оставим подробности вывода в 
стороне. Сначала нужно объявить временную переменную, относящуюся к типу 
TClass; ее можно назвать Cls и присвоить ей класс TForml. Затем в цикле While 
с помощью функции ClassName заполните список именами класса и в заключение 
переназначьте Cls, используя ClassParent. Выходите из цикла, когда Cls 
присвоите ClassParent из TObject. (TObject — корень дерева наследования, 
который не имеет никакого родителя, так что Cls теперь становится равным Nil.) 
Это можно сделать следующим образом. 

    procedure TForml.FormCreate(Sender:   TObject); var 
    Cls:   TClass; begin 
    Cls   :=  TForml; 
    while Assigned(Cls)   do 
    begin 
    ListBoxl.Items.Add(Cls.ClassName) ; Cls := Cls.ClassParent; 
    end; 

    Теперь, когда вы научились извлекать список, который представляет иерархию 
наследования, можете сосредоточить внимание на упорядочении списка внутри 
элемента управления Tree View с TObject наверху и поместить каждого потомка с 
соответствующим отступом ниже предка. Вы уже знаете, как выполнять добавление 
в свойство Items. Но каким образом вставить элемент наверху списка и 
обозначить элементы ниже этого элемента как потомков этого нового элемента? 
Теперь нужно вспомнить, что, когда предварительно разрабатывался метод 
Items.Add, он возвратил TTreeNode переменной Node. Ведь эту ссылку мы еще 
нигде не использовали! 

    var 
    Node:   TTreeNode; begin 
    Node   :=  TreeViewl.Items.Add(TreeViewl.Selected,   Edit1.Text); 
    Эту ссылку можно использовать как параметр для метода MoveTo из 
TTreeNodes, чтобы перемещать существующий TTreeNode под последним добавленным элементом. 
    procedure  TForml.FormCreate(Sender:   TObject); var 
    Cls:   TClass; Node:   TTreeNode; begin 
    Cls   :=  TForml; while Assigned(Cls)   do begin 
    with TreeViewl  do begin 
    Node   :=  Items.Add(Nil,   Cls.ClassName); if   (Node  <>   Items[0])   then 
    Items[0].MoveTo(Node,   naAddChild); 


    Глава 7. Общие элементы управления Win32     201 


    end; 
    Cls   := Cls.ClassParent; end; 
    TreeViewl.FullExpand; end; 

    I Теперь шаг за шагом проследим выполнение этого цикла. 
    1.   Переменной Cls присвоена TForml. 
    2.    При входе в цикл проверяется, что Cls не равно Nil (нулю). 
    3.    Вы добавляете первый узел, содержащий текст TForml. 
    4.    Это первая итерация, так что не нужно перемещать предыдущие узлы, 
причем значения Items [0] и Node равны и метод MoveTo не выполняется. 
    5.   Переменной Cls присваивается ее ClassParent, TForm. 
    6.   Добавляется новый узел с текстом TForm. 
    7.    В этом месте ваш элемент управления TTreeView внес бы в список TForm 
в качестве Items [ 0 ], а затем — TForml в качестве Items [ 1 ], причем оба 
отображались бы на уровне корня TTreeView. Но у вас есть предыдущий элемент, 
так что теперь выполняется метод MoveTo и старый Items [ 0 ] добавляется в 
качестве потомка к недавно добавленному узлу. 
    8.    Цикл продолжается до тех пор, пока Cls имеет тип TOb j ect, причем 
каждый раз старый элемент становится потомком вновь добавленного узла. 

    Исследование компонента TTreeView 

    Элемент управления Tree View имеет огромное количество свойств и методов, 
не считая загрузки списка. Рассмотрим некоторые выдающиеся методы, которые 
работают на всем элементе управления, и конкретные объекты TTreeNode. 
    В следующем примере проекта используются компоненты TToolBar, 
расположенные ниже TTreeView и слева от него, и TPageControl — ниже и справа 
(рис. 7.26). Элемент управления Tool Bar выполняет методы уровня TTreeView и 
свойства, которые управляют расширением и разрушением всех элементов, а также 
графическими особенностями, такими как отступы и появле ние линий и кнопок 
слева от элементов. Кроме того, на Tool Bar расположен набор кнопок, которые 
программно управляют редактированием элемента. 

    Рис. 7.25. Демонстрация иерархии объектов. TListBox показан слева, а 
справа в TTreeView этот список приведен в обратном порядке 

    Рис. 7.26. Исследование TTreeView 

    Вкладка элемента управления Page "Nodes" дает возможность протестировать 
список и проанализировать положение узлов и связь между ними внутри списка. На 
странице "Hit Tests" графически представлены значения, возвращаемые методом 
GetHitTestlinf oAt. На странице "Events" отображается текущий список последних 
50 конкретных событий TTreeView. 

    202      Часть II. Разработка приложений 

    Из-за большого объема кода здесь этот пример приводиться не будет. Вместо 
этого рассмотрим используемые методы, которые не вполне очевидны из текста 
кода или комментариев. Полный проект находится на дополнительно 
распространяемой дискете, в папке \UDELPHI3\CHP7 \TREEVIEW\TVPROPS\. 

    Узлы 

    Событие OnChange из TTreeView полностью управляет отображением информации 
на странице "Nodes". Оно передает измененный TTreeNode, так что можно 
установить with Node do... и отформатировать заголовок для каждого свойства 
или результата метода. 

    procedure  TForml.TreeViewlChange(Sender:   TObject;   Node:   TTreeNode); begin 
    with Node do begin 
    IblCount.Caption     :=   Format(    'Count:   %s',    [IntToStr(Count)]); 
Ibllndex.Caption     :=   Format(    'Index:   %s ' ,    [IntToStr(   Index)]); 


    Рис. 7.27. Страница "Hit Tests" 

    Подобно GetFirstChild, методы TTreeNode предоставляют информацию о связях 
между узлами. Их можно использовать в подпрограммах, где нужно перемещать 
группу связанных элементов внутри Tree View. Операция перетащить и опустить и 
поддержка повышения (или понижения) групп—два вероятных кандидата, где могут 
понадобиться эти методы. 

    Hit Test 

    Продемонстрировать  результаты  работы  метода  GetHitTes-tlnf oAt (рис. 
7.27) можно с помощью страницы "Hit Tests". Например, метод GetHitTestlnfoAt 
получает координаты мыши от события OnMouseDown и возвращает THitTests. 
function  GetHitTestlnfoAt(X,   Y:   Integer): THitTests; 

    THitTest =   (htAbove,   htBelow,   htNowhere, htOnltem,   htOnButton, 
    htOnlcon,   htOnlndent,   htOnLabel,   htOnRight, htOnStatelcon, 
    htToLeft,   htToRight); THitTests  =  set  of  THitTest; 

    Для того чтобьг^бойдптрвушки ужасающег^оп^ер^тоЕаЛ^Е/ТЬеп языка Pascal, а 
также графичесюГ'прёдставить тип Set (множество) языка Pascal, где многие 
элементы принадлежат множеству, нужно представить возможные значения, которые 
могут принадлежать множеству. Можно загрузить TListBox, используя массив 
строк, который соответствует типу множества. 

    const HTest:   array(htAbove..htToRight]   of  String 
    'htAbove', 
    'htBelow', 
    'htNowhere', 
    'htOnltem', 
    'htOnButton', 
    'htOnlcon   ', 
    'htOnlndenf, 
    'htOnLabel', 
    'htOnRighf, 
    'htOnStatelcon', 
    'htToLeff, 
    'htToRight' ); 
    = ( 


    Глава 7. Общие элементы управления Win32    203 


    При использовании этой методики можно отображать почти любое множество 
значений в любом другом множестве значений. Можно было бы внести в список не 
только (и не столько!) строки, но и типы ссылок на заметку    классь,5 
например ссылки, которые позволяют динамически создать ряд объектов. 
    С помощью массива Htest, определенного в предшествующих строках, загрузка 
IbHitTest списка ListBox становится простым делом. 

    var 
    НТ:   THitTest; begin 
    for  HT   :=  htAbove  to  htToRight   do IbHitTest.Items.Add(HTest[HT]) ; 

    Используя переменную НТ в качестве счетчика, можно пройти по значениям 
объекта THitTest и получить доступ к соответствующим описательным строкам в 
массиве HTest (в качестве индекса элементов в массиве можно использовать этот счетчик). 
    Когда для элемента управления Tree View вызывается событие OnMouseDown, 
нужно определить элементы TList-Вох, которые выбираются. Во время разработки 
установите свойство Multiselect окна списка равным True. Снова пройдите по 
значениям THitTest и для каждого узла в TreeView установите свойство Selected 
равным True, если GetHitTestinf oAt возвращает соответствующее значение 
THitTest. Это можно сделать следующим образом. 

    procedure  TForml.TreeViewlMouseDown(Sender:   TObject;   Button:   TMouseButton; 
    Shift:   TShiftState;   X,   Y:   Integer); var 
    HT:   THitTest; begin 
    for  HT   :=  htAbove   to  htToRight  do 
    //   Выбрать   из  поля  списка   элемент,   если   соответствующий  НТ  
был //   возвращен  из  GetHitTestinfoAt. 
    IbHitTest.Selected[Ord(HT)]    :=  HT   in  TreeViewl.GetHitTestinfoAt(X,  
 Y) end; 


    События 

    Хотя на странице Events применяется метод "грубой силы", на ней 
демонстрируется и полезная методика, используемая как для диагностики, так и 
для знакомства с порядком событий (рис. 7.28). Основной подход состоит в том, 
чтобы создать метод, который добавляет строку в TListBox, и вставить обращение 
к методу в обработчики событий, к которым вы питает е интерес. При этом 
неплохо бы каждое обращение снабдить описанием. Единственный недостаток — 
количество элементов ограничено числом 50, поэтому наша хитрость состоит в 
том, чтобы удалять первый элемент по мере того, как прибавляются новые элементы. 

    procedure TForml.LogEvents(EventStr: 
    ShortString); 
    begin 
    // Создать прокручивающийся список, 
    // в котором уничтожается первый элемент.    if IbEvents. Items.Count > 50 
then IbEvents.Items.Delete(0); 
    IbEvents.Items.Add(EventStr); 
    // Передать фокус новому входу. 
    IbEvents.Itemlndex := IbEvents.Items.Count - 1; end; 
    Рис. 7.28. Список событий для компонента TTreeView Вызов: 
    procedure TForml.TreeViewlCollapsed(Sender: TObject; Node: TTreeNode); begin 
    LogEvents('   OnCollapsed'   ); end; 


    204      Часть II. Разработка приложений 


    Резюме 

    В этой главе были рассмотрены приемы работы с общими элементами управления 
как во время разработки, так и во время выполнения, а также способы добавления 
объектов в общие элементы управления, которые имеют массивы с методами Add или 
содержат порожденные объекты. Кроме того, здесь описывались использование 
обращения к API и передача сообщений Windo ws (это помогает расширить 
возможности общих элементов управления). 
    Методы добавления и создания в общих элементах управления 

    В табл. 7.1 представлена информация о некоторых методах добавления и 
создания для общих элементов управления. 

    Таблица 7.1, Методы добавления для общих элементов управления 

    Объект	Объект-потомок или массив	Пример синтаксической конструкции 

    TTabControl	Tabs []	TabControl 1 .Tabs.Add ('sample text'); 
    TPageControl	TTabSheet	MySheet := TTabSheet.Create(Self); 
    MySheetPageControl := PageControl 1 ; 
    MySheetCaption := 'sample text'; 
    TTreeView	TTreeNode	MyNode := TreeViewl.ItemsAdd (Nil, 'sample text'); 
    TListView	TListltem	Myltem := ListViewl. Items. Add; 
    Myltem. Caption := 'sample text'; 
    THeader Control	THeader Sect ion	MySection := HeaderControll.SectionsAdd; 
    MySection.Text := 'sample text'; 
    TRichEdit	Lines [ ]	RichEditl.LinesAdd ('sample text'); 
    TStatusBar	TStatusPanel	MyStatusPanel:= StatusBarl .Panels.Add; 
    MyPanel.Text := 'sample text'; 
    TToolBar	TToolButton	with TToolButton.Create(Self) do Parent := ToolBarl; 
    TCoolBar	TCollectionltem; TCoolBand	var CoolBand: Tcollectionltem; begin 
CoolBand :=CoolBarl. Bands. Add; with CoolBand as TCoolBand do Control 
:=Editl; end; 


    Возвращаясь к металлу 

    Хотя общие элементы управления упрощают работу с элементами управления 
API, они не лишены некоторых недостатков. Библиотека VCL может решать 
проблемы, скрытые в первоначальном элементе управления, или добавлять новые 
проблемы. Кроме того, некоторые функции и особенности могут не быть 
инкапсулированы в элемент управления VCL. К счастью, Delphi пред усматривается 
способ преодоления собственных ограничений, предоставляя доступ к API 
операционной системы Windows. Так, если метода не существует, для выполнения 
трюка можно отослать сообщение непосредственно элементу управления API. 

    Глава 7. Общие элементы управления Win32       205 

    Чтобы использовать функциональные возможности, не предусмотренные в 
Delphi, в некоторых случаях может потребоваться изучить сообщения Windows и 
вызовы API по руководству к Windows API. Такое исследование было выполнено в 
примере с рисованием на элементе управления Tab (было отослано сообщение 
TCM_GETITEMRECT элементу управления Tab). Воспользуйтес ь интерактивной 
справкой, применив "инициалы" элемента управления (например, "инициалам" SB 
соответствуют статьи от SB_GETBORDERS до SB_SIMPLE).'""""** 
    Если особенность или функция нова, поищите ее на компакт-диске Microsoft 
Developers Network (MSDN) в заголовочных файлах языка С, которые должны 
транслироваться в Pascal. 
    Чтобы постигнуть не только очевидные функциональные возможности общих 
элементов управления, нужно ознакомиться с исходными файлами. Общие элементы 
управления упоминаются в ряде файлов, но обязательно просмотрите COMMCTRL. PAS 
и COMMCTRLS . PAS, в которых приведены следующие сведения. 
    •    COMMCTRL. PAS, находящийся в каталоге \source\rtl\win, содержит 
константы низкого уровня, макрокоманды и заголовки функций для Windows API, 
размещенных в comet 132 . dll. 
    •    COMTRLS. PAS, расположенный в каталоге \source\vcl, содержит объекты 
Delphi, которые построены на основе средств, записанных в файле COMMTRL. PAS. 

    Новые стили Windows 

    Пример, показывающий возможности THilightTabControl, устарел, потому что 
появился новый стиль TCS_HOTTRACK, который выделяет текст вкладок, когда над 
текстом находится курсор. Вкладки можно размещать на любой стороне компонента 
TTabControl, используя стили TCS_BOTTOM, TCS_VERTICAL и TCS_RIGHT в методе CreateParams. 
    Позднее, по ходу выпуска Delphi 3, в TCustomTabControl были добавлены 
новые стили окна. Ниже приводится описание процесса вынужденного исследования 
новых функциональных возможностей в библиоте-ках DLL фирмы Microsoft. He все 
новые функциональные возможности в DLL имеют подходящее описание, "  так что 
по-прежнему имеется множество объектов, с которыми можно поэкспериментировать. 
    Мое любимое усовершенствование — стиль TCS_SCROLLOPPOSITE (рис. 7.29). Вы 
когда-либо работали с элементом управления с вкладками, в котором 
относительные позиции для вкладок смещены относительно выбранной вкладки? 
    Изучая новые стили окон, освойте метод CreateParams. Delphi инициализирует 
стиль окна и передает его в структуре Params метода CreateParams. Метод 
CreateParams дает блок перед созданием программы обработки окна. Это может 
показаться непонятным интуитивно, но чтобы добавить стиль, используйте or 
(или), а чтобы вычесть — and (и). 
    Params.Style  := Params.Style or TCS_HOTTRACK; Рис. 7.29. 
Усовершенствованный элемент управления 
    В листинге 7.24 показан результат использования стилей окна TCS_HOTTRACK и 
TCS_SCROLLOPPOSITE. Исходный текст программы IE31. PAS находится на 
дополнительно распространяемой дискете. 

    Листинг 7.24. \UDELPHI3\CHP7\TABCONTROL\IE3\IE31. PAS. Усовершенствованный 
элемент управления Tab 

    unit ТЕЗ1;.............™~"    ............... 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls; 
    type 
    THotTabControl = Class(TTabControl) private 
    procedure CreateParams(var Params: TCreateParams); override; 


    206    Часть II. Разработка приложений 


    end; 
    TForml = class(TForm) 
    procedure FormCreate(Sender: TObject); end; 
    var 
    Forml: TForml; 
    implementation <$R *.DFM} 
    const 
    TCS_SCROLLOPPOSITE =  $0001;   // Вкладка с несколькими строками. 
    TCS_BOTTOM =  $0002; 
    TCS_RIGHT  =  $0002;   // Используется с TCS_VERTICAL. 
    TCS_HOTTRACK   =  $0040; 
    TCS_VERTICAL =    $0080;   // Только для режима с   несколькими строками. 
    procedure THotTabControl.CreateParams(var Params: TCreateParams); begin 
    inherited CreateParams(Params); with Params do Style := Style or 
TCS_HOTTRACK or TCS_SCROLLOPPOSITE; end; 
    procedure TForml.FormCreate(Sender: TObject); var 
    i: integer; begin 
    with THotTabControl.Create(Self) do begin 
    Parent := Self; for i := 0 to 19 do 
    Tabs.Add('Tab ' + IntToStr(i)); end; end; 
    end. 

    В листинге 7.25 приведена прикладная программа базы данных, использующая 
элемент управления Tab. В ней используются некоторые уже рассмотренные 
свойства и методы (рис. 7.30). В этом примере просто загружаются имена и 
дополнительные номера телефонов из таблицы Employee в усовершенствованный 
элемент управления TTabControl, причем каждый символ алфав ита представлен 
вкладкой. Исходный текст программы TCI. PAS содержится на дополнительно 
распространяемой дискете. 

    Рис. 7.30. Элемент управления Tab — список телефонов 

    Листинг 7.25. \UDELPHI3\CHP7 \TABCONTROL\PHONELIST\TCI . PAS. Список 
телефонов в элементе управления Tab 

    unit TCI; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
ExtCtrls, DBCtrls, Grids, DBGrids, DB, DBTables, ComCtrls, StdCtrls; 
    type 


    Глава 7. Общие элементы управления Win32     207 

    THotTabControl = Class(TTabControl) private 
    procedure CreateParams(var Params: TCreateParams); override; end; 
    TForml = class(TForm) 
    procedure FormCreate(Sender: TObject); 
    procedure TabControllChange(Sender: TObject); 
    procedure QuerylFilterRecord(DataSet: TDataSet; var Accept: Boolean); 
    procedure QuerylAfterOpen(DataSet: TDataSet); public 
    Queryl: TQuery; 
    DataSourcel: TDataSource; 
    DBGridl: TDBGrid; 
    HotTabControll: THotTabControl; 
    StatusBarl: TStatusBar; end; 
    var 
    Forml: TForml; 
    implementation ($R *.DFM} 
    const 
    TCS_SCROLLOPPOSITE  =   $0001;   // Вкладка с несколькими строками. 
    TCS_BOTTOM  =  $0002; 
    TCS_RIGHT  =   $0002;   // Используется с  TCS_VERTICAL. 
    TCS_HOTTRACK  =  $0040; 
    TCS_VERTICAL  =  $0080;   // Только для режима с несколькими строками. 
    procedure THotTabControl.CreateParams(var Params: TCreateParams); begin 
    inherited CreateParams(Params); with Params do Style := Style or TCS_HOTTRACK or TCS_BOTTOM; 
    end; 
    procedure TForml.FormCreate(Sender: TObject); var 
    i: integer; begin 
    Queryl := TQuery.Create(Self); 
    with Queryl, SQL do 
    begin 
    OnFilterRecord := QuerylFilterRecord; AfterOpen := QuerylAfterOpen; 
DataBaseName := 'DBDEMOS'; Filtered := True; 
    Add('SELECT FIRSTNAME || " " || LASTNAME AS Name, '); Add('PHONEEXT as 
Ext, LASTNAME '); Add('FROM EMPLOYEE'); Add('ORDER BY LASTNAME'); end; 
    DataSourcel := TDataSource.Create(Self); DataSourcel.DataSet := Queryl; 
    StatusBarl := TStatusBar.Create(Self); StatusBarl.Parent := Self; 
StatusBarl.SimplePanel := True; 


    208           Часть II. Разработка приложений 


    HotTabControll := THotTabControl.Create(Self); 
    with HotTabControll do 
    begin 
    OnChange := TabControllChange; 
    Parent := Self; 
    Align := alClient; 
    Font.Style := [fsBold]; 
    TabWidth := Canvas.TextWidth('W) * 2; 
    TabHeight := Canvas.TextHeight('W} * 2; 
    MultiLine := True; 
    for i := Ord('A') to Ord('Z') do 
    Tabs.Add(Chr(i)); end; 
    DBGridl := TDBGrid.Create(Self); 
    with DBGridl do 
    begin 
    Parent := HotTabControll; 
    Align := alClient; 
    DataSource := DataSourcel; end; 
    TabControllChange(Self); end; 
    procedure TForml.TabControllChange(Sender: TObject); begin 
    Query1.Close; Queryl.Open; 
    { 
    Эта проверка позволяет избежать сообщения "EInvalidOperation 
    'control 'Tabcontroll' has no parent window", 
    которое выдается при закрытии прикладной программы, 
    если фильтру не соответствует ни одна запись в 
    сетке. } 
    if (Queryl.RecordCount = 0) then begin 
    Queryl.Close; 
    StatusBarl.SimpleText := 'No Records Selected.'; end else 
    StatusBarl.SimpleText := 'Records Selected: ' + 
IntToStr(Queryl.RecordCount) ; end; 
    // Допустить записи, у которых первая буква last name 
    // (фамилии) 
    // равна букве на вкладке. 
    procedure TForml.QuerylFilterRecord(DataSet: TDataSet; 
    var Accept: Boolean); begin 
    with (DataSet as TQuery) do 
    Accept := (Copy(FieldByName('LASTNAME').AsString,1,1) = 
    HotTabControll.Tabs[HotTabControll.Tablndex]); end; 
    // Установить атрибуты вывода для столбца с помощью свойств TField. 
procedure TForml.QuerylAfterOpen(DataSet: TDataSet); begin 
    with (DataSet as TQuery) do begin 
    FieldByName('LastName').Visible := false; FieldByName('Name1).DisplayWidth 
:= 30; FieldByName('Ext').DisplayWidth := 15; end; end; 
    end. 


    Глава 7. Общие элементы управления Win32        209 


    Инкапсулирование усовершенствованного элемента управления TPageControl 

    Вряд ли стоит объявлять и создавать новый элемент управления для каждой 
комбинации стилей окна. Но как же тогда динамически изменять стили, если они 
реализуются только во время создания окна? В библиотеке VCL содержится метод 
TWinControl, называемый RecreateWnd, который уничтожает и восстанавливает 
программу обработки окна. Стили окна можно сохран ять в свойствах класса 
объектов так, чтобы, когда свойство, такое как HotTrack, изменяется, можно 
было вызвать метод RecreateWnd и добавить или вычесть стили во время 
CreateParams (создание параметдцд}^ 
    Элементы управления Tab и Page — различные реализации класса Window, 
WC_TABCONTROL (как установлено в методе CreateParams их общего предка 
TCustomTabControl), и поэтому новые стили, которые работали для TTab
Control, работают и для TPageControl. 
    Следующий пример (рис. 7.31 и листинг 7.26) воплощает новые особенности 
внутри нового потомка компонента TPageControl, который вводит новые] свойства 
ScrollOpposite, HotTrack и TabAlign. Разработка этого компо-1 нента 
описывается в этой книге ниже. Здесь приводится листинг компонента, ко- j 
торый будет рассматриваться в дальнейшем. ^сл^даи^^раш^и_вашь1е_влевр^или 
вправо, в Wmglo\ysJ?5могут выводиться неправильно. TCTistomTabControTTripocTO 
избегает этой проблемь!,' жГйс^1 пользуя стилей для выравнивания вкладок 
вправо и влево. 

    I 

    Рис. 7.31. Усовершенствованный элемент управления TPageControl 

    Исходный текст программы NEWTAB. PAS (листинг 7.26) содержится на 
дополнительно распространяемой дискете. 

    Листинг 7.26. \UDELPHI3\CHP7\PAGECONTROL\IE3\NEWTAB. PAS. 
Усовершенствованный элемент управления Page 

    unit Newtab; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls; 
    const 
    TCS_SCROLLOPPOSITE   = $0001;   // Вкладка с несколькими строками. 
    TCS_BOTTOM  = $0002; 
    TCS_RIGHT  = $0002;   // Используется с TCS_VERTICAL. 
    TCS_HOTTRACK = $0040; 
    TCS VERTICAL = $0080;   // Только для режима с несколькими строками. 
    type 
    TabAlignments = (pcaTop, pcaBottom, pcaLeft, pcaRight); 
    THotPageControl = class(TPageControl} private 
    FScrollOpposite: Boolean; 
    FHotTrack: Boolean; 
    FTabAlign: TabAlignments; 
    procedure CreateParams(var Params: TCreateParams); override; 
    procedure SetScrollOpposite(Value: Boolean); 
    procedure SetHotTrack(Value: Boolean); 
    procedure SetTabAlign(Value: TabAlignments); published 
    property ScrollOpposite: Boolean read FScrollOpposite write SetScrollOpposite; 


    210     Часть II. Разработка приложений 


    property HotTrack: Boolean 
    read FHotTrack 
    write SetHotTrack; property TabAlign: TabAlignments 
    read FTabAlign 
    write SetTabAlign; end; 
    procedure Register; implementation 
    procedure THotPageControl.CreateParams (var Params: TCreateParams); begin 
    inherited CreateParams(Params); 
    with Params do 
    begin 
    if FHotTrack then Style := Style or TCS_HOTTRACK; 
    if FScrollOpposite then Style := Style or TCS_SCROLLOPPOSITE; 
    case FTabAlign of 
    pcaBottom: Style := Style or TCS_BOTTOM; pcaLeft:   Style := Style or 
TCS_VERTICAL; pcaRight:  Style := Style or TCS_VERTICAL or TCS_RIGHT ; end; 
end; end; 
    procedure THotPageControl.SetScrollOpposite(Value: Boolean); begin 
    if (FScrollOpposite <> Value) then begin 
    FScrollOpposite := Value; 
    // Метод RecreateWnd унаследован от TWinControl; // этот метод позволяет 
динамически изменять стили окна. RecreateWnd; end; end; 
    procedure THotPageControl.SetHotTrack(Value: Boolean); begin 
    if (FHotTrack <> Value) then begin 
    FHotTrack := Value; RecreateWnd; end; end; 
    procedure THotPageControl.SetTabAlign(Value: TabAlignments); begin 
    if (FTabAlign <> Value) then begin 
    FTabAlign := Value; RecreateWnd; end; end; 
    procedure Register; begin 
    RegisterComponents('UDELPHI3', [THotPageControl]); end; 
    end. 


    Глава 7. Общие элементы управления Win32       211 

