Е. К. Пугачев Объектно-ориентированное программирование Под общей редакцией Ивановой Г. С. Рекомендовано Министерством общего и профессионального образования Российской Федерации в качестве учебник

Вид материалаУчебник

Содержание


5.7.Создание и обработка сообщений и событий
Type TMessage=record
Const Mes1 = WM_USER
Генерация сообщения.
WParam, LParam:LongInt):LongInt
Пример 5.78.
Message wm_mymessage
Form2.Perform (WM_MYMESSAGE,Longint(MessageEdit.Text),0)
Пример 5.79.
Message wm_mymessage
Управление циклом обработки сообщений.
Пример 5.80.
ProgressBar.Parent := Self
Button.Left:=60; Button.Top:=65
Подобный материал:
1   ...   31   32   33   34   35   36   37   38   39
^

5.7.Создание и обработка сообщений и событий


Стандартная библиотека классов Delphi VCL предлагает разработчику достаточно большой набор сообщений и методов их разработки. Однако он имеет возможность добавить новое сообщение или переопределить методы обработки существующих сообщений.

При создании нового сообщения необходимо выполнить следующие действия:
  1. описать тип сообщения;
  2. объявить номер (или индекс) сообщения;
  3. объявить метод обработки нового сообщения в классе, который должен его обрабатывать;
  4. инициализировать (передать) сообщение.

Сообщения Delphi. В Delphi определено около 100 стандартных типов сообщений. В соответствии с правилами Windows сообщение состоит из нескольких полей. Первое поле обычно называется Msg. Оно должно содержать индекс сообщения - 16 разрядное целое положительное число (тип Cardinal). Далее следуют поля, содержащие передаваемые значения. Последние поля обычно используются для записи результата обработки сообщения. Они могут отсутствовать.

Например, основной тип сообщений, используемых в Delphi, определяется следующим образом:

^ Type TMessage=record

Msg:Cardinal;

case Integer of

0: (WParam:LongInt; LParam:LongInt; Result:LongInt);

1: (WParamLo:Word; WParamHi:Word;

LParamLo:Word; LParamHi:Word;

ResultLo:Word; ResultHi:Word);

end;

end;

Номер сообщения. Номер (или индекс) сообщения используется для идентификации сообщения в системе: он определяет вид события, о котором система уведомляет приложение (нажатие клавиш, нажатие кнопок мыши и т.д.).

При создании собственных сообщений следует учитывать, что номера с 0 до $399 зарезервированы за системой. Первый свободный номер обозначен константой WM_USER = $400, относительно которой обычно и определяются номера пользовательских сообщений:

^ Const Mes1 = WM_USER;

Mes2 = WM_USER+1;

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

Метод обработки сообщения по умолчанию является динамическим, причем спецификаторы dinamic или override при его описании опускаются:

Procedure wm<имя метода>(var Message:<тип сообщения>);

message <номер сообщения>;

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

Добавление метода обработки сообщений к классу выполняется так же, как и любого другого метода:

type <имя класса>= class <имя класса-родителя>

public

Procedure wm<имя метода>(var Message:<тип сообщения>);

message <номер сообщения>;

. . .

end;

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

procedure <имя класса>. wm<имя метода>;

begin

<специальная обработка>

inherited;

end;

В том случае, если у объекта для некоторого сообщения не определен соответствующий обработчик, то, в соответствии с правилами подключения динамических методов, проверяются таблицы динамических методов базовых классов. Если обработчик некоторого сообщения не определен и в базовых классах, то производится вызов метода DefaultHandler класса TObject, который обеспечивает «обработку по умолчанию» для этих сообщений.

^ Генерация сообщения. Для передачи сообщений объектам Delphi может использоваться несколько способов.

1. Для передачи сообщения оконному элементу управления через очередь сообщений с ожиданием завершения его обработки используется функция:

function SendMessage (hWnd:Integer, Mes:Cardinal;

^ WParam, LParam:LongInt):LongInt;

Она возвращает результат обработки сообщения. Параметр hWnd определяет номер, под которым окно - адресат сообщения - зарегистрировано в Windows (дескриптор окна). Для каждого оконного элемента управления этот номер хранится в свойстве Handle, определенном в классе TWinControl.

2. Для передачи сообщения оконному элементу управления через очередь сообщений без ожидания завершения его обработки используется функция:

function PostMessage(hWnd:Integer,Mes:Cardinal;

WParam,Param:LongInt):LongBool;

Список параметров функции совпадает со списком SendMessage, но в отличие от SendMessage PostMessage ставит сообщение в очередь сообщений и возвращает управление, не ожидая завершения обработки сообщения. Функция возвращает True, если сообщение поставлено в очередь, и False - в противном случае.

3. Для передачи сообщения элементу управления минуя очередь используется специальный метод этого элемента, определенный в классе TСontrol:

procedure Perform (Mes:Cardinal; WParam, LParam:LongInt);

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

^ Пример 5.78. Передача/прием сообщения

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



