Тема: АНК: Прокрутка окна под курсором мыши без активации окна
Я всегда считал, что прокручиваться должно то, что находится под курсором мыши. Но Windows считает иначе и прокручивает то, что имеет фокус ввода. Возьмём, например, окно с двумя панелями, типа Проводника или той же справки AutoHotkey. Прокручиваешь правую панель, потом переходишь в левую и хочешь прокрутить дерево, но не тут-то было - прокрутка идёт всё туда же, в правую часть. Сначала надо щёлкнуть по нужной панели, чтобы перебросить туда фокус. А когда переходишь обратно - опять та же морока. То же самое и с двумя (и более) отдельными окнами - сначала активируй, потом крути.
На форуме AutoHotkey я нашёл код, решающий эту проблему. Для желающих припасть к первоисточнику: Send mouse scrolls to window under mouse - там много больше, чем тут.
Я взял оттуда код shimanov'a, сократил его, немного модифицировал, добавил также идею Laszlo - ускорение прокрутки при быстром вращении колеса - и получилось довольно натурально, на мой взгляд.
WheelUp::
WheelDown::
Critical
If(A_ThisHotkey!=A_PriorHotkey || A_TimeSincePriorHotkey>40) ; Определить скорость
Scroll:= A_ThisHotkey="WheelUp" ? 120<<16 : -120<<16 ; вращения колеса и
Else ; задать скорость прокрутки.
Scroll:= A_ThisHotkey="WheelUp" ? 240<<16 : -240<<16
CoordMode, Mouse, Screen
MouseGetPos, mX, mY
WinID:=DllCall("WindowFromPoint", "int", mX, "int", mY) ; ID (handle) окна под мышью.
NextID:=DllCall("WindowFromPoint", "int", mX, "int", mY-20)
SendMessage, 0x20A, %Scroll%, (mY<<16)|mX,, ahk_id %WinID% ; 0x20A = WM_MOUSEWHEEL
If (!ErrorLevel && (WinID != NextID))
SendMessage, 0x20A, %Scroll%, (mY<<16)|mX,, ahk_id %NextID%
Return
Основная идея: определяется ID окна под курсором и вращение колеса посылается конкретно в это окно. "Панель" - это тоже окно (дочернее), но в AutoHotkey эти окошки принято называть контролами, а окнами называть только родительские окна. Это различие прослеживается и в соответствующих командах, что не всегда удобно. API-функция WindowFromPoint не делает таких различий.
"Внимательный читатель" может заметить, что зачем-то определяется ещё и ID в точке на 20 пикселов выше курсора (NextID). Это нужно, чтобы обойти один затык, который обнаружился, например, при прокрутке левой панели справки АНК: когда курсор попадает на название, не полностью умещающееся по ширине панели, это название выводится полностью в такой узенькой светлой рамочке. Эта "рамочка" оказалась тоже окном со своим ID. Так что вращение колеса начинает посылаться в неё, а прокрутка дерева останавливается. К счастью, это можно отследить - при этом SendMessage возвращает 0 вместо 1 - и отреагировать на это использованием NextID. Говорю "к счастью", т.к. правая панель справки, например, всегда отвечает нулём. Из-за этого пришлось ввести ещё и проверку на совпадение WinID и NextID, чтобы не посылать в такие "вечно нулевые" окна 2 шага колеса вместо одного.
Использованное тут сообщение WM_MOUSEWHEEL имеет два параметра. Первый, wparam, - это "количество вращения". Стандарт - 120, каких-то условных единиц. Если посылать меньше, то пока не накопится 120, никакой реакции окна не будет. Причём находиться это количество должно в старшем слове wparam, для этого и сдвиг на 16 бит влево. Если колесо вертеть быстро (время между шагами не больше 40 мс), скрипт будет посылать вдвое большее количество вращения. Знак определяет направление вращения: минус - колесо вниз.
Второй параметр, lparam, - координаты (экранные): в старшем слове Y, в младшем X.