Перейти к содержанию

Рекомендуемые сообщения

Возможно иногда я буду писать о приёмах или кто-то решит написать здесь свой или рассмотреть чужие.

Концепция которую я рассмотрю первой касается реализация динамического списка объектов на Дельфи.

Реализация динамического списка объектов.

Делать мы будем такую штуку,

screenbn.png

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


TMyPixel = class(TObject)
FX: Integer;
FY: Integer;
FText: Integer;
constructor Create(X, Y, Num: Integer);
procedure SetPixel;
end;

procedure TMyPixel.SetPixel;
begin
MainForm.Canvas.PolyLine([Point(FX, FY), Point(FX, FY)]);
MainForm.Canvas.TextOut(FX + 1, FY + 1, IntToStr(FText));
end;

procedure TMainForm.SortBtnClick(Sender: TObject);
var i: Integer;
begin
PixList.Sort(PixCompare); // PixList : TList;
with PixList do
for i := 0 to Count - 1 do TMyPixel(Items[i]).FText := i + 1;
end;

Заметка.

Мы можем написать свой динамический список.

Принцип крутится вокруг

setlength(имя массива элементов, размер массива)

и

массив[индекс]:= значение

Это принцип необходимо обернуть в класс и добавить методы: добавление/удаление/вставка/поменять местами и т.п. Каждый раз проверять длину массива length(массив) или sizeof(массив) и если необходимо расширять массив или фрагментировать пустоты в нём при удалении элемента внутри массива.

Существует стандартный класс TObject и класс TList. TList может содержать список объектов. В объект можно обвернуть очень многое.

Если вам нужно то ознакомьтесь с описанием этих классов.

Описание TObject

Тип TObject определяет тип базового класса. Он является самым старым прародителем всех классов - каждый класс, в конечном счете, получен из TObject.

Из-за этого, каждый объект унаследовал методы TObject.

Методы TObject относятся к двум категориям - класс и не класс. Когда предустановленно ключевое слово Class, метод можно вызвать и в объекте класса, и в классе непосредственно. Такой статический метод не может обратиться ни к каким полям класса, потому что сам класс не имеет никаких данных, только объекты - имеют данные.

Некоторые ключевые (статические) методы Class:

function ClassName Выдает имя класса как строку

ClassParent Выдает имя родителя класса

ClassInfo Выдает Run Time информацию класса

InstanceSize Размер объекта класса в байтах

NewInstance Создает новый объект класса

Некоторые ключевые методы Object:

Create Создатель пустого объекта

Free Вызывает Destroy для ненулевых объектов

Destroy Высвобождение памяти объекта

AfterConstruction Вызывается после построения

BeforeDestruction Вызывается перед разрушением

Описание TList

Класс TList очень полезный универсальный контейнер списков. Он отличается от массивов, в которых он обеспечивает более богатые функциональные возможности.

В частности объекты TList могут быть отсортированы. Эта сортировка может быть с использованием любых выбранных критериев. Например, список может содержать набор объектов, которые имеют строку и численные поля. Вы можете отсортировать список по строке, по числу, по обоим, с возрастанием или убыванием, как Вы желаете. И пересортировать позже по другим критериям.

Показанный пример кода показывает такую сортировку.

Ключевые свойства и методы упомянуты ниже.

Свойство Capacity

Используется для установления размера (число указателей на объекты) списка. Предварительно установив в разумное значение, можно избежать множественных перераспределений памяти.

Свойство Count

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

Свойство Items

Позволяет обращаться к элементам в списке. Например, myList.Items[2] ; возвращает 3-ий элемент в списке. Это свойство, заданное по умолчанию, вышеупомянутое может быть упрощено до myList[2];.

Свойство List

Возвращает элементы в массиве.

Метод Add

Добавляет элемент в конец списока.

Метод Assign

Заменяет список содержанием другого списка.

Метод Clear

Удаляет все элементы списка, устанавливая Count в 0.

Метод Delete

Удаляет элемент из списка по его позиции в списке.

Метод Remove

Удаляет элемент из списка по его объектному указателю.

Метод Exchange

Меняет позиции двух элементов

Метод Move

Перемещает элемент в новую позицию списка.

Метод Insert

Вставляет новый элемент в список в данную позицию.

Метод First

Получает первый элемент в списке.

Метод Last

Получает последний элемент в списке.

Метод Sort

Сортирует список в соответствии с вашими указанными критериями. Сортировка списка проводится внутри TList, но каждая пара элемента сравнивается, вызывая функцию, которую вы указали для этого метода.

Метод IndexOf

Выдает позицию указанного объекта в списке.

Полный код:



interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons;

type
TMainForm = class(TForm)
ListBtn: TBitBtn;
ClearBtn: TBitBtn;
DelBtn: TBitBtn;
SortBtn: TBitBtn;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure ListBtnClick(Sender: TObject);
procedure ClearBtnClick(Sender: TObject);
procedure DelBtnClick(Sender: TObject);
procedure SortBtnClick(Sender: TObject);
private
PixList: TList;
PixNum: Integer;
public
{ Public declarations }
end;

TMyPixel = class(TObject)
FX: Integer;
FY: Integer;
FText: Integer;
constructor Create(X, Y, Num: Integer);
procedure SetPixel;
end;


var
MainForm: TMainForm;

implementation

{$R *.dfm}

const PixColor = clRed;

var CurPixel: TMyPixel;

constructor TMyPixel.Create(X, Y, Num: Integer);
begin
inherited Create;
FX := X;
FY := Y;
FText := Num;
SetPixel;
end;

procedure TMyPixel.SetPixel;
begin
MainForm.Canvas.PolyLine([Point(FX, FY), Point(FX, FY)]);
MainForm.Canvas.TextOut(FX + 1, FY + 1, IntToStr(FText));
end;

function PixCompare(Item1, Item2: Pointer): Integer;
var Pix1, Pix2: TMyPixel;
begin
Pix1 := Item1;
Pix2 := Item2;
Result := Pix1.FX - Pix2.FX;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
PixList := TList.Create;
PixNum := 1; {Счетчик точек}
Canvas.Pen.Color := PixColor; {Цвет точки}
Canvas.Pen.Width := 3; {Размер точки}
Canvas.Brush.Color := Color; {Цвет фора текста равен цвету формы}
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
PixList.Free;
end;

procedure TMainForm.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
PixList.Add(TMyPixel.Create(X, Y, PixNum));
Inc(PixNum);
end;

procedure TMainForm.ListBtnClick(Sender: TObject);
var i: Integer;
begin
with PixList do
for i := 0 to Count - 1 do
begin
CurPixel := Items[i];
CurPixel.SetPixel;
end;
end;

procedure TMainForm.ClearBtnClick(Sender: TObject);
begin
Canvas.FillRect(Rect(0, 0, Width, Height));
end;

procedure TMainForm.DelBtnClick(Sender: TObject);
begin
PixList.Clear;
PixNum := 1;
end;

procedure TMainForm.SortBtnClick(Sender: TObject);
var i: Integer;
begin
PixList.Sort(PixCompare);
with PixList do
for i := 0 to Count - 1 do TMyPixel(Items[i]).FText := i + 1;
end;

end.
unit uMain;

Скачать исходники: ссылка

Теперь поговорим что даст список объектов.

Первое - это удобно. Вам не нужно создавать свои классы.

Второе - каждый объект в списке может иметь дочерний объект или родительский. Если один объект разрушается, можно задать разрушение всех дочерних. Вы можете составить список объектов сложной иерархии и через цикл обрабатывать их действия между собой. Можно сказать что я подразумеваю прототип игровых объектов. Они могут толкаться между собой, драться, разрушать делать что-то по определённому алгоритму, получать внешнее воздействие и возможно даже ограниченно приспосабливаться под это воздействие. :)

Ссылка на комментарий
Поделиться на другие сайты

  • 3 недели спустя...

 Мне понадобилась статья по созданию компонента календаря с привязкой к dataset -базе данных. Поскольку этот компонент по работе, то я его приводить не буду, но статью обязательно приведу пока она не исчезла с рунета.

Создание собственного компонента в связке с базой данной позволит удобно встраивать управление базой данных в различных программах. На пример моего компонента его можно будет встроить  в любую новую разрабатываемую программу на Дельфи. Возьмём тот же cheatEngine. На форме достаточно будет разместить компонент календаря и на нём фиксировать события, которые будут выделяться определённым цветом. Например мы не доламали игрушку в различные дни, а зафиксировать заметки как-то нужно. Для этого создадим пользователя под именем "Игра "Название игры" " и на сегодняшней дате поставим логи регистров с бряками. База данных может иметь различный формат экспортироваться и импортироваться....

Я долго время знал Дельфи и не любил возиться с базами данных тем более с визуальными компонентами, теперь я иного мнения :) Ну лучше конечно писать базы данных на C#3.0 с технологией LINQ - более гибкая штука.

Итак сама статья про создание компонента БД.

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

Постановка задачи

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

Реализация

Наиболее правильным, с точки зрения иерархии vcl, методом решения первого пункта поставленной задачи, будет создание нового компонента, в качестве базового класса которого мы выберем tcustomcontrol. Этот класс является базовым для создания компонентов-надстроек над визуальными объектами windows, и предоставляет методы для отрисовки объектов разных форм. Если же у вас нет необходимости наследовать все особенности поведения объектов windows то можете в качестве базового класса использовать tgraphiccontrol, наследники которого отрисовываются быстрее, поскольку не должны следить за уймой Виндовских служебных сообщений.

Сам компонент tcustomcontrol определен в модуле controls.pas следующим образом:


private
fcanvas: tcanvas;
procedure wmpaint(var message: twmpaint); message wm_paint;
protected
procedure paint; virtual;
procedure paintwindow(dc: hdc); override;
property canvas: tcanvas read fcanvas;
public
constructor create(aowner: tcomponent); override;
destructor destroy; override;
end;
tcustomcontrol = class(twincontrol)

Здесь самым интересным для нас является метод paint и свойство canvas. Посредством этих двух членов класса tcustomcontrol мы и будет рисовать нашу кнопку.

Кроме этого мы немножко расширим функциональность нашего компонента и придадим ему возможность устанавливать цвет темного и светлого участка своей границы, а также ее толщину, и наконец определим свойство flat которое отвечает за функциональность аналогичного свойства стандартных компонентов delphi.

Исходя из вышесказанного прототип нашего компонента (tellipsebutton) будет выглядеть следующим образом:


private
fdarkcolor,flightcolor,fbackcolor:tcolor;
fsize:integer;
fpushed:boolean;
rgn:hrgn;
fflat:boolean;
fdrawflat:boolean;
fonmouseenter,fonmouseleave:tnotifyevent;
{ private declarations }
protected
procedure setdarkcolor(value:tcolor);
procedure setlightcolor(value:tcolor);
procedure setsize(size:integer);
procedure setbackcolor(value:tcolor);
procedure dblclick;override;
procedure drawflat;dynamic;
procedure drawnormal;dynamic;
procedure drawpushed;dynamic;
procedure wmlbuttondown(var message:twmmouse);message wm_lbuttondown;
procedure wmlbuttonup(var message:twmmouse);message wm_lbuttonup;
procedure wmmousemove(var message:twmmousemove);message wm_mousemove;
procedure cmmouseenter(var message:tmessage);message cm_mouseenter;
procedure cmmouseleave(var message:tmessage);message cm_mouseleave;
procedure cmtextchanged(var message:tmessage);message cm_textchanged;
procedure setflat(value:boolean);
procedure domouseenter;
procedure domouseleave;
{ protected declarations }
public
constructor create(aowner:tcomponent);override;
procedure afterconstruction;override;
destructor destory;virtual;
procedure repaint;override;
procedure paint;override;
{ public declarations }
property canvas;
published
property darkcolor:tcolor read fdarkcolor write setdarkcolor default clblack;
property lightcolor:tcolor read flightcolor write setlightcolor default clwhite;
property backcolor:tcolor read fbackcolor write setbackcolor default clbtnface;
property size:integer read fsize write setsize;
property flat:boolean read fflat write setflat;
property caption;
{events}
property onclick;
property ondblclick;
property onmousemove;
property onmousedown;
property onmouseup;
property onmouseenter:tnotifyevent read fonmouseenter write fonmouseenter;
property onmouseleave:tnotifyevent read fonmouseleave write fonmouseleave;
{ published declarations }
end;
tellipsebutton = class(tcustomcontrol)

