про TObject.Dispatch
Опубликовано 01.06.2011 г. 23:12
Наверное, механизм обработки сообщений Windows на примере форм в Delphi знаком многим. Но так ли часто вы используете механизмы сообщений для обычных объектов?
Как это выглядит в форме. Например, для обработки нажатия кнопки вы можете заполнить обработчик события OnKeyDown. С другой стороны, если копнуть вглубь VCL, то Windows присылает вам сообщение с кодом WM_CHAR, и данный обработчик на него реагирует. Говоря другими словами, вы можете заменить назначенный обработчик нажатия клавиши на что нить вида
TForm1 = class(TForm) strict private procedure wmChar(var msg : TMessage); message WM_CHAR; end;и когда вы нажмете клавишу, то вызывается указанный метод. Т.е процедура помечается ключевым словом message и его кодом. И VCL с событиями гораздо упрощает все подобное программирование. Можете возразить "какая разница событие я напишу, или метод сообщения, - все равно один метод в форме". Но разница есть весьма большая. В форме вы описываете только сообщения формы. Если вы ставите курсов в Edit то сообщения получает именно тот экземпляр TEdit а не ваша форма. И тогда, чтобы перехватить сообщение WM_CHAR для edit'a вам понадобится сделать свой класс TMyEdit в котором перекрыть метод обработки WM_CHAR, а затем использовать его на форме. Тут и раскрываются события - метод/процедура/функция объекта: действие применяется, например, к экземпляру кнопки, а обработчик ее находится в классе формы. Т.е действие-событие нажатия наступает в edit, а обработчик его в находится в форме - метод другого объекта. Но это все лирическое отступление. Содержательная часть, правда, небольшая. Базовый класс TObject от которого унаследованы все классы в Delphi также позволяет обрабатывать сообщения. Для этого вам требуется идти все тем же путем - добавить message метод.
TTest = class(TObject) strict private procedure messageTest(var msg :TMessage); message 123; end;Какое тут ограничение? Документация говорит, что первые два байта, которые передаются в качестве параметра, должны содержать код сообщения. Имхо правда документация не совсем здесь права, и все таки используются 4 байта. Так что в каждой записи, передаваемой в message метод, первое поле всегда содержит код сообщения. Можете просмотреть модуль messages, и заметите, что всегда запись сообщения начинается с члена msg : Cardinal (ULong, беззнаковое целое, 4 байта). Помимо описания методов у нас есть еще две вещи. Первая - процедура TObject.Dispatch, которая отправляет данному объекту сообщение. Второе - обработчик по умолчанию - DefaultHandler. По умолчанию он конечно же пуст. После отправки сообщения объекту, сначала проверяется таблица его message методов, если соответствующий метод найден, то он вызывается. Если не найден, то вызывается DefaultHandler. Вообще до этой недели я никогда не пользовался сообщениями для обычных объектов. Но как то они удачно мне вспомнились. Допустим у нас вполне обыденная ситуация: Есть некоторый объект. У него есть поле - форма, которую он может открыть при вызове какого то метода. Если форма не создана, то он ее создает. Если уже создана, то создавать ничего не надо. Никаких сложностей нет, когда окно мы показываем модально. По закрытию оно всегда уничтожается. т.е каждый раз мы создаем форму заново.
with TTestForm.Create(nil) do begin ShowModal(); Free(); end;А что же делать, если форма не модальна? - надо в класс добавить новый член - ссылку на форму. Давайте рассмотрим такой класс:
TTest = class(TObject) strict private FForm : TTestForm; public procedure ShowForm(); end;Что мы делаем здесь: если FForm не nil, то создаем форму:
procedure TTest.ShowForm; begin if assigned(FForm) then exit; FForm := TTestForm.Create(nil); FForm.show(); end;Таким образом класс управляет показом формы. Так, но что же происходит когда мы закрываем форму? Да, объект формы уничтожается. Но ссылка FForm не меняется, она все так же продолжает указывать в то же место - на разрушенный объект. Значит наша форма откроется только один раз. Как же можно поступить в таком случае? - При закрытии формы надо насильственно занулить ссылку на нее. Самый простой вариант - предоставить public доступ к FForm. Передать ссылку на объект TTest в форму TTestForm, которая обнулит ссылку на форму в объекте при закрытии. Но можно пойти другим путем. На практике у вас почти всегда будет связь между формой и объектом ее создавшим. Т.е как и в первом случае форма будет знать какой объект ее создал. Возможно объект вы передадите в конструкторе формы. А теперь можно условиться - при закрытии форма передает сообщение объекту ее создавшему. Определим константу и метод обработки:
const TM_FORM_CLOSED = 1; type TTest = class(TObject) strict private FForm : TTestForm; procedure tmFormClosed(var msg : TMessage); message TM_FORM_CLOSED; public procedure ShowForm(); end; procedure TTest.tmFormClosed(var msg: TMessage); begin FForm := nil; end; procedure TTest.ShowForm(); begin if assigned(FForm) then exit; FForm := TTestForm.Create(self); FForm.Show(); end;Тело процедуры будет обнулять ссылку. При создании формы мы будем передавать ссылку на самого себя. Форма ссылку на объект сохранит, и при закрытии передаст объекту сообщение. В чем тут плюс? - форма может не знать вообще ничего о структуре объекта, и сам объект не будет содержать никакого public метода обнуления ссылки, или самой public-ссылки на форму.
TTestForm = class(TForm) procedure FormClose(Sender: TObject; var Action: TCloseAction); private FTestObject : TObject; public constructor Create(aObj : TObject); end; constructor TTestForm.Create(aObj: TObject); begin inherited Create(nil); FTestObject := aObj; end; procedure TTestForm.FormClose(Sender: TObject; var Action: TCloseAction); var msg : TMessage; begin action := caFree; msg.Msg := TM_FORM_CLOSED; FTestObject.Dispatch(msg); end;Связность минимальна? - да безусловно. Объект то естественно о форме знает все. А вот форма об объекте ничего. Она лишь знает, что должна отправить объекту нужное сообщение. Если у нас несколько форм, то мы прекрасно можем использовать, например, WParam сообщения для передачи дескриптора закрывающегося окна.
02.06.2011 в 10:32
02.06.2011 в 11:24
02.06.2011 в 11:16
т.е "public интерфейс" класса остается неизменным.
02.06.2011 в 11:37
02.06.2011 в 17:32
в дельфи есть интерфейсы.
02.06.2011 в 17:01
02.06.2011 в 11:24
А зачем у формы переопределять метод Create?
Если при создании формы мы сделали:
то при обработке закрытия формы мы можем воспользоваться ее свойством Owner
02.06.2011 в 11:33
наш объект owner'ом стать не может, ибо TComponent не его предок.
так что конструктор у нас Create(obj : TObject) ну или можно TTest.
02.06.2011 в 11:08
А за посыл спасибо. Никогда не обращал внимания, что у TObject есть события.
02.06.2011 в 12:42
02.06.2011 в 15:42
1. При создании формы назначить ей собственный обработчик OnClose:
или, если так хочется переопределить конструктор формы,
2. передать в конструктор ссылку на метод объекта
а в форме, при закрытии, вызывать его
оба этих способа хороши тем, что используют "стандартные" средства ОО-языка программирования, а значит понятны любому, в отличие от ваших игр с TObject.Dispatch.
02.06.2011 в 16:40
15.06.2011 в 12:54
Кстати, теперь, что бы перехватить у TEdit сообщение, не надо его переписывать, у объектов появилось свойство WindowProc, которое можно подменить при создании вормы методом формы и там ловить нужные сообщения. Очень удобно...
15.06.2011 в 17:55
опять же подменить методом другого объекта - фактически описать событие.