Обработка события класса с помощью обычной процедуры (Delphi)

Я программно создаю объект подключения к базе данных внутри простой процедуры (а не метода в классе).

mydb:= TUniConnection.Create(nil);
mydb.Database:= knowledge_db_name;
mydb.LoginPrompt:= False;
mydb.Username:= aaa;
mydb.Password:= bbb;

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

mydb.OnError:= OnConnectionError;
mydb.OnConnectionLost:= OnConnectionLost;

Компилятор говорит мне

[DCC Error] test.pas(373): E2009 Incompatible types: 'method pointer and regular procedure'

Как я могу обойти это? Вот определения процедур обработки событий:

procedure OnConnectionError(Sender: TObject; E: EDAError; var Fail: Boolean);
procedure OnConnectionLost(Sender: TObject; Component: TComponent; ConnLostCause: TConnLostCause; var RetryMode: TRetryMode);

person Miguel E    schedule 18.06.2012    source источник


Ответы (4)


Если у вас нет подходящего класса для размещения обработчиков событий, вы можете определить фиктивный класс и сделать обработчики событий class procedures. Тогда вам не нужно создавать экземпляр класса, но вы можете назначить mydb.OnError:= TMyDummyEventHandlerClass.OnConnectionError;.

Вот пример: я использую разные события, потому что у меня нет TUniConnection, но я хочу убедиться, что все компилируется. :-)

type
  // Dummy class to hold event handlers:
  TXMLEventHandlers = class
    // Event handlers:
    class procedure OnBeforeOpen(Sender: TObject);
    class procedure OnAfterOpen(Sender: TObject);
  end;

class procedure TXMLEventHandlers.OnBeforeOpen(Sender: TObject);
begin
  MessageBox(0, PChar(ClassName + '.OnBeforeOpen'), nil, 0)
end;

class procedure TXMLEventHandlers.OnAfterOpen(Sender: TObject);
begin
  MessageBox(0, PChar(ClassName + '.OnAfterOpen'), nil, 0)
end;

procedure Test;
var
  xml: TXMLDocument;
begin
  xml := TXMLDocument.Create(nil);
  try
    // Note: No instance of `TXMLEventHandlers` must be created:
    xml.AfterOpen := TXMLEventHandlers.OnAfterOpen;
    xml.BeforeOpen := TXMLEventHandlers.OnBeforeOpen;

    xml.Active := True; // Calls the two event handlers
  finally
    xml.Free;
  end;
end;
person Uli Gerhardt    schedule 18.06.2012
comment
Хм... это означает, что Self, переданный в процедуры класса, не является типом класса, это экземпляр класса. Я немного ошеломлен тем, что это назначается без ошибок. - person Rudy Velthuis; 12.07.2016
comment
@Rudy, AFAIU, переменная события более или менее TMethod. Data — это любой указатель, а Code указывает на подпрограмму, которая принимает что-то похожее на указатель (например, экземпляр класса или метапеременную) в качестве первого аргумента. С точки зрения ассемблера это одно и то же. - person Uli Gerhardt; 12.07.2016
comment
Синтаксис обработчика событий — это просто синтаксический сахар + к концепции добавлена ​​некоторая проверка типов. - person Uli Gerhardt; 12.07.2016
comment
Да, но именно синтаксическая часть и проверка типов заставляют задуматься, почему он компилируется. - person Rudy Velthuis; 12.07.2016
comment
С точки зрения ассемблера все одинаково. Но в Delphi вы не можете присвоить событию обработчик неправильного типа, так как же вы можете присвоить ему метод класса? - person Rudy Velthuis; 12.07.2016
comment
Я предполагаю, что кто-то реализовал это в компиляторе, потому что это было легко и концептуально правильно: если вы не возитесь с собой (например, с TMethod приведениями), все гарантированно подходит друг к другу: либо переменная экземпляра + обычный метод, либо переменная типа класса (мета) + класс метод. - person Uli Gerhardt; 12.07.2016
comment
Давайте продолжим обсуждение в чате. - person Uli Gerhardt; 12.07.2016

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

type
  TMyEventHandler = record
    procedure OnConnectionError(Sender: TObject; E: EDAError; var Fail: Boolean);
    procedure OnConnectionLost(Sender: TObject; Component: TComponent; ConnLostCause: TConnLostCause; var RetryMode: TRetryMode);
  end;

procedure TMyEventHandler.OnConnectionError(Sender: TObject; E: EDAError; var Fail: Boolean);
begin
  ....
end;

procedure TMyEventHandler.OnConnectionLost(Sender: TObject; Component: TComponent; ConnLostCause: TConnLostCause; var RetryMode: TRetryMode);
begin
  ....
end;

var
  EventHandler: TEventHandler;//global variable

......

