Blog. Just Blog

Обработка нажатия на BUTTON разными кнопками мыши

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Обычно элементы управления типа BUTTON в окне приложения реагируют только на обычное нажатие. При клике левой кнопкой мыши на кнопке или при нажатии на клавиатуре клавиши пробел, окну отправляется сообщение WM_COMMAND с параметром BN_CLICKED и идентификатором нажатой кнопки. На этом все. Но иногда надо сделать так, чтобы кнопки реагировали на клик не только левой кнопки мыши, а, например, правой кнопки или колесика. Этого можно добиться через субклассирование обработчика кнопки, ведь по сути она является самостоятельным окном. При инициализации основного окна повесим субклассированный обработчик на нужную нам кнопку:
  1.         ; Субклассировать кнопку
  2.         invoke  GetDlgItem,[hwnddlg],ID_BTN
  3.         mov     ebx,eax
  4.         ; Установить наш собственный обработчик
  5.         invoke  SetWindowLong,ebx,GWL_WNDPROC,ButtonProc
  6.         ; Сохранить хэндл предыдущего обработчика
  7.         invoke  SetWindowLong,ebx,GWL_USERDATA,eax
Еще нам понадобятся идентификатор сообщения, которое будет отправляться из обработчика родительскому окну и текстовые строчки, при помощи которых родительское окно будет реагировать на это сообщение.
  1. ; Пользовательское сообщение от обработчика кнопки
  2. WM_BUTTON_CLICK = WM_USER + 275
  3.  
  4. szMsg1  db     'Left mouse Button',0
  5. szMsg2  db     'Right mouse Button',0
  6. szMsg3  db     'Middle mouse Button',0
Общую структуру субклассированного обработчика я повторять здесь не буду, на сайте выложена уже не одна статья, где он используется. Но теперь нам надо определиться со списком сообщений, которые он должен обрабатывать. Обычная кнопка срабатывает от клавиатуры и от мышки, значит нам надо обрабатывать как минимум сообщения WM_KEYDOWN и WM_LBUTTONDOWN. А раз мы хотим отлавливать нажатия и других кнопок мыши, то к этому списку добавляются WM_MBUTTONDOWN и WM_RBUTTONDOWN.

Для взаимодействия с родительским окном ему отправляется сообщение, что его дочерняя кнопка нажата. В lparam и wparam родительскому окну можно отправлять, например, идентификатор кнопки в окне и тип кнопки, которой по ней нажали. В нашем случае родительскому окну передается указатель на строку с названием нажатой кнопки мыши.
  1.         ; Отправить наше сообщение родительскому окну кнопки
  2.         invoke  GetParent,[hBtn]
  3.         invoke  PostMessage,eax,WM_BUTTON_CLICK,0,szMsg1
В родительском окне ожидается получение сообщения WM_BUTTON_CLICK и на основании значений его параметров wparam и lparam выполняются те или иные действия. В принципе, этого было бы достаточно, чтобы кнопка уже заработала и чтобы родительское окно по-разному обрабатывало нажатия разными кнопками мыши. Но можно добавить красивый визуальный эффект нажатия и отжатия кнопки, как это бывает при обычном нажатии на кнопку в окне. Этот эффект достигается отправкой кнопке сообщения BM_SETSTATE с параметром TRUE для нажатого состояния и, соответственно, FALSE для отжатого состояния.

Чтобы обработать левый клик мышкой и нажатие кнопки с клавиатуры, велик соблазн использовать обычный обработчик кнопок в родительском окне через WM_COMMAND и BN_CLICKED. Но здесь есть одна особенность. При симуляции переключения кнопки в нажатое положение сперва срабатывает обработчик главного окна, как если бы мы нажали на эту кнопку левой кнопкой мыши, и в нашем случае это сообщение отправляется независимо от того, какой кнопкой мыши мы щелкнули. Поэтому для нашей программы обычная связка не годится, все придется пропускать через субклассированный обработчик.

