0

I have a ScrollBar with mouse events assigned to onChange, onMouseWheel and onMouseUp. The onChange and wheel events work fine, but the onMouseUp event does not fire. Drilling down to the TControl method on debug, I noticed that the event variable (FOnMouseUp) is nill. The event is assigned in the IDE and I put it in the onCreate event of the form, plus I tried assigning it in various other places after the form is created, but to no avail. What gives?


Here is a simple reproducible example, in which all three scroll bar mouse events do not fire:

 `TForm4 = class(TForm)
    ScrollBar1: TScrollBar;
    Label1: TLabel;
    procedure ScrollBar1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Single);
    procedure ScrollBar1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure ScrollBar1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure ScrollBar1Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.fmx}

procedure TForm4.ScrollBar1Change(Sender: TObject);
begin
  Label1.Text := 'onChange: ' + Screen.MousePos.Y.ToString;
end;

procedure TForm4.ScrollBar1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  Label1.Text := 'mousedown: ' + Y.ToString;
end;

procedure TForm4.ScrollBar1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
begin
  Label1.Text := 'mousemove: ' + Y.ToString;
end;

procedure TForm4.ScrollBar1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  Label1.Text := 'mouseUP: ' + Y.ToString;
end;

end.`

And the .FMX:

`object Form4: TForm4
  Left = 0
  Top = 0
  Caption = 'Form4'
  ClientHeight = 480
  ClientWidth = 640
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object ScrollBar1: TScrollBar
    SmallChange = 0.000000000000000000
    Orientation = Vertical
    Position.X = 616.000000000000000000
    Position.Y = 8.000000000000000000
    Size.Width = 18.000000000000000000
    Size.Height = 449.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 0
    OnChange = ScrollBar1Change
    OnMouseDown = ScrollBar1MouseDown
    OnMouseMove = ScrollBar1MouseMove
    OnMouseUp = ScrollBar1MouseUp
  end
  object Label1: TLabel
    Position.X = 568.000000000000000000
    Position.Y = 152.000000000000000000
    Text = 'Label1'
    TabOrder = 1
  end
end`
PABEIER
  • 17
  • 3
  • How can we reproduce your test setup? Please show the `.fmx` content for details. – Tom Brunberg Oct 01 '21 at 15:21
  • You've not provided a [mre] that demonstrates the issue. How are we supposed to tell you what's wrong with your code when we can't see it? You need to provide a [mre] that allows us to reproduce it. You'll find your experiences here will be much better if you spend some time taking the [tour] and reading the [help] pages to learn how the site works before you begin posting. – Ken White Oct 01 '21 at 17:49
  • Thanks to both of you - I'm new here and appreciate your help. – PABEIER Oct 02 '21 at 06:13
  • OK, I tried this: new project with one vertical scrollbar and one label. Even here, none of the mouse events of the scroll bar fire! – PABEIER Oct 02 '21 at 07:25

1 Answers1

1

The reason is that the scroll bar contains child objects such as a track, a thumb and min and max buttons. It's these objects that respond to mouse events, not the parent object. So the solution is to set your mouse events to those objects. The problem is that those object are protected, so you'll have to create a new Scroll bar class that sets those events. The child objects don't yet exist in the TScrollBar constructor, so the best place to assign them I've found is on the first paint event.

I asked almost exactly the same question a couple of weeks ago. See my own answer here.

FMX: TScrollBar MouseDown and MouseUp events not triggering

Here's your example, which now works. I've also replaced your one label with 4 labels to make it easier to see which events get called.

New scroll bar class that does respond to mouse events:

unit ScrollBarMouse;

interface

uses
  System.Classes, System.UITypes, FMX.StdCtrls, FMX.Types;

type

  // A scroll bar that responds to mouse events
  TScrollBarMouse = class(TScrollBar)
  private
    FMouseEventsSet : Boolean;
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
  end;


implementation

constructor TScrollBarMouse.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FMouseEventsSet := False;
end;

procedure TScrollBarMouse.Paint;
begin
  inherited;

  // Track and Buttons are not assigned in constructor, so set mouse events on first paint
  if not FMouseEventsSet and Assigned(Track) and Assigned(Track.Thumb)
    and Assigned(MinButton) and Assigned(MaxButton) then begin
    Track.OnMouseDown       := OnMouseDown;
    Track.OnMouseUp         := OnMouseUp;
    Track.OnMouseMove       := OnMouseMove;
    Track.Thumb.OnMouseDown := OnMouseDown;
    Track.Thumb.OnMouseUp   := OnMouseUp;
    Track.Thumb.OnMouseMove := OnMouseMove;
    MinButton.OnMouseDown   := OnMouseDown;
    MinButton.OnMouseUp     := OnMouseUp;
    MinButton.OnMouseMove   := OnMouseMove;
    MaxButton.OnMouseDown   := OnMouseDown;
    MaxButton.OnMouseUp     := OnMouseUp;
    MaxButton.OnMouseMove   := OnMouseMove;
    FMouseEventsSet := True;
  end;
