Hans♫ 75 Posted October 11, 2021 Having various components on a TListbox, scrolling does not work when i touch a component that has HitTest=True. What is the best way to solve this? I have comboboxes, radiobuttons, checkboxes, etc on Listboxitems to create a dynamic interface. So far I have only used native components on iOS (TMS iCL) where scrolling vs control interaction, is handled automatically by the OS in a very smooth way. Now we are preparing an Android version based on our FMX view, but on FMX it does not seem to work out of the box. If HitTest is true for a component, then scrolling does not work, and if HitTest is false for a component, then it does not respond, but scrolling works. I found this nice solution made by David Nottage, but it requires to create a descending class of each component used: https://stackoverflow.com/questions/57452568/prevent-firing-events-while-scrolling-tvertscrollbox Share this post Link to post
vfbb 285 Posted October 11, 2021 (edited) You can use OnClick normally with this code: uses FMX.Objects, FMX.Math, FMX.Math.Vectors; type TipControl = class(TRectangle) private const DEFAULT_MOVEMENT_TOLERANCE = 4; private FAbsolutePressedPoint: TPointF; FIsMouseDown: Boolean; FIsValidClick: Boolean; FLastMouseActivePoint: TPointF; FMovementTolerance: Single; function IsMovementToleranceStored: Boolean; protected function CheckHasValidClick: Boolean; virtual; procedure Click; override; procedure DoClick; virtual; procedure MouseDown(AButton: TMouseButton; AShift: TShiftState; X, Y: Single); override; procedure MouseMove(AShift: TShiftState; X, Y: Single); override; procedure MouseUp(AButton: TMouseButton; AShift: TShiftState; X, Y: Single); override; public constructor Create(AOwner: TComponent); override; property IsValidClick: Boolean read FIsValidClick; published property MovementTolerance: Single read FMovementTolerance write FMovementTolerance stored IsMovementToleranceStored; end; function TipControl.CheckHasValidClick: Boolean; function CheckIsMouseOver: Boolean; begin Result := FIsMouseOver and TouchTargetExpansion.MarginRect(LocalRect).Contains(FLastMouseActivePoint); end; begin Result := FIsValidClick and FIsMouseDown and CheckIsMouseOver and ((FMovementTolerance = 0) or ((PressedPosition.Distance(FLastMouseActivePoint) <= FMovementTolerance) and (FAbsolutePressedPoint.Distance(LocalToAbsolute(FLastMouseActivePoint)) <= FMovementTolerance))); end; procedure TipControl.Click; begin if Pressed then begin if not CheckHasValidClick then FIsValidClick := False; if IsValidClick then DoClick; end else DoClick; end; constructor TipControl.Create(AOwner: TComponent); begin inherited; FMovementTolerance := DEFAULT_MOVEMENT_TOLERANCE; FLastMouseActivePoint := PointF(-10000,-10000); end; procedure TipControl.DoClick; begin inherited Click; end; function TipControl.IsMovementToleranceStored: Boolean; begin Result := not SameValue(FMovementTolerance, DEFAULT_MOVEMENT_TOLERANCE, TEpsilon.Position); end; procedure TipControl.MouseDown(AButton: TMouseButton; AShift: TShiftState; X, Y: Single); begin FAbsolutePressedPoint := LocalToAbsolute(PointF(X, Y)); FIsValidClick := True; FIsMouseDown := True; FLastMouseActivePoint := PointF(X, Y); inherited; end; procedure TipControl.MouseMove(AShift: TShiftState; X, Y: Single); begin if CheckHasValidClick then begin FLastMouseActivePoint := PointF(X, Y); if not CheckHasValidClick then FIsValidClick := False; end else FLastMouseActivePoint := PointF(X, Y); inherited; end; procedure TipControl.MouseUp(AButton: TMouseButton; AShift: TShiftState; X, Y: Single); var LInvalidPressed: Boolean; begin FLastMouseActivePoint := PointF(X, Y); LInvalidPressed := not CheckHasValidClick; if LInvalidPressed then FIsValidClick := False; FIsMouseDown := False; inherited; end; Edited October 11, 2021 by vfbb 1 Share this post Link to post
Hans♫ 75 Posted October 11, 2021 32 minutes ago, vfbb said: You can try something like this: Thank you, but it seems to be a solution similar to the one I refer to? I prefer not to have a custom edition of each components placed on the Listbox. However, I also realize that both solutions relies on that scrolling actually does work when tapping a control, but it does not work for me. As soon as Hittest=true for the control on the listbox, then it does not scroll at all when I touch that control. Share this post Link to post
vfbb 285 Posted October 11, 2021 This is strange because the scrollbox does not use mousedown, mouseup, it uses gestures, and with hittest or without hit test in the components above, it should work in the same way. Share this post Link to post
Hans♫ 75 Posted October 12, 2021 19 hours ago, vfbb said: This is strange because the scrollbox does not use mousedown, mouseup, it uses gestures, and with hittest or without hit test in the components above, it should work in the same way. Thank you for your reply. If thats true, then there must be something in my setup that causes the default behavior not to work. I wonder what that is? My parentship hierachy looks like this: MainForm>TLayout>TRectangle>TRectangle>TListBox>TListBoxItem>TRadioButton When I set HitTest=true for the RadioButton, then touch scrolling of the listbox only works if I touch outside the Radiobutton. If I touch the radiobutton then it selects the radiobutton, but scrolling does not work. I have no Gesture manager assigned and no InteractiveGestures selected. Share this post Link to post
vfbb 285 Posted October 12, 2021 @Hans♫ I couldn't simulate your problem. I set up the same scenario you described in a blank project and the scroll worked perfectly. I also mounted on a TVertScrollBox and it worked perfectly. I'm using RAD Studio 11, but it works the same as in previous versions. Try simulating on a blank project to see where the problem is. I don't know what's going on, but maybe some other control is capturing the gesture... Share this post Link to post
Hans♫ 75 Posted October 13, 2021 I found the reason for the problem: Delphi handles touch events by ownership and not by parentship. Scrolling only works for scrollboxes OWNED by the mainform. It is not enough that that the mainform is their parent. In my situation my ownership looks like this: MainForm>TLayout (the layout animates the showing of other "windows") In another unit I have: MyFrame>TRectangle>TRectangle>TListBox Now when I want to show the content of the Frame, I Create the frame with the Application as the owner, and set the parent of the root Rectangle to the TLayout of the mainform, to slide it in. With this structure, scrolling does NOT work. However, if I create MyFrame with the MainForm as the owner, then it works! Share this post Link to post
Rollo62 536 Posted October 13, 2021 (edited) 51 minutes ago, Hans♫ said: However, if I create MyFrame with the MainForm as the owner, then it works! Thanks for the insights. I'm wondering if this might cause any problem with memory leaks or AV, when creating/destroying frames at runtime, in changing, random order. If the Application->MainForm is used over several units, inside specific components only, this always leaves a bad taste. This leads me to the question what is the right, intended, official way to use the Application->MainForm and the AOwner of Frames/Components. Shall it be in a strict tree-structure, or does it not matter, to mix tree and direct owenerships as you like ? Does it matter if the relations were mixed by designtime and runtime ? Does FMX behaves differently than VCL in that regard ? Thanks god, the termination sequence on mobile is not that critical, since this is killed by phone mostly Edited October 13, 2021 by Rollo62 Share this post Link to post