Как видим, здесь помимо базовых конструктора create и метода afterconstruction переопределены и методы paint и repaint.

Вся функциональность этого компонента в основном заключена в динамических методах drawflat, drawnormal, drawpushed которые отвечают за рисование компонента соответственно в режиме flat, в нормальном приподнятом режиме и в нажатом режиме.

Собственно рисование делается с помощью метода canvas.arc, который рисует часть эллипса заданным цветом. Таким образом мы рисуем одну половину темным цветом а другую – светлым и получаем эффект выпуклости. Поменяв местами цвета мы достигаем эффекта «нажатия» для нашей кнопки. Ну а использовав в качестве цвета фона – средний между темным и светлым цветами границы – мы получаем ефект flat:


var x,y:integer;
begin
canvas.lock;
try
inherited paint;
canvas.brush.color:=backcolor;
canvas.pen.color:=clgray;
canvas.arc(0,0,width,height,0,height,width,0);
canvas.brush.style:=bsclear;
canvas.ellipse(clientrect);
canvas.font.size:=5;
x:=self.clientwidth-canvas.textwidth(caption);
x:=x div 2;
y:=self.clientheight-canvas.textheight(caption);
y:=y div 2;
canvas.textrect(self.clientrect,x,y,caption);
finally
canvas.unlock;
end;
end;

procedure tellipsebutton.drawnormal;
var i:integer;x,y:integer;
begin
canvas.lock;
try
inherited paint;
canvas.brush.style:=bsclear;
canvas.brush.color:=backcolor;
canvas.pen.color:=darkcolor;
canvas.arc(0,0,width,height,0,height,width,0);
for i:=0 to fsize do
canvas.arc(i,i,width-i,height-i,i,height-i,width-i,i);
canvas.pen.color:=lightcolor;
canvas.arc(0,0,width,height,width,0,0,height);
for i:=0 to fsize do
canvas.arc(i,i,width-i,height-i,width-i,i,i,height-i);
canvas.brush.style:=bsclear;
canvas.font.size:=5;
x:=self.clientwidth-canvas.textwidth(caption);
x:=x div 2;
y:=self.clientheight-canvas.textheight(caption);
y:=y div 2;
canvas.textrect(self.clientrect,x,y,caption);
finally
canvas.unlock;
end;
end;

procedure tellipsebutton.drawpushed;
var i:integer;x,y:integer;
begin
canvas.lock;
try
inherited paint;
canvas.brush.style:=bsclear;
canvas.brush.color:=backcolor;
canvas.pen.color:=lightcolor;
canvas.arc(0,0,width,height,0,height,width,0);
for i:=0 to fsize do
canvas.arc(i,i,width-i,height-i,i,height-i,width-i,i);
canvas.pen.color:=darkcolor;
canvas.arc(0,0,width,height,width,0,0,height);
for i:=0 to fsize do
canvas.arc(i,i,width-i,height-i,width-i,i,i,height-i);
canvas.brush.style:=bsclear;
canvas.font.size:=5;
x:=self.clientwidth-canvas.textwidth(caption);
x:=x div 2;
y:=self.clientheight-canvas.textheight(caption);
y:=y div 2;
canvas.textrect(self.clientrect,x,y,caption);
finally
canvas.unlock;
end;
end;
procedure tellipsebutton.drawflat;

Теперь, оснастив наш компонент необходимыми функциями мы можем приступить к его «причесыванию», т.е. написанию рутинных методов по присвоению значений свойствам и отладке. Первым делом здесь надо реализовать реакцию компонента на события мыши. Это мы делаем посредством методов wmlbuttondown, wmlbuttonup, wmmousemove.


begin
inherited;
paint;
end;

procedure tellipsebutton.wmlbuttonup;
begin
inherited;
paint;
end;
procedure tellipsebutton.wmmousemove;
begin
inherited;
if csclicked in controlstate then
begin
if ptinrect(clientrect,smallpointtopoint(message.pos)) then
begin
if not fpushed then drawpushed;
fpushed:=true;
end else
begin
if fpushed then drawnormal;
fpushed:=false;
end
end;
end;
procedure tellipsebutton.wmlbuttondown;

Здесь также мы реализуем функциональность свойства flat. (в wmmousemove).

Кроме этого мы используем методы cmmouseenter, cmmouseleave для вызова соответствующих обработчиков событий.

А также реализовываем метод cmtextchanged для правильного отображения текста кнопки:


begin
invalidate;
end;
procedure tellipsebutton.cmtextchanged;

Теперь же дело только за методами paint и repaint, которые мы реализовываем следующим образом:


begin
if not fflat then
begin
if not (csclicked in controlstate) then
drawnormal else drawpushed;
end else
if fdrawflat then drawflat else
if not (csclicked in controlstate) then drawnormal else drawpushed;
end;

procedure tellipsebutton.repaint;
begin
inherited;
paint;
end;
procedure tellipsebutton.paint;

Теперь наш компонент готов к испытаниям. И перед тем как его регистрировать и кидать на палитру компонентов настоятельно рекомендую Вам проверить его функциональность в runtime режиме. В противном же случае вы рискуете повесить всю ide delphi при добавлении компонента на форму.

Проверка компонента

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

-создать новое приложение

-в секции uses разместить ссылку на модуль с вашим компонентом (ellipsebutton.pas)

-объявить переменную типа tellipsebutton

-создать компонент, заполнить все его свойства и показать.



interface

uses
windows, messages, sysutils, variants, classes, graphics, controls, forms,
dialogs, mycontrols;

type
tform1 = class(tform)
ellipsebutton1: tellipsebutton;
procedure formcreate(sender:tobject);
procedure formdestroy(sender:tobject);
private
{ private declarations }
public
{ public declarations }
end;

var
form1: tform1;

implementation

{$r *.dfm}
procedure tform1.formcreate(sender:tobject);
begin
ellipsebutton1:=tellipsebutton.create(self);
ellipsebutton1.parent:=self;
ellipsebutton1.setbounds(10,10,100,100);
ellipsebutton1.visible:=true;
end;

procedure tform1.formdestroy(sender:tobject);
begin
ellipsebutton1.free;
end;

end.
unit main;

После такой, наглядной проверки и отладки вы можете спокойно регистрировать ваш компонент:


begin
registercomponents('usable', [tellipsebutton]);
end;
procedure register;

И использовать уже в ваших приложениях для быстрого создания эллипсоидных кнопок.

Итоги

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

Автор: Михаил Продан

Ссылка на комментарий
Поделиться на другие сайты

