Глава 12

Стандартные программные механизмы

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

Ярлычки (hints)

Сегодняшний пользовательский интерфейс трудно представить без ярлычков с оперативной подсказкой (Hints). Если задержать курсор, например, над кнопкой или компонентом палитры самой среды Delphi, появляется маленький прямоугольник яркого цвета (окно подсказки), в котором одной строкой сказано о названии этого элемента или связанном с ним действии. Delphi поддерживает механизмы создания и отображения таких ярлычков в создаваемых программах.

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

property ShowHint: Boolean;

Если значение свойства ShowHint установлено равным True, и во время выполнения курсор задержался над компонентом на некоторое время (см. ниже), в окне подсказки высвечивается текстовая строка с подсказкой, которая задана свойством:

property Hint: string;

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

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

У объекта Application также имеется свойство с именем showHint. (Его значение можно устанавливать во время выполнения, например в обработчике события oncreate главной формы приложения). Оно является главенствующим для всей системы подсказок: если его значение установлено в False, ярлычки не возникают.

Есть у Application и собственное свойство Hint. Значение ему автоматически присваивается при смене текущего элемента управления или при выборе пункта меню. Если в строке Hint текущего элемента управления (пункта меню) встречается специальный символ-разделитель "[", то часть строки до него ("короткая") передается в окно подсказки, а после ("длинная") — присваивается свойству Hint объекта Application. Длинную часть можно использовать, например, в строке состояния внизу главной формы приложения (см. пример ниже). Для разделения строки на две части используются функции:

function GetLongHint(const Hint: string): string;

function GetShortHint(const Hint: string): string;

При изменении значения свойства Hint в объекте Application возникает событие:

property OnHint: TNotifyEvent;

Пример:

procedure TFormI.AppHint(Sender: TObject);

begin

StatusBarl.SimpleText := Application.Hint;

end;

procedure TFormI.FormCreate(Sender: TObject);

begin

Application.OnHint := AppHint;

end;

В этом примере текст подсказки будет отображаться в строке состояния StatusBarl независимо от значения свойства showHint у любого объекта — лишь бы этот текст был в наличии. Для этого разделяйте подсказку у элементов управления вашего приложения на две части при помощи символа "|" — краткая информация появится рядом с элементом, а более полная — в строке состояния.

Создать обработчики свойств объекта Application проще всего с помощью компонента ApplicationEvents, впервые появившегося в Delphi 5.

Фоновый цвет окна подсказки можно изменить посредством свойства:

property HintColor: TColor;

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

Если в данный момент другие ярлычки не показываются, то интервал времени этого ожидания задается свойством HintPause:

property HintPause: Integer;

Этот интервал времени по умолчанию равен 500 мс. Если в данный момент уже виден ярлычок другого компонента, то интервал времени ожидания задается свойством:

property HintShortPause: Integer;

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

property OnShowHint: TShowHintEvent;

TShowHintEvent = procedure (var HintStr:

string; var CanShow: Boolean;

var Hintlnfo: THintInfo) of object;

с такими параметрами:

THintInfo = record

HintControl: TControl;

HintWindowClass: THintWindowClass;

HintPos: TPoint;

HintMaxWidth: Integer;

HintColor: TColor;

CursorRect: TRect;

CursorPos: TPoint;

ReshowTimeout: integer;

HideTimeout: Integer;

HintStr: string;

HintData: Pointer;

end;

Для показа окна подсказки необходимо также, чтобы у элемента управления или хотя бы у его родителей в цепочке строка Hint (ее значение присваивается параметру HintStr описываемого метода) была непустой. Впрочем, это еще не поздно исправить в обработчике onShowHint, пример:

procedure TFormI.AppShowHint(var HintStr: string; var CanShow: Boolean;

var Hintlnfo: THintInfo);

begin

if HintStr='' then

begin

HintStr := Hintlnfo.HintControl.Name;

Hintlnfo.HintColor := clRed;

CanShow := True;

end;

end;

Присвоив этот метод обработчику события Application. OnShowHint, установив значение Form.showHint равным True и очистив все строки Hint всех элементов управления, получим в качестве текста ярлычка имя каждого элемента.

Если вы хотите сделать текст ярлычка многострочным, вы можете изменить значение поля HintMaxWidth структуры Hintlnfo. По умолчанию максимальная ширина не ограничена (она равна ширине экрана в пикселах). Но, если уменьшить это значение, ваш длинный текст отобразится в нескольких строках.