Но раз мы симулировали нажатие, то должны реализовать и отжатие кнопки через некоторое время, сама она в прежнее положение не вернется. Первым приходит в голову решение с задержкой через Sleep, но тут есть большие минусы. Во-первых, на время выполнения команды Sleep поток программы будет остановлен. Во-вторых, если кнопка мыши будет отпущена до момента завершения команды Sleep, то кнопка в окне так и останется залипшей до завершения паузы. Обойти обе проблемы позволяет установка в субклассированном обработчике таймера отжатия и обработка сообщений WM_LBUTTONUP, WM_MBUTTONUP, WM_RBUTTONUP. При симуляции нажатия взводится таймер, по срабатыванию которого кнопка будет отжата. Если отпущена кнопка мыши до срабатывания таймера, то таймер сбрасывается, а кнопка в окне отжимается немедленно. Таймер необходим, чтобы исключить ситуации, когда кнопка в окне была нажата, а затем при зажатой кнопке мыши курсор был выведен за ее пределы. В этом случае при отпускании кнопки мыши, кнопка в окне об этом никак не узнает, и, соответственно, останется в залипшем состоянии.
  1. ;--------------------------------------------
  2. ; Обработка нажатия на кнопку
  3. ;--------------------------------------------
  4. proc ButtonProc hBtn:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
  5.         ; Нажата кнопка на клавиатуре
  6.         cmp     [uMsg],WM_KEYDOWN
  7.         je      .chk_char
  8.  
  9.         ; Нажата средняя кнопка мыши
  10.         cmp     [uMsg],WM_LBUTTONDOWN
  11.         je      .click_left
  12.         ; Нажата средняя кнопка мыши
  13.         cmp     [uMsg],WM_MBUTTONDOWN
  14.         je      .click_middle
  15.         ; Нажата правая кнопка мыши
  16.         cmp     [uMsg],WM_RBUTTONDOWN
  17.         je      .click_right
  18.  
  19.         ; Отжата какая-нибудь кнопка мыши
  20.         cmp     [uMsg],WM_LBUTTONUP
  21.         je      .unclick
  22.         cmp     [uMsg],WM_MBUTTONUP
  23.         je      .unclick
  24.         cmp     [uMsg],WM_RBUTTONUP
  25.         je      .unclick
  26.  
  27.         ; Сработал таймер отжатия
  28.         cmp     [uMsg],WM_TIMER
  29.         je      .unclick
  30.  
  31. .process_ok:
  32.         ; Получить адрес предыдущего обработчика
  33.         invoke  GetWindowLong,[hBtn],GWL_USERDATA
  34.         ; Передать управление предыдущему обработчику
  35.         invoke  CallWindowProc,eax,[hBtn],[uMsg],[wParam],[lParam]
  36.         ret
  37.  
  38. .chk_char:
  39.         ; Получить скан-код нажатой кнопки
  40.         mov     eax,[wParam]
  41.         ; Пробел
  42.         cmp     al,VK_SPACE
  43.         je      .click_left
  44.         ; Кнопка Application
  45.         cmp     al,VK_APPS
  46.         je      .click_right
  47.         ; Неизвестные кнопки не обрабатываем
  48.         jmp     .process_ok
  49.  
  50. .click_left:
  51.         ; Установить статус кнопки "нажата"
  52.         invoke  SendMessage,[hBtn],BM_SETSTATE,TRUE,0
  53.         ; Отправить сообщение родительскому окну кнопки
  54.         invoke  GetParent,[hBtn]
  55.         invoke  PostMessage,eax,WM_BUTTON_CLICK,0,szMsg1
  56.         ; Установить таймер нажатия
  57.         invoke  SetTimer,[hBtn],1,300,NULL
  58.         jmp     .exit_proc
  59. .click_right:
  60.         ; Установить статус кнопки "нажата"
  61.         invoke  SendMessage,[hBtn],BM_SETSTATE,TRUE,0
  62.         ; Отправить сообщение родительскому окну кнопки
  63.         invoke  GetParent,[hBtn]
  64.         invoke  PostMessage,eax,WM_BUTTON_CLICK,1,szMsg2
  65.         ; Установить таймер нажатия
  66.         invoke  SetTimer,[hBtn],1,300,NULL
  67.         jmp     .exit_proc
  68. .click_middle:
  69.         ; Установить статус кнопки "нажата"
  70.         invoke  SendMessage,[hBtn],BM_SETSTATE,TRUE,0
  71.         ; Отправить сообщение родительскому окну кнопки
  72.         invoke  GetParent,[hBtn]
  73.         invoke  PostMessage,eax,WM_BUTTON_CLICK,2,szMsg3
  74.         ; Установить таймер нажатия
  75.         invoke  SetTimer,[hBtn],1,300,NULL
  76.         jmp     .exit_proc
  77.  
  78. .unclick:
  79.         ; Сбросить статус кнопки "нажата"
  80.         invoke  SendMessage,[hBtn],BM_SETSTATE,FALSE,0
  81.         invoke  KillTimer,[hBtn],1
  82.  
  83. .exit_proc:
  84.         xor     eax,eax
  85.         ret
  86. endp
