在按钮类中添加长按事件的最佳方式是什么? [英] What is the best way to add long press event to button class?

查看:460
本文介绍了在按钮类中添加长按事件的最佳方式是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通过长按,我的意思是按下按钮/面板并按住一段时间(例如2秒),而不会释放或拖动。在手机和触摸设备中是常见的。



我已经尝试使用手势,在TabletOptions中检查到PressAndHold,并在InteractiveGestureOptions中检查所有,但长时间不会导致OnGesture Call。 p>

我可以想到的另一个实现是添加一个定时器,在MouseDown中启动它,并将其结束于Timer Fired,StartDrag,MouseUp或MouseLeave。然而,由于我想将这个行为添加到几个不同的按钮和面板组件中,我必须在每个类中重写一个早期的程序,并为每个组件复制代码。



有没有更好的方式实现?






编辑:



到NGLN



Woo,伟大的工作!加上您对这些滚动效果的回答,VCL几乎可以实现移动操作系统的外观和感觉!



您的代码与常规控件完美配合,但在我的案例中我有2个问题


  1. Long单击表单无法检测到(原因为
    不是自己的父项)我将Find FChild代码转移到单独的
    过程,并从WMParentNotify和FormMouseDown调用
    解决它

  2. 我有一些自定义按钮,其中有一些禁用的HTML
    标签(标题,标题,页脚)覆盖标签原始
    表面,使用代码,FChild将是其中一个标签,但是
    不会得到MouseCapture。我添加以下行来克服它:



    而不是TControlAccess(FChild).Enabled do
    FChild:= FChild.Parent;



    总而言之,我们可以创建一个支持LongPress的表单/面板来托管所有其他控件。这比实现LongPress特性的Component by Component更容易了!非常感谢!






    Edit2:



    到NGLN

    再次感谢您的组件版本,这是非常好的方法,不需要对现有组件进行任何修改,并且可以检测到长按!



    对于你的信息,我做了几个修改,以满足我自己的需要。


    1. TCustomForm vs TWinControl:我的应用程序只有一个主窗体,所有其他视觉单元是我自己创建的框架(不是从TFrame,而是TscrollingWinControl与ccpack支持),假设TCustomForm不适合我。所以我已经删除了属性窗体(但保留了ActiveControl的FForm),并创建了一个已发布的属性Host:TWinControl作为父主机。这样,我也可以将检测限制在一些有限的面板上。当分配主机时,我使用GetParentForm(FHost)检查并找到FForm。

    2. 禁用控件:如前所述,我有一些禁用的TJvHTLabel覆盖了我的按钮,您的组件在标签上工作。我可以通过标签找回按钮,但是如果它已经被新的组件处理,我觉得会更方便。所以我添加一个属性SkipDisabled,如果设置为turn,在其父行中循环查找第一个启用的控件。

    3. 我添加一个PreserveFocus属性,让组件用户选择保持最后一次主动控制。

    4. 带有项目的控件。我更改了你的TLongPressEvent,添加了ClickPos作为第二个参数。所以,现在我可以使用ClickPos来查找列表框中的哪个项目等等。

    5. 在我看来,FindVCLWindow与FindControlAtPos有相同的效果? / li>

    再次感谢你的伟大工作。

    解决方案

    在鼠标左键单击 WM_PARENTNOTIFY 发送给所有(盛大)被点击的控制。因此,这可以用于跟踪长按的起点,并且可以使用定时器定时按下持续时间。剩下的是决定什么时候新闻被称为长按。在这个组件中, OnLongPress 事件中的$ {code

    满足以下条件时,处理程序将被触发:




    • 间隔后,控件仍具有鼠标捕获或仍具有焦点,或者是禁用,

    • 间隔后,鼠标没有移动超过 Mouse.DragThreshold



    有关代码的一些解释:




    • 它临时替换控件的 OnMouseUp 事件处理程序,否则连续点击也可能导致长按。中间事件处理程序禁用跟踪定时器,调用原始事件处理程序并将其替换回来。

    • 长按后,主动控制将重置,因为我认为长时间未按意图集中控制。但是这只是我的猜测,它可能是一个财产的候选人。

    • 还跟踪表单本身(而不是只有它的孩子)的长按。

    • 在任意窗口上定制 FindControlAtPos 例程,执行深层搜索。替代方案是(1) TWinControl.ControlAtPos ,但它只搜索一级深度,(2) Controls.FindDragTarget ,但尽管 AllowDisabled 参数,它无法找到禁用的控件。



    < h3>

      unit LongPressEvent; 

    接口

    使用
    类,控件,消息,Windows,窗体,ExtCtrls;

    类型
    TLongPressEvent =对象的过程(控件:TControl);

    TLongPressTracker = class(TComponent)
    private
    FChild:TControl;
    FClickPos:TPoint;
    FForm:TCustomForm;
    FOldChildOnMouseUp:TMouseEvent;
    FOldFormWndProc:TFarProc;
    FOnLongPress:TLongPressEvent;
    FPrevActiveControl:TWinControl;
    FTimer:TTimer;
    程序AttachForm;
    procedure DetachForm;
    函数GetDuration:Cardinal;
    procedure LongPressed(Sender:TObject);
    procedure NewChildMouseUp(Sender:TObject; Button:TMouseButton;
    Shift:TShiftState; X,Y:Integer);
    procedure NewFormWndProc(var Message:TMessage);
    程序SetDuration(Value:Cardinal);
    程序SetForm(Value:TCustomForm);
    程序StartTracking;
    protected
    procedure Notification(AComponent:TComponent; Operation:TOperation);
    覆盖;
    public
    构造函数Create(AOwner:TComponent);覆盖
    析构函数覆盖
    属性窗体:TCustomForm读取FForm写SetForm;
    发布
    属性持续时间:Cardinal读取GetDuration写入SetDuration
    默认1000;
    属性OnLongPress:TLongPressEvent读取FOnLongPress
    写入FOnLongPress;
    结束

    程序注册;

    执行

    程序注册;
    begin
    RegisterComponents('Samples',[TLongPressTracker]);
    结束

    函数FindControlAtPos(Window:TWinControl;
    const ScreenPos:TPoint):TControl;
    var
    I:整数;
    C:TControl;
    begin
    for I:= Window.ControlCount - 1 downto 0 do
    begin
    C:= Window.Controls [I];
    如果C.Visible和PtInRect(C.ClientRect,C.ScreenToClient(ScreenPos))然后
    begin
    如果C是TWinControl然后
    结果:= FindControlAtPos(TWinControl(C) ,ScreenPos)
    else
    结果:= C;
    退出;
    结束
    结束
    结果:=窗口;
    结束

    {TLongPressTracker}

    type
    TControlAccess = class(TControl);

    程序TLongPressTracker.AttachForm;
    begin
    如果FForm<> nil then
    begin
    FForm.HandleNeeded;
    FOldFormWndProc:=指针(GetWindowLong(FForm.Handle,GWL_WNDPROC));
    SetWindowLong(FForm.Handle,GWL_WNDPROC,
    Integer(MakeObjectInstance(NewFormWndProc)));
    结束
    结束

    构造函数TLongPressTracker.Create(AOwner:TComponent);
    begin
    继承Create(AOwner);
    FTimer:= TTimer.Create(Self);
    FTimer.Enabled:= False;
    FTimer.Interval:= 1000;
    FTimer.OnTimer:= LongPressed;
    如果AOwner是TCustomForm然后
    SetForm(TCustomForm(AOwner));
    结束

    析构函数TLongPressTracker.Destroy;
    begin
    如果FTimer.Enabled然后
    begin
    FTimer.Enabled:= False;
    TControlAccess(FChild).OnMouseUp:= FOldChildOnMouseUp;
    结束
    DetachForm;
    继承了Destroy;
    结束

    程序TLongPressTracker.DetachForm;
    begin
    如果FForm<>然后
    begin
    如果FForm.HandleAllocated然后
    SetWindowLong(FForm.Handle,GWL_WNDPROC,Integer(FOldFormWndProc));
    FForm:= nil;
    结束
    结束

    函数TLongPressTracker.GetDuration:Cardinal;
    begin
    结果:= FTimer.Interval;
    结束

    程序TLongPressTracker.LongPressed(Sender:TObject);
    begin
    FTimer.Enabled:= False;
    if(Abs(FClickPos.X - Mouse.CursorPos.X)< Mouse.DragThreshold)和
    (Abs(FClickPos.Y - Mouse.CursorPos.Y)< Mouse.DragThreshold)和
    ((FChild是TWinControl)和TWinControl(FChild).Focused)或
    (TControlAccess(FChild).MouseCapture或(不是FChild.Enabled)))然后
    begin
    FForm .ActiveControl:= FPrevActiveControl;
    如果分配(FOnLongPress)然后
    FOnLongPress(FChild);
    结束
    TControlAccess(FChild).OnMouseUp:= FOldChildOnMouseUp;
    结束

    程序TLongPressTracker.NewChildMouseUp(发件人:TObject;
    按钮:TMouseButton; Shift:TShiftState; X,Y:整数);
    begin
    FTimer.Enabled:= False;
    如果分配(FOldChildOnMouseUp)然后
    FOldChildOnMouseUp(发件人,按钮,Shift,X,Y);
    TControlAccess(FChild).OnMouseUp:= FOldChildOnMouseUp;
    结束

    程序TLongPressTracker.NewFormWndProc(var Message:TMessage);
    begin
    case Message.Msg
    WM_PARENTNOTIFY:
    如果TWMParentNotify(Message).Event = WM_LBUTTONDOWN然后
    StartTracking;
    WM_LBUTTONDOWN:
    StartTracking;
    结束
    with Message do
    结果:= CallWindowProc(FOldFormWndProc,FForm.Handle,Msg,WParam,
    LParam);
    结束

    程序TLongPressTracker.Notification(AComponent:TComponent;
    操作:TOperation);
    begin
    继承的通知(AComponent,Operation);
    if(AComponent = FForm)和(Operation = opRemove)then
    DetachForm;
    if(AComponent = FChild)和(Operation = opRemove)然后
    begin
    FTimer.Enabled:= False;
    FChild:= nil;
    结束
    结束

    程序TLongPressTracker.SetDuration(Value:Cardinal);
    begin
    FTimer.Interval:= Value;
    结束

    程序TLongPressTracker.SetForm(Value:TCustomForm);
    begin
    如果FForm<>值然后
    begin
    DetachForm;
    FForm:= Value;
    FForm.FreeNotification(Self);
    AttachForm;
    结束
    结束

    程序TLongPressTracker.StartTracking;
    begin
    FClickPos:= Mouse.CursorPos;
    FChild:= FindControlAtPos(FForm,FClickPos);
    FChild.FreeNotification(Self);
    FPrevActiveControl:= FForm.ActiveControl;
    FOldChildOnMouseUp:= TControlAccess(FChild).OnMouseUp;
    TControlAccess(FChild).OnMouseUp:= NewChildMouseUp;
    FTimer.Enabled:= True;
    结束

    结束。

    要获取此组件的工作,将其添加到包中,或使用此运行时代码:

      ... 
    private
    程序LongPress(Control:TControl);
    结束

    ...

    程序TForm1.FormCreate(发件人:TObject);
    begin
    with TLongPressTracker.Create(Self)do
    OnLongPress:= LongPress;
    结束

    procedure TForm1.LongPress(Control:TControl);
    begin
    Caption:='长按发生在:'+ Sender.ClassName;
    结束


    By Long Press, I mean pressing a button / panel and hold for a period (say 2 seconds) without releasing or dragging around. It is common in mobile phone and touch device.

    I had tried using Gesture, checked toPressAndHold in TabletOptions and Checked all in InteractiveGestureOptions but long pressing cause no OnGesture Call.

    Another implementation I can think of is adding a timer, start it in MouseDown and end it in either Timer Fired, StartDrag, MouseUp or MouseLeave. However, as I want to add this behavior to several different buttons and panel component, I would have to override a brunch of procedure in each class and copy the code around for each component.

    Is there a better way of achieving that?


    Edit :

    To NGLN

    Woo, great piece of work! Together with your answer to those scrolling effects, VCL can almost achieve mobile OS look and feel!

    Your code work perfectly with common controls but I got 2 issues in my case

    1. Long Clicking on the form cannot be detected (of cause as the form is not parent of itself) I shift the Find FChild Code to separate procedure and call from both WMParentNotify and FormMouseDown to solve it.
    2. I got some custom button which has some disabled HTML labels (Header, Caption, Footer) covering up the label original surface, Using your code, FChild will be one of those label but it do not get MouseCapture. I add the below line to overcome it :

      while not TControlAccess(FChild).Enabled do FChild := FChild.Parent;

    Finally, for some more complicated controls like TCategoryButtons or TListBox, the user of the event might need to check not against the whole control but a specify item in the control. So I think we need to save the original CursorPos and fire another event when the timer triggered to let manual determination of whether it meet the long press condition or not. If yes or event not assigned, then use your default code for determination.

    All in all, we can just create a LongPress supported form / panel to host all other controls. This is much more easier then implementing the LongPress feature Component by Component! Great Thanks!


    Edit2 :

    To NGLN

    Thanks again for your component version, which is excellent approach, not needing to do any modification to existing components and can detect long press everywhere!

    For your information, I had do several modification to suit my own need.

    1. TCustomForm vs TWinControl : As most of my application has only 1 main form and all other visual units are my own created frame (not from TFrame but TScrollingWinControl with ccpack support), assuming TCustomForm do not work for me. So I had deleted property form (but retain FForm for ActiveControl) and create a published property Host : TWinControl to act as the parent host. In that way, I can also limit the detection to some limited panel. When Assigning Host, I check and find the FForm using GetParentForm(FHost).
    2. Disabled Controls : As I said previously, I got some disabled TJvHTLabel covering my buttons and your component work on the labels. I can of cause find back the button by the label, but I think it would be more convenient if it had been handled by the new component. So I add a property SkipDisabled and if set to turn, loop in its parent line to find first enabled control.
    3. I add a PreserveFocus property to let component user choose to keep last activecontrol or not.
    4. Controls with items. I changed your TLongPressEvent, adding the ClickPos as the 2nd parameter. So, I can now use the ClickPos to find which item in a list box or the like had been long held.
    5. It seems to me that FindVCLWindow is having same effect with your FindControlAtPos?

    Thank you again for your great work.

    解决方案

    At every left mouse button click, WM_PARENTNOTIFY is send to all (grand) parents of the clicked control. So this can be used for tracking the starting point of a long press, and the duration of a press can be timed with a timer. What is left is to decide when a press should be called a long press. And to wrap this all up in a nice component of course.

    In the component written below, the OnLongPress event handler is fired when the following conditions are met:

    • after the interval, the control still has mouse capture, or still has focus, or is disabled,
    • after the interval, the mouse has not moved more then Mouse.DragThreshold.

    Some explanation on the code:

    • It temporarily replaces the control's OnMouseUp event handler, otherwise consecutive clicks might also result in a long press. The intermediate event handler disables the tracking timer, calls the original event handler and replaces it back.
    • After the long press, the active control is reset, because I thought a long press is not done with the intention to focus the control. But that's just my guess, and it might be candidate for a property.
    • Also tracks for long presses on the form itself (rather then only its childs).
    • Has a customized FindControlAtPos routine which performs a deep search on an arbitrary window. Alternatives were (1) TWinControl.ControlAtPos, but it searches just one level deep, and (2) Controls.FindDragTarget, but despite the AllowDisabled parameter, it is not able of finding disabled controls.

    unit LongPressEvent;
    
    interface
    
    uses
      Classes, Controls, Messages, Windows, Forms, ExtCtrls;
    
    type
      TLongPressEvent = procedure(Control: TControl) of object;
    
      TLongPressTracker = class(TComponent)
      private
        FChild: TControl;
        FClickPos: TPoint;
        FForm: TCustomForm;
        FOldChildOnMouseUp: TMouseEvent;
        FOldFormWndProc: TFarProc;
        FOnLongPress: TLongPressEvent;
        FPrevActiveControl: TWinControl;
        FTimer: TTimer;
        procedure AttachForm;
        procedure DetachForm;
        function GetDuration: Cardinal;
        procedure LongPressed(Sender: TObject);
        procedure NewChildMouseUp(Sender: TObject; Button: TMouseButton;
          Shift: TShiftState; X, Y: Integer);
        procedure NewFormWndProc(var Message: TMessage);
        procedure SetDuration(Value: Cardinal);
        procedure SetForm(Value: TCustomForm);
        procedure StartTracking;
      protected
        procedure Notification(AComponent: TComponent; Operation: TOperation);
          override;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
        property Form: TCustomForm read FForm write SetForm;
      published
        property Duration: Cardinal read GetDuration write SetDuration
          default 1000;
        property OnLongPress: TLongPressEvent read FOnLongPress
          write FOnLongPress;
      end;
    
    procedure Register;
    
    implementation
    
    procedure Register;
    begin
      RegisterComponents('Samples', [TLongPressTracker]);
    end;
    
    function FindControlAtPos(Window: TWinControl;
      const ScreenPos: TPoint): TControl;
    var
      I: Integer;
      C: TControl;
    begin
      for I := Window.ControlCount - 1 downto 0 do
      begin
        C := Window.Controls[I];
        if C.Visible and PtInRect(C.ClientRect, C.ScreenToClient(ScreenPos)) then
        begin
          if C is TWinControl then
            Result := FindControlAtPos(TWinControl(C), ScreenPos)
          else
            Result := C;
          Exit;
        end;
      end;
      Result := Window;
    end;
    
    { TLongPressTracker }
    
    type
      TControlAccess = class(TControl);
    
    procedure TLongPressTracker.AttachForm;
    begin
      if FForm <> nil then
      begin
        FForm.HandleNeeded;
        FOldFormWndProc := Pointer(GetWindowLong(FForm.Handle, GWL_WNDPROC));
        SetWindowLong(FForm.Handle, GWL_WNDPROC,
          Integer(MakeObjectInstance(NewFormWndProc)));
      end;
    end;
    
    constructor TLongPressTracker.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      FTimer := TTimer.Create(Self);
      FTimer.Enabled := False;
      FTimer.Interval := 1000;
      FTimer.OnTimer := LongPressed;
      if AOwner is TCustomForm then
        SetForm(TCustomForm(AOwner));
    end;
    
    destructor TLongPressTracker.Destroy;
    begin
      if FTimer.Enabled then
      begin
        FTimer.Enabled := False;
        TControlAccess(FChild).OnMouseUp := FOldChildOnMouseUp;
      end;
      DetachForm;
      inherited Destroy;
    end;
    
    procedure TLongPressTracker.DetachForm;
    begin
      if FForm <> nil then
      begin
        if FForm.HandleAllocated then
          SetWindowLong(FForm.Handle, GWL_WNDPROC, Integer(FOldFormWndProc));
        FForm := nil;
      end;
    end;
    
    function TLongPressTracker.GetDuration: Cardinal;
    begin
      Result := FTimer.Interval;
    end;
    
    procedure TLongPressTracker.LongPressed(Sender: TObject);
    begin
      FTimer.Enabled := False;
      if (Abs(FClickPos.X - Mouse.CursorPos.X) < Mouse.DragThreshold) and
        (Abs(FClickPos.Y - Mouse.CursorPos.Y) < Mouse.DragThreshold) and
        (((FChild is TWinControl) and TWinControl(FChild).Focused) or
          (TControlAccess(FChild).MouseCapture or (not FChild.Enabled))) then
      begin
        FForm.ActiveControl := FPrevActiveControl;
        if Assigned(FOnLongPress) then
          FOnLongPress(FChild);
      end;
      TControlAccess(FChild).OnMouseUp := FOldChildOnMouseUp;
    end;
    
    procedure TLongPressTracker.NewChildMouseUp(Sender: TObject;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    begin
      FTimer.Enabled := False;
      if Assigned(FOldChildOnMouseUp) then
        FOldChildOnMouseUp(Sender, Button, Shift, X, Y);
      TControlAccess(FChild).OnMouseUp := FOldChildOnMouseUp;
    end;
    
    procedure TLongPressTracker.NewFormWndProc(var Message: TMessage);
    begin
      case Message.Msg of
        WM_PARENTNOTIFY:
          if TWMParentNotify(Message).Event = WM_LBUTTONDOWN then
            StartTracking;
        WM_LBUTTONDOWN:
          StartTracking;
      end;
      with Message do
        Result := CallWindowProc(FOldFormWndProc, FForm.Handle, Msg, WParam,
          LParam);
    end;
    
    procedure TLongPressTracker.Notification(AComponent: TComponent;
      Operation: TOperation);
    begin
      inherited Notification(AComponent, Operation);
      if (AComponent = FForm) and (Operation = opRemove) then
        DetachForm;
      if (AComponent = FChild) and (Operation = opRemove) then
      begin
        FTimer.Enabled := False;
        FChild := nil;
      end;
    end;
    
    procedure TLongPressTracker.SetDuration(Value: Cardinal);
    begin
      FTimer.Interval := Value;
    end;
    
    procedure TLongPressTracker.SetForm(Value: TCustomForm);
    begin
      if FForm <> Value then
      begin
        DetachForm;
        FForm := Value;
        FForm.FreeNotification(Self);
        AttachForm;
      end;
    end;
    
    procedure TLongPressTracker.StartTracking;
    begin
      FClickPos := Mouse.CursorPos;
      FChild := FindControlAtPos(FForm, FClickPos);
      FChild.FreeNotification(Self);
      FPrevActiveControl := FForm.ActiveControl;
      FOldChildOnMouseUp := TControlAccess(FChild).OnMouseUp;
      TControlAccess(FChild).OnMouseUp := NewChildMouseUp;
      FTimer.Enabled := True;
    end;
    
    end.
    

    To get this component working, add it to a package, or use this runtime code:

      ...
      private
        procedure LongPress(Control: TControl);
      end;
    
    ...
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      with TLongPressTracker.Create(Self) do
        OnLongPress := LongPress;
    end;
    
    procedure TForm1.LongPress(Control: TControl);
    begin
      Caption := 'Long press occurred on: ' + Sender.ClassName;
    end;
    

    这篇关于在按钮类中添加长按事件的最佳方式是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