Понимаю, тема изъезженная, однако, блуждая по бесконечным просторам инета, на текущий момент не сумел найти ответа на вот какой вопрос. Есть форма (UserForm), напичканная множеством объектов. Контроль внутри происходящего в форме, как в муравейнике - четко и жестко, мышь не проскользнет. Но вот засада. Мерзкий пользователь, работая с формой, вдруг по непонятно каким соображениям перемещает курсор мыши за границы формы. И вот тут как раз и требуется просигнализировать, так сказать, дать понять исполняемому коду, что нарушена граница, пересечена "точка события", если уж совсем увлекаться в эпитеты, анализируя структуры объектов, аналогичных Черным дырам. Пусть это даже будет вызов простейшего макроса, содержащего всего лишь вывод на экран предупреждающего сообщения, не важно. Важно понять как вызвать этот макрос, к какому событию какого объекта формы обратиться. Я скрупулезно исследовал. Использование события формы UserForm_MouseMove не помогает. И даже пытался зайти с другой стороны Изучил возможности функции API GetCursorPos, определяющей координаты текущего расположения курсора на экране, в результате чего научился дрессировать UserForm, как в цирке на арене, располагая ее где захочу и как захочу. Пытался даже комбинировать, но безрезультатно. Чувствую, что решение где-то рядом, но найти пока не могу. Люди добрые, "поможите" чем можете!
Предлагаю такой вариант названия темы: "Запуск процедуры (макроса) при перемещении курсора мыши за пределы границ пользовательской формы, показанной на экране".
Цитата
БМВ написал: Если форма запущена как модальная, то я с трудом понимаю как нарушены границы.
Элементарно. Форма загружена, показана пользователю на экране в определенных координатах. Ничто не мешает пользователю, управляя мышью, перемещать курсор по всему экрану. Важно отловить момент, когда курсор, находящийся в пределах координат формы, затем переводится пользователем в координаты, находящимися за пределами этой формы.
_Igor_61 написал: Вы же хотите просто определять положение (координаты) мыши
Да в том-то и дело, что координаты я прекрасно научился считывать. Не получается вот что. Попробую пояснить простеньким примером. Предположим, есть пустая форма, в которой расположен, ну, пусть будет 'элементарный Label, при наведении на который цвет фона становится красный. Теперь пользователь переводит курсор за границы формы. Как только курсор покинет пределы координат формы визуально хочется получить, чтобы цвет фона этого Label стал, например, зеленым.
Такого идиотского странного способа запуска макроса встречать еще не доводилось
Не вопрос. Не претендую на безукоризненность. Если резюме относится именно к формулировке, то можно переформулировать, как "Создание события отслеживания покидания курсором предела пользовательской форме на экране". Если резюме относится к сути самой задачи, то не вижу в ней ничего идиотского. Более того, именно поэтому и обратился за помощью. Возможно, не спорю, есть идеи и получше. С удовольствием выслушаю рекомендации, предложения.
Владимир Баукин написал: визуальное предоставление пользователю состояния формы при нахождении курсора вне ее координат
Владимир, состояние форме задает либо текущий пользователь, либо тот, кто контролирует эту форму независимо от других пользователей. Если Вы уже умеете определять координаты мыши, как говорите, о чем вопрос? Это Вы так просто количество сообщений набираете?
Есть, конечно, одна идея. Например, обрамить границы формы объектами Label. Тогда в любых вариантах передвижения курсора пользователем, используя событие MoseMove этих пограничных объектов, приводить вид объектов внутри формы к задуманному состоянию, которое хочется визуально получить, когда курсор переводится за границы формы. Но может есть предложения поинтереснее?
Цитата
_Igor_61 написал: Это Вы так просто количество сообщений набираете?
Я бы с радостью перешел скорее к реализации следующего этапа своей задачи, решив эту. Но проблема этого этапа пока не решена. Да, я знаю координаты формы, могу определить координаты курсора. Но не понимаю пока, как это объединить воедино, чтобы как только курсор получил координаты, находящимися за пределами границы формы произошел бы вызов какой-либо процедуры, которая бы изменила визуальное представление объектов формы
Проведите эксперимент. Создайте форму с одним текстовым полем, запустите на выполнение и уведите курсор мыши далеко от формы. Начните вводить текст с клавиатуры. Вы видите курсор (клавиатуры) в тексте. Далее, сделайте движение мышью. Курсор мыши как был далеко от формы, так и остался. На поведение (модальной) формы никак не влияют движения мыши вне этой формы. Когда мышь пересекает границу формы начинает срабатывать событие формы (или ее элементов управления) MouseMove.
нет такого события и обрамления вам не помогут, так как быстрое перемещение не вызовет событие. Остается крутить по таймеру получение позиции курсора и сравнение с координатами формы.
Владимир Баукин написал: обрамить границы формы объектами Label.
И сколько их потребуется? Если в форме больше хотя бы трех элементов управления? А через месяц понадобится добавить в форму еще что-то... Следите за мышью
Решение в принципе есть. На основе варианта от ZVI: подогнать размер формы (или размеры ячеек) таким образом, чтобы границы формы совпадали с границей некого диапазона. И контролировать выход за пределы ЭТОГО диапазона. Но остаётся проблема со стартовой позицией указателя мыхи. Я не знаю, как его принудительно установить в нужное место.
Вниманию модераторов! Прошу переформулировать тему в окончательный правильный вариант: Изменение свойств объектов (Controls) загруженной и показанной на экране пользовательской формы (UserForm) при перемещении указателя курсора мыши по экрану пользователем.
Предварительно отдельно хочу поблагодарить БМВ, что не позволил сойти с пути истинного. На этом лирику закончим и перейдем к конкретике.
Никак не могу найти ошибку в своем коде в силу недостаточности уровня знаний в понимании правил использования обратного вызова с использованием API функций. Начнем по порядку. Итак, есть задача - изменять для визуальной наглядности пользователю состояние свойств объектов (Controls) загруженной и выведенной на экран формы (UserForm) при перемещении пользователем мыши по экрану окна. Возьмем для простоты простейшую форму, содержащую объект Image. Желаемое: Если пользователь навел курсор мыши на форму (курсор мыши внутри координат формы), то фон Image зеленый. Если пользователь перевел курсор мыши за пределы координат формы , то фон Image становится красный. Все это я пытаюсь реализовать в 64-bit операционной системе с установленной версией Office, использующего 64-разрядную версию VBA. Создавая различного рода запросы в поисковике для решения своей задачи я выяснил, что в помощь мне функция API TRACKMOUSEEVENT. И даже нашел практический пример по ссылке: http://rusproject.narod.ru/winapi/t/trackmouseevent.html Изучив его, я понял, что мне его необходимо адаптировать в свою операционную среду. И тут снова мне в помощь пришел найденный справочный материал: https://codekabinett.com/rdumps.php?Lang=2&targetDoc=windows-api-declaration-vba-64-bit Изучив на его основании типы аргументов необходимых мне API функций, я адаптировал их декларацию под версию VBA 64-bit. При проверке компиляции кода в среде VBA.Project все хорошо. Однако результат выполнения кода не дает нужного результата. Более того, при пошаговом выполнении наблюдается серьезный сбой, когда приложение Excel просто перезагружается или даже закрывается. И это происходит именно на этапе срабатывания функции обратного вызова. Теперь конкретно о коде.
Вот код основного модуля
Код
Option Explicit
Public Const WM_MOUSELEAVE As Long = &H2A3&
Public Declare PtrSafe Function TRACKMOUSEEVENT Lib "user32" Alias "TrackMouseEvent" ( _
lpEventTrack As TRACKMOUSEEVENT) As Long
Public Type TRACKMOUSEEVENT
cbSize As Long
dwFlags As Long
hwndTrack As LongPtr
dwHoverTime As Long
End Type
Public Const TME_LEAVE As Long = &H2
Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongPtrA" (ByVal hWnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
Declare PtrSafe Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As LongPtr, ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
Private Const GWLP_WNDPROC = (-4)
Dim PrevProc As LongPtr
Public Track As TRACKMOUSEEVENT
Public NoRecursForm As Boolean
Public Sub Hook(ByVal frmHWnd As LongPtr)
PrevProc = SetWindowLong(frmHWnd, GWLP_WNDPROC, AddressOf WindowProc)
End Sub
Public Sub UnHook(ByVal frmHWnd As LongPtr)
SetWindowLong frmHWnd, GWLP_WNDPROC, PrevProc
End Sub
Public Function WindowProc(ByVal hWnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
If uMsg = WM_MOUSELEAVE Then
If UserForm1.Image1.BackColor = vbGreen Then
UserForm1.Image1.BackColor = vbRed
Else
UserForm1.Image1.BackColor = vbGreen
End If
End If
WindowProc = CallWindowProc(PrevProc, hWnd, uMsg, wParam, lParam)
End Function
Sub Example()
Load UserForm1
UserForm1.Show
End Sub
В процедуре Hook основного модуля используется параметр дескриптора окна пользовательской формы frmHwnd. Я знаю, что у UserForm и его Controls нет свойства hwnd, поэтому мне опять же в помощь пришел справочный материал: https://colinlegg.wordpress.com/2016/05/06/getting-a-handle-on-userforms-vba/, изучив который я научился считывать дескриптор пользовательской формы.
Соответственно, код класса оговоренной для примера формы приобрел следующий вид:
Код
Option Explicit
Private Declare PtrSafe Function FindWindowA Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private mlnghWnd As LongPtr
Public Property Get hWnd() As LongPtr
hWnd = mlnghWnd
End Property
Private Sub UserForm_Initialize()
StorehWnd
Hook Me.hWnd
With Track
.cbSize = Len(Track)
.dwFlags = TME_LEAVE
.hwndTrack = Me.hWnd
.dwHoverTime = 400
End With
End Sub
Private Sub StorehWnd()
Dim strCaption As String
Dim strClass As String
'class name changed in Office 2000
If Val(Application.Version) >= 9 Then
strClass = "ThunderDFrame"
Else
strClass = "ThunderXFrame"
End If
'remember the caption so we can
'restore it when we're done
strCaption = Me.Caption
'give the userform a random
'unique caption so we can reliably
'get a handle to its window
Randomize
Me.Caption = CStr(Rnd)
'store the handle so we can use
'it for the userform's lifetime
mlnghWnd = FindWindowA(strClass, Me.Caption)
'set the caption back again
Me.Caption = strCaption
End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
TRACKMOUSEEVENT Track
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
UnHook Me.hWnd
End Sub
Однако не работает( При пошаговом исполнении при обращении к функции WindowProc, я заметил, что параметр uMsg вообще даже близко не сопоставляется с установленной константой WM_MOUSELEAVE. При этом происходит рекурсивное обращение к указанной функции, параметр uMsg меняется каждый раз, а после раза 4-5 рекурсивного вызова, приложение просто перезапускается или выгружается.
Уважаемые знатоки темы, подскажите, где у меня ошибка и/или чего мне не хватает (кроме ума и знаний, тут, надеюсь, понятно, шутки будут неуместны).
Мда... Не удалось победить модальный вариант формы даже с помощью API функции TrackMouseEvent. Раскопал в загашнике 32-х битную версию винды, посмотрел, как работает эта функция на примере, упомянутом в ссылке вышеизложенного поста и понял, что все равно, как не трудись, а все упирается в событие MouseMove, которое при определенных ситуациях срабатывает, к сожалению, далеко не всегда. Удалось это даже реализовать "костыльным" методом в 64-х разрядной операционной среде Однако не сожалению о приобретенных знаниях, изучая справочные материалы, к адаптации API функций к 64-bit системе. Задачку все-таки удалось решить, то есть достигнуть желаемого результата пусть и не совсем так, как это изложено в теме. Сосредоточился на использование немодального варианта UserForm. Если в листе книги Excel вызывается подобного рода форма и этот лист не обременен необходимостью обрабатывать свои события, то решение простенькое, наглядное, с которым можно ознакомиться в прилагаемом примере (Пример 1.zip) Другое дело, если в листе книги Excel, в котором вызывается форма, задействуются события, такие как, например, SelectionChange. В этом случае, особенно если для последующих вычислений, переадресации результирующих данных активного листа книги в другие области, организации ссылок и т.п. используются данные из вызванной формы, которые изменяют свойства объектов активного листа, то применение немодальной формы, когда возможно прямое изменение свойств объектов листа книги Excel пользователем в обход вызванного на экран диалогового окна, чревато непредсказуемыми последствиями. Соответственно, пользователя нужно попытаться оградить от случайных "нежданчиков", когда у него случайно дернется рука, находящаяся на мышке, или просто обернувшись, случайно заденет мышь локтем, чтобы он визуально увидел, что диалог прервался. Много различных вариантов можно придумать Либо, если это злоумышленник, то не позволить ему совершить свое коварное дело изменить результат путем ручного (не через форму) изменения ключевых данных листа книги Excel Но машине-то по фигу, она реагирует на событие, созданное пользователем. Поэтому, в результате упорных поисков и экспериментов, пришел к альтернативному варианту использования немодальной формы и принудительного контроля курсора мыши. При этом управление курсором мыши я захватываю уже на этапе загрузки формы. Желающие могут ознакомиться с решением в прилагаемом файле Пример 2.zip