end;

end.

Form unit:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.Controls.Presentation, FMX.StdCtrls, ScrollBarMouse;

type

TForm4 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    procedure ScrollBar1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Single);
    procedure ScrollBar1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure ScrollBar1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure ScrollBar1Change(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    ScrollBar1 : TScrollBarMouse;
  end;

var
  Form4: TForm4;

implementation

{$R *.fmx}

procedure TForm4.FormCreate(Sender: TObject);
begin
  // Create the scroll bar object
  ScrollBar1 := TScrollBarMouse.Create(Self);
  with ScrollBar1 do begin
    Parent := Self;
    Orientation := TOrientation.Vertical;
    Position.X := 616;
    Position.Y := 8;
    Size.Width := 18;
    Size.Height := 449;
    OnMouseDown := ScrollBar1MouseDown;
    OnMouseUp := ScrollBar1MouseUp;
    OnMouseMove := ScrollBar1MouseMove;
    OnChange := ScrollBar1Change;
  end;
end;

procedure TForm4.ScrollBar1Change(Sender: TObject);
begin
  Label1.Text := 'onChange: ' + IntToStr(Round(Screen.MousePos.Y));
end;

procedure TForm4.ScrollBar1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  Label2.Text := 'mousedown: ' + IntToStr(Round(Y));
end;

procedure TForm4.ScrollBar1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
begin
  Label3.Text := 'mousemove: ' + IntToStr(Round(Y));
end;

procedure TForm4.ScrollBar1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  Label4.Text := 'mouseUP: ' + IntToStr(Round(Y));
end;

end.

Form (scroll bar is removed since it's created at run time):

object Form4: TForm4
  Left = 0
  Top = 0
  Caption = 'Form4'
  ClientHeight = 480
  ClientWidth = 640
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  OnCreate = FormCreate
  DesignerMasterStyle = 0
  object Label1: TLabel
    Position.X = 424.000000000000000000
    Position.Y = 144.000000000000000000
    Size.Width = 121.000000000000000000
    Size.Height = 17.000000000000000000
    Size.PlatformDefault = False
    Text = 'Label1'
    TabOrder = 3
  end
  object Label2: TLabel
    Position.X = 424.000000000000000000
    Position.Y = 168.000000000000000000
    Size.Width = 121.000000000000000000
    Size.Height = 17.000000000000000000
    Size.PlatformDefault = False
    Text = 'Label1'
    TabOrder = 2
  end
  object Label3: TLabel
    Position.X = 424.000000000000000000
    Position.Y = 192.000000000000000000
    Size.Width = 121.000000000000000000
    Size.Height = 17.000000000000000000
    Size.PlatformDefault = False
    Text = 'Label1'
    TabOrder = 1
  end
  object Label4: TLabel
    Position.X = 424.000000000000000000
    Position.Y = 216.000000000000000000
    Size.Width = 121.000000000000000000
    Size.Height = 17.000000000000000000
    Size.PlatformDefault = False
    Text = 'Label1'
    TabOrder = 0
  end
end

This was built using Delphi 10.4 and run in Windows 10.

XylemFlow
  • 963
  • 5
  • 12
  • This is just what the doctor ordered! Thanks. What I don't understand is why the event assignments have to be done in the paint method, which means, I think, that they are constantly being reassigned. Couldn't you do it in a constructor or just after the control is created? – PABEIER Oct 03 '21 at 13:33
  • The child objects are not yet assigned in the constructor so can't be done there. It will only do it the first time in the paint method because it sets the FMouseEventsSet flag. – XylemFlow Oct 03 '21 at 18:11
  • Thanks for accepting the answer. Note that I just simplified the code a bit. I realised that there's no need for separate mouse events in the new class. – XylemFlow Oct 04 '21 at 08:12
  • Oh, the new version is even better! I did have to alter the code in the paint event of the custom scrollbar, eliminating all of the assigment conditions in the if statement. The actual mouse event assignments never got made, since the if requirments were not met. so I just used `if not FMouseEventsSet then begin`. I don't understand why you wrote the more complicated if statement in the form you used! Anyway, thanks again, this really did the trick. – PABEIER Oct 04 '21 at 17:11
  • The if statement conditions are to ensure that the objects exist before trying to assign the mouse events to them. I'm not sure why they would fail for you. It worked for me. Maybe because you hadn't set all 3 of the mouse events. Checking those can probably be removed but I would leave the checks for the child objects. – XylemFlow Oct 04 '21 at 21:40