Рис. 5.87. Вид окон приложения «Передача/прием сообщений»

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

Прежде всего, в интерфейсной части модуля Unit1 определим тип нового сообщения и константу, определяющую его номер. Далее в классе TForm1 зарезервируем поле для хранения дескриптора второй формы и организуем пересылку сообщения в очередь по нажатию кнопки SendButton:

Unit Unit1;

Interface

Type MyMessage=Record

Msg:Cardinal; {номер сообщения}

PString:PChar; {адрес строки сообщения}

Result:LongInt; {поле для записи результата обработки}

End;

Const WM_MYMESSAGE=WM_USER; {первый номер из диапазона

пользовательских номеров}

Type

TForm1 = class(TForm)

. . .

public SecondHandle:Integer; {переменная для хранения дескриптора второй формы}

end;


Implementation

Procedure TForm1. SendButtonClick(Sender: TObject);

Begin

SendMessage(SecondHandle,WM_MYMESSAGE,

Longint(MessageEdit.Text),0);{генерация и пересылка сообщения}

. . .

End; . . .

Вторая форма, должна принимать и обрабатывать новое сообщение. Соответственно, класс TForm2 должен включать метод обработки этого сообщения. Кроме этого, при создании формы ее дескриптор должен запоминаться в первой форме:

Unit Unit2;

Interface

Uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Unit1{содержит описание типа сообщения};

Type TForm2 = class(TForm)

MessageEdit: TEdit;

procedure FormCreate(Sender: TObject);

public

Procedure WMMyMessage(var Msg:MyMessage);

^ MESSAGE WM_MYMESSAGE;{метод обработки сообщения}

end;

Var Form2: TForm2;

Implementation

{$R *.DFM}

Procedure TForm2.FormCreate(Sender: TObject);

Begin

Form1.SecondHandle:=Handle; {запомнить дескриптор окна второй формы}

End;

Procedure TForm2.WMMyMessage(var Msg:MyMessage);

Begin

MessageEdit.Text:=Msg.PString; {вывести сообщение}

End;

End.

Аналогично для передачи сообщения можно было бы использовать функцию PostMessage:

PostMessage(SecondHandle,WM_MYMESSAGE,

Longint(MessageEdit.Text),0);

Если с той же целью использовать метод Perform, то необходимо

1) в секции реализации модуля Unit1 разрешить использование модуля Unit2:

Uses Unit2;

2) для передачи сообщения использовать метод объекта Form2:

^ Form2.Perform (WM_MYMESSAGE,Longint(MessageEdit.Text),0);

При этом поле для хранения дескриптора в TForm1 становится ненужным.

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

Как уже говорилось ранее, событие реализуется как свойство процедурного типа. Соответственно, определив событие, мы должны предусмотреть его обработчик. В Delphi все события обычно имеют имена, начинающиеся с префикса «On»: OnClick, OnCreate и т.д. Для проверки наличия обработчика события используется специальная функция

function Assigned(var P): Boolean;

Эта функция проверяет, присвоено ли какое-либо значение переменной процедурного типа. Функция возвращает True, если присвоено, и False – в противном случае.

^ Пример 5.79. Создание события

Создадим событие в обработчике сообщения, рассмотренном в предыдущем примере. МодульUnit1 при этом не изменится.

В модуле Unit2 определим тип обработчика события TPCharEvent, получающего два параметра: стандартный параметр Sender – адрес объекта-инициатора события и специальный параметр MyString – адрес строки, пересылаемый в сообщении. Затем объявим событие OnPChar и метод – обработчик этого события PCharProc. Подключение метода – обработчика события выполним при создании формы FormCreate.

Unit Unit2;

Interface

Uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Unit1;

TypeTPCharEvent=procedure(Sender: TObject;MyString:PChar) of object;TForm2 = class(TForm)

MessageEdit: TEdit;

procedure FormCreate(Sender: TObject);

private

FOnPChar:TPCharEvent; {поле процедурного типа}

public

property OnPChar:TPCharEvent read FOnMessage

write FOnMessage; {свойство-событие OnPChar }

Procedure PCharProc(Sender: TObject; MyString:PChar);

{обработчик события OnPChar}

Procedure WMMyMessage(var Msg:MyMessage);

^ MESSAGE WM_MYMESSAGE;

end;

Var Form2: TForm2;

Implementation

{$R *.DFM}

Procedure TForm2.FormCreate(Sender: TObject);

Begin

Form1.SecondHandle:=Handle;

OnPChar:= PCharProc; {подключение обработчика события}

End;

Procedure TForm2.WMMyMessage(var Msg:MyMessage);

Begin

if Assigned(OnPChar) then {если обработчик события определен, то}

OnPChar (Self, Msg.PString); {выполнить его}

End;

Procedure TForm2.PCharProc(Sender: TObject;MyString:PСhar);

Begin MessageEdit.Text:=MyString; End;

End.