Основы работы с Win32API (прим. от меня применительно к Дельфи

Автор: Акулов Николай

Введение

Цель этого обзора - помочь человеку перейти от использования средств Delphi к функциям Win API. Предполагается, что читатель уже неплохо владеет Delphi... 

Продолжение ниже:

Кроме того, многие авторы книг по Delphi не уделяют достаточно внимания функциям Win API, предназначенным для работы с окнами и графикой, потому что считают, что VCL Delphi достаточно хорошо справляется с этими задачами. Так что часто приходится учиться работе с Win API по книгам по 16-разряд-ному Borland Pascal'ю. Поэтому я буду обращать внимание и на отличие 32-разрядных версий от 16-разрядных. Но я не буду без особой необходимости останавливаться на подробном опи-сании конкретных функций, так как это всё сделано в справочной системе. Я также остановлюсь и на этой самой справочной системе, потому что начинающему программисту может оказаться не очень просто разобраться с ней.

Что такое Win API

Win API - это набор функций, предоставляемых операционной системой каждой программе. Эти функции находятся в стандартных динамически компонуемых библиотеках (Dynamic Linked Li-brary, DLL), таких как kernel32.dll, user32.dll, gdi32.dll. Эти файлы находятся в директории Win-dow. Вообще говоря, каждая программа должна самостоятельно заботится о том, чтобы подключить эти библиотеки. DLL могут подключаться к программе статически и дина-мически. В первом случае программа <освобождает> DLL только при завершении, во втором освобождение может произойти в любой момент. Если после освобождения DLL оказывается, что её больше не использует ни одна программа, она выгружается из памяти. Так как стандартные библиотеки используются самой системой, они всегда находятся в памяти, и поэтому использование динамического подключения бессмысленно. Чтобы статически подключить в Delphi некоторую функцию Win API, например, функцию GetWindowDC из модуля user32.dll, надо написать конструкцию вида

function GetWindowDC(Wnd: HWnd); HDC;

stdcall; external 'user32.dll' name 'GetWindowDC';

Такая запись обеспечивает одновременно и статическое подключение библиотеки user32, и декларацию функции GetWindowDC, что позволяет компилятору в дальнейшем работать с ней. Обратите внимание, что все функции Win API написаны в соответствии с моделью вызова stdcall, а в Delphi по умолчанию используется другая модель - register (модель вызова определяет, как функции передаются параметры). Поэтому при импорте функций из стандартных биб-лиотек необходимо явно указывать эту модель. Далее указывается, из какой библиотеки импор-тируется функция и какое название в ней она имеет. Дело в том, что имя функции в библиотеке может не совпадать с тем, под которым она становится известна компилятору. Позже я остановлюсь на тех случаях, когда это используется. Главным недостатком DLL следует считать то, что в них сохраняется информация только об именах функций, но не об их параметрах. По-этому, если при импорте функции указать не те параметры, какие подразумевались автором DLL, то программа будет работать неправильно (вплоть до зависания), а ни компилятор, ни операционная система не смогут указать на ошибку.

Обычно программа использует довольно большое число функций Win API. Декларировать их все довольно утомительно. К счастью, Delphi избавляет программиста от этой работы: все эти функции уже описаны в соответствующих модулях, достаточно упомянуть их имена в разделе uses. Например, большинство общеупотребительных функций описаны в модулях Windows.dcu и Messages.dcu.

Как получить справку по функциям Win API

Для тех, кто решил использовать Win API, самым необходимым инструментом становится ка-кая-либо документация по этим функциям. Их так много, что запомнить все совершенно нере-ально, поэтому работа без справочника под рукой просто невозможна. Наиболее доступным справочником для российского программиста является Win32 Developer's Reference, справочная система фирмы Microsoft, потому что фирма Inprise (тогда ещё Borland), получила лицензию на включение её в состав Delphi. Сам я буду постоянно ссылаться на эту справку, потому что под-робное описание функций займёт слишком много места, да и нет особого смысла описывать то, что описали и без меня.

Хотя в комплект поставки Delphi и входит эта справочная система, содержащая описание всех функций Win API, получение справки по ним не настолько удобное, как по стандартным функ-циям Delphi. Если набрать в редакторе Delphi имя какой-нибудь функции Win API, поставить курсор в начало этой функции и нажать F1, то откроется справка по ней, как и в случае обыч-ных функций и процедур. Однако функции Win API не появляются в предметном указателе справочной системы. Авторы Delphi объясняют это ограничениями, накладываемыми самой справочной системой (как обычно, всех собак вешают Windows). Они же советуют создать в меню кнопки <Пуск> ярлык к справочной системе Win32 Developer's Reference. Мне остаётся только присоединиться к этому совету и добавить, что ярлык надо создавать к файлу MSTools.hlp, который находится в директории $(Delphi).

Другая проблема заключается в том, что, так как система Windows написана на Си, все описа-ния функций Win API даны в соответствии с синтаксисом именно этого языка, а не Паскаля. Во-первых, необходимо разобраться с типами. Ниже приведена таблица, какие типы Си чему соответствуют.

WCHAR = WideChar;

LPSTR = PChar;

LPCSTR = PChar;

LPWSTR = PWideChar;

LPCWSTR = PWideChar;

DWORD = Integer;

BOOL = LongBool;

PBOOL = ^BOOL;

PINT = ^Integer;

PWORD = ^Word;

PDWORD = ^DWORD;

LPDWORD = PDWORD;

UCHAR = Byte;

PUCHAR = ^Byte;

SHORT = Smallint;

UINT = Integer;

PUINT = ^UINT;

ULONG = Longint;

PULONG = ^ULONG;

LCID = DWORD;

LANGID = Word;

int = Integer;

long = LongInt;

PVOID = Pointer;

HANDLE = THandle;

LPPOINT = TPoint;

RECT = TRect;

LPRECT = PRect;

LPSIZE = PSize;

BITMAP = TBitmap;

Все типы, приведённые в первой части таблицы, в целях совместимости описаны в модуле Win-dows.dcu, поэтому их можно использовать наравне с обычными типами Delphi. Кроме этих типов общего назначения существуют ещё специальные. Например, дескриптор окна имеет тип HWND, первый параметр сообщения - тип WPARAM. Эти специальные типы также описаны в Windows.dcu. В некоторых имена типов, встречающихся в справке, и соответствующих им ти-пов из Windows.dcu отличаются только добавлением буквы , как это можно видеть из вто-рой части таблицы. Кстати, не следует путать тип TBitmap, определённый в Windows.dcu, с классом TBitmap, определённым в Graphics.dcu. Зачем разработчикам Delphi потребовалось на-зывать разные типы одним именем, не понятно, тем более что во второй версии Delphi был тип BITMAP, который куда-то исчез в третьей. Зато в четвёртой версии снова появился BITMAP, остался TBitmap, да ещё добавился tagBITMAP, и все эти три типа означают то же самое.

Теперь о синтаксисе описания самой функции в Си. Оно имеет вид

<Тип функции> <Имя функции>(<Тип параметра> <Имя параметра>,

<Тип параметра2> <Имя параметра2>, ...);

Еще в Си различается верхний и нижний регистр, поэтому идентификаторы HDC, hdc, hDC и т. д. - разные идентификаторы (автор Си очень любил краткость и хотел, чтобы можно было делать не 26, а 52 переменные с именем из одной буквы). Поэтому часто можно встретить, что имя параметра и его тип совпадают с точностью до регистра. К счастью, при описании функции в Delphi мы не обязаны сохранять имена параметров, значение имеют лишь их типы и порядок следования. С учётом всего этого функция, описанная в справке как

HMETAFILE CopyMetaFile(HMETAFILE hmfSrc, LPCTSTR lpszFile);

в Delphi имеет вид:

function CopyMetaFile(hmfSrc: HMETAFILE; lpszFile: LPCTSTR): HMETAFILE;

или, что то же самое,

function CopyMetaFile(hmfSrc: HMetaFile; lpszFile: PChar): HMetaFile;

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

Несколько особняком стоит тип VOID. Если функция имеет такой тип, то в Паскале она описы-вается как процедура. Если вместо параметров у функции в скобках стоит VOID, это означает, что функция не имеет параметров. Например, функция

VOID CloseLogFile(VOID);

в Delphi описывается как

procedure CloseLogFile;

Не путайте VOID и PVOID. PVOID - это нетипизированный указатель, соответствующий типу Pointer.

В тех случаях, когда тип параметра является указателем на другой тип (обычно начинается с букв LP), при описании этой функции в Delphi можно пользоваться параметром-переменной, так как в этом случае функции передаётся указатель. Например, функция

int GetRgnBox(HRGN hrgn, LPRECT lprc);

в файле Windows.pas описана

function GetRgnBox(RGN: HRGN; var p2: TRect): Integer;

И, наконец, если не удаётся понять, как функция, описанная в справке, должна быть переведена на Паскаль, можно попытаться найти описание этой функции в исходных текстах модулей, по-ставляемых вместе с Delphi. Эти модули находятся в директории $(DELPHI). Можно также воспользоваться подсказкой, которая всплывает в редакторе Delphi после того, как будет набрано имя функции.

Если посмотреть справку, например, по функции GetSystemMetrics, то видно, что эта функция должна иметь один целочисленный параметр. Однако далее в справке предлагается при вызове этой функции подставлять в качестве параметра не числа, а SM_ARRANGE, SM_CLEANBOOT и т. д. Подобная ситуация и со многими другими функциями Win API. Все эти SM_ARRANGE, SM_CLEANBOOT и т. д. являются именами числовых констант. Эти константы описаны в том же модуле, в котором описана функция, использующая их, поэтому можно не выяснять числен-ные значения этих констант, а указывать при вызове функций их имена, например, GetSystem-Metric-s(SM_Arrange); Если по каким-то причинам всё-таки потребовалось выяснить численные значения, то в справочной системе их искать не стоит - их там нет. Я могу только опять отправить к исходным текстам модулей Delphi, в которых эти константы описаны. Так, например, просматривая Windows.pas, можно узнать, что SM_ARRANGE = 56. Кстати, всем, кто решиться самостоятельно просматривать исходники, я очень рекомендую использовать для этого не текстовый редактор, а программу, которая может только показать, но не изменить файл (что-то вроде просмотра по F3 в Norton Commander'е). Так безопаснее. Или же стоит по-думать о резервной копии.

В описании многих функций Win API вверху можно увидеть три ссылки: QuickInfo, Overview и Group. Первая даёт краткую информацию о функции. Самой полезной частью этой информации является то, для каких версий Windows эта функция реализована. Например, очень полезна функция MaskBlt, однако QuickInfo показывает, что она реализована только в Windows NT. Программа, использующая эту функцию, не будет работать в Windows 95. Иногда напротив на-звания одной из систем стоит слово , которое переводится как <пень>, <обрубок> (например, для функции GetDeviceGammaRamp это слово стоит напротив Windows NT). Это означает, что в данной версии эта функция присутствует (то есть обращение к ней не вызывает ошибки), но ничего не делает. Оставим на совести программистов из Microsoft вопрос, зачем нужны такие пни. Overview - это краткий обзор какой-то большой темы. Например, для любой функции, работающей с растровыми изображениями, обзор будет в двух словах объяснять, за-чем в принципе нужны эти самые растровые изображения. Судя по непроверенным данным, первоначально эти обзоры замышлялись как нечто большее, но потом остановились на таких вот лаконичных фразах. Как бы то ни было, найти в обзоре полезную информацию удаётся крайне редко, поэтому заглядывать туда стоит только если ну совсем ничего не понятно. И, наконец, Group. Эта ссылка приводит к списку всех функций, родственных данной. Например, для функции CreateRectRgn группу будут составлять все функции, имеющие отношение к ре-гионам. Если теперь нажимать на кнопку << (два знака <меньше>) сразу под главным меню окна справки, то будут появляться страницы с кратким описанием возможных применений объ-ектов, с которыми работают функции (в приведённом примере описание возможностей ре-гионов). Чтобы читать их в нормальной последовательности, лучше всего нажать на < столько раз, сколько возможно, а затем пойти в противоположном направлении с помощью кнопки >.

Иногда в справке можно встретить указания или . К этим замечаниям следует относится критически, так как справка написана для Windows 95, когда ещё не было Windows NT 4.0, описывается версия со старым интерфейсом. Так что то, про что на-писано , может вполне успешно работать и в Windows NT 4.0 и выше, осо-бенно если это <что-то> связано с пользовательским интерфейсом. То же самое относится и к QuickInfo. Такие вещи лучше всего проверять на практике.

Ещё несколько слов о числовых константах. В справке можно встретить числа вида, например, 0xC56F или 0x3341. Префикс <0x> в Си означает шестнадцатеричное число. В Delphi надо его заменить на <{:content:}gt;, то есть вышеназванные числа должны быть записаны как $C56F и $3341 соот-ветственно.

Дескрипторы вместо классов

Программируя в Delphi, мы быстро привыкаем к тому, что каждый объект реализуется экземпляром соответствующего класса. Например, кнопка реализуется экземпляром класса TButton, контекст устройства - классом TCanvas. Но когда создавались первые версии Windows, объ-ектно-ориентированный метод программирования ещё не был общепризнанным, поэтому он не был реализован. Современные версии Windows частично унаследовали этот недостаток, по-этому в большинстве случаев приходится работать по старинке, тем более что DLL могут экс-пор-тировать только функции, но не классы.

Когда мы создаём некоторый объект в Windows, ему присваивается уникальный 32-разрядный номер, называемый дескриптором. В дальнейшем при работе с этим объектом каждой функции передаётся этот дескриптор. В этом и заключается главное различие между методами класса и функциями Win API. Первые связаны с тем экземпляром класса, через который они вызыва-ются, и поэтому не требуют явного указания на объект. Вторым необходимо такое указание, так как они сами по себе никак не связаны ни с одним объектом.

Не следует думать, что при работе с Win API следует полностью отказываться от классов Delphi. Эти методы прекрасно работают вместе. Правда, внутренние механизмы Delphi не могут вклю-читься, если изменение объекта происходит через Win API. Например, если спрятать окно не с помощью метода Hide, а с помощью вызова функции Win API ShowWindow(Handle, SW_Hide), не возникнет событие OnHide, потому что оно запускается теми самыми внутренними механиз-мами Delphi. Но такие недоразумения случаются обычно только тогда, когда функциями Win API дублируется то, что можно сделать и с помощью Delphi. Для вызова функций Win API объ-екта, созданного с помощью Delphi, используйте свойство Handle. В нём хранится дескриптор.

В некоторых случаях класс Delphi инкапсулирует несколько объектов Windows. Например, класс TBitmap включает в себя HBitmap и HPalette - картинку и палитру к ней. Соответственно, он хранит два дескриптора - в свойствах Handle и Palette.

Все экземпляры классов, созданные в Delphi, должны удаляться. В некоторых случаях это происходит автоматически, в некоторых программист должен сам позаботиться о <выносе му-сора>. Аналогичная ситуация и с объектами, создаваемыми в Win API. Если посмотреть справку по функции, создающей какой-то объект, то там обязательно будет информация о том, какой функцией можно удалить объект и может ли система сделать это автоматически. Во многих случаях совершенно разные объекты могут удаляться одной и той же функцией. Так, функция DeleteObject удаляет косметические карандаши, геометрические карандаши, кисти, шрифты, регионы, растровые изображения и палитры. Обращайте внимание на возможные исключения. Например, регионы не удаляются системой автоматически, однако если вызвать для региона функцию SetWindowRgn, то этот регион переходит в собственность операционной системы. Никакие дальнейшие операции с ним, в том числе и удаление, совершать нельзя

Формы Delphi и окна Windows

Принято считать, что класс TForm реализует окно. Это не совсем верно, потому что TForm реализует лишь часть тех объектов, которые принято называть окнами. Например, кнопка - это тоже окно, но реализуется она классом TButton.

Каждое окно принадлежит к какому-то оконному классу. Не следует путать оконный класс с классами Delphi. Это некий шаблон, определяющий базовые свойства окна. Каждому такому шаблону присваивается имя, уникальное в его области видимости. Классы делятся на локаль-ные (видимые только в приложении, регистрирующем их) и глобальные (видимые вне прило-жения). В 16-разрядных версиях Windows локальный класс, зарегистрированный приложением, был виден всем другим экземплярам этого же приложения. В 32-разрядных версиях различные экземпляры одного приложения стали более самостоятельными, поэтому каждый экземпляр должен заново регистрировать все свои классы. Перед использованием класс необходимо заре-гистрировать ( функция RegisterClassEx). При завершении программы все классы, зарегистриро-ванные в ней, удаляются автоматически, хотя при необходимости их можно удалить и само-стоятельно. Отсюда очевидно, что глобальный оконный класс, доступный всем программам, должен быть зарегистрирован динамической библиотекой, постоянно находящейся в памяти. Как это сделать, можно прочитать в Win32 Developer's Reference, тема WNDCLASS. Если же глобальный класс регистрируется программой обычным образом, то это означает, что он будет доступен не только самой программе, но и всем библиотекам, вызванным ею, но никак не другим программам и не другим экземплярам этой программы. Если наоборот, глобальный класс регистрирует DLL, то он становится доступным всем программам, использующим эту DLL. Но в этом случае класс не удаляется автоматически.

При создании окна обязательно указывать его класс. Данный класс должен быть к этому мо-менту зарегистрирован. В Delphi имя оконного класса для окон, созданных наследниками TForm, всегда совпадает с именем класса Delphi. Существуют предопределённые классы Win-dows, которые не надо регистрировать. Это 'BUTTON', 'COMBOBOX', 'EDIT', 'LISTBOX', 'MDICLIENT', 'SCROLLBAR' и 'STATIC'. Назначение этих классов понятно из их названий (класс 'STATIC' реализует статические, то есть не реагирующие на мышь и клавиатуру, но имеющие дескриптор элементы, текстовые или графические). Впрочем, можно определить локальный класс с зарезервированным именем, он перекроет глобальный в пределах приложения.

Кроме имени, класс включает в себя другие параметры, такие как стиль, кисть и т. д. Они подробно перечислены в справке по теме WNDCLASS.

Кроме класса нужно указать стиль окна и расширенный стиль. Они определяют поведение конкретного окна и не имеют ничего общего со стилем класса. Возможные значения этих стилей перечислены в справке по функциям CreateWindow и CreateWindowEx. Результатом работы этих функций является дескриптор созданного ими окна.

Функции, создающие окна, требуют указать дескриптор приложения. В Delphi этот дескриптор хранится стразу в двух переменных - MainInstance модуля System и HInstance модуля SysInit. Оба эти модуля автоматически подключаются к любому модулю, созданному в Delphi, так что можно использовать ту или иную переменную по своему вкусу. Кстати, не следует путать авто-матическое подключение этих модулей с автоматической генерацией кода IDE Delphi для под-ключения таких модулей, как Windows, Forms, SysUtils и т. д. В первом случае модули подклю-чаются несмотря на то, что не упомянуты в списке uses. Более того, их упоминание там приве-дёт к ошибке. Во втором случае эти модули явным образом подключаются, просто Delphi автоматически пишет эту часть программы за программиста. Можно написать модуль или даже це-лую программу, которые не будут использовать SysUtils, но нельзя написать такие, которые не будут использовать System.

Создание окон через Win API требует кропотливой работы. VCL Delphi справляется с этой задачей замечательно, поэтому создавать окна самостоятельно приходится только тогда, когда ис-пользование VCL нежелательно, например, если необходимо написать как можно более ком-пактное приложение. Во всех остальных случаях приходится только слегка подправлять работу VCL. Например, с помощью Win API можно изменить форму окна или убрать из него заголо-вок, оставив рамку. Подобные действия не требуют от программиста создания нового окна, можно воспользоваться тем, что уже создано VCL.

Другой случай, когда могут понадобиться функции Win API для окон - если приложение должно что-то делать с чужими окнами. Например, хотя бы просто перечислить все окна, от-крытые в данный момент, как это делает WinSight32. Но в этом случае также не приходится самому создавать окна, работа идёт с уже имеющимися.

Callback функции

Прежде чем двигаться дальше, необходимо разобраться с тем, что такое callback функции. На русский язык это обычно переводится как функции косвенного вызова. Эти функции в программе описываются, но обычно не вызываются напрямую, хотя ничто не запрещает сделать это. В этом они похожи на те методы класса, которые связаны с событиями. Ничто не мешает вызывать напрямую, например, метод FormCreate, но делать это приходится крайне редко. С другой стороны, даже если этот метод не вызывается явно, он всё равно выполняется, потому что VCL автоматически вызывает его без прямого указания программиста. Еще одно общее свойство - конкретное имя метода при косвенном вызове не важно. Можно изменить его, но если этот метод по-прежнему будет связан с событием OnCreate, он так же будет успешно вы-зываться. Разница заключается только в том, что такие методы вызываются внутренними меха-низмами Delphi, а callback функции - самой системой Windows. Соответственно, на эти функции налагаются следующие требования: во-первых, эти функции должны быть именно функциями, а не методами класса (впрочем, иногда это условие удаётся обойти); во-вторых, эти функции должны быть написаны в соответствии с моделью вызова stdcall. Справочная система предла-гает использовать модель callback, которая в имеющихся версиях Windows совпадает с stdcall. Однако в Delphi такая модель не поддерживается. Что же касается того, как программист сообщает системе о том, что он написал callback функцию, то это в каждом случае по-своему.

Очень часто функции косвенного вызова используются при перечислении некоторых объектов. В качестве примера рассмотрим перечисление окон с помощью функции EnumWindows. В справке она описана так:

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);

Соответственно, в Windows.pas она имеет вид

function EnumWindows(lpEnumFunc: TFNWndEnumProc;

lParam: LPARAM): BOOL; stdcall;

тип TFNWndEnumProc совпадает с типом Pointer. Здесь должен стоять указатель на callback функцию. Синтаксис этой функции описан так:

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);

