понедельник, 7 сентября 2009 г.

Реализация щелчков высоких порядков

Это перевод Implementing higher-order clicks. Автор: Реймонд Чен.

Другим часто задаваемым вопросом является "Как мне сделать реакцию на тройной щелчок или выше?". Когда вы увидели алгоритм для двойных щелчков, расширение его на щелчки более высоких порядков будет довольно естественным.

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

type
  TForm1 = class(TForm)
    ...
  protected
    { Protected declarations }
    procedure CreateParams(var Params: TCreateParams); override;
  end;

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited;
  with Params.WindowClass do
    Style := Style and (not CS_DBLCLKS);
end;

Далее вы просто реализуете тот же механизм, что и оконный менеджер, но только берёте большее число, а не два. Давайте сделаем это. Начнём с пустого VCL приложения, уберём у окна стиль CS_DBLCLKS и добавим следующее:

type
  TForm1 = class(TForm)
    ... 
  private
  { Private declarations }
    g_rcClick: TRect;
    g_tmLastClick: DWORD;
    g_cClicks: Integer;
    procedure ResetClicks;
  end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  Pt: TPoint;
  tmClick: DWORD;
begin
  if Button = mbLeft then
  begin
    Pt.X := X;
    Pt.Y := Y;
    tmClick := GetMessageTime;

    if (not PtInRect(g_rcClick, pt)) or (tmClick - g_tmLastClick > GetDoubleClickTime) then
      g_cClicks := 0;
    Inc(g_cClicks);

    g_tmLastClick := tmClick;
    SetRect(g_rcClick, X, Y, X, Y);
    InflateRect(g_rcClick, GetSystemMetrics(SM_CXDOUBLECLK) div 2, GetSystemMetrics(SM_CYDOUBLECLK) div 2);

    Caption := IntToStr(g_cClicks);
  end
  else
    ResetClicks;
end;

procedure TForm1.ResetClicks;
begin
  g_cClicks := 0;
  Caption := 'Form1';
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  ResetClicks;
end;

Основная идея здесь проста: когда происходит клик, мы смотрим, находится ли он в "дабл-клик зоне" и произошёл ли он в период задержки дабл-клик. Если нет, то мы сбрасываем отсчёт щелчков.

(Заметьте, что значения SM_CXDOUBLECLK и SM_CYDOUBLECLK описывают зону целиком, так что мы обрезаем их наполовину, когда расширяем прямоугольник. Да, это означает, что мы теряем пиксель, если дабл-клик длина или высота нечётны, но Windows аккуратна и всегда ставит значения в чётные числа).

Потом мы записываем координаты и время текущего щелчка, чтобы потом сравнить следующий щелчок с ними.

Наконец, мы реагируем на щелчок, выводя соответствующий номер в заголовке окна (вместо этого мы могли бы вызывать OnClick, OnDblClick, OnTripleClick, OnQuadClick и т.д. - прим.пер.).

В этом коде есть несколько подводных камней. Во-первых, заметим, что установка g_cClicks в ноль заставит следующий щелчок рассматриваться как первый щелчок в серии, вне зависимости от его соответствия другим критериям, всё, что случится - счётчик кликов будет увеличен на 1.

Далее, заметим, что способ проверки попадания двух щелчков в интервал не чувствителен к переполнению тиков таймера. Если бы мы написали

if (not PtInRect(g_rcClick, pt)) or (tmClick > g_tmLastClick + GetDoubleClickTime) then

тогда мы бы не смогли правильно определить несколько тиков в момент около переполнения тиков таймера (убедитесь, что вы это поняли).

В-третьих, заметим, что мы сбрасываем счётчик, когда окно теряет или приобретает фокус. Таким образом, если пользователь щёлкает, затем переключает окна, и снова щёлкает, то эта последовательность не будет распознана как двойной щелчок. Мы поступаем аналогично для правой кнопки мыши (вы можете заметить, что не так много программ вообще учитывают эти тонкости).

Упражнение: предположим, что ваша программа не заинтересована в чём-либо, кроме тройных щелчков. Как бы вы изменили пример выше, способом, согласованным с тем, как оконный менеджер останавливается по двойным-щелчкам?

2 комментария:

  1. Немного потерялся смысл при переводе. После фразы "Далее, заметим, что способ проверки попадания двух щелчков в интервал не чувствителен к переполнению тиков таймера. Если бы мы написали..." в оригинале другой код.

    ОтветитьУдалить
  2. Дьявол! :D Спасибо, поправил :)

    Хотел избавиться от подчёркивания красным и вынес в добавку больше кода, чтобы было проще понять, о каком изменении идёт речь. В процессе Copy&Paste забыл про само изменение!

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.