Текущий архив: 2005.12.25;
Скачать: CL | DM;
Вниз
Рассылка событий группе объектов. Найти похожие ветки [D7, WinXP]
← →
GuAV © (2005-11-23 14:09) [0]Здравсвуйте.
Мне нужно вызывать из одного объекта методы (обработчики событий) нескольких других объектов, причём вызывающий заранее не знает каким объектам, но целевой класс "знает", какие события ему нужны (и предоставляет свои методы).
В данный момент используется следующее решение:
type
TOnCaptionChanged = procedure(Sender: TObject; const Old, New: string) of object;
TOnObjectChanged = procedure(Sender: TMapPolygon) of object;
...
type
TObjChangeNotifier = class(TObject)
private
FCaptionChanged, FObjectChanged, ... : TList;
procedure RegisterHandler(List: TList; const Handler: TMethod; Register: Boolean);
procedure ClearList(List: TList);
public
constructor Create;
destructor Destroy; override;
procedure CaptionChanged(Sender: TObject; const Old, New: string);
procedure ObjectChanged(Sender: TMapPolygon);
...
procedure RegisterOnCaptionChanged(const Handler: TOnCaptionChanged; Register: Boolean);
procedure RegisterOnObjectChanged(const Handler: TOnObjectChanged; Register: Boolean);
...
end;
constructor TObjChangeNotifier.Create;
begin
inherited;
FCaptionChanged := TList.Create;
FObjectChanged := TList.Create;
...
end;
destructor TObjChangeNotifier.Destroy;
begin
ClearList(FCaptionChanged);
ClearList(FObjectChanged);
...
FreeAndNil(FCaptionChanged);
FreeAndNil(FObjectChanged);
...
inherited;
end;
procedure TObjChangeNotifier.RegisterHandler(List: TList;
const Handler: TMethod; Register: Boolean);
var
M: PMethod;
I: Integer;
begin
with List do
if Register then
begin
New(M);
M^ := Handler;
Add(M);
end
else
begin
for I := Count - 1 downto 0 do
begin
M := PMethod(Items[I]);
with M^ do
if (Code = Handler.Code) and (Data = Handler.Data) then
begin
Delete(I);
Dispose(M);
end;
end;
end
end;
procedure TObjChangeNotifier.CaptionChanged(Sender: TObject; const Old,
New: string);
var
I: Integer;
begin
with FCaptionChanged do
for I := 0 to Count - 1 do
TOnCaptionChanged(PMethod(Items[I])^)(Sender, Old, New);
end;
procedure TObjChangeNotifier.ObjectChanged(Sender: TMapPolygon);
var
I: Integer;
begin
with FObjectChanged do
for I := 0 to Count - 1 do
TOnObjectChanged(PMethod(Items[I])^)(Sender);
end;
....
procedure TObjChangeNotifier.RegisterOnCaptionChanged(
const Handler: TOnCaptionChanged; Register: Boolean);
begin
RegisterHandler(FCaptionChanged, TMethod(Handler), Register);
end;
procedure TObjChangeNotifier.RegisterOnObjectChanged(
const Handler: TOnObjectChanged; Register: Boolean);
begin
RegisterHandler(FObjectChanged, TMethod(Handler), Register);
end;
...
в AfterConstruction целевого класса
ObjChangeNotifier.RegisterOnCaptionChanged(CaptionChanged, True);
в BeforeDestruction целевого класса
ObjChangeNotifier.RegisterOnCaptionChanged(CaptionChanged, False);
Вызов события
ObjChangeNotifier.ObjectChanged(CurrentObject);
Однако, очевидно, что для большого числа методов такое решение не является удобным. Есть идея перейти на использование сообщений delphi, тогда TObjChangeNotifier будет содержать только один список (или он сам и будет наседником списка) и один метод диспетчеризации (возможно даже его же TObject.Dispatch), при этом элементами будут не методы а сами объекты (избавление от New и Dsipose). Какие возможны проблемы при этом и как это лучше реализовать ?
Методы должны вызываться синхронно. Порядок роли не играет. Число параметров различных методов различно, переменных параметров нет. Производительность (быстродейтсвие) является фактором, однако удобство главнее.
Хотелось бы услышать ваше мнение как удобнее реализовать рассылку событий группе объектов - через сообщения Delphi, сообщения Windows или другим способом - и как именно. Вообще, как Вы реализуете (или реализовали бы) сабж в своих проектах.
← →
Игорь Шевченко © (2005-11-23 15:25) [1]Я бы предложил при регистрации обработчика события указывать еще и тип события, на который обработчик должен реагировать, а параметры передавать одним Variant (в который можно поместить массив других вариантов). Тогда один список, разные обработчики знают, какой список параметров соотвествует их событию и т.д.
← →
jack128 © (2005-11-23 15:41) [2]Я делаю так:
TSomeObjEvents = class
public
property Obj: TObjChangeNotifier read write;
property OnCaptionChanged: TOnCaptionChanged read write;
property OnObjectChanged: TOnObjectChanged read write;
end;
Подписчики создают объект TSomeObjEvents и задают свойство Obj. А Obj храниться список соответствующих TSomeObjEvents
GuAV © (23.11.05 14:09)
тогда TObjChangeNotifier будет содержать только один список
А какая разница один или несколько?
GuAV © (23.11.05 14:09)
и один метод диспетчеризации
Ничего хорошего в этом не вижу
GuAV © (23.11.05 14:09)
(избавление от New и Dsipose).
А в чем проблема?
ЗЫ
GuAV © (23.11.05 14:09)
for I := 0 to Count - 1 do
TOnObjectChanged(PMethod(Items[I])^)(Sender);
Я обычно здесь делаю обратный цикл, чтобы дать возможность подписчику отписаться в самом обработчике события.
← →
GuAV © (2005-11-23 16:46) [3]Игорь Шевченко © (23.11.05 15:25) [1]
> указывать еще и тип события, на который обработчик
> должен реагировать
Как указывать ? В настоящем варианте просто используются разные методы для регистрации различных событий.
Если завести Enumeration для типа событий, то можно указывать его в методе-"регистраторе". Вы это имеете ввиду?
Если же делать сообщения или нечто "сообщенияподобное", то необходимость указания типа обработчика отпадает - класс безусловно готов принять все события, а нужны ли они ему - он сам решит.
При использовании именно message-методов обработчика может не существовать - однако меня интересует, какая реакция будет при этом. Существуют контролы (фреймы) с созданным или несозданным окном, а также прямые наследники TObject, и хотелось бы обеспечить отсутствие реакции на необрабатываемое во всех трёх случаях. Возможно ли
это, и если да то как ?
Игорь Шевченко © (23.11.05 15:25) [1]
> а параметры передавать одним Variant (в который можно
> поместить массив других вариантов).
Рассмотрю как вариант. Однако, как мне кажется удобнее принимать один нетипизированый const или var параметр (в обработчике привести к нужному Record), а если таки ипользовать сообщения Delphi, то это - "естественный" путь.
Что касается заполнения - в варианте с const или var параметром в классе-генераторе события объявляется нужная запись и заполняется - это более удобно, так как есть типизация, в отличие от Variant.
jack128 © (23.11.05 15:41) [2]
> Я делаю так:
Я понял. Идея ApplicationEvents. смотрел. Особенно хорошо для визуального применения, если TSomeObjEvents - компонент и зарегистрирован (этот вариант тоже рассматривал).
>>тогда TObjChangeNotifier будет содержать только один список
> А какая разница один или несколько?
Обрати внимание на код [0]. Каждый метод-диспетчер сканит свой список, в комструкторе и деструкторе все списки упоминаются по разу - неудобно, главная причина отказа.
>>и один метод диспетчеризации
> Ничего хорошего в этом не вижу
А какая разница один или несколько? © jack128
Буду ли я указывать индексом события или именем метода - ИМХО безразлично.
Зато один метод - и цикл писать один.
Теряется типизация, но её можно вернуть пачкой методов, в них запускать главный с соотв. индексом.
Кстати, при использовании индексных свойств заместо методов, можно из одного сделать несколько. Но это уже типизацию не восстановит.
>>(избавление от New и Dsipose).
> А в чем проблема?
Да, не так чтобы сильно, но лишнее выделение/освобождение и лишний уровень косвенности. Мелочь, но неприятно.
> Я обычно здесь делаю обратный цикл, чтобы дать
> возможность подписчику отписаться в самом обработчике
> события.
Спасибо. Но мне пока не требуется - подписка или до BeforeDestruction или вообще пожизненная (до Destroy).
Ещё была идея: интерфейс, реализованный диспетчером и классами. Преимущество - максимальная типизация.
Но сущесвенный недостаток: необходимость добавления пустого метода в каждый класс при появлении нового события.
Склоняюсь к варианту:
Сообщения, для каждого события своё.
События:
TOnCaptionChanged = record
Message: LongWord; { = WM_USER + 3}
Sender: TObject;
Old, New: string;
end;
TOnObjectChanged = record
Message: LongWord; { = WM_USER + 4}
Sender: TMapPolygon;
end;
...
Главный метод диспетчеризации - TObject.Dispatch диспечера, вспомогательные по именам событий для типизации входного recordа, заполняют Message и вызывают главный. Жаль, что строки и варианты не допускаются в переменной части записи.
← →
Игорь Шевченко © (2005-11-23 16:57) [4]GuAV © (23.11.05 16:46) [3]
> Если завести Enumeration для типа событий, то можно указывать
> его в методе-"регистраторе". Вы это имеете ввиду?
Например. Или просто константу. Я не совсем понимаю, какой смысл использовать сообщения (Windows или Delphi, не слишком большая разница).
Я бы использовал именно Enumeration, чтобы возможные ошибки (опечатки, например) были выловлены на стадии компиляции.
> Что касается заполнения - в варианте с const или var параметром
> в классе-генераторе события объявляется нужная запись и
> заполняется - это более удобно, так как есть типизация,
> в отличие от Variant.
Я не совсем понимаю, так как в случае с Variant приведение типов контролируется также во время выполнения, а в случае с const или var параметрами приведение типов остается на совести программиста.
> Но сущесвенный недостаток: необходимость добавления пустого
> метода в каждый класс при появлении нового события.
На паттерн Visitor не хочешь посмотреть ?
← →
GuAV © (2005-11-23 17:15) [5]
> Я бы использовал именно Enumeration, чтобы возможные
> ошибки (опечатки, например) были выловлены на стадии
> компиляции.
Согласен, однако целочисленные константы с осмысленными идентификаторами снижают риск опечатки так же хорошо :)
При этом, можно задать тип поддиапазона TObjMessage = OM_FIRST..OM_LAST, и использовать его.
> Я не совсем понимаю, так как в случае с Variant
> приведение типов контролируется также во время
> выполнения, а в случае с const или var параметрами
> приведение типов остается на совести программиста.
Ошибки типа "вызванно не то событие" в любом случае недопустимы и не контролируются. И в обоих случаях обработчик (молча или с исключением, но) проглотит не его данные (ещё неизвестно что хуже, получить там AV, или привести catption равный 2 к double, второе ловить сложнее).
Ошибки типа не тот параметр не на том месте (и в обработчике и в вызывающем) легче исключить именно в случае с записью.
И потом, Sender: TObject, Sender: TMapPolygon, Sender: TList - это классы, как их в вариант пихать ?
> На паттерн Visitor не хочешь посмотреть ?
Пошел искать. Спасибо.
← →
GuAV © (2005-11-23 17:30) [6]
> > На паттерн Visitor не хочешь посмотреть ?
Насколько я понял он позволяет добавить метод при фиксированной иерархии классов. У меня никакая не иерархия, просто куча разных классов, и, что является проблемой, рост их числа и рост числа методов одинаково ожидается.
← →
Игорь Шевченко © (2005-11-23 17:38) [7]
> И потом, Sender: TObject, Sender: TMapPolygon, Sender: TList
> - это классы, как их в вариант пихать ?
Точно также, как все остальное ? :)
← →
GuAV © (2005-11-23 21:11) [8]С сообщениями ничего не вышло. При вызове диспача из себя же начинаются различные странности, вроде из цикла for считает выше границы (от этого спасла обёртка PUSHA/POPA вокруг вызова Dispatch ! хотя другого asm-кода нет, абсолютно везде модель register !), и другие странности, например колонки в гридах съезжают. Не исключаю кривизну своих рук, но похоже на глюк RTL.
Пока оставил как есть. С delphi сообщениями больше не связываюсь. К рассмотернию вариант jack128.
← →
Leonid Troyanovsky © (2005-11-24 08:40) [9]
> GuAV © (23.11.05 21:11) [8]
> Пока оставил как есть. С delphi сообщениями больше не связываюсь.
С сообщениями все в порядке, дело в твоей реализации.
А, вообще-то, сама исходная постановка хромает на обе ноги.
Т.е., либо нужна модель, основанная на TComponent with Actions, либо
все классы д.б. собс-ручные, с тем самым интерфейсом, который будет
вызываться по списку.
Но, и в последнем случае, TComponent предпочтительней, бо уже обладает
механизмом Notification, позволяющим оный список обновлять.
--
Regards, LVT.
← →
Владислав © (2005-11-24 09:03) [10]А если кинуть взгляд на IConnectionPoint? На мой взгляд верх гибкости. :)
Когда подобный вопрос возник, это было первое, что пришло на ум. Однако, нужно было, чтобы реализация плавно вписалась в инфраструктуру приложений, которые пишу не я один. Поэтому после некоторых упрощений пришел к такой реализации. Может что-то для себя подчерпнете.
// Классы для реализации подписки на события
// Класс события
// Класс, который реализует подписку на свои события создает
// экземпляр класса TEventObject перед уведомлением подписчиков о событии
TEventObject = class(TObject)
private
FEventID: Integer;
FSender: TObject;
protected
// Наследники могу воспользоваться этим методом для установки read only
// свойств Sender и EventID
procedure SetEventInfo(ASender: TObject; AEventID: Integer);
public
constructor Create(ASender: TObject; AEventID: Integer); virtual;
// Экземпляр отправитель события
property Sender: TObject read FSender;
// Идентификатор события
// Каждый класс реализующий подписку на свои события создает карту
// идентификаторов
property EventID: Integer read FEventID;
end;
// Тип метода-события уведомления
TEventNotification = procedure(const AEventObject: TEventObject) of object;
// Экземпляры этого класса предназначены для хранения информации
// о зарегистрированных приемниках уведомлений о событии
// Класс предназначен только для хранения информации
// Не пытайтесь создавать или освобождать экземпляры этого класса
TEventConnectionInfo = class(TObject)
private
FOnEventNotification: TEventNotification;
public
constructor Create(AEventNotification: TEventNotification);
procedure Notify(EventObject: TEventObject);
end;
// Базовый интерфейс для точек соединения с классами, реализующими подписку
// на события
// Интерфейс предназначен для реализации подписки в классах, которые не могут
// наследоваться от TEventConnectionPoint (или это не целесообразно)
// Для реализации этого интерфейса в передатчике событий можно использовать
// композицию класса TEventConnectionPoint
IEventConnectionPoint = interface(IUnknown)
["{C0BAC6F7-B6A3-44CA-B8AC-F9ECE673E57C}"]
// Подписаться на события
// Вызывается объектом, желающим подписаться на события
// Результат функции - идентификатор подписки, используется
// при вызове метода Unadvise для отписки
function Advise(AEventNotification: TEventNotification): Integer;
// Отписаться от события, AdviseID - идентификатор подписки
procedure Unadvise(AdviseID: Integer);
end;
// Класс, реализующий функциональность подписки на события и отписки от них
// К одному и тому же отправителю событий может подписаться несколько клиентов
// Подписка клиентов хранится в списке подписчиков
TEventConnectionPoint = class(TUnknownBase)
private
FAdviseList: TList;
FEventObject: TEventObject;
function GetAdviseList: TList;
function GetCount: Integer;
function GetEventObject: TEventObject;
private
property AdviseList: TList read GetAdviseList;
property Count: Integer read GetCount;
property EventObject: TEventObject read GetEventObject;
public
destructor Destroy; override;
// Подписаться на событие (см. IEventConnectionPoint.Advise)
function Advise(AEventNotification: TEventNotification): Integer;
// Отписаться от события (см. IEventConnectionPoint.Unadvise)
procedure Unadvise(AdviseID: Integer);
// Получить экземпляр класса TEventObject и установить его свойства
// Эта функция-помощник предназначена для отправки уведомлений о событиях
// класса TEventObject. Функция создает класс только один раз и кеширует
// экземпляр TEventObject
// Не освобождайте экземпляр, полученный от этой функции.
// если необходимы более сложные объекты события - создавайте их самостоятельно
function GetSimpleEventObject(ASender: TObject; AEventID: Integer): TEventObject;
// Послать уведомления о событии AEventObject всем подписчикам
procedure Notify(AEventObject: TEventObject);
end;
// ******************************************************
{ TEventObject }
constructor TEventObject.Create(ASender: TObject; AEventID: Integer);
begin
SetEventInfo(ASender, AEventID);
end;
procedure TEventObject.SetEventInfo(ASender: TObject; AEventID: Integer);
begin
FSender := ASender;
FEventID := AEventID;
end;
{ TEventConnectionInfo }
constructor TEventConnectionInfo.Create(
AEventNotification: TEventNotification);
begin
FOnEventNotification := AEventNotification;
end;
procedure TEventConnectionInfo.Notify(EventObject: TEventObject);
begin
FOnEventNotification(EventObject);
end;
{ TEventConnectionPoint }
destructor TEventConnectionPoint.Destroy;
begin
FreeAndNil(FEventObject);
FreeAndNil(FAdviseList);
inherited;
end;
function TEventConnectionPoint.GetEventObject: TEventObject;
begin
if FEventObject = nil then
FEventObject := TEventObject.Create(nil, 0);
Result := FEventObject;
end;
function TEventConnectionPoint.GetSimpleEventObject(ASender: TObject;
AEventID: Integer): TEventObject;
begin
Result := EventObject;
Result.SetEventInfo(ASender, AEventID);
end;
function TEventConnectionPoint.GetAdviseList: TList;
begin
if FAdviseList = nil then
FAdviseList := TList.Create;
Result := FAdviseList
end;
function TEventConnectionPoint.GetCount: Integer;
begin
if FAdviseList <> nil then
Result := FAdviseList.Count
else
Result := 0;
end;
function TEventConnectionPoint.Advise(
AEventNotification: TEventNotification): Integer;
var
ConnectionInfo: TEventConnectionInfo;
LocalAdviseList: TList;
begin
LocalAdviseList := AdviseList;
LocalAdviseList.Expand;
ConnectionInfo := TEventConnectionInfo.Create(AEventNotification);
LocalAdviseList.Add(ConnectionInfo);
Result := Integer(Pointer(ConnectionInfo));
end;
procedure TEventConnectionPoint.Unadvise(AdviseID: Integer);
var
Index: Integer;
ConnectionInfo: TEventConnectionInfo;
LocalAdviseList: TList;
begin
ConnectionInfo := TEventConnectionInfo(Pointer(AdviseID));
Assert(Count > 0);
if Count = 0 then
Exit;
LocalAdviseList := AdviseList;
Index := LocalAdviseList.IndexOf(ConnectionInfo);
Assert(Index >= 0);
if Index < 0 then
Exit;
LocalAdviseList.Delete(Index);
ConnectionInfo.Free;
end;
procedure TEventConnectionPoint.Notify(AEventObject: TEventObject);
var
i: Integer;
LocalAdviseList: TList;
ConnectionInfo: TEventConnectionInfo;
begin
if Count > 0 then
begin
LocalAdviseList := AdviseList;
for i := 0 to LocalAdviseList.Count - 1 do
begin
ConnectionInfo := TEventConnectionInfo(LocalAdviseList.Items[i]);
ConnectionInfo.Notify(AEventObject);
end;
end
end;
//***************************************************
← →
Владислав © (2005-11-24 09:04) [11]Уважаемые модераторы, удалите предыдущий пост пожалуйста. Забыл про форматирование :(
А если кинуть взгляд на IConnectionPoint? На мой взгляд верх гибкости. :)
Когда подобный вопрос возник, это было первое, что пришло на ум. Однако, нужно было, чтобы реализация плавно вписалась в инфраструктуру приложений, которые пишу не я один. Поэтому после некоторых упрощений пришел к такой реализации. Может что-то для себя подчерпнете.// Классы для реализации подписки на события
// Класс события
// Класс, который реализует подписку на свои события создает
// экземпляр класса TEventObject перед уведомлением подписчиков о событии
TEventObject = class(TObject)
private
FEventID: Integer;
FSender: TObject;
protected
// Наследники могу воспользоваться этим методом для установки read only
// свойств Sender и EventID
procedure SetEventInfo(ASender: TObject; AEventID: Integer);
public
constructor Create(ASender: TObject; AEventID: Integer); virtual;
// Экземпляр отправитель события
property Sender: TObject read FSender;
// Идентификатор события
// Каждый класс реализующий подписку на свои события создает карту
// идентификаторов
property EventID: Integer read FEventID;
end;
// Тип метода-события уведомления
TEventNotification = procedure(const AEventObject: TEventObject) of object;
// Экземпляры этого класса предназначены для хранения информации
// о зарегистрированных приемниках уведомлений о событии
// Класс предназначен только для хранения информации
// Не пытайтесь создавать или освобождать экземпляры этого класса
TEventConnectionInfo = class(TObject)
private
FOnEventNotification: TEventNotification;
public
constructor Create(AEventNotification: TEventNotification);
procedure Notify(EventObject: TEventObject);
end;
// Базовый интерфейс для точек соединения с классами, реализующими подписку
// на события
// Интерфейс предназначен для реализации подписки в классах, которые не могут
// наследоваться от TEventConnectionPoint (или это не целесообразно)
// Для реализации этого интерфейса в передатчике событий можно использовать
// композицию класса TEventConnectionPoint
IEventConnectionPoint = interface(IUnknown)
["{C0BAC6F7-B6A3-44CA-B8AC-F9ECE673E57C}"]
// Подписаться на события
// Вызывается объектом, желающим подписаться на события
// Результат функции - идентификатор подписки, используется
// при вызове метода Unadvise для отписки
function Advise(AEventNotification: TEventNotification): Integer;
// Отписаться от события, AdviseID - идентификатор подписки
procedure Unadvise(AdviseID: Integer);
end;
// Класс, реализующий функциональность подписки на события и отписки от них
// К одному и тому же отправителю событий может подписаться несколько клиентов
// Подписка клиентов хранится в списке подписчиков
TEventConnectionPoint = class(TUnknownBase)
private
FAdviseList: TList;
FEventObject: TEventObject;
function GetAdviseList: TList;
function GetCount: Integer;
function GetEventObject: TEventObject;
private
property AdviseList: TList read GetAdviseList;
property Count: Integer read GetCount;
property EventObject: TEventObject read GetEventObject;
public
destructor Destroy; override;
// Подписаться на событие (см. IEventConnectionPoint.Advise)
function Advise(AEventNotification: TEventNotification): Integer;
// Отписаться от события (см. IEventConnectionPoint.Unadvise)
procedure Unadvise(AdviseID: Integer);
// Получить экземпляр класса TEventObject и установить его свойства
// Эта функция-помощник предназначена для отправки уведомлений о событиях
// класса TEventObject. Функция создает класс только один раз и кеширует
// экземпляр TEventObject
// Не освобождайте экземпляр, полученный от этой функции.
// если необходимы более сложные объекты события - создавайте их самостоятельно
function GetSimpleEventObject(ASender: TObject; AEventID: Integer): TEventObject;
// Послать уведомления о событии AEventObject всем подписчикам
procedure Notify(AEventObject: TEventObject);
end;
// ******************************************************
{ TEventObject }
constructor TEventObject.Create(ASender: TObject; AEventID: Integer);
begin
SetEventInfo(ASender, AEventID);
end;
procedure TEventObject.SetEventInfo(ASender: TObject; AEventID: Integer);
begin
FSender := ASender;
FEventID := AEventID;
end;
{ TEventConnectionInfo }
constructor TEventConnectionInfo.Create(
AEventNotification: TEventNotification);
begin
FOnEventNotification := AEventNotification;
end;
procedure TEventConnectionInfo.Notify(EventObject: TEventObject);
begin
FOnEventNotification(EventObject);
end;
{ TEventConnectionPoint }
destructor TEventConnectionPoint.Destroy;
begin
FreeAndNil(FEventObject);
FreeAndNil(FAdviseList);
inherited;
end;
function TEventConnectionPoint.GetEventObject: TEventObject;
begin
if FEventObject = nil then
FEventObject := TEventObject.Create(nil, 0);
Result := FEventObject;
end;
function TEventConnectionPoint.GetSimpleEventObject(ASender: TObject;
AEventID: Integer): TEventObject;
begin
Result := EventObject;
Result.SetEventInfo(ASender, AEventID);
end;
function TEventConnectionPoint.GetAdviseList: TList;
begin
if FAdviseList = nil then
FAdviseList := TList.Create;
Result := FAdviseList
end;
function TEventConnectionPoint.GetCount: Integer;
begin
if FAdviseList <> nil then
Result := FAdviseList.Count
else
Result := 0;
end;
function TEventConnectionPoint.Advise(
AEventNotification: TEventNotification): Integer;
var
ConnectionInfo: TEventConnectionInfo;
LocalAdviseList: TList;
begin
LocalAdviseList := AdviseList;
LocalAdviseList.Expand;
ConnectionInfo := TEventConnectionInfo.Create(AEventNotification);
LocalAdviseList.Add(ConnectionInfo);
Result := Integer(Pointer(ConnectionInfo));
end;
procedure TEventConnectionPoint.Unadvise(AdviseID: Integer);
var
Index: Integer;
ConnectionInfo: TEventConnectionInfo;
LocalAdviseList: TList;
begin
ConnectionInfo := TEventConnectionInfo(Pointer(AdviseID));
Assert(Count > 0);
if Count = 0 then
Exit;
LocalAdviseList := AdviseList;
Index := LocalAdviseList.IndexOf(ConnectionInfo);
Assert(Index >= 0);
if Index < 0 then
Exit;
LocalAdviseList.Delete(Index);
ConnectionInfo.Free;
end;
procedure TEventConnectionPoint.Notify(AEventObject: TEventObject);
var
i: Integer;
LocalAdviseList: TList;
ConnectionInfo: TEventConnectionInfo;
begin
if Count > 0 then
begin
LocalAdviseList := AdviseList;
for i := 0 to LocalAdviseList.Count - 1 do
begin
ConnectionInfo := TEventConnectionInfo(LocalAdviseList.Items[i]);
ConnectionInfo.Notify(AEventObject);
end;
end
end;
//***************************************************
Сейчас еще дам пример использования...
← →
Владислав © (2005-11-24 09:16) [12]Продолжение...
Какой-то класс должен уведомлять о своих изменениях.
Вот простой пример реализации:// идентификаторы событий:
TIB_EVENT_LIST_IDS = (
EVENTID_IB_EVENT_LIST_ADD = 0,
EVENTID_IB_EVENT_LIST_CLEAR = 1
);
// Это класс для объектов специального события. В нем есть дополнительная информация о событии
TIBEventListNotifyObject = class(TEventObject)
private
FEventInfo: TIBEventInfo;
public
property EventInfo: TIBEventInfo read FEventInfo;
end;
// Класс, реализующий подписку на свои события
// (только ключевые моменты)
// Интерфейс подписчика
TIBEventList = class(TObject, IEventConnectionPoint)
...
// Агрегируем объект, который реализует подписку
FEventConnectionPoint: TEventConnectionPoint;
...
property EventConnectionPoint: TEventConnectionPoint
read FEventConnectionPoint implements IEventConnectionPoint;
// Оповещение о простых событиях
procedure TIBEventList.Clear;
var
LocalEventObject: TEventObject;
...
begin
...
with EventConnectionPoint do
begin
LocalEventObject :=
GetSimpleEventObject(Self, Integer(EVENTID_IB_EVENT_LIST_CLEAR));
Notify(LocalEventObject);
end;
end;
// А это событие посложнее...
procedure TIBEventList.AddEvent(const Database: string; Event: string;
Count: Integer);
var
LocalEventObject: TIBEventListNotifyObject;
...
begin
...
LocalEventObject :=
TIBEventListNotifyObject.Create(Self, Integer(EVENTID_IB_EVENT_LIST_ADD));
try
LocalEventObject.FEventInfo := EventInfo;
EventConnectionPoint.Notify(LocalEventObject);
finally
LocalEventObject.Free;
end;
end;
А сейчас покажу подписку и получение событий клиентом...
← →
Владислав © (2005-11-24 09:21) [13]
// Подписка на события клиентом.
constructor TfmAlerts.Create(AOwner: TComponent);
var
i: Integer;
EventList: TIBEventList;
EventInfo: TIBEventInfo;
ConnectionPoint: IEventConnectionPoint;
begin
inherited;
...
EventList := TIBEventList.Create;
try
// Получили интерфейс
ConnectionPoint := EventList;
// подписались и сохранили результат (идентификатор подписки)
FAdviseID := ConnectionPoint.Advise(EventNotification);
...
finally
// Не удивляйтесь, что мы уничтожаем экземпляр.
// Он у нас "одиночка" с подсчетом ссылок
EventList.Free;
end
end;
// Отписка
destructor TfmAlerts.Destroy;
var
EventList: TIBEventList;
ConnectionPoint: IEventConnectionPoint;
begin
EventList := TIBEventList.Create;
try
ConnectionPoint := EventList;
ConnectionPoint.Unadvise(FAdviseID);
finally
EventList.Free;
end;
...
inherited;
end;
// Обработка событий
procedure TfmAlerts.EventNotification(const AEventObject: TEventObject);
var
AddEventObject: TIBEventListNotifyObject;
EventInfo: TIBEventInfo;
begin
try
case AEventObject.EventID of
Integer(EVENTID_IB_EVENT_LIST_CLEAR):
begin
tlAlerts.Clear;
end;
Integer(EVENTID_IB_EVENT_LIST_ADD):
begin
if AEventObject is TIBEventListNotifyObject then
begin
AddEventObject := TIBEventListNotifyObject(AEventObject);
EventInfo := AddEventObject.EventInfo;
AddEvent(EventInfo.DatabaseName, EventInfo.EventName,
EventInfo.EventCount, EventInfo.DateTime);
end
end;
end;
except
Application.HandleException(Self);
end
end;
← →
GuAV © (2005-11-24 15:06) [14]Leonid Troyanovsky © (24.11.05 8:40) [9]
> С сообщениями все в порядке, дело в твоей реализации.
Наверное. В любом случае сообщения не самоцель, цель сабж, поэтому больше тратить времени на них не хочу.
> А, вообще-то, сама исходная постановка хромает на обе
> ноги.
?
Думаю нет.
> Т.е., либо нужна модель, основанная на TComponent with
>Actions, либо
> все классы д.б. собс-ручные, с тем самым интерфейсом,
>который будет
>вызываться по списку.
> Но, и в последнем случае, TComponent предпочтительней,
>бо уже обладает
> механизмом Notification, позволяющим оный список
> обновлять.
Пожалуйста, если можно, подробнее. Дело в том, что, как сейчас уже выяснилось все "подписчики" являются компонентами, ActionList-ы используются. Но я так и не понял, как создать свой Aсtion со своими параметрами и запускать его.
Владислав © (24.11.05 9:03) [10]
Спасибо. Интересно. Но сложновато.
Как я понял идею (поправьте если не так):
Существуют один или несколько классов списков событий, все они агрегируют TEventConnectionPoint для реализации IEventConnectionPoint.
TEventConnectionPoint является фактическим менеджером списка подписчиков и диспетчером событий, он готов разослать всем подписчикам любого наследника TEventObject.
У подписчиков единый обработчик типа TEventNotification. События различаются по Id, затем явно приводятся к нужному классу-наследнику TEventObject.
Преимущества:
- Один цикл рассылки и одна реализация регистратора не только для всех событий списка, но и для всех списков событий. Очень хорошо.
Недостатки:
- Сложная реализация.
- Один обработчик с большим case. Это неприятно. (именно по этому я пытался завязаться на сообщениях).
- Необходимость запоминать свой AdviseID. Я бы возможно сделал поиск TEventNotification в Unadvise для исключения этого (на этом ИМХО много не потеряешь).
Посмотрите, кстати, [2] последняя строка, обратный цикл и в Вашей реализации будет лучше.
← →
GuAV © (2005-11-24 15:08) [15]Сейчас используется вариант jack128 - лучше чем было.
type
TObjEvents = class(TObject)
private
FCreator: TObject;
public
...
FOnObjectsChanged: procedure(Sender: TList) of object;
FOnMatrixChanged: procedure(Sender: TObject) of object;
constructor Create(Creator: TObject);
destructor Destroy; override;
...
class procedure DoObjectChanged(Sender: TMapPolygon);
class procedure DoMatrixChanged(Sender: TObject);
end;
...
class function TObjEvents.Find(Creator: TObject): TObjEvents;
var I: Integer;
begin
for I := EventsList.Count - 1 downto 0 do
with TObjEvents(EventsList[I]) do
if FCreator = Creator then
begin
Result := TObjEvents(EventsList[I]);
Exit;
end;
Result := nil;
end;
...
class procedure TObjEvents.DoObjectChanged(Sender: TMapPolygon);
var I: Integer;
begin
for I := EventsList.Count - 1 downto 0 do
with TObjEvents(EventsList[I]) do
if Assigned(FOnObjectChanged) then
FOnObjectChanged(Sender);
end;
...
constructor TObjEvents.Create(Creator: TObject);
begin
inherited Create;
FCreator := Creator;
EventsList.Add(Self);
end;
destructor TObjEvents.Destroy;
begin
EventsList.Remove(Self);
inherited;
end;
initialization
EventsList := TObjectList.Create;
...
finalization
...
EventsList.Free;
end.
Подписка:
with TObjEvents.Create(Self) do
begin
FOnObjectChanged := ObjectChanged;
FOnObjectsChanged := ObjectsChanged;
FOnGeoCoordChanged := GeoCoordChanged;
end;
Отписка:
TObjEvents.Find(Self).Free;
Запуск:
TObjEvents.DoMatrixChanged(Self);
Преимущества:
- Очень простые запуск, подписка и отписка.
- "Натуральные" события, свой обработчик для каждого события.
- Простая и "легковесная" реализация.
- Единый список.
Недостаток:
- Свой цикл для каждого события.
← →
GuAV © (2005-11-24 15:40) [16]GuAV © (24.11.05 15:08) [15]
>EventsList.Remove(Self);
EventsList.Extract(Self);
т.к. я перешел на владеющий TObjectList.
← →
Leonid Troyanovsky © (2005-11-24 15:47) [17]
> GuAV © (24.11.05 15:06) [14]
> Пожалуйста, если можно, подробнее. Дело в том, что, как
> сейчас уже выяснилось все "подписчики" являются компонентами,
> ActionList-ы используются. Но я так и не понял, как создать
> свой Aсtion со своими параметрами и запускать его.
Имелось ввиду, что нужна реализация Actions for TComponent,
бо Actions for TWincontrol расчитана на видимые контролы.
Ну, а реализацию можно подсмотреть в том же VCL.
А если уж выяснилось, что это компоненты, то, по-крайней мере,
необходимо использовать FreeNotification для обновления списка.
--
Regards, LVT.
← →
GuAV © (2005-11-24 16:10) [18]Leonid Troyanovsky © (24.11.05 8:40) [9]
>либо
> все классы д.б. собс-ручные, с тем самым интерфейсом,
>который будет
>вызываться по списку.
> Но, и в последнем случае, TComponent предпочтительней,
>бо уже обладает
> механизмом Notification, позволяющим оный список
> обновлять.
Один интерфейс на все события не годится. Т.к. добавление новых событий будет связано с изменением этого интерфейса. Можно завести для каждого события (или для небольших групп событий) свой интерфейс. Тогда при вызове события диспечтер запрашивает у каждого подписчика интерфейс события и вызывает его при его наличии.
Однако, это ничем не лучше варианта [15], т.к. всё равно несколько циклов.
Вообще говоря, чтобы обеспечить единый цикл рассылки требуется ввести единый тип параметра события - в любом случае (ассемблер и манипуляция с моделями вызовов неприемлимы, при том и они вряд ли бы помогли). Это уже можно сделать при варианте [15], передавая в каждое событие один var параметр (а для цикла расылки он будет нетипизированным var параметром). Я посмотрю, стоит ли.
Notification не нужен, желательна подписка и отписка вручную.
← →
Leonid Troyanovsky © (2005-11-24 16:14) [19]
> Leonid Troyanovsky © (24.11.05 15:47) [17]
>
> > GuAV © (24.11.05 15:06) [14]
Пару слов дополню про TComponent.
Если нет возможности (желания) сделать все на основе базового класса,
то в нашем распоряжении есть Tag в котором можно сохранить ссылку на
тот самый метод, который будет вызываться по списку.
В качестве Sender, видимо, будет использован вызывающий,
а Self - сам элемент списка {TNotifyEvent(TMethod)}.
Можно обойтись и без собственного списка, если сделать вызывающий
компонент Owner вызываемых.
Notification в таком случае он будет получать автоматом.
В любом случае, чем проще и понятнее будет эта модель,
тем волосы будут более пушистыми.
--
Regards, LVT.
← →
Leonid Troyanovsky © (2005-11-24 16:23) [20]
> GuAV © (24.11.05 16:10) [18]
> Один интерфейс на все события не годится. Т.к. добавление
> новых событий будет связано с
Если внимательно и мучительно размышлять, то окажется, что
интерфейса TNotifyEvent вполне хватит для своих событий, т.е.,
для тех, что происходят в собственном приложении.
Просто, это самый распространенный обработчик (а откуда вдруг
возьмутся другие?).
Для взаимодействия с другими приложениями есть другие решения,
типа издатель-подписчик (см пример в demos).
Ну, а по поводу Notification - это должно быть правилом при любом
использовании ссылок на другие компоненты .
--
Regards, LVT.
← →
Владислав © (2005-11-24 16:33) [21]
> Владислав © (24.11.05 9:03) [10]
>
> Спасибо. Интересно. Но сложновато.
На самом деле нет. Даже реализация идеи проста (пост 11), а использование еще проще.
> Как я понял идею (поправьте если не так):
> Существуют один или несколько классов списков событий, все
> они агрегируют TEventConnectionPoint для реализации IEventConnectionPoint.
>
>
> TEventConnectionPoint является фактическим менеджером списка
> подписчиков и диспетчером событий, он готов разослать всем
> подписчикам любого наследника TEventObject.
>
> У подписчиков единый обработчик типа TEventNotification.
> События различаются по Id, затем явно приводятся к нужному
> классу-наследнику TEventObject.
Ну где-то верно поняли, а где-то кто-то другого не понял :)
Я своими словами изложу суть. Может это что-то прояснит.
1. Есть какой-то объект (пусть это будет тип № 1) (экземпляр какого-то класса), в котором могут происходить различные по своей сути события. Этот объект может оповещать об этих событиях желающих (В моей ситуации такой класс уже был написан до меня, но оповещения не было, а желающие появились :-) ).
2. Есть какие-то объекты (пусть их тип будет № 2) (экземпляры других и различных классов, не связанных общим предком, ну кроме TObject :-) ), которым было бы неплохо получать уведомления о каких-то событиях, происходящих в каких-то объектах. Замечу, что не всегда, опять же, у последних может быть общий предок, кроме TObject.
Это я свою конкретную ситуацию изложил.
Ну а теперь в этой ситуации нужно организовать посылку уведомлений.
Вот последовательность действий.
1. Объект первого типа "объявляет события", о которых он будет уведомлять. В данном случае нужно объявить целочисленные константы.
2. Объект первого типа "агрегирует" экземпляр TEventConnectionPoint.
Т.е. в класс добавляем наследование интерфейса IEventConnectionPoint. Где-то, например в конструкторе/деструкторе добавляем создание/уничтожение экземпляра TEventConnectionPoint.
3. Объект первого типа "добавляет" уведомления о событиях. Т.е. в местах кода, где возникает событие, добавляем код вызова TEventConnectionPoint.Notify.
4. В объекты второго типа добавляется метод, который будет вызываться при событии.
5. В объекты второго типа добавляется код Advise/Unadvise.
>
> Преимущества:
> - Один цикл рассылки и одна реализация регистратора не только
> для всех событий списка, но и для всех списков событий.
> Очень хорошо.
+ Ни подписчики ни отправитель уведомлений не обязаны быть наследниками каких то классов, значит можно добавить эту реализацию в уже существующую систему.
+ По той же причине, что и в предыдущем случае... Ни подписчик, ни отправитель могут ничего не знать ни о интерфейсе, ни о реализации друг-друга. Возможно независимое изменение.
+ К одному отправителю уведомлений могут подписаться несколько подписчиков.
+ Один подписчик может подписаться на уведомления разных подписчиков. Для этого не обязательно писать дополнительный код.
+ Подписчик не обязан реагировать на все уведомления. При этом исключается написание дополнительного кода.
+ Возможность отправки/получения уведомлений, содержащих фактически любой объем информации о событии.
> Недостатки:
> - Сложная реализация.
> - Один обработчик с большим case. Это неприятно. (именно
> по этому я пытался завязаться на сообщениях).
> - Необходимость запоминать свой AdviseID. Я бы возможно
> сделал поиск TEventNotification в Unadvise для исключения
> этого (на этом ИМХО много не потеряешь).
На счет сложности я уже сказал.
На счет case - это единственное место, где нужно писать возможно (!) объемный код. Однако заметьте, на сообщениях вы так или иначе напишите что-то, что будет аналогом.
Запомнить свой AdviseID - 4 байта памяти и пара дополнительных строк кода. Уберем запоминание - вычеркнем некоторые плюсы. Так что спорно.
- На мой взгляд самый большой минус, это обработка каких то сложных событий, для которых пишется свой класс. Приходится проверять и приводить тип события. Здесь, чтобы избежать долгих часов поиска, почему же не приходит уведомление после некоторых изменений классов, нужно наверное генерировать исключение, если получили не тот тип, который ожидали. У меня этого нет. Подумаю над этим.
> Посмотрите, кстати, [2] последняя строка, обратный цикл
> и в Вашей реализации будет лучше.
Замечание здравое. Спасибо. Я об этом не задумывался.
← →
GuAV © (2005-11-24 16:50) [22]Про реакцию на Action я понял следующее: у компонента есть реакция на Action в ExecuteAction; по умолчанию, если у компонента нет особой реакции на Action, сам Action реагирует на компонент (как в паттерн Visitor).
Как правильно запустить диспетчеризацию Action, что делает ActionLink ?
> Если нет возможности (желания) сделать все на основе
> базового класса
возможности (желания) нет.
> то в нашем распоряжении есть Tag в котором можно
>сохранить ссылку на
> тот самый метод, который будет вызываться по списку.
Не хочу "один метод", лучше много методов.
> Если внимательно и мучительно размышлять, то окажется,
>что
> интерфейса TNotifyEvent вполне хватит для своих
>событий, т.е.,
> для тех, что происходят в собственном приложении.
Вот например событиеFOnCaptionChanged: procedure(Sender: TObject; const Old, New: string) of object;
. Где получатель возьмёт старый Caption ?
> Для взаимодействия с другими приложениями
Чур меня :) Здесь одно приложение, своих dll нет и даже один поток.
> Ну, а по поводу Notification - это должно быть
>правилом при любом
> использовании ссылок на другие компоненты .
Ладно. Посмотрю, сделать ли TObjEvents компонентом (с Owner заместо Creator), или делать нотификацию вручную.
← →
GuAV © (2005-11-24 17:06) [23]Владислав © (24.11.05 9:03) [13]
Теперь вроде всё ясно.
Моя пропозиция:
function Advise(AEventNotification: TEventNotification): Integer; overload;
function Advise(AEventNotification: TEventNotification;
AcceptedEvents: array of TClass;
RaiseExceptionIfUnaccepted: Boolean = False): Integer; overload;
procedure Unadvise(AdviseID: Integer); overload;
procedure Unadvise(AEventNotification: TEventNotification); overload;
← →
Владислав © (2005-11-24 17:22) [24]
function Advise(AEventNotification: TEventNotification;
AcceptedEvents: array of TClass; // 1).
RaiseExceptionIfUnaccepted: Boolean = False // 2).
): Integer; overload;
1). Я именно это и пытался обойти. Зависимость между классами.
2). А хорошо ли, если остальные подписчики событие не получат?
← →
GuAV © (2005-11-24 17:38) [25]
> 2). А хорошо ли, если остальные подписчики событие не
> получат?
Исключение может быть поднято после рассылки и содержать информацию обо всех подписчиках, не принявших свои сообщения.
> 1). Я именно это и пытался обойти. Зависимость между
> классами.
Тогда поясните мысль, что значит "зависимость между классами". Подписчик в любом случае должне знать что за класс он принимает.
Если речь идёт об игнорировании старых классов, nо исключении при вновь появившихся, то возможно определить константу EmptyEventNotification (пустой классовый метод) и регистрировать её так
Advise(EmptyEventNotification,
[все, известные, приёмнику, события], True).
Альтернатива Advise была предложена для возможности избежания того самогоcase
сis
. Можно задать различные методы для различных классов событий, при этом в параметре уже правильный класс, а приводить при Advise.
← →
Владислав © (2005-11-24 17:56) [26]
> Тогда поясните мысль, что значит "зависимость между классами".
> Подписчик в любом случае должне знать что за класс он принимает.
Должен то он должен. Но при добавлении нового класса события в издателя, подписчики, которые не изменялись, начнут выкидывать исключения? А может новое добавленное событие им совсем не нужно.
> Если речь идёт об игнорировании старых классов, nо исключении
> при вновь появившихся, то возможно определить константу
> EmptyEventNotification (пустой классовый метод) и регистрировать
> её так
> Advise(EmptyEventNotification,
> [все, известные, приёмнику, события], True).
>
> Альтернатива Advise была предложена для возможности избежания
> того самого case с is. Можно задать различные методы для
> различных классов событий, при этом в параметре уже правильный
> класс, а приводить при Advise.
Не совсем понял эту мысль.
← →
Leonid Troyanovsky © (2005-11-24 18:13) [27]
> GuAV © (24.11.05 16:50) [22]
> Как правильно запустить диспетчеризацию Action, что делает
> ActionLink ?
После некоторого раздумья пришел к выводу, что распространение
Actions на TComponent не имеет особого смысла, т.к. требует изменений
базового класса, что, собс-но, нужно было б избежать.
> Вот например событие FOnCaptionChanged: procedure(Sender:
> TObject; const Old, New: string) of object; . Где получатель
> возьмёт старый Caption ?
Я думаю, что Sender их знать должен.
--
Regards, LVT.
← →
GuAV © (2005-11-24 18:33) [28]Владислав © (24.11.05 17:56) [26]
Тогда противоречие. С одной стороны, Вы хотите поднять исключение при неожиданном событии:
Владислав © (24.11.05 9:03) [21]
> Приходится проверять и приводить тип события. Здесь,
> чтобы избежать долгих часов поиска, почему же не
> приходит уведомление после некоторых изменений
> классов, нужно наверное генерировать исключение, если
> получили не тот тип, который ожидали.
С другой стороны, искличение не требуется:
Владислав © (24.11.05 17:56) [26]
> Но при добавлении нового класса события в издателя,
> подписчики, которые не изменялись, начнут выкидывать
> исключения? А может новое добавленное событие им
> совсем не нужно.
Код из [25] приводит к исключению при новом для объекта событии.
-----
Вообще, не вижу проблемы, т.к. изменение класса события будут видны всем, а имя класса и идентификатор менять вроде как незачем.
Поясняю
GuAV © (24.11.05 17:38) [25]
> Альтернатива Advise была предложена для возможности
> избежания того самого case с is. Можно задать
> различные методы для различных классов событий, при
> этом в параметре уже правильный класс, а приводить при
> Advise.
У меня класс имеющий два обработчикаprocedure OnEvent1(const AEventObject: TEventObject) of object;
procedure OnEvent2(const AEventObject: TEventObject) of object;
Я их регистрирую:Advise(OnEvent1, [TEvent1]): Integer; overload;
Advise(OnEvent2, [TEvent2]): Integer; overload;
К сожалению
>при
> этом в параметре уже правильный класс, а приводить при
> Advise.
не удасться, синтаксис не позволяет.
Вместо различия по классам, можно было бы ввести различие по EventID.
Однако, я считаю, что классов вполне достаточно и на каждое событие следует завести свой класс:type
TEvent1 = class(TEventObject)
end;
TEvent2 = class(TEventObject)
end;
← →
GuAV © (2005-11-24 18:46) [29]
> После некоторого раздумья пришел к выводу, что
>распространение
> Actions на TComponent не имеет особого смысла, т.к.
>требует изменений
> базового класса, что, собс-но, нужно было б избежать.
Как раз с этим проблем нет. Все подписчики - компоненты. Все actions дисптчеризируются всем компонентам (иначе как статусбар получает новый хинт), при этом кому не надо, игнорирует (например TXPMainfest).
Речь идёт не об неизменности базового класса при необходимости новой обработки (что тоже достигается, код TComponent.ExecuteAction вызывает Action.ExecuteTarget), а об неизменности классов, которым событие не нужно.
> Я думаю, что Sender их знать должен.
Если бы, у меня был бы один тип события. В моих событиях параметр Old - единственная копия старого значения (параметр New действительно избыточен, но уже так есть).
← →
Владислав © (2005-11-25 09:24) [30]
> GuAV © (24.11.05 18:33) [28]
>
> Тогда противоречие. С одной стороны, Вы хотите поднять исключение
> при неожиданном событии:
Нет противоречия. Здесь речь о разных вещах.
// Пример № 1
// Например, издатель объявляет событие:
EVENTID_SOME_EVENT = 1;
// Класс для него
TSomeEvent = class(TEventObject)
...
end;
// Пример № 1
// В подписчике есть обработчик
...
case AEventObject.EventID of
EVENTID_SOME_EVENT:
SomeEventHandler(AEventObject as TSomeEvent); // вот это исключение мне нужно.
// Если при EventID = EVENTID_SOME_EVENT класс объекта события не TSomeEvent - это плохо. Это ошибка.
Дальше... Все течет, все изменяется. Система усложнилась. В издателе добавилось новое событие.
// Пример № 1
EVENTID_NEW_EVENT = 2
TNewEvent = class(TEventObject)
...
end;
Ну и добавилось, ну и слава богу. Возможно какому-то из подписчиков такое событие понадобилось. Вот такого подписчика и нужно научить обрабатывать новое событие. А все остальные должны продолжать работать без изменений. Т.е. обработчик в примере № 2 должен остаться работоспособным без переписывания его кода.
Надеюсь, понятно изложил.
← →
GuAV © (2005-11-25 12:00) [31]Владислав © (25.11.05 9:24) [30]
> SomeEventHandler(AEventObject as TSomeEvent); // вот
> это исключение мне нужно
> // Если при EventID = EVENTID_SOME_EVENT класс объекта
> события не TSomeEvent - это плохо. Это ошибка.
Каким образом это может оказаться ?
И я всё же предлагаю для каждого события писать свой класс и различать события именно по классам.
Думаю, если оставлять как есть, наилучшим вариантом будет проверка уже в обработчике, как в Вашем примере. Проверку в TEventConnectionPoint можно осуществлять так:
proceudre TEventConnectionPoint.AddIdChecking(Event: TClass; ID: Integer);
proceudre TEventConnectionPoint.DoIdChecking(Event: TEventObject)
Исключение будет выброшенно, если ID в списке упоминается, но ни разу с этим Event
← →
Владислав © (2005-11-25 12:53) [32]
> Каким образом это может оказаться ?
Человеку (программисту) свойственно ошибаться.
> И я всё же предлагаю для каждого события писать свой класс
> и различать события именно по классам.
Слишком накладно для наращивания, мне кажется. Да и если обработчик таки один, то какая разница? Проверка типов нужна будет.
> proceudre TEventConnectionPoint.AddIdChecking(Event: TClass;
> ID: Integer);
>
> proceudre TEventConnectionPoint.DoIdChecking(Event: TEventObject)
Это дополнительный код, который должен вызывать подписчик.
А вот это не проще написать AEventObject as TSomeEvent? ;)
← →
Leonid Troyanovsky © (2005-11-25 14:39) [33]
> GuAV © (24.11.05 18:46) [29]
> Речь идёт не об неизменности базового класса при необходимости
> новой обработки (что тоже достигается, код TComponent.ExecuteAction
> вызывает Action.ExecuteTarget), а об неизменности классов,
> которым событие не нужно.
TComponent.ExecuteAction д.б. override, т.е., для уже существуещего
компонента это не пройдет. Ну, или, нет даже у TControl того самого
поля, которое можно было задействовать для этой цели.
> > Я думаю, что Sender их знать должен.
> Если бы, у меня был бы один тип события. В моих событиях
> параметр Old - единственная копия старого значения (параметр
> New действительно избыточен, но уже так есть).
Ну, хорошо, пусть Sender будет промежуточным компонентом,
определенного класса, известного получателю.
Т.е., там будут как поля для записи, так и для чтения.
А после вызова (отправки компонента получателю)
отправляющий может проанализировать изменения.
Ну, а получатели будут выбирать из посылок лишь те объекты,
в обработке которых они заинтересованы (которым обучены).
Хотя, если вся эта модель направлена на согласование поведения
(состояния) группы контролов, то я бы избрал немного другой путь.
--
Regards, LVT.
← →
GuAV © (2005-11-25 15:05) [34]Владислав © (25.11.05 12:53) [32]
> А вот это не проще написать AEventObject as
> TSomeEvent? ;)
Проще.
---
Спасибо за идею.
Но я при её реализации всё наверное же буду делать несколько обработчиков и без EventID, с определнием обработчика по классу события.
Leonid Troyanovsky © (25.11.05 14:39) [33]
> TComponent.ExecuteAction д.б. override, т.е., для уже
>существуещего
> компонента это не пройдет.
Не обязательно. Вот например Edit ничего не знает о TEditAction и его наследнике TEditCopy (доказано GREPом), а они есть:procedure TEditCopy.ExecuteTarget(Target: TObject);
begin
GetControl(Target).CopyToClipboard;
end;
function TEditAction.GetControl(Target: TObject): TCustomEdit;
begin
{ We could hard cast Target as a TCustomEdit since HandlesTarget "should" be
called before ExecuteTarget and UpdateTarget, however, we"re being safe. }
Result := Target as TCustomEdit;
end;
function TEditAction.HandlesTarget(Target: TObject): Boolean;
begin
Result := ((Control <> nil) and (Target = Control) or
(Control = nil) and (Target is TCustomEdit)) and TCustomEdit(Target).Focused;
end;
> Хотя, если вся эта модель направлена на согласование
>поведения
> (состояния) группы контролов, то я бы избрал немного
> другой путь.
Частично согласование группы контролов (тут реально нужен или Sender или ничего, хотя в существующем коде юзается новый параметр), частично перерисовка PaintBox"ов (старый и новый параметры для рассчёта региона перерисовки). Какой путь ?
← →
GuAV © (2005-11-25 15:09) [35]Владислав © (25.11.05 12:53) [32]
> Слишком накладно для наращивания, мне кажется. Да и
> если обработчик таки один, то какая разница? Проверка
> типов нужна будет.
Никакой разницы, добавить Id или пустой класс-потомок. Можно взять за правило делать событие пустым классом-наследником какого либо класса и накладности не будет.
Зато рассылка событися по соответствующим обработчикам (по типу класса, указанному при регистрации обработчика), проверка в них уже не нужна - только приведение.
← →
Владислав © (2005-11-25 15:46) [36]
> Никакой разницы, добавить Id или пустой класс-потомок. Можно
> взять за правило делать событие пустым классом-наследником
> какого либо класса и накладности не будет.
Ну либо я опять не понял Вашу идею, либо разница есть. Может быть она не так ощутима. В моем варианте подписчик получит уведомление без изменения обработчика, после добавления нового события ;) Хотя это уже мелочи.
Впрочем, у нас задачи разные, так что я перестаю советовать :)
← →
Leonid Troyanovsky © (2005-11-25 16:10) [37]
> GuAV © (25.11.05 15:05) [34]
> Не обязательно. Вот например Edit ничего не знает о TEditAction
> и его наследнике TEditCopy
Да с TWinControl проблем-то и нет, бо для них Actions и сделаны.
А уже у TControl тех полей (которые можно б было задействовать
уже нет). Это и понятно, ведь их назначение - согласование контролов,
отвечающих за пользовательский ввод, ну, а о своем содержимом
винконтролы пусть заботятся сами.
> Частично согласование группы контролов (тут реально нужен
> или Sender или ничего, хотя в существующем коде юзается
> новый параметр), частично перерисовка PaintBox"ов (старый
> и новый параметры для рассчёта региона перерисовки). Какой
> путь ?
Когда-то давно, когда еще не было никаких Actons люди пользовались
таким приемом (пример) : для согласования состояния меню,
кнопок и т.п. с дублирующей функциональностью делалось некое
свойство у, например, содержащей их формы.
Это свойсто может быть set или нечто более сложное, но главным
для этого свойста были побочные эффекты на, например, на write,
в котором устанавливались нужные состояния затронутых изменением
контролов.
Т.е., для того, чтобы, например, сделать (не)доступным все контролы,
относящиеся к чему-либо, достаточно в обработчике события этого
контрола (методе формы) изменить нужным образом это свойство.
Если попытаться сказать кратко, то это проблема относится не
столько к самим контролам, а к их контейнеру, и решаться должна
не модификацией контролов, а модификацией контейнера, для
обеспечения требуемого поведения.
--
Regards, LVT.
← →
GuAV © (2005-11-25 16:25) [38]
> В моем варианте подписчик получит уведомление без
> изменения обработчика, после добавления нового события
> ;)
А зачем оно ему нужно, если он не знает что делать ?
Разница есть, но ИМХО оба варианта имеют право на существование, и даже могут быть реализованы одним диспетчером.
> так что я перестаю советовать :)
Собственно идею Вашу я уже понял, однако действительно интерес чисто теоретический, на будущее. Удобство внедрения - хорошее преимущество, но в моём случае уже всё внедрено :)
← →
GuAV © (2005-11-25 16:40) [39]Leonid Troyanovsky © (25.11.05 16:10) [37]
У меня разные формы, фреймы. Контейнером для всех, разумеется является Application, однако в VCL проектах наследника Application не пишут.
Делать одну из форм диспетчером - неудобно. Поэтому отдельный класс диспетчера. Более того, приёмниками событий изначально были не только контролы, но и классы в некоторых списках, это уже переделано.
Но зачем прописывать каждый контрол, если он может прописать себя сам, тогда это будет в его модуле, а не в модуль диспетчера.
С контролами одного модуля у меня именно так, через
св-ва контейнера.
← →
Владислав © (2005-11-25 16:45) [40]
> А зачем оно ему нужно, если он не знает что делать ?
Ну кроме case еще есть if... Может быть группа событий :)
if
(AEventObject.EventID >= EVENTID_SOME_FIRST) and
(AEventObject.EventID <= EVENTID_SOME_LAST)
then
ReloadSomeProperties(AEventObject);
Страницы: 1 2 вся ветка
Текущий архив: 2005.12.25;
Скачать: CL | DM;
Память: 0.71 MB
Время: 0.014 c