Функции с таким именем не существует в Win API. Это так называемый прототип функции, согласно которому следует описывать callback функцию. На самом деле этот прототип предос-тавляет большую свободу, чем это может показаться на первый взгляд. Как я уже сказал выше, имя может быть любым. Любыми могут быть и типы функции и параметров, при условии что новые типы совпадают по размерам с теми, которые указываются. Что касается типа функции и типа первого параметра, то они имеют определённый смысл и менять их тип практически бес-смысленно. Другое дело со вторым параметром. Он предназначен специально для передачи значения, которое программист волен использовать по своему усмотрению, система не имеет на него никаких видов. А программисту может показаться удобнее работать не с типом LPARAM (то есть LongInt), а, например, с указателем или же с массивом из четырёх байт. Лишь бы были именно четыре байта, а не восемь, шестнадцать или ещё какое-то число. Можно даже превратить этот параметр в параметр-переменную, так как при этом функции будут переда-ваться всё те же четыре байта - адрес переменной. Но эти удовольствия для тех, кто хорошо разбирается с тем, как используется стек для передачи параметров при различных моделях вы-зова.

Как же работает EnumWindows? После вызова эта функция начинает по очереди перебирать все имеющиеся в данный момент окна верхнего уровня, то есть те, у которых нет родителя. Для каждого такого окна вызывается эта самая callback функция, в качестве первого параметра ей передаётся дескриптор данного окна (каждый раз, естественно, новый), в качестве второго - то, что было передано самой функции EnumWindows в качестве второго параметра (каждый раз одно и то же). Что же может делать callback функция с этим дескриптором? А всё, на что у про-граммиста хватит фантазии. Например, можно минимизировать или вообще закрыть все эти окна, хотя не понятно, с чего бы вдруг устраивать такую диверсию. Или можно проверять все эти окна на соответствие какому-то условию, пытаясь найти нужное. А значение, возвращаемое callback функцией, влияет на работу EnumWindows. Если она возвращает False, значит, всё, что нужно, уже сделано, можно не перебирать остальные окна.

Окончательный код для того случая, когда второй параметр имеет тип Pointer, выглядит так:

function MyCallbackFunction(Wnd:HWnd; P: Pointer):Bool; stdcall;

begin

  { что-то делаем }

end;

...

var

  MyPointer: Pointer;

...

EnumWindows(@MyCallbackFunction, LongInt(MyPointer));

Что бы мы ни делали с типом второго параметра callback функции, тип соответствующего па-раметра EnumWindows не меняется. Поэтому необходимо явное приведение передаваемого па-раметра к типу LongInt. Обратное преобразование типов при вызове MyCallbackFunction осуще-ствляется автоматически.

В 16-разрядных версиях Windows вызов callback функций осложнялся тем, что для них необхо-димо было делать специальный код, называемый прологом. Пролог создавался с помощью функции MakeProcInstance, удалялся после завершения с помощью FreeProcInstance. То есть вызов EnumWindows должен был бы выглядеть так:

var

  MyProcInstnace: TFarProc;

...

MyProcInstance := MakeProcInstance(@MyCallbackFunction, HInstance);

EnumWindows(MyProcInstance, LongInt(MyPointer));

FreeProcInstance(MyProcInstance);

В Delphi этот код будет работоспособным, так как для совместимости MyProcInstance и FreePro-cInstance оставлены. Но они ничего не делают (в чём легко убедиться, просмотрев ис-ходный файл Windows.pas), поэтому можно обойтись и без них. Другой способ, с помощью которого в 16-разрядных версиях можно сделать пролог - описать функцию с директивой export. Эта директива сохранена для совместимости и в Delphi, но в 32-разрядных версиях она также ничего не делает (несмотря на то, что справка, например, по Delphi 3.0 утверждает обрат-ное; в справке по Delphi 4.3 этой ошибки уже нет).

Сообщения Windows

Человеку, знакомому с Delphi, должна быть ясна схема событийного управления. Программист пишет только код реакции на какое-либо событие, а дальше программа ждёт, когда система сочтёт, что настало время передать управление этому участку кода. Простые программы в Del-phi состоят исключительно из методов реакции на события вроде OnCreate, onclick, OnClose-Qerry и т. д. Причём событием называется не только событие в обычном смысле этого слова, то есть когда происходит что-то внешнее, но и ситуация, когда событие используется просто для передачи управления основной программе в тех случаях, когда VCL не может сама справиться с какой-то задачей. Примером такого события является, например, TListBox.OnDrawItem. Устанавливая стиль списка в lbOwnerDrawFixed или lbOwnerDrawVariable, программист как бы сообщает VCL, что он не доволен теми средствами рисования элементов списка, которыми она располагает, и что он берёт эту часть задачи на себя. И каждый раз, когда возникает необходимость в рисовании элемента, VCL передаёт управление специально написанному коду. На самом деле разница между двумя типами собы-тий весьма условна. Можно так же сказать, что когда пользователь нажимает клавишу, VCL не знает, что делать, и поэтому передаёт управление обработчику onkeypress.

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

В Delphi для реакции на каждое событие обычно создаётся свой метод. В Windows одна проце-дура, называемая оконной, обрабатывает все сообщения. В языке Си нет понятия <процедура>, поэтому при использовании Паскаля может возникнуть путаница. Дело в том, что то, что называется оконной процедурой, на самом деле является функцией. Тем не менее, я буду использовать общепринятый термин <оконная процедура>. Каждое сообщение имеет свой уни-кальный номер, а оконная процедура обычно целиком состоит из оператора case, и каждому сообщению соответствует своя альтернатива этого оператора. Номера сообщений учить не надо, потому что можно использовать константы, описанные в модуле Messages.dcu. Эти кон-станты начинаются с префикса, указывающего на принадлежность сообщения к какой-то группе. Например, сообщения общего назначения начинаются с WM_: например, WM_Paint, WM_GetTextLength. Сообщения, специфичные, например, для кнопок, начинаются с префикса BM_. Остальные группы сообщений также связаны либо с теми или иными элементами управления, либо со специальными действиями, например, с динамическим обменом данными (dy-namic data exchange, DDE). Обычной программе приходится обрабатывать довольно много сообщений, поэтому оконная процедура бывает, как правило, очень длинной и громоздкой. Оконная процедура описывается программистом как callback функция и указывается при созда-нии оконного класса. Таким образом все окна данного класса имеют одну и ту же оконную процедуру. Впрочем, существует возможность породить так называемый подкласс, то есть новый класс, наследующий все свойства существующего, за исключением оконной процедуры. Несколько подробнее об этом будет сказано далее.

