1 (изменено: YMP, 2013-11-05 11:10:05)

Тема: АНК: Прокрутка окна под курсором мыши без активации окна

Я всегда считал, что прокручиваться должно то, что находится под курсором мыши. Но 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.

2 (изменено: YMP, 2013-11-05 09:39:59)

Re: АНК: Прокрутка окна под курсором мыши без активации окна

Удалено, т.к. фактически повторяет код выше. Исправление перенесено туда.

3

Re: АНК: Прокрутка окна под курсором мыши без активации окна

Попробовав настоящий скрипт, оченно возрадовался. Фантастически удобно. Спасибо всем сочинителям.
Денек побаловался и, кажется, сделал скрипт чуть лучше: теперь ускорение добавляется не скачком, а плавно  растет вместе с раскруткой колеса (разгон примерно линейно зависит от скорости вращения).
Опробовал на десятке программ - везде хуже или лучше работает. Всем очень рекомендую.
В системных настройках мыши полезно установить минимальный шаг.

WheelUp::
WheelDown::
  Critical
  Sign :=  A_ThisHotkey="WheelUp" ? 1 : -1
  Acceleration = 10 ;степень ускорения проматывания (от 0 до ...)
  Scroll := 1 + 10*( Acceleration / (A_TimeSincePriorHotkey + 1) ) ;добавляем ускорение
  Scroll *= Sign * 120 << 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

4 (изменено: NikVasKo, 2008-08-04 16:42:49)

Re: АНК: Прокрутка окна под курсором мыши без активации окна

Еще побаловался и нашел прокольчик в скрипте. Есть у меня маленькая любиная программулька VolumeControl - сидит в трее и. когда над ее иконкой крутишь колесо, - меняет громкость. Так вот, при запущеном скрипте - не работает.
Было у меня подозрение на закоменнтированные строчки кода в следующем скрипте, но не оправдалось.
Однако, скрипт без этих строчек работает так же как и с ними. Признаюсь, назначение их не понимаю. Посему к  YMP, как автору кода, есть просьба дать пояснения.
В своей части кода я внес малые усовершенствования - ускорение меняется теперь нелинейно. И жить стало лучше, стало веселее.

WheelUp::
WheelDown::
  Critical
  Acceleration = 50 ;ускорение (НА мой вкус, хорошо от 30 до 100)

  Scroll :=  A_ThisHotkey="WheelUp" ? +1 : -1
  Scroll <<= 30 - 4*ATan( A_TimeSincePriorHotkey/(1+Acceleration) )

  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

5

Re: АНК: Прокрутка окна под курсором мыши без активации окна

Вариант кода из первого сообщения. В отличие от него также работает в AutoHotkey x64 (проверено) и должен работать (не проверено) при отрицательных значениях mX. Последнее возможно при расширении Рабочего стола на второй монитор, стоящий слева. Проверить нет возможности за отсутствием второго монитора.

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
    mX &= 0x00000000FFFFFFFF
    Point := mX | mY<<32
    WinID:=DllCall("WindowFromPoint", "int64", Point, "ptr")   ; ID (handle) окна
    Point := mX | (mY-20)<<32                                  ; под мышью.
    NextID:=DllCall("WindowFromPoint", "int64", Point, "ptr")
    mX &= 0x0000FFFF
    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