如何在大型工作表上的无模式 vba 用户窗体中实现对控件的响应式鼠标悬停效果
How to achieve a responsive mouseover effect on Controls in a modeless vba Userform on a large Worksheet
我有以下代码,它在普通 VBA 用户窗体上工作得很好:每当鼠标悬停在标签上的任何地方时,所述标签的颜色为红色,否则为白色。此效果非常灵敏,使标签感觉非常像按钮。
带有 1 个标签的用户表单代码:
Dim active As Boolean
Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If active = False Then
Label1.BackColor = RGB(255, 0, 0)
active = True
End If
End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If active = True Then
Label1.BackColor = RGB(255, 255, 255)
active = False
End If
End Sub
如果我将 UserForm 更改为无模式,从这样的模块:
Sub loader()
UserForm1.Show vbModeless
End Sub
鼠标悬停效果仍然有效,但变得非常缓慢且无响应。好像刷新率大幅下降了。
编辑:我发现这个问题只有在Active Worksheet很大时才会出现,这显然会减慢一切。让我头疼的 sheet 有大约 1000 行和 50 列,其中许多单元格包含更长的字符串。我认为 sheet 本身大约有 1MB 的数据。 Forumlas 设置为仅手动刷新。我在一台配备 i7 8550U 和 8GB 内存的笔记本电脑上使用 Office 32 位。
我的问题是:
是否可以在无模式用户窗体中实现模式用户窗体的行为?
我寻找了操纵无模式用户窗体刷新率的方法,但找不到任何有用的方法。
另一种解决方案是在用户窗体以模态模式显示时使工作sheet中的滚动成为可能。
另一种解决方案可能是在鼠标悬停在用户窗体上时使用户窗体成为模态,而在鼠标离开某个区域(用户窗体边界)后成为无模式。这可能吗?
编辑:经过一天的反复试验,我找到了一个简单的解决方案
我现在将描述简单的解决方案,并将我之前找到的复杂解决方案留在下面,作为替代方案。
简单的解决方案:
1st 我们需要从 Windows API:
获取睡眠函数
#If Win64 Then
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#If Win32 Then
Public Declare Sub Sleep Lib "kernel32" (ByVal lngMilliSeconds As Long)
#End If
注意:仅针对 32 位 Office 进行了测试,但应该也适用于 64 位
其次,我们声明一个布尔值,它将指示用户窗体当前是打开还是关闭:
Public UF1open As Boolean
最后我们在 Userforms Activate 事件中包含以下代码:
Private Sub UserForm_Activate()
UF1open = True
Do
Sleep 1 'this correlates to the "refresh rate" of the mouseover effect, sleep 100 leads to sluggish behaviour
DoEvents
Loop Until UF1open = False
End Sub
以及用户窗体终止事件中的以下内容:
Private Sub UserForm_Terminate()
UF1open = False
End Sub
下面是我想出的第一个复杂困难的解决方案:
这使用我提出的最后一个解决方案思路解决了问题。
我使用户窗体在鼠标位于用户窗体区域时自动进入模式,并在鼠标离开用户窗体区域后自动进入无模式。这样做需要一堆 API 函数。
以下不是最干净的代码,也不是很稳定(粗心的错误处理)但证明了这个概念:
这是调用用户窗体的模块:
Option Explicit
#If Win32 Then
Public Declare Function GetCursorPos Lib "user32" (lpPoint As PointAPI) As Long
Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal lngMilliSeconds As Long)
Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Declare Function GetDeviceCaps Lib "Gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hDC As Long) As Long
#End If
Const LogPixelsX = 88
Const LogPixelsY = 90
Public Type PointAPI
x As Long
y As Long
End Type
Public ufXposScreen As Long
Public ufYposScreen As Long
Public ufXposVBA As Long
Public ufYposVBA As Long
Public ufXpos2Screen As Long
Public ufYpos2Screen As Long
Public ufXpos2VBA As Long
Public ufYpos2VBA As Long
Public UFname As String
Public JustStarted As Boolean 'to catch the first time a function is called
Public ModleS As Boolean 'indicate whether the UF is currently moedless or not
Sub loader()
#If Win64 Then
MsgBox "Sorry 64 bit not supported"
Exit Sub
#End If
ModleS = False
JustStarted = True
UserForm1.Show
End Sub
Public Function IsLoaded(formName As String) As Boolean 'returns if UF is currently loaded or not
Dim frm As Object
For Each frm In VBA.UserForms
If frm.Name = formName Then
IsLoaded = True
Exit Function
End If
Next frm
IsLoaded = False
End Function
Public Function pointsPerPixelX() As Double 'Used for transforming windows API Mouse-coordinates to vba coordinates
Dim hDC As Long
hDC = GetDC(0)
pointsPerPixelX = 72 / GetDeviceCaps(hDC, LogPixelsX)
ReleaseDC 0, hDC
End Function
Public Function pointsPerPixelY() As Double 'Used for transforming windows API Mouse-coordinates to vba coordinates
Dim hDC As Long
hDC = GetDC(0)
pointsPerPixelY = 72 / GetDeviceCaps(hDC, LogPixelsY)
ReleaseDC 0, hDC
End Function
Public Function GetX() As Long 'Get current X coordinate of Mouse
Dim n As PointAPI
GetCursorPos n
GetX = n.x
End Function
Public Function GetY() As Long 'Get current y coordinate of Mouse
Dim n As PointAPI
GetCursorPos n
GetY = n.y
End Function
Public Sub WaitSeconds(sngSeconds As Single) 'sub pausing application for given value of seconds
On Error GoTo errHand
Dim s As Single
s = Timer + sngSeconds
Do
Sleep 1 'this correlates to the "refresh rate" of the mouseover effect, sleep 100 leads to sluggish behaviour
DoEvents
Loop Until Timer >= s
Done:
Exit Sub
errHand:
MsgBox "Error: " & Err.Number & ". " & Err.Description, , "modDateTime.WaitSeconds"
Resume Done
End Sub
Public Sub RunAllTime(ByRef UF As Object)
'// this sub is called in the uf_activate event and loops
'// all the time. if the mouse leaves the uf area if makes
'// the userform go modeless, if the mouse reenters the area
'// the sub exits, but not before using uf.show to make the
'// uf modal again. uf.show automatically recalls this sub
'// because of the activate event.
Dim x As Long
Dim y As Long
If JustStarted Then
UFname = UF.Name
JustStarted = False
End If
Do
WaitSeconds 0.5
If IsLoaded(UFname) = False Then
End
End If
x = GetX()
y = GetY()
With UF
If .Left <> ufXposVBA Or .Top <> ufYposVBA Or (.Left + .Width) <> ufXpos2VBA Or (.Top + .Height) <> ufYpos2VBA Then
ufXposVBA = .Left
ufYposVBA = .Top
ufXposScreen = .Left / pointsPerPixelX()
ufYposScreen = .Top / pointsPerPixelY()
ufXpos2VBA = .Left + .Width
ufYpos2VBA = .Top + .Height
ufXpos2Screen = (.Left + .Width) / pointsPerPixelX()
ufYpos2Screen = (.Top + .Height) / pointsPerPixelY()
End If
If ModleS = False Then
If x < ufXposScreen Or x > ufXpos2Screen Or y < ufYposScreen Or y > ufYpos2Screen Then
UF.Hide
UF.Show vbModeless
ModleS = True
End If
Else
If x > ufXposScreen And x < ufXpos2Screen And y > ufYposScreen And y < ufYpos2Screen Then
UF.Hide
ModleS = False
UF.Show
Exit Sub
End If
End If
End With
Loop
End Sub
这是用户表单的代码:
Dim active As Boolean
Private Sub UserForm_Initialize()
With UserForm1
ufXposVBA = .Left
ufYposVBA = .Top
ufXposScreen = .Left / pointsPerPixelX()
ufYposScreen = .Top / pointsPerPixelY()
ufXpos2VBA = .Left + .Width
ufYpos2VBA = .Top + .Height
ufXpos2Screen = (.Left + .Width) / pointsPerPixelX()
ufYpos2Screen = (.Top + .Height) / pointsPerPixelY()
End With
End Sub
Private Sub UserForm_Activate()
RunAllTime UserForm1
End Sub
Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
If active = False Then
Label1.BackColor = RGB(255, 0, 0)
active = True
End If
End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
If active = True Then
Label1.BackColor = RGB(255, 255, 255)
active = False
End If
End Sub
如果有人对此感兴趣并设法找到更好的解决方案或可以改进我的代码,请在此处post。
我有以下代码,它在普通 VBA 用户窗体上工作得很好:每当鼠标悬停在标签上的任何地方时,所述标签的颜色为红色,否则为白色。此效果非常灵敏,使标签感觉非常像按钮。
带有 1 个标签的用户表单代码:
Dim active As Boolean
Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If active = False Then
Label1.BackColor = RGB(255, 0, 0)
active = True
End If
End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If active = True Then
Label1.BackColor = RGB(255, 255, 255)
active = False
End If
End Sub
如果我将 UserForm 更改为无模式,从这样的模块:
Sub loader()
UserForm1.Show vbModeless
End Sub
鼠标悬停效果仍然有效,但变得非常缓慢且无响应。好像刷新率大幅下降了。
编辑:我发现这个问题只有在Active Worksheet很大时才会出现,这显然会减慢一切。让我头疼的 sheet 有大约 1000 行和 50 列,其中许多单元格包含更长的字符串。我认为 sheet 本身大约有 1MB 的数据。 Forumlas 设置为仅手动刷新。我在一台配备 i7 8550U 和 8GB 内存的笔记本电脑上使用 Office 32 位。
我的问题是:
是否可以在无模式用户窗体中实现模式用户窗体的行为? 我寻找了操纵无模式用户窗体刷新率的方法,但找不到任何有用的方法。
另一种解决方案是在用户窗体以模态模式显示时使工作sheet中的滚动成为可能。
另一种解决方案可能是在鼠标悬停在用户窗体上时使用户窗体成为模态,而在鼠标离开某个区域(用户窗体边界)后成为无模式。这可能吗?
编辑:经过一天的反复试验,我找到了一个简单的解决方案 我现在将描述简单的解决方案,并将我之前找到的复杂解决方案留在下面,作为替代方案。
简单的解决方案: 1st 我们需要从 Windows API:
获取睡眠函数#If Win64 Then
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#If Win32 Then
Public Declare Sub Sleep Lib "kernel32" (ByVal lngMilliSeconds As Long)
#End If
注意:仅针对 32 位 Office 进行了测试,但应该也适用于 64 位
其次,我们声明一个布尔值,它将指示用户窗体当前是打开还是关闭:
Public UF1open As Boolean
最后我们在 Userforms Activate 事件中包含以下代码:
Private Sub UserForm_Activate()
UF1open = True
Do
Sleep 1 'this correlates to the "refresh rate" of the mouseover effect, sleep 100 leads to sluggish behaviour
DoEvents
Loop Until UF1open = False
End Sub
以及用户窗体终止事件中的以下内容:
Private Sub UserForm_Terminate()
UF1open = False
End Sub
下面是我想出的第一个复杂困难的解决方案:
这使用我提出的最后一个解决方案思路解决了问题。 我使用户窗体在鼠标位于用户窗体区域时自动进入模式,并在鼠标离开用户窗体区域后自动进入无模式。这样做需要一堆 API 函数。 以下不是最干净的代码,也不是很稳定(粗心的错误处理)但证明了这个概念:
这是调用用户窗体的模块:
Option Explicit
#If Win32 Then
Public Declare Function GetCursorPos Lib "user32" (lpPoint As PointAPI) As Long
Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal lngMilliSeconds As Long)
Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Declare Function GetDeviceCaps Lib "Gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hDC As Long) As Long
#End If
Const LogPixelsX = 88
Const LogPixelsY = 90
Public Type PointAPI
x As Long
y As Long
End Type
Public ufXposScreen As Long
Public ufYposScreen As Long
Public ufXposVBA As Long
Public ufYposVBA As Long
Public ufXpos2Screen As Long
Public ufYpos2Screen As Long
Public ufXpos2VBA As Long
Public ufYpos2VBA As Long
Public UFname As String
Public JustStarted As Boolean 'to catch the first time a function is called
Public ModleS As Boolean 'indicate whether the UF is currently moedless or not
Sub loader()
#If Win64 Then
MsgBox "Sorry 64 bit not supported"
Exit Sub
#End If
ModleS = False
JustStarted = True
UserForm1.Show
End Sub
Public Function IsLoaded(formName As String) As Boolean 'returns if UF is currently loaded or not
Dim frm As Object
For Each frm In VBA.UserForms
If frm.Name = formName Then
IsLoaded = True
Exit Function
End If
Next frm
IsLoaded = False
End Function
Public Function pointsPerPixelX() As Double 'Used for transforming windows API Mouse-coordinates to vba coordinates
Dim hDC As Long
hDC = GetDC(0)
pointsPerPixelX = 72 / GetDeviceCaps(hDC, LogPixelsX)
ReleaseDC 0, hDC
End Function
Public Function pointsPerPixelY() As Double 'Used for transforming windows API Mouse-coordinates to vba coordinates
Dim hDC As Long
hDC = GetDC(0)
pointsPerPixelY = 72 / GetDeviceCaps(hDC, LogPixelsY)
ReleaseDC 0, hDC
End Function
Public Function GetX() As Long 'Get current X coordinate of Mouse
Dim n As PointAPI
GetCursorPos n
GetX = n.x
End Function
Public Function GetY() As Long 'Get current y coordinate of Mouse
Dim n As PointAPI
GetCursorPos n
GetY = n.y
End Function
Public Sub WaitSeconds(sngSeconds As Single) 'sub pausing application for given value of seconds
On Error GoTo errHand
Dim s As Single
s = Timer + sngSeconds
Do
Sleep 1 'this correlates to the "refresh rate" of the mouseover effect, sleep 100 leads to sluggish behaviour
DoEvents
Loop Until Timer >= s
Done:
Exit Sub
errHand:
MsgBox "Error: " & Err.Number & ". " & Err.Description, , "modDateTime.WaitSeconds"
Resume Done
End Sub
Public Sub RunAllTime(ByRef UF As Object)
'// this sub is called in the uf_activate event and loops
'// all the time. if the mouse leaves the uf area if makes
'// the userform go modeless, if the mouse reenters the area
'// the sub exits, but not before using uf.show to make the
'// uf modal again. uf.show automatically recalls this sub
'// because of the activate event.
Dim x As Long
Dim y As Long
If JustStarted Then
UFname = UF.Name
JustStarted = False
End If
Do
WaitSeconds 0.5
If IsLoaded(UFname) = False Then
End
End If
x = GetX()
y = GetY()
With UF
If .Left <> ufXposVBA Or .Top <> ufYposVBA Or (.Left + .Width) <> ufXpos2VBA Or (.Top + .Height) <> ufYpos2VBA Then
ufXposVBA = .Left
ufYposVBA = .Top
ufXposScreen = .Left / pointsPerPixelX()
ufYposScreen = .Top / pointsPerPixelY()
ufXpos2VBA = .Left + .Width
ufYpos2VBA = .Top + .Height
ufXpos2Screen = (.Left + .Width) / pointsPerPixelX()
ufYpos2Screen = (.Top + .Height) / pointsPerPixelY()
End If
If ModleS = False Then
If x < ufXposScreen Or x > ufXpos2Screen Or y < ufYposScreen Or y > ufYpos2Screen Then
UF.Hide
UF.Show vbModeless
ModleS = True
End If
Else
If x > ufXposScreen And x < ufXpos2Screen And y > ufYposScreen And y < ufYpos2Screen Then
UF.Hide
ModleS = False
UF.Show
Exit Sub
End If
End If
End With
Loop
End Sub
这是用户表单的代码:
Dim active As Boolean
Private Sub UserForm_Initialize()
With UserForm1
ufXposVBA = .Left
ufYposVBA = .Top
ufXposScreen = .Left / pointsPerPixelX()
ufYposScreen = .Top / pointsPerPixelY()
ufXpos2VBA = .Left + .Width
ufYpos2VBA = .Top + .Height
ufXpos2Screen = (.Left + .Width) / pointsPerPixelX()
ufYpos2Screen = (.Top + .Height) / pointsPerPixelY()
End With
End Sub
Private Sub UserForm_Activate()
RunAllTime UserForm1
End Sub
Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
If active = False Then
Label1.BackColor = RGB(255, 0, 0)
active = True
End If
End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
If active = True Then
Label1.BackColor = RGB(255, 255, 255)
active = False
End If
End Sub
如果有人对此感兴趣并设法找到更好的解决方案或可以改进我的代码,请在此处post。