Кроме номера, каждое сообщение содержит два параметра - WParam и LParam. Буквы и означают и , то есть первый параметр 16-разрядный, а второй - 32-разряд-ный. Однако так было только в старых, 16-разрядных версиях Windows. В 32-разрядных вер-сиях оба параметра 32-разрядные, несмотря на их названия. Конкретный смысл каждого пара-метра зависит от сообщения. В некоторых сообщениях один или оба параметра могут вообще не использоваться, в других - наоборот, двух параметров даже не хватает. В этом случае один из параметров (обычно LParam) содержит указатель на дополнительные данные. После обра-ботки сообщения оконная процедура должна вернуть какое-то значение. Обычно это значение просто сигнализирует, что сообщение не нуж-дается в дополнительной обработке, но в некоторых случаях оно более осмысленно, например, WM_SetIcon должно вернуть дескриптор иконки, которая была установлена ранее. Если про-граммист не хочет обрабатывать сообщение самостоятельно, он должен вызвать для его обра-ботки функцию DefWindowProc.

Обработка сообщения требует времени, иногда довольно значительного. За это время окну может быть отправлено ещё несколько сообщений. Чтобы они не пропали, Windows организует так называемую очередь сообщений. Очередь сообщений своя для каждой нити. Нить должна сама выбирать сообщения из этой очереди, транслировать их и затем вызывать функцию Dispatch-Message, чтобы направить это сообщение в нужную оконную процедуру. Всё это лучше не писать самому, а оставить на совести VCL, которая прекрасно с этим справляется. При программировании в Delphi обычно требуется либо нестандартная реакция на сообщение, либо отправка сообщения другому окну.

Отправка сообщения другому окну может осуществляться достаточно разнообразными способами. Можно послать сообщение в очередь, а можно заставить Windows вызвать оконную про-цедуру напрямую, в обход очереди. Можно установить максимальное время ожидания отклика от окна, которому послано сообщение. Все эти функции хорошо описаны в справке (см., на-пример, функцию SendMessage и группу родственных функций).

Кроме параметров WParam и LParam, каждому сообщению приписывается время возникновения и координаты курсора в момент возникновения. Эти параметры можно узнать с помощью функций GetMessagePos и GetMessageTime.

Разумеется, что Delphi предоставляет программисту все средства, необходимые для обработки сообщений. Самый простой способ - описать метод для обработки сообщения с директивой message. Это выглядит примерно так

type

  TSomeForm = class(TForm)

...

procedure WMSomeMessage(var message: TMessage);

message WM_SomeMessage;

...

procedure TSomeForm.WMSomeMessage;

begin

  ...

  inherited

end;

Стандартная оконная процедура в Delphi устроена так, что она ищет среди методов класса специальные методы для обработки каждого сообщения. Эти методы во многом подобны обыкновенным виртуальным методам. Другими словами, если переопределить такой метод, будет вы-зван именно новый, а не старый вариант. Вообще говоря, в классе-родителе метода для обра-ботки какого-то конкретного сообщения может и не существовать. Это, однако, никак не ска-зывается на синтаксисе (в отличие от обычных виртуальных методов, где приходится писать директиву virtual для вновь созданных и override для перекрытых). Кроме того, при перекрытии методов обработки сообщений не важно имя метода, значение имеет только константа, стоящая после message. Именно поэтому при вызове перекрытого метода для обработки данного сообщения достаточно просто написать inherited, без указания имени метода. Такой способ вызова не приведёт к ошибке даже в том случае, если класс-родитель вообще не имел метода для обработки такого сообщения.

Тип TMessage сделан специально для обработки сообщений. Это запись, содержащая 32-раз-рядные целые поля Msg, WParam, LParam и Result. Первое поле содержит номер сообщения, два следующих - параметры сообщения, а полю Result метод должен присвоить то значение, кото-рое потом вернёт системе оконная процедура. Именно из-за необходимости передавать значе-ние параметр метода обработки сообщения должен быть переменной. При обработке сообще-ний часто приходится сталкиваться с ситуациями, когда один 32-разрядный параметр используется для передачи двух 16-разрядных значений. Чтобы облегчить программисту работу в таких случаях, тип TMessage описан как вариантная запись, поэтому в нём есть поля WParamLo, WParamHi, LParamLo, LParamHi, ResultLo и ResultHi, имеющие тип Word и дающие доступ к старшему и младшему словам соответствующего параметра.

Так как параметры WParam и LParam могут иметь совершенно различный смысл для разных сообщений, не всегда удобно представлять их в виде чисел. Иногда предпочтительнее, чтобы они имели тип Pointer, или LongBool, или ещё какой-либо. Поэтому тип TMessage - не единственный тип, который может иметь параметр метода обработки сообщения. Для многих сооб-щений в модуле Messages.dcu описаны собственные типы. Их названия образованы от названия соответствующих сообщений. Например, для сообщения WM_Paint описан тип TWMPaint, для WM_GetText - TWMGetText, и так далее. В этих типах все поля имеют тот тип, который наи-лучшим образом подходит для обработки именно этого сообщения. Кроме того, поля имеют названия, отражающие их назначения, что делает программу более удобной для чтения. Но такие типы описаны не для всех сообщений, поэтому иногда приходится пользоваться универ-сальным TMessage. Кстати, если по каким-то причинам в методе обработки сообщения потре-буется использовать не тот тип, который используется в соответствующем методе класса-роди-теля, никаких проблем не возникнет: в данном случае приведение типов выполняется автома-тически. Узнать, есть ли специальный тип для данного сообщения, можно двумя способами: либо поискать этот тип в Messages.pas, либо просто проверить, <съест> его компилятор или нет.

Сообщения, определяемые пользователем

Использование сообщений очень удобно в тех случаях, когда нужно заставить окно выполнить какое-то действие. Поэтому Windows предоставляет возможность программисту создавать свои сообщения, которые могут быть локальными или глобальными. Использование локальных со-общений связано с некоторым риском. Дело в том, что эти сообщения должны посылаться только <своим> окнам, то есть тем, оконные процедуры которых написаны так, чтобы пра-вильно интерпретировать это сообщение. Если послать такое сообщение <чужому> окну, его реакция может быть непредсказуемой, потому что человек, писавший его оконную процедуру, мог использовать сообщение с этим же номером для своих целей. Всё это вовсе не значит, что обмен локальными сообщениями возможен только внутри одной программы: если разные про-граммы написаны так, что они правильно понимают одно и то же локальное сообщение, они могут без каких-либо ограничений обмениваться им. Немного повторюсь: важно только чтобы отправитель и получатель сообщения одинаково понимали его. В справочной системе специально указывается, что недопустимо отправлять такие сообщения окнам классов 'BUTTON', 'EDIT', 'LISTBOX' и 'COMBOBOX'.

В Windows (и, соответственно, в модуле Messages.dcu) определена специальная константа WM_User, равная $400 (1024). Впрочем, нет гарантии, что в следующих версиях Windows зна-чение этой константы не изменится. Номера стандартных сообщений лежат в диапазоне от 0 до WM_User-1. Для локальных пользовательских сообщений оставлен диапазон от WM_User до $7FFF (32767). Забегая чуть вперёд, скажу, что для глобальных пользовательских сообщений оставлен диапазон от $C000 до $FFFF (от 49152 до 65535).

Глобальные пользовательские сообщения, называемые также строковыми, предназначены специально для тех случаев, когда локальные сообщения оказываются слишком ненадёжными. Например, может потребоваться написать несколько программ, которые взаимодействуют ме-жду собой. Поиск окон, принадлежащих этим программам, можно осуществлять, посылая всем окнам какое-либо специальное сообщение. Те, которые правильно откликнулись - <свои>. Так как сообщения посылаются всем окнам, <чужие> тоже будут его получать. Нужна гарантия, что они никак не отреагируют на такое сообщение. Для этого существует регистрация сообщений, обеспечивающая уникальный номер каждому, кто в нём нуждается.

Прежде чем зарегистрировать сообщение, необходимо придумать ему имя (именно поэтому они называются строковыми). Если давать своим сообщениям осмысленные имена, а не что-то вроде WM_MyMessage1, слишком мала вероятность случайного совпадения. Далее это сообще-ние регистрируется функцией RegisterWindowMessage, которая возвращает уникальный номер этого сообщения. Если сообщение с таким именем регистрируется впервые, номер выбирается из числа ещё не занятых. Если же сообщение с таким именем уже было зарегистрировано, то возвращается тот же самый номер, который был присвоен ему при первой регистрации. Таким образом разные программы, регистрирующие сообщения с одинаковыми именами, получат одинаковые номера и смогут понимать друг друга. Для прочих же окон это сообщение не будет иметь никакого смысла.

Неудобство использования таких сообщений очевидно - их номера определяются только после начала выполнения программы, при компиляции они ещё неизвестны. Поэтому обработка та-ких сообщений описанным ранее методом невозможна - мы не знаем, какой номер писать по-сле слова message. Здесь может помочь виртуальный метод WndProc, имеющийся в классе TControl (и в TForm как в его потомке). Этот метод получает все сообщения, поступающие окну. Если перекрыть этот метод, то ничего не мешает сравнивать внутри него номер пришед-шего и определённого пользователем сообщения. Например, так:

var

  WM_MyUserDemoMessage: Cardinal;

...

procedure TForm1.FormCreate(Sender: TObject);

begin

  WM_MyUserDemoMessage := RegisterWndowMessage('WM_MyUserDemoMessage')

end;

...

procedure TForm1.WndProc(var message: TMessage);

begin

  if message.Msg = WM_MyUserDemoMessage then

  begin

    ...

  end

  else

    inherited WndProc(message)

end;

Метод WndProc <первичнее>, чем методы с директивой message. Он раньше получает сообщения, и он же содержит код, который при необходимости ищет и затем вызывает для каждого сообщения соответствующий метод обработки сообщения. И он же вызывает функцию Win API DefWndProc для стандартной реакции на сообщение. Если при перекрытии не вызывать унасле-дованный метод, то придётся самостоятельно реализовывать эти действия или же подумать, как обойтись без них.

Диапазон номеров сообщений от $8000 (32768) до $BFFF (49151) пока ничем не занят, но зарезервирован Windows для использования в будущем. Авторы Delphi поступили не совсем кор-ректно, использовав верхнюю часть этого диапазона (с адреса $B000 (45046)) для своих собственных сообщений. Именованные константы для этих сообщений находятся в модуле Controls.dcu и начинаются с префикса CM_. Эти сообщения обычно бесполезны для автора готовых программ, но бывают крайне необходимы при написании своих компонентов. Эти сообщения, к сожалению, никак не упомянуты в справке Delphi, поэтому разбираться с ними приходится по исходным файлам VCL.

Особые сообщения

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

Сообщение WM_CopyData используется для передачи блока данных от одного процесса к дру-гому. В 32-разрядных версиях Windows память, выделенная процессу, недоступна для всех ос-тальных процессов. Поэтому просто передать указатель другому процессу нельзя - он не смо-жет получить доступ к этой области памяти. Для сообщения WM_CopyData приходится делать исключение: блок данных временно становится доступным другому процессу. Это требует оп-ределённой синхронности действий от двух процессов, поэтому для отправки этого сообщения можно использовать только SendMessage, прямо вызывающую оконную процедуру. PostMessage использовать нельзя.

Сообщение WM_Paint предназначено для перерисовки клиентской области окна. Если изобра-жение сложное, перерисовка занимает много времени. Чтобы улучшить быстродействие сис-темы, авторы Windows сделали так, что сообщение WM_Paint пропускает все остальные сооб-щения в очереди, и передаётся окну только тогда, когда в очереди не остаётся никаких других сообщений. Если в очереди оказываются несколько сообщений WM_Paint, они объединяются в одно. Просто так послать сообщение WM_Paint невозможно. Для этого надо сначала объявить, что окно или его часть нуждаются в перерисовке (InvalidateRect, InvalidateRgn).