Общая длительность показа ярлычка задается свойством:

property HintHidePause: Integer;

По умолчанию его значение равно 2500 мс. Свойство

property HintShortCuts: Boolean;

отвечает за показ вместе с текстом ярлычка описания горячих клавиш данного элемента управления.

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

procedure ActivateHint(CursorPos: TPoint);

ярлычок показывается в точке CursorPos (система координат экранная). "Спрятать" окно подсказки можно с помощью метода:

procedure CancelHint;

Без повторного перемещения мыши на текущий элемент оно более не возникнет.

Интерфейс перетаскивания (Drag-and-Drop)

Для библиотеки VCL фирмой Inprise реализована собственная версия интерфейса Drag-and-Drop (переводится как "перетаскивание"). Интерфейс этот внутренний — передавать и принимать можно любые управляющие элементы Delphi внутри формы (кроме самой формы). Он реализован без использования соответствующих функций API Windows и OLE — их нужно применять при организации общения с другими процессами в системе путем перетаскивания.

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

Способ работы с этим интерфейсом в VCL определяется свойствами:

property DragMode: TDragMode;

TDragMode = (dmManual, dniAutomatic);

и

property DragKind: TDragKind;

type TDragKind = (dkDrag, dkDock);

Для автоматического включения механизмов, имеющихся в VCL, необходимо, чтобы значение свойства компонента DragMode было установлено равным dmAutomatic. Это означает, что на всех стадиях перетаскивания нужные функции вызываются без участия программиста. Его задача состоит только в том, чтобы определить методы-обработчики соответствующих событий. В режиме dmManual (принимаемом по умолчанию) все необходимые вызовы функций нужно делать самому.

Второе свойство задает, какой из двух интерфейсов: Drag-and-Drop или Drag-and-Dock активизируется при начале перетаскивания. Интерфейс Drag-and-Dock будет рассмотрен в следующем разделе, а здесь рассмотрим подробнее формат обработчиков трех основных событий интерфейса Drag-and-Drop.

Во время перетаскивания при перемещении курсора мыши с перетаскиваемым элементом над другим элементом управления, а также при отпускании кнопки для последнего возникает событие:

property OnDragOver: TDragOverEvent;

TDragOverEvent = procedure(Sender, Source: TObject; X,Y: Integer; State:

TDragState; var Accept: Boolean) of object;

Параметры события:

Обработчик этого события должен возвратить решение, примет ли данный элемент объект source или нет, в булевой переменной Accept. Если обработчик этого события отсутствует, то элемент управления не может работать приемником, т. е. на него нельзя ничего "перетащить".

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

property DragCursor: TCursor;

Если компонент не является приемником Drag-and-Drop, или обработчик события onDragOver отсутствует или возвращает значение False, то появляется другой курсор (по умолчанию crNoDrop).

Событие:

property OnDragDrop: TDragDropEvent;

TDragDropEvent = procedure(Sender, Source: TObject; X, Y: Integer) of object;

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

В приведенном примере вы можете перетащить одну из геометрических фигур в вашей форме (компонент TShape) на статический текст Labell. Для этого в Labell описано два метода: LabellDragOver сигнализирует о готовности принять только компоненты класса TShape, a LabellDragDrop вычисляет площадь фигуры в квадратных сантиметрах в зависимости от ее вида (эллипс, прямоугольник, прямоугольник с закругленными углами).

procedure TForml.LabeilDragOver(Sender, Source: TObject; X, Y: Integer;

State: TDragState; var Accept: Boolean);

begin

Accept := Source is TShape;

end;

procedure TForml.LabellDragDrop(Sender,Source: TObject; X,Y: Integer);

var f : single; I : Integer;

begin

with Source as TShape do begin i := Width; if i > Height then i:= Height;

case Shape of stRectangle: f := Width*Height;

stSquare: f := i * i;

stCircle: f := Pi * i*i / 4;

stElllpse: f := Pi * Width*Height / 4;

stRoundRect, stRoundSquare:

begin

if Shape = stRoundRect then f := Width*Height else f := i * i;

i := (i - Pen.Width + 1) div 4;

f := f - (4-Pi)*I*I;

end;

end;

end;

f := f / Sqr(Forml.PixelsPerInch / 2.54);

Labell.Caption := FloatToStrF(f, ffFixed, 5, 2)+ 'кв.см';

end;