mydb.OnError := EventHandler.OnConnectionError;
mydb.OnConnectionLost := EventHandler.OnConnectionLost;
person David Heffernan    schedule 18.06.2012
comment
Я думаю, что ответ Ули лучше этого. На вашем месте я бы принял ответ Ули. - person David Heffernan; 18.06.2012

Две ваши процедуры обработки событий должны быть методами класса, а не локальными/глобальными процедурами. Например:

procedure TForm1.OnConnectionError(Sender: TObject; E: EDAError; var Fail: Boolean);
person pritaeas    schedule 18.06.2012
comment
Я знаю это, но вопрос в том, смогу ли я сделать это без использования класса (может быть, какого-то приведения...). - person Miguel E; 18.06.2012
comment
Если у вас нет класса, готового для размещения обработчиков событий, вы можете определить фиктивный класс и сделать обработчики событий class procedures. Тогда вам не нужно создавать экземпляр класса, но вы можете назначить mydb.OnError:= TMyDummyEventHandlerClass.OnConnectionError; . - person Uli Gerhardt; 18.06.2012
comment
@DavidHeffernan Можете ли вы привести пример глобального экземпляра записи? - person Miguel E; 18.06.2012
comment
@DavidHeffernan Вам нужно очень постараться, чтобы ошибиться в коде с процедурой класса. Параметр Self для процедуры класса является ссылкой на класс, а не экземпляром, поэтому вы даже не можете случайно получить доступ к каким-либо членам класса, которые вы могли добавить по ошибке. nil Я гораздо легче случайно ошибиться. - person ; 18.06.2012
comment
@DavidHeffernan У вас действительно не будет экземпляра, но компилятор тоже не подумает, что у вас есть экземпляр (в отличие от нулевого приведения). Попробуйте получить доступ к члену экземпляра и получите ошибку компилятора. - person ; 18.06.2012
comment
@DavidHeffernan Нет, это работает. Процедура класса имеет скрытый параметр Self, но это ссылка на класс (думаю, TClass). - person ; 18.06.2012
comment
@DavidHeffernan Это работает. Я убедился в этом, прежде чем комментировать. :) - person ; 18.06.2012
comment
@ Дэвид, подожди - я сделаю свой комментарий компилируемым ответом. :-) - person Uli Gerhardt; 18.06.2012
comment
О, я говорю кучу чепухи. Я сегодня кое-что узнал! Очередной раз! Я всегда предполагал, что of object требуется информация об экземпляре. Но теперь я вижу, что это не так, и, думая об этом, я полностью понимаю, почему это работает нормально. Спасибо, что просветили меня, и извините за то, что написал такую ​​ерунду. И метод класса на самом деле более чистый способ сделать это, чем мой подход к записи. - person David Heffernan; 18.06.2012
comment
Извините, неправильно понял. Если бы не все комментарии, я бы удалил свой ответ. - person pritaeas; 18.06.2012

Хотя использование метода class является чистым решением, возможно использовать обычную неклассовую процедуру в качестве обработчика событий, вы просто не можете назначить ее напрямую, но вы можете назначить его косвенно с помощью записи TMethod . Вам просто нужно добавить в процедуру явный параметр Self.

Например:

procedure OnConnectionError(Self: Pointer; Sender: TObject; E: EDAError; var Fail: Boolean);
begin
  ...
end;

procedure OnConnectionLost(Self: Pointer; Sender: TObject; Component: TComponent; ConnLostCause: TConnLostCause; var RetryMode: TRetryMode);
begin
  ...
end;

var
  M: TMethod;
begin
  M.Data := ... whatever you want ...;
  M.Code := @OnConnectionError;
  mydb.OnError := TDAConnectionErrorEvent(M);

  M.Data := ... whatever you want ...;
  M.Code := @OnConnectionLost;
  mydb.OnConnectionLost := TConnectionLostEvent(M);
end;

В качестве альтернативы:

procedure OnConnectionError(Self: Pointer; Sender: TObject; E: EDAError; var Fail: Boolean);
begin
  ...
end;

procedure OnConnectionLost(Self: Pointer; Sender: TObject; Component: TComponent; ConnLostCause: TConnLostCause; var RetryMode: TRetryMode);
begin
  ...
end;

var
  ErrorHandler: TDAConnectionErrorEvent;
  LostHandler: TConnectionLostEvent;
begin
  TMethod(ErrorHandler).Data := ... whatever you want ...;
  TMethod(ErrorHandler).Code := @OnConnectionError;
  mydb.OnError := ErrorHandler;

  TMethod(LostHandler).Data := ... whatever you want ...;
  TMethod(LostHandler).Code := @OnConnectionLost;
  mydb.OnConnectionLost := LostHandler;
end;

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

person Remy Lebeau    schedule 20.07.2020