Каков наилучший способ добавить событие длительного нажатия в класс кнопки? - PullRequest
4 голосов
/ 09 марта 2012

Под длительным нажатием я имею в виду нажатие кнопки / панели и удерживание в течение некоторого времени (скажем, 2 секунды) без отпускания или перемещения по экрану.Это часто встречается в мобильных телефонах и сенсорных устройствах.

Я пытался использовать Gesture, проверил toPressAndHold в TabletOptions и проверил все в InteractiveGestureOptions, но длительное нажатие не вызывает вызова OnGesture.

Другая реализация, которую я могу себе представитьдобавление таймера, запуск его в MouseDown и завершение в Timer Fired, StartDrag, MouseUp или MouseLeave.Однако, поскольку я хочу добавить это поведение к нескольким различным кнопкам и компоненту панели, мне придется переопределить набор процедур в каждом классе и скопировать код для каждого компонента.

Есть ли лучший способдостижения?


Редактировать:

В NGLN

Ву, отличная работа!Вместе с вашим ответом на эти эффекты прокрутки, VCL может почти достичь внешнего вида и ощущения мобильной ОС!

Ваш код отлично работает с общими элементами управления, но у меня есть 2 проблемы в моем случае

  1. Длительное нажатие на форму не может быть обнаружено (причина, поскольку форма не является родительской для себя)Я сдвигаю Find FChild Code в отдельную процедуру и вызываю как WMParentNotify, так и FormMouseDown для ее решения.
  2. У меня есть несколько пользовательских кнопок, на которых есть некоторые отключенные метки HTML (Header, Caption, Footer), закрывающие исходную поверхность метки. Используя ваш код, FChild будет одной из тех меток, но не получитMouseCapture.Я добавляю следующую строку, чтобы преодолеть это:

    , но не TControlAccess (FChild) .Enabled do FChild: = FChild.Parent;

Наконец, для некоторых более сложных элементов управленияподобно TCategoryButtons или TListBox, пользователю события может потребоваться проверка не всего элемента управления, а определенного элемента в элементе управления.Поэтому я думаю, что нам нужно сохранить исходные CursorPos и ​​запустить другое событие, когда таймер сработал, чтобы дать возможность вручную определить, соответствует ли он условиям длительного нажатия или нет.Если да или событие не назначено, используйте для определения ваш код по умолчанию.

В общем, мы можем просто создать форму / панель, поддерживаемую LongPress, для размещения всех других элементов управления.Это гораздо проще, чем реализовать функцию LongPress Component by Component!Большое спасибо!


Edit2:

NGLN

Еще раз спасибо за вашу версию компонента, которая является отличным подходом, не требуя каких-либо изменений существующих компонентов иможет обнаружить долгое нажатие повсюду!

Для вашей информации, я должен был сделать несколько модификаций в соответствии со своими потребностями.

  1. TCustomForm vs TWinControl: Поскольку в большинстве моих приложений есть только 1 основная формаа все остальные визуальные блоки - это мои собственные созданные кадры (не из TFrame, а из TScrollingWinControl с поддержкой ccpack), предполагая, что TCustomForm у меня не работает.Поэтому я удалил форму свойства (но сохранил FForm для ActiveControl) и создал опубликованное свойство Host: TWinControl, чтобы действовать в качестве родительского хоста.Таким образом, я также могу ограничить обнаружение до некоторой ограниченной панели.При назначении хоста я проверяю и нахожу FForm, используя GetParentForm (FHost).
  2. Отключенные элементы управления: как я уже говорил ранее, у меня есть несколько отключенных TJvHTLabel, покрывающих мои кнопки, и ваш компонент работает с метками.Я могу найти кнопку назад по метке, но я думаю, что было бы удобнее, если бы она была обработана новым компонентом.Поэтому я добавляю свойство SkipDisabled и, если установлено значение turn, зацикливаюсь в его родительской строке, чтобы найти первый включенный элемент управления.
  3. Я добавляю свойство PreserveFocus, чтобы разрешить пользователю компонента сохранять последнее activecontrol или нет.
  4. Управление с предметами.Я изменил ваш TLongPressEvent, добавив ClickPos в качестве второго параметра.Итак, теперь я могу использовать ClickPos, чтобы найти, какой элемент в списке и т.п. долго удерживался.
  5. Мне кажется, что FindVCLWindow имеет такой же эффект с вашим FindControlAtPos?

Еще раз спасибо за вашу прекрасную работу.

1 Ответ

12 голосов
/ 09 марта 2012

При каждом щелчке левой кнопкой мыши WM_PARENTNOTIFY отправляется всем (большим) родителям выбранного элемента управления.Так что это может быть использовано для отслеживания начальной точки длинного нажатия, а продолжительность нажатия может быть рассчитана по таймеру.Осталось решить, когда пресс должен называться долгим нажатием .И, конечно же, чтобы обернуть все это в приятный компонент.

В компоненте, написанном ниже, обработчик события OnLongPress запускается, когда выполняются следующие условия:

  • послеинтервал, элемент управления все еще имеет захват мыши, или все еще имеет фокус, или отключен,
  • после интервала, мышь не переместилась больше чем Mouse.DragThreshold.

Некоторыепояснение к коду:

  • Он временно заменяет обработчик событий OnMouseUp элемента управления, в противном случае последовательные нажатия могут также привести к длительному нажатию .Обработчик промежуточных событий отключает таймер отслеживания, вызывает оригинальный обработчик событий и заменяет его обратно.
  • После длительного нажатия активный элемент управления сбрасывается, поскольку я думал, что длительное нажатие не было выполнено с целью сосредоточитьсяконтроль.Но это только мое предположение, и оно может быть кандидатом на свойство.
  • Также отслеживает длительные нажатия на самой форме (а не только на ее дочерних элементах).
  • Имеет настроенный FindControlAtPosподпрограмма, которая выполняет глубокий поиск в произвольном окне.Альтернативы были (1) TWinControl.ControlAtPos, но он ищет только на один уровень глубины, и (2) Controls.FindDragTarget, но, несмотря на параметр AllowDisabled, он не может найти отключенные элементы управления.

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.

Чтобы этот компонент работал, добавьте его в пакет или используйте этот код времени выполнения:

  ...
  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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...