Теперь после субклассирования кнопка в окне приложения работает следующим образом. При клике любой кнопкой или колесиком мыши на кнопке, родительскому окну передается соответствующее сообщение. На основании этого сообщения на кнопке меняется текст. Кроме мыши обрабатывается и клавиатура, нажатие клавиши пробела на активной кнопке приравнивается к клику левой кнопкой мыши, а нажатие на клавишу контекстного меню ("Application key" - дополнительная кнопка на клавиатуре рядом с кнопкой "Windows") приравнивается к клику правой кнопкой мыши. Во всех случаях визуально симулируется нажатие и отжатие кнопки. При отпускании кнопки мыши отжатие произойдет немедленно, а если после нажатия изловчиться и вывести курсор за границы кнопки, то отжатие произойдет по таймеру.

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

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Button.Click.Demo.zip (2,928 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 9040 | Комментариев: 3

Метки: Assembler, окна
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Лестер Глючный (31.12.2022 в 15:54):
Некоторые мыши умудряются отправлять нажатие уже нажатой кнопки, фактически, аппаратно включая залипание (никак не поддающееся выключению в винде) – достаточно нажать кнопку во время её перемещения (например, при перетягивании окна или ползунка) и окно или ползунок продолжает менять позицию даже после отпускания мыши! Всякие ClickFix и прочие MouseFix`ы только на двойные нажатия расчитаны, но ведь "отпускания" не отправлялись между DOWN`ами (press, press… release) – боковые кнопки-то тоже (значит «дребезг» исключён)!
Сменив её на "более дорогую" 8-кнопочную (программируемую), теперь хочу заставить винду пересылать нажатия:
кнопка6 в DIMOUSE_BUTTON5 или DIMOFS_BUTTON5,
кнопка7 в DIMOUSE_BUTTON6 или DIMOFS_BUTTON6,
кнопка8 в DIMOUSE_BUTTON7 или DIMOFS_BUTTON7.
Поддержка производителя этой мыши что-то "обещала", но ни драйвер, ни программку так и не починили! А в начале/середине того года прекратила поддержку 32-бит, окончательно уйдя в GG для бесятки :(

USB-снифферы ловят приходящие от мыши 9 байт, по которым можно отследить не только номер "отправляемой" (запрограммированной) кнопки самым первым байтом, но и физической (внутренний номер кнопки) предпоследним байтом, и мне не понятно, почему даже «программа (или драйвер?) от производителя этой мышки» в упор игнорирует верхние три разряда самого первого байта! Одним фильтром мыши|hid здесь не обойтись (нужен "свой" hidusb.sys/mouhid.sys)?
Yoshida (12.01.2014 в 14:08):
Интересно, спасибо!
Grey (09.01.2014 в 13:16):
Спасибо.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.08 сек. / MySQL: 2 (0.005 сек.) / Память: 4.5 Mb
Наверх