1

I have to connect several measurement devices to my app (ie. caliper, weight scale, ...), not being tied to a specific brand nor model, so on client side I use interfaces with generic methods (QueryValue). Devices are connected on COM port and accessed on an asynchronous way:

  1. Ask for a value (= send a specific character sequence on COM port)
  2. Wait for a response

On 'business' side my components use TComPort internally, which data reception event is TComPort.OnRxChar. I wonder how I could fire this event through an interface? Here is what I've done so far:

IDevice = interface
  procedure QueryValue;
  function GetValue: Double;
end;

TDevice = class(TInterfacedObject, IDevice)
private
  FComPort: TComPort;
  FValue: Double;
protected
  procedure ComPortRxChar;
public
  constructor Create;
  procedure QueryValue;
  function GetValue: Double;
end;

constructor TDevice.Create;
begin
  FComPort := TComPort.Create;
  FComPort.OnRxChar := ComPortRxChar;
end;

// COM port receiving data
procedure TDevice.ComPortRxChar;
begin
  FValue := ...
end;

procedure TDevice.GetValue;
begin
  Result := FValue;
end;

But I need an event to know when to call GetValue on client side. What's the usual way to perform that kind of data flow?

paradise
  • 336
  • 2
  • 15

1 Answers1

1

You can add event property to interface

IDevice = interface
  function GetValue: Double;
  procedure SetMyEvent(const Value: TNotifyEvent);
  function GetMyEvent: TNotifyEvent;
  property MyEvent: TNotifyEvent read GetMyEvent write SetMyEvent;
end;

and realize it in TDevice class

TDevice = class(TInterfacedObject, IDevice)
private
  FMyEvent: TNotifyEvent;
  procedure SetMyEvent(const Value: TNotifyEvent);
  function GetMyEvent: TNotifyEvent;
public
  function GetValue: Double;
  procedure EmulChar;
end;

Then as usually call FMyEvent handler (if assigned) in the end of ComPortRxChar.

 Tform1...
  procedure EventHandler(Sender: TObject);

procedure TForm1.EventHandler(Sender: TObject);
var
  d: Integer;
  i: IDevice;
begin
  i := TDevice(Sender) as IDevice;
  d := Round(i.GetValue);
  ShowMessage(Format('The answer is %d...', [d]));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  id: IDevice;
begin
  id:= TDevice.Create;
  id.MyEvent := EventHandler;
  (id as TDevice).EmulChar; //emulate rxchar arrival
end;

procedure TDevice.EmulChar;
begin
  if Assigned(FMyEvent) then
    FMyEvent(Self);
end;

function TDevice.GetMyEvent: TNotifyEvent;
begin
  Result := FMyEvent;
end;

function TDevice.GetValue: Double;
begin
  Result := 42;
end;

procedure TDevice.SetMyEvent(const Value: TNotifyEvent);
begin
  FMyEvent := Value;
end;
MBo
  • 77,366
  • 5
  • 53
  • 86
  • Could you develop please? In `TDevice` how do I 'connect' `FMyEvent` to `Get/SetMyEvent`? If in `TDevice.ComPortRxChar` method I write `if Assigned(FMyEvent) then FMyEvent(Self);` (as I used to do - I mean without interfaces), when are `Get/SetMyEvent` implied? – paradise Apr 19 '16 at 10:08
  • Thanks! That's how I understood it ;) That's a pretty good solution but I still see a downside: as I use **only** interfaces on client-side and not implementations (that's the main goal), is there a way in `EventHandler` to cast `Sender` as an `IDevice`? Otherwise I should declare IDevice var (your "id" in `Button1Click`) as a class var. – paradise Apr 20 '16 at 12:56
  • My example does cast Sender to IDevice. And I don't understand why IDevice should be a class var...What is lifecycle of your IDevice? – MBo Apr 20 '16 at 14:19
  • In `EventHandler`, `TDevice(Sender)` is tightly coupled to `TDevice` type but it shouldn't. Same for `id := TDevice.Create;` in your `Button1Click` method, but for instanciation no problem I use a type resolver which does all the dependency injection work. The class var would be precisely to use the same var in all class methods, if I can't cast the `Sender` to expected type. – paradise Apr 20 '16 at 14:31
  • Do you want that EventHandler parameter has interface type? I think it is possible to declare handler procedure type with IDevice param and use it instead of TNotifyEvent. And if your IDevice is singleton, you can avoid using Sender at all. – MBo Apr 20 '16 at 15:37
  • I'd prefer to use the Sender indeed, as I could in the future manage several devices simultaneously. Set IDevice type as a parameter is a good idea, but I thought that event handlers weren't customisables... I'll try asap. – paradise Apr 21 '16 at 10:31