0

I am quite new to Delphi and wanted to practise a little bit.

While trying to implement a basic custom component I couldn't figure out how to "catch" events like "OnMouseWheel" or "OnMouseMove" etc.. (the component just should let the user zoom into an TImage)

At the moment I wrote some public functions like LMouseWheel(...), now the user of the component has to implement the OnMouseWheel-Function, but only has to call the public MouseWheel(...)-Method to get the component working. Is there a way, that the MouseWheel-Method gets called by default?

The code is an abstract of my custom component. What do I have to do, to immediately call the LMouseWheel(...)-Method, when the user scrolls the mouse wheel over my component?

unit TLZoomage;

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}
interface

{$IFDEF MSWINDOWS}
uses
  Classes, SysUtils, FileUtil, Forms, LCLType, Controls, Graphics,
  Dialogs, ExtCtrls, Spin, Types, Math;

type

  { TLZoomage }

  TLZoomage = class(TImage)
  private
    { Private-Deklarationen }
    FStartZoom: integer;
    FmaxZoom: integer;
    FminZoom: integer;
    FcurrentZoom: integer;
    FzoomSpeed: integer;

    mouseMoveOrigin: TPoint;

    procedure setCurrentZoom(AValue: integer);
    procedure setMaxZoom(AValue: integer);
    procedure setMinZoom(AValue: integer);
    procedure setStartZoom(AValue: integer);
  protected
    { Protected-Deklarationen }
    property currentZoom: integer read FcurrentZoom write setCurrentZoom;
  public
    { Public-Deklarationen }
    constructor Create(AOwner: TComponent); override;

    //###################################################################
    //###################################################################
    //
    // This should get called automatically
    //
    //###################################################################
    //###################################################################
    procedure LMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: integer; MousePos: TPoint; var  Handled: boolean);

  published
    property maxZoom: integer read FmaxZoom write setMaxZoom;
    property minZoom: integer read FminZoom write setMinZoom;
    property startZoom: integer read FStartZoom write setStartZoom;
    property zoomSpeed: integer read FzoomSpeed write FzoomSpeed;
  end;

{$ENDIF}
procedure Register;

implementation

{$IFnDEF MSWINDOWS}
procedure Register;
begin

end;

{$ELSE}
procedure Register;
begin
  RegisterComponents('test', [TLZoomage]);
end;

{ TLZoomage }

//###################################################################
//###################################################################
//
// This should get called automatically
//
//###################################################################
//###################################################################
procedure TLZoomage.LMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: integer; MousePos: TPoint; var Handled: boolean);
var
  xZoomPoint: double;
  yZoomPoint: double;
begin
  if (ssCtrl in Shift) then
  begin
    xZoomPoint := MousePos.x / self.Width;
    yZoomPoint := MousePos.y / self.Height;
    // der Benutzer möchte zoomen
    currentZoom := currentZoom + Sign(WheelDelta) * scrollSpeed;

    self.Left := round(self.Left + MousePos.x - (xZoomPoint * self.Width));
    self.Top := round(self.Top + MousePos.y - (yZoomPoint * self.Height));
  end;
  Handled:=true;
end;

procedure TLZoomage.setCurrentZoom(AValue: integer);
var
  ChildScaleFactor: double;
  ParentScaleFactor: double;
begin
  FcurrentZoom := AValue;
  if (FcurrentZoom < minZoom) then
    FcurrentZoom := minZoom;
  if (FcurrentZoom > maxZoom) then
    FcurrentZoom := maxZoom;
  if Assigned(self.Picture) then
  begin
    self.Width := round(self.Picture.Width * FcurrentZoom / 100);
    self.Height := round(self.Picture.Height * FcurrentZoom / 100);
    if Assigned(self.Parent) then
    begin
      if (self.Width < self.Parent.Width) and (self.Height < self.Parent.Height) and
        (self.Height <> 0) then
      begin
        ChildScaleFactor := self.Width / self.Height;
        ParentScaleFactor := self.Parent.Width / self.Parent.Height;
        // Parent ist breiter -> Höhe gibt die größe vor
        if (ParentScaleFactor > ChildScaleFactor) then
        begin
          self.Height := self.Parent.Height;
          self.Width := round(ChildScaleFactor * self.Parent.Height);
        end
        else
          // Parent ist höher -> Breite gibt die Größe vor
        begin
          self.Width := self.Parent.Width;
          self.Height := round(self.Parent.Width / ChildScaleFactor);
        end;
      end;
    end;
  end;