При обработке сообщений от клавиатуры можно использовать функцию GetKeyState, которая возвращает состояние любой клавиши (нажата-отпущена) в момент возникновения данного события. Именно в момент возникновения, а не в момент вызова функции.

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

Так как стандартные средства Delphi не позволяют использовать все инструменты Win API для работы с окнами, иногда приходится писать компоненты, модифицирующие форму. Мне, на-пример, приходилось писать компоненты, при помещении которых на форму она становится непрямоугольной или полупрозрачной. Очень часто таким компонентам приходится обрабаты-вать те сообщения, которые предназначены форме-хозяину. Delphi даёт возможность компо-ненту перехватить сообщения, хотя, на мой взгляд, механизм перехвата оставляет желать луч-шего, потому что он не допускает возможности взаимодействия нескольких перехватчиков.

Вместо общего описания алгоритма перехвата я далее просто приведу один из способов сделать это. Способ этот не единственный верный, многие детали можно модифицировать для нужд конкретной задачи, однако основная идея (и основные недостатки) никуда не денутся. Далее я буду предполагать, что компонент перехватывает сообщения владельца (Owner). Что нужно изменить, чтобы он начал перехватывать сообщения родителя (Parent), я скажу чуть позже.

Прежде всего, владелец должен быть окном, поэтому в конструкторе компонента надо прове-рить класс владельца. Это очень полезно ещё и потому, что тогда в остальных методах объекта можно обойтись без такой проверки, что делает код более эффективным. Чтобы предотвратить создание компонента, достаточно в его конструкторе возбудить какое-либо исключение

Метод компонента не может быть оконной процедурой, потому что методу всегда неявно пере-даётся <лишний> параметр Self. Поэтому нужна генерация специального кода входа и выхода для того, чтобы вызывать метод вместо оконной процедуры. Этот код генерируется с помощью специальной функции Delphi, которая создаёт в памяти нужный код и возвращает на него ука-затель. Поэтому компонент должен иметь указатель на этот код (я условно назову этот указа-тель NewWndProc). Сам метод, обрабатывающий события (условно - HookWndProc) должен иметь один параметр-переменную типа TMessage, и может быть как статическим, тик и вирту-альным или динамическим. Кроме того, нужен указатель на старую процедуру, которая была до установки компонента (OldWndProc). Далее, компонент должен содержать два метода для пере-хвата и освобождения, которые выглядят так:

procedure TMyComponent.HookOwner;

begin

  if Assigned(Owner) then

  begin

    OldWndProc := Pointer(GetWindowLong(TForm(Owner).Handle, GWL_WndProc));

    NewWndProc := MakeObjectInstance(HookWndProc);

    SetWindowLong(TForm(Owner).Handle, GWL_WndProc, LongInt(NewWndProc))

  end

end;

procedure TMyComponent.UnhookOwner;

begin

  if Assigned(Owner) and Assigned(OldWndProc) then

    SetWindowLong(TForm(Owner).Handle, GWL_WndProc, LongInt(OldWndProc));

  if Assigned(NewWndProc) then

    FreeObjectInstance(NewWndProc);

  NewWndProc := nil;

  OldWndProc := nil

end;

Функции Win API GetWindowLong и SetWindowLong предназначены для получения и изменения 32-разрядного значения, связанного с данным окном. В данном случае мы с их помощью рабо-таем с 32-разрядным параметром - адресом оконной процедуры. Изменение адреса оконной процедуры с помощью SetWindowLong и есть то самое порождение оконного подкласса, о ко-тором я писал ранее. Функция MakeObjectInstance - это та самая функция, которая превращает метод в оконную процедуру. FreeObjectInstance освобождает память, выделенную для создания кода входа и выхода функцией MakeObjectInstance.

Было бы глупо перехватывать сообщения и при этом не иметь возможности вызвать ту окон-ную процедуру, которая была до перехвата. Если необходимо вызвать её для обработки сооб-щения Msg с параметрами WParam и LParam, нужно воспользоваться следующим кодом:

CallWindowProc(OldWndProc, TForm(Owner).Handle, Msg, WParam, LParam);

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

Вызов процедуры HookOwner я обычно помещаю в самый конец конструктора компонента, Un-hookOwner - в самое начало деструктора. Но в некоторых ситуациях VCL Delphi уничтожает окно и вновь создаёт его с новыми свойствами. Это происходит очень быстро, пользователь ничего не замечает. (Такое <пересоздание> формы может потребоваться при изменении во время выполнения свойств FormStyle, BorderStyle и BorderIcons.) Однако VCL ничего не знает о перехвате и поэтому не может корректно удалить его, а уж о восстановлении его потом и речи быть не может. Чтобы избежать такой ситуации, необходимо обрабатывать сообщение CM_RecreateWnd: перед вызовом унаследованного метода для обработки этого события компо-нент должен снять перехват, после - восстановить его.

Если форма содержит несколько компонентов, перехватывающих сообщения, могут возникнуть конфликты. Снятие и восстановление перехвата через обработку сообщения CM_RecreateWnd безопасно в этом смысле, потому что компоненты обрабатывают это сообщение в порядке, об-ратном порядку создания. Но если приходится удалять компонент-перехватчик, он не исклю-чает себя из цепочки перехватчиков, а просто обрывает её, и все перехватчики, созданные после него, оказываются не у дел. Именно это я и считаю главным недостатком механизма перехвата.

Если есть необходимость перехватывать сообщения не владельца, а родителя, нужно сделать всё то же самое с точностью до замены Owner на Parent. Но владельца компонент в принципе не может поменять, а вот родителя - вполне. Поэтому нужно ещё перекрыть виртуальный метод SetParent, в котором снимается перехватчик со старого родителя, затем вызывается унаследо-ванный SetParent, затем уже ставится обработчик на нового родителя

Графические функции Win API

Та часть Win API, которая служит для работы с графикой, обычно называется GDI (Graphic De-vice Interface). Ключевым в GDI является понятие контекста устройства (Device Context, DC). Кон-текст устройства - это специфический объект, хранящий информацию о возможностях устрой-ства, о способе работы с ним и о разрешённой для изменения области. В Delphi контекст уст-ройства представлен классом TCanvas, свойство Handle которого содержит дескриптор контек-ста устройства. TCanvas универсален в том смысле, что с его помощью рисование в окне, на принтере или в метафайле выглядит одинаково. То же самое справедливо и для контекста уст-ройства. Разница заключается только в том, как получить в разных случаях дескриптор контек-ста.

Большинство методов класса TCanvas являются <калькой> с соответствующих и, в большинстве случаев, одноимённых функций GDI. Но в некоторых случаях (прежде всего в методах вы-вода текста и рисования многоугольников) параметры методов TCanvas имеют более удобный тип, чем функции GDI. Например, метод TCanvas.Polygon требует в качестве параметра от-крытый массив элементов типа TPoint, а соответствующая функция GDI - указатель на та-кой массив и число элементов в нём. Это означает, что для массива до вызова функции надо выделить память, а потом - освободить её. Ещё нужен код, который заполнит эту область па-мяти нужными значениями. И ни в коем случае нельзя ошибаться в количестве элементов мас-сива. Если зарезервировать память для одного числа точек, а при вызове функции указать дру-гое, программа будет работать неправильно. Но для простых функций работа через GDI ничуть не сложнее, чем через TCanvas.

Для получения дескриптора контекста устройства существует много функций. Только для того, чтобы получить дескриптор контекста обычного окна, существуют три функции: BeginPaint, GetDC, GetWindowDC и GetDCEx. Первая из них может использоваться только при обработке сообщения WM_Paint. Вторая даёт контекст клиентской области окна. Третья позволяет полу-чить контекст всего окна, вместе с неклиентской частью. Последняя же позволяет получить контекст определённой области клиентской части окна.

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

Разумеется, можно вызывать функции GDI при работе через TCanvas. Для этого им просто надо передать в качестве дескриптора контекста Canvas.Handle. Коротко пере-числю те возможности GDI, которые разработчики Delphi почему-то не сочли нужным включать в TCanvas: установка прозрачного фона у текста без изменения кисти; рисование кри-вых Безье; работа с регионами; выравнивание текста по любому углу или по центру; установка собственной координатной системы; получение детальной информации об устройстве; исполь-зование геометрических карандашей; вывод текста под углом к горизонтали.

Использование кистей, карандашей и шрифтов в GDI принципиально отличается от того, что привычно в Delphi. Класс TCanvas имеет свойства Brush, Pen и Font, изменение атрибутов которых приводит к выбору того или иного карандаша, шрифта, кисти. В GDI эти объекты самостоятельны, должны создаваться, получать свой дескриптор, <выбираться> в нужный кон-текст устройства с помощью функции SelectObject и уничтожаться после использования. При-чём удалять можно только те объекты, которые не выбраны ни в одном контексте. Есть также несколько стандартных объектов, которые не надо ни создавать, ни удалять. Их дескрипторы можно получить с помощью функции GetStockObject. Чтобы продемонстрировать это, приведу фрагмент программы, рисующей на контексте с дескриптором DC две линии - синюю и крас-ную. В этом фрагменте используется то, что функция SelectObject возвращает дескриптор объ-екта, родственного выбираемому, который был выбран ранее. Так, при выборе нового карандаша она вернёт дескриптор того карандаша, который был выбран до этого.

SelectObject(DC, CreatePen(PS_Solid, 1, RGB(255, 0, 0)));

MoveToEx(DC, 100, 100, nil);

LineTo(DC, 200, 200);

DeleteObject(SelectObject(DC, CreatePen(PS_Solid, 1, RGB(0, 0, 255))));

MoveToEx(DC, 200, 100, nil);

LineTo(DC, 100, 200);

DeleteObject(SelectObject(DC, GetStockObject(Black_Pen)));

Особым образом следует работать через GDI с растровыми изображениями. Эта тема на-столько сложна, что в таком кратком обзоре не стоит и начинать её. Скажу только, что при ис-пользовании 24-битных изображений лучше не комбинировать Delphi и GDI. Если передать TBitmap.Handle какой-нибудь функции GDII, у этой картинки иногда портятся последние несколько байт. Так как строки в растровом изображении располагаются снизу вверх, то это приводит к порче правого верхнего угла рисунка. Такой глюк я наблюдал в Delphi 3.0, про ос-тальные версии Delphi ничего сказать не могу.

При переходе на 32-разрядную версию Windows многие функции были исключены из GDI и заменены новыми. Список устаревших функций и соответствующих им новых можно найти в справке в разделе 'Graphics Functions'.

Ещё одно отличие от 16-разрядных версий заключается в том, что ранее дескрипторы графических объектов были глобальными, то есть объект, созданный одной программой, можно было использовать в другой, если эти программы могли передавать друг другу дескрипторы. В 32-разрядных версиях дескрипторы объектов, созданные одним процессом, не имеют смысла для другого.

Существует одна проблема при работе с метафайлами в Windows 95 (возможно, эта же проблема есть в Windows 98 и NT, но я не проверял). Метафайл создаётся с помощью функции Cre-ateEnhMetaFile. Она возвращает дескриптор контекста метафайла, который можно использовать для рисования. Затем вызывается CloseEnhMetaFile, закрывающая метафайл для рисования, освобождающая контекст устройства и возвращающая дескриптор метафайла. После использования метафайл удаляется функцией DeleteEnhMetaFile, которая освобождает память, связанную с метафайлом, и его дескриптор. Одна из функций, освобождающих дескрипторы, работает неправильно, и дескриптор не освобождается. Если программе часто приходится создавать и уничтожать метафайлы, это быстро приводит к тому, что все дескрипторы оказываются заняты, и система перестаёт работать корректно. Бороться с этим, пользуясь классами TMetafile и TMetafileCanvas, нельзя, потому что они работают через эти же функции.

Работа со строками в Win API