При завершении перетаскивания, вне зависимости от готовности приемника всегда возникает еще одно событие:

property OnEndDrag: TEndDragEvent ;

TEndDragEvent = procedure(Sender, Target: TObject;X,Y:Integer) of object;

Его параметры идентичны описанным выше.

Для управления перетаскиванием вручную (в режиме dmManual) есть следующие возможности. Начало перетаскивания происходит при вызове метода:

procedure BeginDrag(Immediate: Boolean);

Программист должен связать вызов этого метода с каким-либо событием в системе. (Если значение свойства DragMode установлено равным dmAutomatic BeginDrag вызывается функцией окна при нажатии левой кнопки мыши.) Параметр immediate определяет, когда именно возникает состояние Drag-and-Drop: в случае значения True немедленно, в случае False — после смещения мыши с нажатой левой кнопкой на 5 точек по любой из осей. Последний вариант дает возможность использовать нажатие левой кнопки и для перетаскивания, и для регистрации щелчков на элементе управления (скажем, на кнопке). В режиме dmAutomatic такой возможности нет. Метод:

procedure DragDrop(DragObject: TObject; X, Y: Integer); dynamic;

вызывает обработчик события OnDragDrop, а производит все завершающие действия метод:

procedure EndDrag(Drop: Boolean);

Он инициирует события OnDragDrop (при возможности приема) и

OnEndDrag. Метод:

function Dragging: Boolean;

возвращает значение True, если данный элемент в настоящий момент перетаскивается.

Перенос компонентов (docking)

Эта возможность впервые появилась в Delphi 4. Она "подсмотрена" у разработчиков из Microsoft, внедривших плавающие панели инструментов в MS Office, Internet Explorer и другие продукты.

Речь идет о том, что ряд элементов управления (а конкретно — потомки класса TWinControi) могут служить носителями (доками) для других элементов управления, с возможностью их динамического перемещения из одного дока в другой при помощи мыши. Перетаскивать можно практически все — от статического текста до форм включительно. Пример использования техники Drag-and-Dock дает сама среда разработки Delphi — с ее помощью можно объединять на экране различные инструменты, такие как Инспектор объектов и Менеджер проекта.

Как и в случае с технологией перетаскивания (Drag-and-Drop), возможны два варианта реализации техники Drag-and-Dock: автоматический и ручной. В первом случае дело сводится к установке нужных значений для нескольких свойств, а остальную часть работы берет на себя код VCL; во втором, как следует из названия, вся работа возлагается на программиста.

Итак, что же нужно сделать для внедрения Drag-and-Dock? В Инспекторе объектов нужно изменить значение свойства DragKind на dkDock, а свойства DragMode — на dmAutomatic. Теперь этот элемент управления можно перетаскивать с одного носителя-дока на другой.

Носителем других компонентов может служить потомок TWinControl. У него есть свойство DockSite, установка значения True которого разрешает перенос на него других компонентов. Если при этом еще и установить значение свойства AutoSize равным True, док будет автоматически масштабироваться в зависимости от того, что на нем находится. В принципе, этими тремя операциями исчерпывается минимальный обязательный набор. На рис. 12.1 показан пример использования плавающих панелей инструментов.

Рис. 12.1. Плавающие панели инструментов

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

type TStartDockEvent = procedure(Sender: TObject; var DragObject: TDrag-DockObject) of object;

TEndDraqEvent = procedure(Sender, Target: TObject; X, Y: Integer) of object;

В первом из методов sender — это переносимый объект, а DragObject — специальный объект, создаваемый на время процесса переноса и содержащий его свойства. Во втором Sender — это также переносимый объект, a Target —-объект-док. Док тоже извещается о событиях во время переноса:

type TGetSiteInfoEvent = procedure(Sender: TObject; DockClient: TControl;

var influenceRect: TRect; MousePos: TPoint; var CanDock: Boolean) of object;

TDockOverEvent = procedure(Sender: TObject; Source: TDragDockObject; X, Y: Integer; State: TDragState; var Accept: Boolean) of object;

TDockDropEvent = procedure(Sender: TObject; Source: TDragDockObject; X, Y: Integer) of object;

TUnDockEvent = procedure(Sender: TObject; Client: TControl; NewTarget:

TWinControl; var Allow: Boolean) of object;