end;

procedure TLZoomage.setMaxZoom(AValue: integer);
begin
  FmaxZoom := AValue;
  currentZoom := currentZoom;
end;

procedure TLZoomage.setMinZoom(AValue: integer);
begin
  FminZoom := AValue;
  currentZoom := currentZoom;
end;

procedure TLZoomage.setStartZoom(AValue: integer);
begin
  currentZoom := AValue;
  FstartZoom := currentZoom;
end;

procedure TLZoomage.limitImgPos();
begin
  if Assigned(self.Parent) then
  begin
  // limit the Scrolling
  if self.Left > 0 then
    self.Left := 0;
  if self.Left < -(self.Width - self.Parent.Width) then
    self.Left := -(self.Width - self.Parent.Width);

  if self.Top > 0 then
    self.Top := 0;
  if self.Top < -(self.Height - self.Parent.Height) then
    self.Top := -(self.Height - self.Parent.Height);

  end;
end;

constructor TLZoomage.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  maxZoom := 200;
  minZoom := 10;
  startZoom := 100;
  FzoomSpeed := 10;
  currentZoom := startZoom;
end;

{$ENDIF}

end.

Solution: The simplest solution was, to override the following procedure / functions out of TControl, thanks to "Remy Lebeau":

function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; override;
procedure MouseDown(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override;
procedure MouseMove(Shift: TShiftState; X,Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override;
Muperman
  • 344
  • 2
  • 14
  • 2
    Use a windowed control. – Sertac Akyuz Apr 17 '19 at 14:53
  • Indeed, `TImage` is based off of `TGraphicControl` which does not have a window handle. If you need to display an image only, simply draw it onto your control canvas. No need to inherit from a ready-made control. – Jerry Dodge Apr 17 '19 at 14:59

1 Answers1

0

Delphi's VCL TControl has virtual DoMouseWheel(Down|Up)() and Mouse(Down|Move|Up)() methods that your component can override as needed:

function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; dynamic;
function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean; dynamic;
function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean; dynamic;
...
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); dynamic;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;

Delphi's FMX TControl has virtual Mouse(Down|Move|Up|Wheel)() methods:

procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); virtual;
procedure MouseMove(Shift: TShiftState; X, Y: Single); virtual;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);  virtual;
procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); virtual;

FreePascal's TControl has virtual Mouse(Down|Move|Up)() and DoMouseWheel(Down|Up)() methods that mirror VCL, as well as additional virtual DoMouseWheel(Horz|Left|Right) methods:

procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); virtual;
procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); virtual;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); virtual;
...
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelHorz(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelLeft(Shift: TShiftState; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelRight(Shift: TShiftState; MousePos: TPoint): Boolean; virtual;

In all cases, the framework handles catching the actual mouse events from the OS and then calls the per-component virtual methods automatically as needed. This works even for graphical controls, as a parent windowed control will detect mouse activity over a graphical child control and delegate accordingly.

UPDATE: in the case of Delphi's VCL TControl (not sure about Delphi's FMX TControl, or FreePascal's TControl), delegation of mouse clicks works as expected, but delegation of mouse wheel movements does not. You have to take some extra steps to receive mouse wheel notifications in a graphical control:

How to add mouse wheel support to a component descended from TGraphicControl?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • - *This works even for graphical controls, ...* - Not in VCL of XE2, later versions perhaps? – Sertac Akyuz Apr 17 '19 at 21:51
  • @SertacAkyuz AFAIK, it works in *all* versions, and always has. If there is a particular *bug* in XE2, that is a different issue than how the *interface* is designed. Components are intended to override the virtual methods, and they exist in `TControl` instead of `TWinControl` so that graphical and windowed controls can leverage them equally. A recent system failure (of my XE2 system, of all things) required me to get a new computer, and I haven't installed the IDE onto it yet, so I can't test this for myself at the moment. – Remy Lebeau Apr 17 '19 at 22:10
  • I don't see any delegation code in XE2. If a wincontrol does not handle a wheel message it passes it to it's parent. And there it remains. Now tested with D2007 too, same... – Sertac Akyuz Apr 17 '19 at 22:16
  • @SertacAkyuz you are right: [How to add mouse wheel support to a component descended from TGraphicControl](https://stackoverflow.com/questions/456488/). I updated my answer. – Remy Lebeau Apr 17 '19 at 22:23