Функции Win API не поддерживают тип string, принятый в Delphi. Они работают со строками, оканчивающимися на #0 (нуль-терминированные строки, null-terminated strings). Это означает, что строкой называется указатель на цепочку символов. Признаком конца такой цепочки явля-ется символ #0. Раньше для таких строк использовали термин ASCIIZ. ASCII - обозначение кодировки, Z - zero. Сейчас кодировка ASCII заменена на ANSI, поэтому этот термин больше не применяется, хотя это те же самые по своей сути строки. Обычно программисту приходится работать с кодировкой ANSI, но это не единственная кодировка, поддерживаемая Windows.

В Delphi определён тип PChar, содержащий указатель на такую строку. Если один из параметров функции Win API имеет такой тип, то можно либо передать ему строковую константу, заклю-чённую в одинарные кавычки, как если бы это был тип string, либо выражение PChar(S), где S - параметр типа string, возможно, сложное выражение. Ещё один способ - воспользоваться функ-циями модуля SysUtils.dcu для работы с нуль-терминированными строками и самостоятельно сформировать строку типа PChar. При этом надо будет самостоятельно выделять и освобождать память для цепочки символов, что обычно приводит только к лишним проблемам. Обычно го-раздо проще работать с типом string, и лишь при вызове соответствующей функции преобразо-вать его к типу PChar. Для любителей оптимизации кода замечу, что такое преобразование не расходует ни память, ни процессорное время, потому что тип string - сам по себе указатель, он указывает именно на строку, завершающуюся нулём, а дополнительная информация, специфическая для типа string, имеет отрицательное смещение относительно этого указателя. Поэтому выражение PChar(S) не приводит к генерации кода, а лишь разрешает компилятору использовать этот указатель в качестве PChar.

Получить строку от функции Win API несколько сложнее, чем передать её. Обычно это делается в несколько этапов. Сначала с помощью функций Win API выясняется, какова длина строки. Затем резервируется место для неё. А только затем вызывается та функция, которая копирует строку в приготовленный буфер. Например, для получения заголовка окна нужно использовать функции GetWindowTextLength и GetWindowText. В некоторых случаях можно облегчить себе жизнь, если существует ограничение на максимальную длину строки. Например, атом не может быть длиннее 255-ти символов. Поэтому можно выделить буфер размером 256 символов (один - для завершающего нуля), и сразу копировать туда атом. В любом случае полученная строка будет нуль-терминированной. Чтобы преобразовать её к обычной, используйте функцию StrPas. Или же можно просто выполнить присвоение S := P, где S - типа string, P - PChar.

Другой тип кодировки, поддерживаемый в Windows, называется Wide. В отличие от ANSI в нём для представления одного символа используется не один, а два байта. Все функции, работающие со строками, написаны в двух модификациях - для ANSI и для Wide. Например, если посмотреть модуль user32, в котором, как утверждает справка, описана функция GetWindowText, то видно, что там нет такой функции. Там есть две другие функции - GetWindowTextA и GetWindowTextW, работающие каждая с соответствующей кодировкой. И это относится ко всем функциям, работающим со строками. К тому имени функции, которое указано в справке, необходимо добавить 'A' или 'W', в зависимости от используемой кодировки.

Разработчики Delphi при написании Windows.pas использовали маленькую хитрость, помогающую начинающему программисту не запутаться. Вот, например, цитата из этого модуля:

function GetWindowTextA(hWnd: HWND; lpString: PAnsiChar;

nMaxCount: Integer): Integer; stdcall;

function GetWindowTextW(hWnd: HWND; lpString: PWideChar;

nMaxCount: Integer): Integer; stdcall;

function GetWindowText(hWnd: HWND; lpString: PChar;

nMaxCount: Integer): Integer; stdcall;

{ Это написано в интерфейсной части модуля }

...

function GetWindowTextA; external user32 name 'GetWindowTextA';

function GetWindowTextW; external user32 name 'GetWindowTextW';

function GetWindowText; external user32 name 'GetWindowTextA';

{ А это - в разделе реализации }

Видно, что функция GetWindowTextA импортируется дважды - один раз под своим настоящим именем, а второй раз - под именем GetWindowText (это и есть тот случай, когда имя функции в библиотеке и то имя, под которым она становится известна компилятору, не совпадают). Поэтому программисту в Delphi нет разницы, писать или , потому что единственное различие у них - тип параметра lpString. Но из исходного текста всё того же модуля видно, что это на самом деле один и тот же тип. По такой же схеме импортируются и все остальные строковые функции Win API.

Заключение

Функции Win API - не такая уж сложная штука. Они часто используют идеологию, не похожую ни на какую другую, но и с этим легко разобраться. Проблема только в том, где и как получить по ним информацию. Будем откровенны: в нашей стране далеко не все, мягко говоря, используют честно купленные программные продукты. Лицензионный Windows сейчас не в диковинку только потому, что его часто устанавливают на новые компьютеры. Лицензионный Delphi приобретают некоторые фирмы. Но много ли людей в России может похвастаться, что они видели документацию по Win API фирмы Microsoft? А эту документацию на русском языке? А ведь авторы западных книг по программированию обычно предполагают, что читателю есть куда заглянуть для справки по этим функциям, и поэтому особенно их не разбирают. Так что нашему программисту доступны следующие пути: по крупицам вытаскивать информацию из тех книг, где Win API упоминается; читать Win32 Develpoer's References; изучать исходные файлы RTL и VCL Delphi; искать информацию в интернете (могу посоветовать сайт http://delphi.vitpc.com). Всё. Если человек не готов часами и даже днями искать информацию о нужной функции, лучше ему не становиться программистом. Главная цель этой статьи - облегчить начало этого поиска. Но дальше человек должен идти сам.

Ссылка на комментарий
Поделиться на другие сайты

В геймхакинге применение событий определить можно  - это очень большее "поле" для раздумий. Например в терйнере можно перехватывать событие закрытия окна процесса игры и предпринять в это случае определённые действия. Особое внимание можно уделить событиям мышки и нажатиям клавиш на клавиатуре, т.к. это связано с действиями в игре, а если более конкретно связано с данными по адресам в игре. Перехватывая и инициализируя события мышки и клавиатуры можно что-то интересное сделать. Например, если по адресу такому-то такое-то значение, а событие от клавиатуры(мышки) такое то запустить такой-то чит. Короче говоря можно многое что придумать + даже придумать применении в инструментах по гемхакингу...

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

Тема События в Дельфи.

Ниже будут темы которые на мой взгляд неплохо знать, т.е знать о их существовании, а потом лазить по справкам если что. События вещь очень полезная. Представьте что у вас есть доступ ко всей очереди событий происходящий в Windows? Это и нажатие кнопок в каких-то окнах, открытие окон/закрытие и т.п. + даже свои события можно создавать (там ограниченное количество). Ваша программа может обрабатывать эти события вплоть, выбрасывать из очереди сообщений и добавлении свои события...

Автор: Briculski

Тема: Добавление события на примере onmouseleave 

Все потомки TComponent могут посылать сообщения CM_MOUSEENTER и CM_MOUSELEAVE во время вхождения и покидания курсора мыши области компонента. Если вам необходимо, чтобы ваши компоненты обладали реакцией на эти события, необходио написать для них соответствующие обработчики. 

procedure CMMouseEnter(var msg:TMessage); message CM_MOUSEENTER;

procedure CMMouseLeave(var msg: TMessage); message CM_MOUSELEAVE;

..

..

..

procedure MyComponent.CMMouseEnter(var msg:TMessage);

begin

inherited;

{действия на вход мыши в область компонента}

end;

procedure MyComponent.CMMouseLeave(var msg: TMessage);

begin

inherited;

{действия на покидание мыши области компонента}

end;

Дополнение

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

MouseEnter - когда событие мыши входит в пределы визуального компонента;

MouseLeave - когда событие мыши оставляет его пределы.

Известно, что все Delphi объявляет эти сообщения в виде:

Cm_MouseEnter;

Cm_MouseLeave.

Т.е. все визуальные компоненты, которые порождены от TControl, могут отлавливать эти события. Следующий пример показывает как создать наследника от TLabel и добавить два необходимых события onmouseleave и onmouseenter.

