Команды DispID и Implements
а) КомандаDispID используется для присвоения свойству значения Dispatch ГО, которое применяется в автоматизации OLE. Это целочисленное значение обычно используется при объявлении свойств Dispatch интерфейсов. Эта команда может быть указана только у свойства, объявляемого в разделе Automated класса. Более подробно эта команда будет рассмотрена в главе, посвященной интерфейсам.
б) КомандаImplements используется при делегировании полномочий интерфейсов свойству класса. Применяется для реализации классов поддерживающих интерфейсы и будет рассмотрена позднее во второй части пособия в главе, посвященной интерфейсам.
Переопределение свойств при наследовании
При наследовании свойств можно в классе-потомке заменить унаследованную или добавить отсутствующую команду, изменить видимость и даже
повторно объявить свойство.
а) Для изменения области видимости необходимо в классе-потомке объявить имя свойства без указания типа и команд в разделе с большей видимостью, обычно в Public или Published, т.е. в сторону увеличения видимости.
Public Property MyProperty;
б) Для изменения или добавления команды следует объявить свойство без указания типа с дополнительной и/или измененной командой (Read] Write|Stored|Default|NoDefault). Одновременно можно изменить и область видимости.
в) Для повторного объявления свойства следует указать имя свойства вместе с новым типом. Обязательно должна быть хотя бы одна команда доступа (Read|Write). При переопределении свойства можно вместо старых методов доступа объявить и использовать новые.
Отметим, что переопределение не запрещает доступ к исходному свойству. Путем приведения типов можно обратиться к свойству предка или потомка. При этом будут вызваны методы доступа свойства того класса, объект которого указывается при приведении типов. Таким образом, полиморфизм при доступе к свойству не наблюдается.
Объявим два класса, включающих свойства и методы доступа. При этом во втором классе свойство переопределим заново, изменив одновременно и тип свойства и методы доступа.
Type
TlstClass=Class // Первый класс
FFId: Integer; // Поле целого типа
Function Getint: Integer; // Метод чтения
Procedure Setlnt(lnt: Integer); // Метод записи
Property Prop: Integer Read Getint Write Setint; // Свойство целого типа
End;
T2ndClass=Class(TlstClass) II Второй класс
Function GetStr: String; II Метод чтения
Procedure SetStr(St: String); II Метод записи
Property Prop: String Read GetStr Write SetStr; // Свойство строкового типа
End;
Var Objl: TIstClass; II Переменная первого класса
Obj2: TZndClass; // Переменная второго класса
После объявления реализации указанных методов и инициализации экземпляров указанных классов и допустимы следующие операторы:
Procedure TForml.ButtonlClick(Sender: TObject);
Begin
Editl.Text:=IntToStr(Objl.Prop); II Доступ к свойству 1-го объекта Edit2.Text:=T2ndClass(Obj l).Prop; II Доступ к свойству 1-го объекта
End;
Procedure TFonnl.Button2Click(Sender: TObject);
Begin
Edit3.Text:=Obj2.Prop; II Доступ к свойству 2-го объекта Edit4.Text:=IntToStr(TlstClass(Obj2).Prop); // К свойству 2-го объекта
End;
Procedure TForinl.Button3Click(Sender: TObject);
Begin
Edit5.Text:=T2ndClass(Objl).Prop; II Доступ к свойству 1-го объекта
End;
Третий обработчик показывает, что в поле ввода Edit5 заносится значение свойства строкового типа. Однако с помощью приведения типов вызывается объект первого класса, у которого одноименное свойство имеет целый тип. Тем не менее все корректно работает. Обращение к свойству приводит к вызову соответствующего ему метода.
г) Команда Implements позволяет для наследованных свойств добавить список интерфейсов, не меняя других характеристик свойства.
СОБЫТИЯ (EVENTS)
Наиболее общее определение события - любое происшествие, вызванное вмешательством пользователя, поведением системы или логикой кода приложения. Событие в программе может быть связано с некоторым кодом, называемым обработчиком события и отвечающим на это происшествие, что позволяет настраивать поведение компонентов без необходимости модернизировать классы.
С точки зрения разработчика, событие - это имя, к которому обработчик события может быть присоединен в программе.
События имеются почти у всех компонентов. Компоненты используют свойства, чтобы определить и выполнить события. При этом обычно используется прямой доступ к полям. Кроме того, пользователь может создавать новые события, переопределять стандартные события и определять код обработчика события.
Если разработчик реализовал код обработчика события, то он выполняется, когда это событие происходит. В противном случае обычно ничего не происходит. Компоненты Delphi имеют достаточно большое количество событий, однако в приложениях создаются обработчики только для некоторых из них.
Имеется два вида событий: взаимодействие пользователя и изменение состояния. События взаимодействия пользователя почти всегда вызываются в соответствии с сообщениями Windows, указывая, что пользователь делал кое-что, поэтому компонент может ответить. События изменения состояния также могут быть сведены к сообщениям Windows.
Обработчики событий могут не возвращать никакой информации о событии, которое называется в таком случае событием уведомления или возвращать дополнительную специфическую информацию, связанную с событием. В этом случае его называют специфическим событием.
События уведомления просто сообщают, что некоторое событие случилось, без предоставления информации о том, где и когда. Уведомления обычно использую тип TNotifyEvent, который имеет только один параметр -отправитель события. Так событие OnClick - уведомление. Уведомление -односторонний процесс. Нет никакого механизма для обеспечения обратной связи или предотвращения дальнейшей обработки уведомления. Когда создается обработчик для обработки такого события, то все, что мы знаем - то, что это событие произошло, и какой компонент вызвал событие.
Специфические события отличаются от событий уведомления, и используются в тех случаях, когда хотелось бы знать, не только какое событие случилось и какой компонент его вызвал, но и получить сопутствующую дополнительную информацию. Например, если событие - нажатая клавиша, то, вероятно, хотелось бы знать, какая клавиша нажата. В подобных случаях необходимы обработчики, которые включают параметры для дополнительной информации. В подобных случаях нужны обработчики специфических событий. Специфических событий достаточно много.
Объявление событий
Синтаксически события объявляются почти полностью аналогично свойствам:
Property <имя события>: <указателъ на метод> [Index <целое число} [Read <поле события метод чтения>] [Write <поле события\метод записи>} [Stored Логическое выражение>} [Default Nil|NoDefault];
Примечания:
• Хотя в командах чтения и записи могут использоваться методы, на практике они не используются. Обычно используется прямой доступ к полю.
• Команда Index используется вместе с методами записи или чтения. Поскольку методы практически не используются, то и команда Index также.
• Команда Default фактически допускает только одно значение по умолчанию - Nil, которое и так устанавливается компилятором. Поэтому использование команды Default и, соответственно, NoDefault нецелесообразно.
• Все события в Delphi принято именовать с "On ": OnCreate, OnClick и т.д.
Событие OnClick объявлено в классе TControl следующим образом:
Type
TControl=Class(TComponent) Private
FOnClick; TNotifyEvent; // Объявляется none типа указатель на метод
Function IsOnClickStored: Boolean;
II Другие объявления Protected
Procedure Click; Dynamic; // Корреспондирующий метод события
Property OnClick: TNotifyEvent Read FOnClick Write FOnClick
Stored IsOnClickStored; // Событие
End;
В данном случае событие и соответствующее поле имеют тип TNotifyEvent.
Таким образом, в общем случае для объявления события необходимо сделать следующее:
• определить или выбрать тип данных события (тип указателя на метод);
• объявить поле соответствующего типа для хранения значения события;
• объявить (корреспондирующий - event-dispatching method) метод, который будет вызывать обработчик события, когда оно произойдет. Представить реализацию этого метода и определить механизм его вызова;
• объявить собственно событие.
Синтаксис объявления события уже рассмотрен. Синтаксис объявления соответствующего поля не отличается от объявления любого другого поля, если к этому моменту известен тип данных поля. Рассмотрим поэтому как следует решать остальные проблемы.
а) Объявление типа события.
Событие - это механизм, с помощью которого связывается некоторое происшествие с некоторым кодом. Код оформляется в виде метода, тип которого объявляется до объявления класса, включающего объявление события. Этот метод должен принадлежать не классу, а экземпляру класса, поскольку даже для объектов одного класса исполняемый код обычно бывает различным. Создание кода метода, реагирующего на событие - это привилегия программиста, Delphi же обеспечивает связь этого метода с событием для вызова этого метода.
Связь события и метода, выполняющего в ответ на это событие, осуществляется в Delphi с помощью указателя на метод. Точнее - это указатель на метод в экземпляре класса. Указатели на метод работают точно также как любой другой процедурный тип, но еще они поддерживают скрытый указатель на объект. Как и всякий указатель, это фактически адрес, в данном случае, адрес метода, и он требует четыре байта для размещения в памяти. Поскольку эти методы предназначены для обработки событий, их называют обработчиками событий (event handler), а управление объектами - управлением по событиям (event driven).
Таким образом, для объявления события необходимо предварительно объявить или выбрать стандартный тип обработчика события. От обычного процедурного типа указатели на метод отличаются тем, что используют специальный синтаксис:
Type <имятипа>=Procedure(Sender: T0bject[; <параметры>})Of Object;
Дополнительные ключевые слова Of Object указывают компилятору о том, что это указатель на метод объекта, а не самостоятельная процедура. Таким образом, для обработки событий в Delphi предусмотрено объявление указателей на метод, обеспечивающих неявную передачу указателя на экземпляр (переменная Self), вызвавший то или иное событие.
В большинстве случаев указатели на метод объявляются как процедуры. Хотя компилятор Delphi позволяет объявлять функции-указатели на метод использовать их для обработчиков событий следует осторожно. Программист может не создать код обработчика для события, а поскольку пустая функция возвращает неопределенный результат, то пустой обработчик события недопустим.
Несмотря на то, что обработчики события нецелесообразно объявлять функциями, можно получать информацию из них, используя Var параметр.
Type TKeyPressEvent=Procedure(Sender: TObject; Var Key: Char) Of Object;
Обработчик события для такого указателя на метод может иметь в дальнейшем, например, следующий вид:
Procedure TForml.EditlKeyPressed(Sender: TObject; Var Key: Char);
Begin
Key:=UpCase(Key);
End;
Можно использовать Var параметры и для того, чтобы отменять заданную по умолчанию обработку.
Используются, хотя и редко, функции - указатели на метод. Например, в самой Delphi объявлен указатель на метод в виде функции:
Type THelpEvent=Function(Command: Word; Data: Longint;
Var CallHelp: Boolean): Boolean Of Object;
Property OnHelp: THelpEvent;
Список событий в Delphi достать 'то велик. Однако для простых средств управления он небольшой и соответствующие им указатели на метод приведены в табл.2.
TCloseEvent=Procedure(Sender: TObject; Var Action: TCloseAction) Of Object; | OnClose |
TDockDropEvent=Procedure(Sender: TObject; Source: TDragDockObject; X, Y: Integer) Of Object; | OnDockDrop |
TDockOverEvent=Procedure(Sender: TObject; Source: TDragDockObject; X, Y: Integer; State: TDragState;Var Accept: Boolean) Of Object; | OnDockOver |
TStartDockEvent=Procedure(Sender: TObject; Var DragObject: TDragDockObject) Of Object; | OnStartDock |
TStartDragEvent=Procedure(Sender: TObject; Var DragObject: TDragObject) Of Object; | OnStartDrag |
TActionEvent=Procedure(Action: TBasicAction; Var Handled: Boolean) Of Object; | OnActionExecute, OnUpdate, OnExecute, OnActionUpdate, |
TContextPopupEvent=:Proceduгe(Sendeг: TObject; MousePos: TPoint, Var Handled: Boolean) Of Object; | OnContextPopup |
Есть группа специальных событий, используемых при работе с базами данных. Internet и т.п. С ними можно познакомиться в справочной системе Delphi. Прежде, чем объявлять собственный тип указателя на метод следует познакомиться с уже имеющимися.
б) Определение механизма возбуждения события.
Важным моментом в создании пользовательского события является определение того, как будет вызываться (возбуждаться) событие. Кроме того, если это не уведомительное событие, то следует знать, как будут определяться значения дополнительных параметров.
Этот механизм должен фактически реагировать на какие-то ситуации, происшествия, в ответ на возникновение которых и должно возбуждаться события. Например, событие OnClick реагирует на щелчок мыши по компоненту или форме.
Для инициализации события можно использовать либо непосредственный вызов соответствующего метода, либо использовать для этого механизм сообщений. Для присвоения значений дополнительным параметрам необходимо вставлять соответствующий код, который будет определять их значения и передавать в обработчик пользовательского события.
в) Объявление и реализация метода, вызывающего обработчик события. После определения механизма возбуждения пользовательского события, следует решить, какие реакции (видимые и невидимые) должны быть у объекта, и как будет вызываться обработчик события. Во многих случаях обработчики могут представлять собой простые уведомления определенного типа. В особых случаях может также потребоваться возврат значения из обработчика.
Обычно для реализации кода реакции объекта на событие и вызова обработчика события объявляется и создается реализация соответствующего (корреспондирующего) виртуального защищенного метода.
Так, в задачу динамического метода Click, помимо изменения внешнего представления кнопки (кнопка "утопает"), входит проверка наличия обработчика события, и если он есть, передача управления ему.
Синтаксис объявления такого метода следующий:
Procedure <имя метода>[(<парсшетры>)];Dynamic;
Например, для события OnMouseMove корреспондирующий метод с параметрами объявлен в классе TControl следующим образом:
Procedure MouseMove(Shift: TShiftState; X, Y: Integer); Dynamic;
После объявления метода необходимо в разделе Implementation модуля представить его реализацию. Реализация должна позволить компоненту отвечать на действия, активизируемые вашим методом сообщения о событии.
Внутри библиотеки RTL Delphi вызовы обработчиков событий находятся в методах, обрабатывающих сообщения Windows. Необходимо при создании события создать и свой метод диспетчеризации вызова события. В принципе его код может быть достаточно прост. Фактически необходимо проверить наличие обработчика события и, если он реализован, то вызвать этот обработчик, передав ему в качестве параметра объект, вызвавший событие, а при необходимости и дополнительные параметры.
Стандартный синтаксис для реализации метода уведомления о событии имеет следующий вид:
Procedure <гшя класса>.<гшя метода>[(<объявление параметров>)];
Begin
<некий код> If Assigaed(<none события>)
Then <поле co6bimw>(Self[, '^дополнительные параметры>]);
<некий код> End;
Например, вызов обработчика события OnMouseMove в модуле Controls сделан следующим образом:
Procedure TControl.MouseMove(Shift: TShiftState; X, Y: Integer);
Begin