4
votes

I am programmatically creating a database connection object inside a simple procedure (not a method in a class).

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

now I need to handle errors and disconnections with other procedures. When I try to do:

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

The compiler tells me

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

How can I work around this? Here are the definitions of the event procedures:

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

4 Answers

18
votes

If you don't have a suitable class to put the event handlers in you can define a dummy class and make the event handlers class procedures. Then you don't have to create an instance of the class but can assign mydb.OnError:= TMyDummyEventHandlerClass.OnConnectionError;.

Here is an example - I use different events because I don't have TUniConnection but want to be sure everything compiles. :-)

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;
4
votes

If you don't want to instantiate an instance of a class on the heap, you could use record methods. I sometimes do that to avoid using the heap, but that approach could be convenient for your needs.

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;
0
votes

Your two event procedures need to be methods of a class, instead of local/global procedures. For example:

procedure TForm1.OnConnectionError(Sender: TObject; E: EDAError; var Fail: Boolean);
0
votes

While using a class method is a clean solution, it is possible to use a regular non-class procedure as an event handler, you just can't assign it directly, but you can assign it indirectly using the TMethod record. You will just have to add an explicit Self parameter to the procedure.

For example:

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;

Alternatively:

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;

The benefit of this approach is that you can set the Self parameter to whatever value you want, which is useful for passing user-defined data into an event handler.