(*///////////////////////////////////////////////////////*)

(*// Author: Briculski Serge

(*// E-Mail: bserge@airport.md

(*// Date: 26 Apr 2000

(*///////////////////////////////////////////////////////*)

unit BS_Label;

interface

uses

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

  StdCtrls;

type

  TBS_Label = class(TLabel)

  private

    { Private declarations }

    Fonmouseleave: TNotifyEvent;

    Fonmouseenter: TNotifyEvent;

    procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;

    procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;

  protected

    { Protected declarations }

  public

    { Public declarations }

  published

    { Published declarations }

    property onmouseleave: TNotifyEvent read Fonmouseleave write Fonmouseleave;

    property onmouseenter: TNotifyEvent read Fonmouseenter write Fonmouseenter;

  end;

procedure Register;

implementation

procedure Register;

begin

  RegisterComponents('Custom', [TBS_Label]);

end;

{ TBS_Label }

procedure TBS_Label.CMMouseEnter(var Message: TMessage);

begin

  if Assigned(Fonmouseenter) then

    Fonmouseenter(Self);

end;

procedure TBS_Label.CMMouseLeave(var Message: TMessage);

begin

  if Assigned(Fonmouseleave) then

    Fonmouseleave(Self);

end;

end.

Тема: "Великолепный метод Perform"

Автор: Михаил Христосенко

WEB сайт: http://mihandelphi.narod.ru

 В этой статье я постараюсь показать что можно делать с помощью метода Perform, и какие интересные вещи скрываются в VCL кодах (в частности messages.pas). Метод Perform дает вам возможность посылать сообщения различным компонентам. Все сообщения описаны в файле Messages.pas (настоятельно рекомендую вам его посмотреть!!!). Данный метод надо вызывать по такой схеме: 

Имя_компонента.Perform(Сообщение, верхний параметр: Integer, нижний параметр: Integer);

Начнем с самого простого. Попробуем закрыть форму. Для этого поставьте на форму одну кнопку и в обработчике ее события onclick напишите:

Form1.Perform(WM_CLOSE, 0, 0);

Теперь попробуем изменить иконку вашей программы. Поставьте на форму компонент Image и загрузите в него какую-нибудь иконку. Будем использовать сообщение WM_SETICON. А поскольку в качестве параметров необходима величина типа Integer, то мы воспользуемся указателем на иконку (handle). Теперь обработчик нажатия кнопки может иметь вид:

procedure TForm1.Button1Click(Sender: TObject);

begin

  Form1.Perform(WM_SETICON, 0, image1.Picture.Icon.Handle);

end;

Теперь попробуем осуществить программный клик по кнопке 1. Поставьте на форму еще одну кнопку и в ее обработчике события onclick, напишите:

Button1.Perform(WM_LBUTTONDOWN, 0, 0);

Button1.Perform(WM_LBUTTONUP, 0, 0);

А можно и попроще реализовать:

Button1.Perform(BM_CLICK, 0, 0);

Объясняю почему два обработчика. Вначале посылается сообщение, что кнопка нажата, затем сообщение - что отпущена, тогда то и происходит клик. Если вы все сделали правильно и ничего с формы не убирали, то иконка вашего приложения вновь поменяется.

Теперь маленько коснемся компонентов для работы с текстом. Для начала установите на форму компонент Memo. Будем делать с ним разные стандартные вещи: добавлять символы, копировать, вставлять, вырезать, отменять и т.д.

Начнем с вырезания текста. Обработчик кнопки поменяйте на:

Memo1.SelectAll;

Memo1.Perform(WM_CUT, 0, 0);

С начала выделяется весь текст, а потом вырезается и помещается в буфер. Также можно и копировать текст, только надо изменить сообщение на: WM_COPY. Соответственно, чтобы вставить текст из буфера напишите:

Memo1.Perform(WM_PASTE, 0, 0);

Для очистки содержимого Memo, воспользуйтесь сообщением WM_CLEAR с параметрами 0,0. Для того, чтобы отменить введенный текст напишите следующее:

Memo1.Perform(EM_UNDO, 0, 0);

Чтобы добавить символ в Memo нужно написать так:

Memo1.Perform(WM_CHAR, 192, 0);

где 192, номер символа 'A', этот вызов метода Perform, можно заменить на аналогичный:

Memo1.Perform(WM_CHAR, LongInt(char('A')), 0);

Здесь значение символа 'A' как тип Char преобразуется в тип LongInt, а затем добавляется в Memo.

Теперь будем разбираться с Listbox' ами. Для этого добавьте его на форму а событие onclick кнопки замените на:

Listbox1.Perform(LB_ADDSTRING, 0, LongInt(Pchar('Эта строка появится в ListBoxe')));

Если заменить LB_ADDSTRING на LB_INSERTSTRING, то строка будет вставляться в зависимости от первого параметра, который равен 0 в первом случае.

Для того, чтобы выделить какую-нибудь строку в Listbox'e зная ее имя нужно написать следующий код:

Listbox1.Perform(LB_SELECTSTRING, 0, LongInt(pchar('текст строки, которую нужно найти')));

С помощью приведенной выше строчки кода можно реализовать поиск в ListBox'e, наподобие того, как это делается в FontDialog. В текстовом поле ввода вы вводите текст, и наиболее похожий по шрифт выделяется. Чтобы это осуществить поставьте на форму компонент Edit. А в обработчике его события onchange напишите:

Listbox1.Perform(LB_SELECTSTRING, 0, LongInt(pchar(Edit1.Text)));

Ну вот кратенький обзор метода Perform подошел к концу. Хочется посоветовать только одного, смотрите файл Messages.pas, ищите новые решения и не бойтесь пробовать, а вдруг сработает!!!

Тема: "События в Delphi" (из статей Введение в DELPHI)

Содержание:

Обзор

События в Delphi

Понимание событий

Обработка сообщений Windows в Delphi

Обзор

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

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

Delphi предоставляет полный доступ к подструктуре событий, предоставляемой Windows. С другой стороны, Delphi упрощает программирование обработчиков таких событий.

В данном уроке приводится несколько примеров того, как обрабатывать события в Delphi, дается более детальное объяснение работы системы, ориентированной на события.

События в Delphi

Объекты из библиотеки визуальных компонент (VCL) Delphi, равно как и объекты реального мира, имеют свой набор свойств и свое поведение - набор откликов на события, происходящие с ними. Список событий для данного объекта, на которые он реагирует, можно посмотреть, например, в Инспекторе Объектов на странице событий. (На самом деле, на этой странице представлен список свойств, которые имеют тип вроде TMouseMoveEvent и представляют из себя процедуры-обработчики событий. Существует соглашение по названиям данных свойств. Например, ondblclick соответствует двойному щелчку мыши, а onkeyup - событию, когда нажатая клавиша была отпущена.) Среди набора событий для различных объектов из VCL есть как события, портируемые из Windows (MouseMove, KeyDown), так и события, порождаемые непосредственно в программе (DataChange для TDataSource).

Поведение объекта определяется тем, какие обработчики и для каких событий он имеет. Создание приложения в Delphi состоит из настройки свойств используемых объектов и создания обработчиков событий.

Простейшие события, на которые иногда нужно реагировать - это, например, события, связанные с мышкой (они есть практически у всех видимых объектов) или событие Click для кнопки TButton. Предположим, что вы хотите перехватить щелчок левой кнопки мыши на форме. Чтобы сделать это - создайте новый проект, в Инспекторе Объектов выберите страницу событий и сделайте двойной щелчок на правой части для свойства onclick. Вы получите заготовку для обработчика данного события:

procedure TForm1.FormClick(Sender: TObject);

begin

end;

Напишите здесь следующее:

procedure TForm1.FormClick(Sender: TObject);

begin

  MessageDlg('Hello', mtInformation, [mbOk], 0);

end;

Каждый раз, когда делается щелчок левой кнопки мыши над формой будет появляться окно диалога (см. рис.1).

post-3-1279435900,99_thumb.gif

Рис.1: Диалог, появляющийся при щелчке мыши на форме.

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

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

procedure TForm1.FormMouseDown(Sender: TObject;

                               Button: TMouseButton;

                               Shift: TShiftState;

                               X, Y: Integer);

begin

  Canvas.TextOut(X, Y, 'X='+IntToStr(X)+' Y='+IntToStr(Y));

end;

Запустите программу, пощелкайте мышкой на форме:

post-3-1279435937,1_thumb.gif

Рис.2

Как видите, в Delphi очень просто отвечать на события. И не только на события, связанные с мышкой. Например, можно создать обработчик для onkeydown (нажата клавиша):

procedure TForm1.FormKeyDown(Sender: TObject;

                             var Key: Word;

                        Shift: TShiftState);

begin

  MessageDlg(Chr(Key), mtInformation, [mbOk], 0);

end;

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

Понимание событий

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

begin

  InitializeMemory;

  repeat

    CheckForMouseEvent(Events);

    CheckForKeyPress(Events)

    HandleEvents(Events);

  until Done := True;

  DisposeMemory;

end.

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

Переменная Events может быть записью с простой структурой:

TEvent = record

  X, Y: Integer;

  MouseButton: TButton;

  Key: Word;

end;

Тип TButton, указанный выше, можно декларировать так:

TButton = (lButton, rButton);

Эти структуры позволяют вам проследить, где находится мышь, каково состояние ее кнопок, и значение нажатой клавиши на клавиатуре. Конечно, это пример очень простой структуры, но заложенные здесь принципы отражают то, что происходит внутри Windows или внутри других систем, ориентированных на события, вроде Turbo Vision. Если бы программа, приведенная выше, была редактором текста, то обработчик HandleEvent для такой программы мог бы иметь вид:

procedure HandleEvent(Events: TEvent);

begin

  case Events.Key of

   'A'..'z': Write(Events.Key);

    EnterKey: Write(CarriageReturn);

    EscapeKey: Done := True;

  end;

end;

Согласно коду выше, программа будет печатать букву 'a' при нажатии этой клавиши и перейдет на новую строку, если нажата клавиша 'Enter'. Нажатие 'Esc' приведет к завершению программы.

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

Надеюсь, что приведенный пример дает некоторое понимание работы ориентированной на события системы. Единственное, что осталось пропущенным - почему система Windows так спроектирована.

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

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

Когда пользователь щелкает мышкой, операционная система обрабатывает это событие и передает его в окно, которое должно обработать данное событие. Созданное сообщение, в этом случае, пересылается в некую процедуру DefWindowProc окна (default window procedure). DefWindowProc - аналог процедуры HandleEvent из примера, приведенного выше.

Каждое окно в Windows имеет свою DefWindowProc. Чтобы полностью понять данное утверждение, представьте, что каждая кнопка, каждый ListBox, каждое поле ввода и т.д. на самом деле являются окнами и имеют свою процедуру DefWindowProc. Это очень гибкая и мощная система, но она может заставить программиста писать очень сложный код. Delphi дает возможность быть защищенным от такой структуры программы.

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

Если посмотреть на DefWindowProc в справочнике по Windows API, то можно увидеть следующее определение:

function DefWindowProc(Wnd: HWnd; Msg, wParam: Word;

                       lParam: LongInt);

Каждое сообщение, посылаемое в окно, состоит из четырех частей: первая часть - handle окна, получающего сообщение, Msg сообщает, что произошло а третья и четвертая части (wParam и lParam) содержат дополнительную информацию о событии. Вместе эти четыре части являются аналогом показанной выше структуры TEvent.

Вторая часть сообщения имеет длину 16 бит и сообщает, что за событие произошло. Например, если нажата левая кнопка на мыши, то переменная Msg содержит значение WM_LBUTTONDOWN. Существуют десятки различного типа cообщений и они называются вроде WM_GETTEXT, WM_HSCROLL, WM_GETTEXTLENGTH и т.п. Список всех сообщений можно видеть в справочнике по Windows API (on-line help).

Последние две переменные, длиной 16 и 32 бита, называются wParam и lParam. Они сообщают программисту важную дополнительную информацию о каждом событии. Например при нажатии кнопки мыши, lParam содержит координаты указателя мыши.

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

procedure TForm1.FormMouseDown(Sender: TObject;

                               Button: TMouseButton;

                               Shift: TShiftState;

                               X, Y: Integer);

Итак, если подвести итог, то должно стать ясным следующее:

Windows является системой ориентированной на события;

События в Windows принимают форму сообщений;

В недрах VCL Delphi сообщения Windows обрабатываются и преобразуются в более простую для программиста форму;

Обработка событий в Delphi сводится к написанию для каждого объекта своих обработчиков;

События в программе на Delphi вызываются не только сообщениями Windows, но и внутренними процессами.

Обработка сообщений Windows в Delphi

Конечно, нельзя придумать такую библиотеку объектов, которые бы полностью соответствовали потребностям программистов. Всегда возникнет необходимость дополнения или изменения свойств и поведения объектов. В этом случае, так же, как и при создании своих собственных компонент в Delphi, часто требуется обрабатывать сообщения Windows. Поскольку Object Pascal является развитием и продолжением Borland Pascal 7.0, то это выполняется сходным с BP способом.

Общий синтаксис для декларации обработчика сообщений Windows:

procedure Handler_Name(var Msg : MessageType);

message WM_XXXXX;

Handler_Name обозначает имя метода; Msg - имя передаваемого параметра; MessageType - какой либо тип записи, подходящий для данного сообщения; директива message указывает, что данный метод является обработчиком сообщения; WM_XXXXX - константа или выражение, которое определяет номер обрабатываемого сообщения Windows.

Рассмотрим обработку сообщений на примере. Например, при нажатии правой кнопки мыши на форме в программе появляется всплывающее меню (pop-up menu, если оно было привязано к этой форме). Программист может захотеть привязать к правой кнопке какое-нибудь другое событие. Это можно сделать так:

type

   TForm1 = class(TForm)

        PopupMenu1: TPopupMenu;

        MenuItem1: TMenuItem;

        MenuItem2: TMenuItem;

        MenuItem3: TMenuItem;

   private

     { Private declarations }

        procedure WMRButtonDown(var Msg : TWMMouse); message

WM_RBUTTONDOWN;

   public

     { Public declarations }

   end;

Подчеркнут код, добавленный в декларацию объекта TForm1 вручную. Далее, в секции implementation нужно написать обработчик:

procedure TForm1.WMRButtonDown(var Msg : TWMMouse);

begin

  MessageDlg('Right mouse button click.', mtInformation,

    [mbOK], 0);

end;

В данном случае при нажатии правой кнопки мыши будет появляться диалог.

Вообще-то, у класса TForm уже есть унаследованный от дальнего предка обработчик данного события, который называется точно также и вызывает то самое pop-up меню. Если в новом обработчике сообщения нужно выполнить действия, которые производились в старом, то для этого применяется ключевое слово inherited. Если слегка модифицировать наш обработчик, то после диалога будет появляться pop-up меню:

procedure TForm1.WMRButtonDown(var Msg : TWMMouse);

begin

  MessageDlg('Right mouse button click.', mtInformation,

    [mbOK], 0);

  inherited;

end;

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

procedure TForm1.FormCreate(Sender: TObject);

begin

  Application.OnMessage:=AOM;

end;

procedure TForm1.AOM(var Msg: TMsg; var Handled: Boolean);

begin

   Handled:=False;

   if Msg.Message = WM_LBUTTONDBLCLK then begin

     MessageDlg('Double click.', mtInformation, [mbOK], 0);

     Handled:=True;

   end;

end;

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

Если найду ещё статьи по событиям, то добавлю. Рихтера приводить не буду, кто желает - тогда сразу к нему книга "Windows для профессионалов"

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.