Обработка сообщений компонентов VCL. Библиотека VCL Delphi использует достаточно сложные трассы передачи сообщений между компонентами и формой. Это позволяет более гибко организовать их обработку, так как сообщение может быть обработано на любом этапе.

Например, сообщение WM_KeyDoun, переданное оконному управляющему элементу класса TEdit, принадлежащему некоторой форме, обрабатывается следующим образом (рис. 5.21).



Рис. 5.88. Генерация внутренних сообщений

Сначала сообщение WM_KeyDoun поступает в приложение. Получив это сообщение, приложение генерирует сообщение Cn_KeyDoun для элемента управления редактированием. Получив это сообщение элемент управления редактированием генерирует сообщение Cm_KeyDoun сначала самому себе, а затем передает его элементу родителю, пока оно не будет передано форме. Если форма возвращает 0 (т.е. отказывается обрабатывать это сообщение), то элемент управления редактированием возвращает 0 сам себе, а затем 0 – приложению.

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

^ Управление циклом обработки сообщений. Цикл обработки сообщений приложения в Delphi скрыт в методе Run класса TApplication. Это метод получает управление после инициализации приложения и построения его окон и выполняется следующим образом. Сообщение, извлеченное из очереди методом HandleMessage, передается соответствующему оконному элементу управления для обработки. После завершения обработки сообщения из очереди извлекается следующее сообщение, и т.д., до получения сообщения о прекращении работы приложения WM_QUIT. При обработке сообщения WM_QUIT свойство Terminated устанавливается равным True и цикл обработки сообщений завершается:

repeat HandleMessage

until Terminated;…

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

Для отображения длительной обработки используют специальные элементы TProgressBar или анимацию. Изменения в окне приложения показывают пользователю, что оно выполняет какие-то действия.

Для того чтобы приложение получило возможность обрабатывать сообщение из очереди, оно должно в цикле длительной обработки периодически вызывать метод ProcessMessages класса TAplication. Этот метод прерывает выполнение приложения для выборки сообщений из очереди, после чего возвращает управление прерванной программе.

Для прекращения длительной обработки приходится использовать специальные приемы, так как просто инициация сообщения WM_QUIT (например, при нажатии кнопки завершения приложения) не приводит к завершению приложения до окончания обработки. Это связано с тем, что при вызове ProcessMessages не происходит возврата в цикл обработки сообщений и, следовательно, не анализируется значение свойства Terminated, устанавливаемое при нажатии кнопки завершения приложения.

^ Пример 5.80. Прерывание длительной обработки

Рассмотрим приложение, которое выводит в окно некоторые числа. Процесс вывода чисел отображается специальным элементом класса TProgressBar, который показывает, какая часть процесса уже завершена. При желании пользователь может остановить процесс нажатием специальной кнопки Прервать (рис. 5.22).



Рис. 5.89. Вид главного окна приложения «Прекращение длительной обработки»

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

Unit Unit1;

Interface

Uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls;

Type

TForm1 = class(TForm)

StartButton: TButton; ExitButton: TButton;

procedure StartButtonClick(Sender: TObject);

procedure ExitButtonClick(Sender: TObject);

procedure ButtonClick(Sender: TObject);

private Cancel:Boolean;

end;

Var Form1: TForm1;

Implementation

{$R *.DFM}

Procedure TForm1.StartButtonClick(Sender: TObject);

Var i,j:integer; ProgressBar: TProgressBar; Button:TButton;

Begin

ProgressBar := TProgressBar.Create(Self);{создать вспомогательный объект TProgressBar, объявляя основным Form1}

Button:=TButton.Create(Self); {создать вспомогательный объект-кнопку, объявляя основным Form1}

^ ProgressBar.Parent := Self; {объявить Form1 старшим }

ProgressBar.Left:=30; ProgressBar.Top:=45;{определить координаты изображения}

Button.Parent:=Self; {объявить Form1старшим}

Button.Caption:='Прервать'; {определить название кнопки}

^ Button.Left:=60; Button.Top:=65; {определить координаты изображения}

Button.OnClick:=ButtonClick; {подключить обработчик нажатия кнопки}

for j:=0 to 9 do

begin

for i:=1 to 10000 do

begin

Canvas.TextOut(90,20,IntToStr(i));

Application.ProcessMessages; {прервать обработку, чтобы проверить наличие сообщений в очереди}

if Cancel then break; {если установлено свойство Cancel, то прекратить обработку}

end;

ProgressBar.StepIt; {вывести в окно изображения ProgressBar очередной закрашенный прямоугольник}

if Cancel then break; {если установлено свойство Cancel, то прекратить обработку}

end;

ProgressBar.Free; {уничтожить объект TProgressBar}

Button.Free; {уничтожить кнопку TButton}

Canvas.TextOut(70,20,'Вывод завершен');

end;

Procedure TForm1.ButtonClick; {обработчик нажатия на кнопку Прервать}

Begin Cancel:=true; End; {устанавливаем свойство Cancel}

Procedure TForm1.ExitButtonClick(Sender: TObject);

Begin Close; End;

End.