Как только пользователь нажал кнопку мыши над переносимым компонентом и начал сдвигать его с места, всем потенциальным докам (компонентам, значение свойства которых Docksite установлено равным True) рассылается событие onGetsiteinfo. С ним передаются параметры: кто хочет "причалить " (параметр Dockciient) и где (Mousepos). В ответ док должен сообщить решение, принимает он компонент (параметр canDock) и предоставляемый прямоугольник (influenceRect) или нет. При помощи этого события можно принимать только определенные элементы управления, как показано в примере:

procedure TFonnl.PanellGetSiteInfо(Sender: TObject; DockClient: TControl;

var InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean);

begin if DockClient is TBitBtn then CanDock := False;

end;

Два последующих события в точности соответствуют своим аналогам из механизма перетаскивания (Drag-and-Drop). Событие OnDockOver происходит при перемещении перетаскиваемого компонента над доком, onDockDrop — в момент его отпускания. Наконец, onUnDock сигнализирует об уходе компонента с дока и происходит в момент его "причаливания" в другом месте.

Между доком и содержащимися на нем элементами управления есть двухсторонняя связь. Все "припаркованные" элементы управления содержатся в векторном свойстве Dockciients, а их количество можно узнать из свойства

DockClientCount:

S : = " ;

for i := 0 to Panell.DockClientCount-1 do AppendStrfs,Panel 1.DockClients[i].Name+#$D#$A);

ShowMessage(s);

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

procedure TMyForm.ButtonlEndDock(Sender, Target: TObject; X, Y: Integers); begin

(Sender as TControi).HostDocksite.SetTextBuf(pChar((Sender as TControl) .Name)) ;

end;

Компоненты можно не только переносить с одного дока на другой, но и отпускать в любом месте. Хотя сам по себе компонент TControl и его потомки не являются окнами Windows, но специально для этого случая создается окно-носитель. Свойство FioatingDocksiteciass как раз и определяет класс создаваемого окна. По умолчанию для большинства компонентов значение этого свойства равно TCustomDockForm. Это форма, которая обладает свойствами дока и создается в момент отпускания элемента управления вне других доков. Внешне она ничем нс отличается от обычной стандартной формы. Если вы хотите, чтобы ваша плавающая панель инструментов выглядела по-особенному, нужно породить потомка от TCustomDockForm и связать свойство FloatingDockSiteCiass с этим порожденным классом:

TMyCustomFloatingForm - class(TCustomDockForm) public

constructor Create(AOwner: TComponent); override;

end;

constructor TMyCustomFloatingForm.Create

(AOwner: TComponent);

begin

inherited Create(AOwner);

BorderStyle := bsNone;

end;

procedure TFormI.FormCreate(Sender: TObject);

begin

ToolBarl.FloatingDockSiteCiass := TMyCustomFloatingForm;

end;

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

Рис. 12.2. Плавающие панели инструментов без заголовка окна

Переносить компоненты можно не только с помощью мыши, но и программно. Для этого есть пара методов ManualDock и ManualFloat. В приводимом ниже примере нажатие кнопки с именем BitBtnl переносит форму custForm на док MainForm. panell и размещает ее по всей доступной площади (параметр выравнивания alciient). Нажатие кнопки BitBtn2 снимает эту форму с дока и выравнивает ее по центру экрана. В свойствах undockHeight и undockwidth хранятся высота и ширина элемента управления на момент, предшествующий помещению на док:

procedure TMainFom.BitBtnIClick (Sender: TObject);

begin CustForm.ManualDock(MainForm.Panell,nil,

alciient);

end;

procedure TMainForm.BitBtn2Click(Sender: TObject);

begin

with CustForm do begin ManualFloat(Rect( (Screen.Width-UndockWidth) div 2,

(Screen. Height-U'ndockHeight) div 2, (Screen.Width+UndockWidth) div 2, (Screen.Height+UndockHeight) div 2));

end;

Полное рассмотрение внутреннего устройства механизмов Drag-and-Dock потребовало бы расширения объема этой главы. Тем, кто хочет использовать их на все 100%, рекомендуем обратиться к свойствам UseDockManager и DockManager. Последнее представляет собой СОМ-интерфейс, позволяющий расширить возможности дока, вплоть до записи его состояния в поток (TStream).

Резюме

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

Если вы интересуетесь дополнительными возможностями "настоящего" Drag-and-Drop, реализованного в Windows, вам следует обратиться к главе 27, посвященной библиотеке Shell API. В ее недрах есть все необходимое для переноса объектов в рамках возможностей пользователя Windows.