【问题标题】:How to get the border sizes of a userform?如何获取用户窗体的边框大小?
【发布时间】:2020-04-17 02:13:49
【问题描述】:

我有一个带有多个控件的用户表单 (userform1)。一个控件是一个命令按钮,它将打开第二个用户窗体 (userform2)。

我希望 userform2 立即在按钮下方打开并以它为中心。

为了无论 Windows 的系统/主题定义如何都具有相同的行为,我需要知道 userform1 的边框大小。

经过 3 天的挖掘,我使用了 API 函数 GetWindowRect 和 GetWindowClient。通过这两个 API 例程,我可以找到水平边框(上加下)和垂直边框(左加右)的总大小,但不能单独找到它们。

对于垂直边框,它们具有相同的粗细(宽度)是常识——事实上,我从未见过左右边框不同的窗口。因此,解决方案是将总大小除以 2。但是,对于水平边框,这不能使用,因为上边框通常比下边框厚。

最终,我找到了解决该问题的方法,但不能始终应用它。也就是说,如果 userform1 内部有一个框架控件,那么 API 函数 GetWindowRect 可以用来找到框架的“绝对”坐标,即参考屏幕,而不是 userform1。然后,上边框大小由下式给出:frame.top_Absolute - (Userform1.top_Absolute - frame.top_RelativeToUserform1)。

这种方法的问题是,用户窗体总是没有框架控件。另一方面,并​​非所有控件都具有“矩形”属性。因此,GetWindowRect 不能用于所有控件。

问题:有没有一种“直接”的方法可以找到用户窗体的边框大小?

代码

在普通模块中:

Option Explicit

'API Declarations

#If VBA7 Then
Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal Index As Long) As Long
Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnD As Long) As Long
Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hWnD As Long, ByVal hDC As Long) As Long
Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal Index As Long) As Long
Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hWnD As Long, ByRef lpRect As udtRECT) As Long
Declare PtrSafe Function GetClientRect Lib "user32" (ByVal hWnD As Long, ByRef lpRect As udtRECT) As Long
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
#Else
Declare Function GetSystemMetrics Lib "user32" (ByVal Index As Long) As Long
Declare Function GetDC Lib "user32" (ByVal hWnD As Long) As Long
Declare Function ReleaseDC Lib "user32" (ByVal hWnD As Long, ByVal hDC As Long) As Long
Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal Index As Long) As Long
Declare Function GetWindowRect Lib "user32" (ByVal hWnD As Long, ByRef lpRect As udtRECT) As Long
Declare Function GetClientRect Lib "user32" (ByVal hWnD As Long, ByRef lpRect As udtRECT) As Long
Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
#End If

Type udtRECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Public Type BorderSize
    TopHeight As Long
    LeftWidth As Long
    BottomHeight As Long
    RightWidth As Long
End Type

Public FormBorders As BorderSize

'To determine the sizes of the borders

Public Sub GetFormBorders(ByVal FormHandler As Long, ByVal FrameHandler As Long)

Dim rectForm As udtRECT
Dim rectFrame As udtRECT
Dim rectClientForm As udtRECT
Dim Trash As Long

Trash = GetWindowRect(FormHandler, rectForm)
Trash = GetWindowRect(FrameHandler, rectFrame)
Trash = GetClientRect(FormHandler, rectClientForm)

FormBorders.TopHeight = ConvertPixelsToPoints(rectFrame.Top - rectForm.Top, "Y") - frmFlyschGSI.fraRockProp.Top         'userform1.frame.top
FormBorders.LeftWidth = ConvertPixelsToPoints(rectFrame.Left - rectForm.Left, "X") - frmFlyschGSI.fraRockProp.Left
FormBorders.BottomHeight = ConvertPixelsToPoints(rectForm.Bottom - rectForm.Top, "Y") - FormBorders.TopHeight - _
                           ConvertPixelsToPoints(rectClientForm.Bottom - rectClientForm.Top, "Y")
FormBorders.RightWidth = ConvertPixelsToPoints(rectForm.Right - rectForm.Left, "X") - FormBorders.LeftWidth - _
                         ConvertPixelsToPoints(rectClientForm.Right - rectClientForm.Left, "X")

Debug.Print FormBorders.TopHeight, FormBorders.LeftWidth, FormBorders.BottomHeight, FormBorders.RightWidth

End Sub

'To convert pixels to points

Public Function ConvertPixelsToPoints(ByVal sngPixels As Single, ByVal sXorY As String) As Single

'Credits to: https://bettersolutions.com/vba/userforms/positioning.htm

Dim hDC As Long

hDC = GetDC(0)

If sXorY = "X" Then
    ConvertPixelsToPoints = sngPixels * (72 / GetDeviceCaps(hDC, 88))
End If

If sXorY = "Y" Then
    ConvertPixelsToPoints = sngPixels * (72 / GetDeviceCaps(hDC, 90))
End If

Call ReleaseDC(0, hDC)

End Function

'在用户表单代码表中:

Option Explicit


Private Sub UserForm_Initialize()

'Some code here

If Me.Visible = False Then
    Call GetFormBorders(FindWindow(vbNullString, frmFlyschGSI.Caption), frmFlyschGSI.fraRockProp.[_GethWnd])
End If

'More code here

End Sub


Private Sub cmdMiHarder_Click()

Dim FrameBorder As udtRECT
Dim Trash As Long
Dim sngTopBorder As Single
Dim sngLeftBorder As Single

'Some code here

Trash = GetWindowRect(Me.fraRockProp.[_GethWnd], FrameBorder)

sngTopBorder = ConvertPixelsToPoints(FrameBorder.Top, "Y") - (Me.Top + Me.fraRockProp.Top)
sngLeftBorder = ConvertPixelsToPoints(FrameBorder.Left, "X") - (Me.Left + Me.fraRockProp.Left)

'More code here

End Sub

【问题讨论】:

  • THIS 你在尝试什么?
  • 是的,在运行时类似,但 userform2 的顶部“接触”了 CommandButton1 的底部

标签: excel vba api


【解决方案1】:

逻辑:

  1. 将 Userform1 显示为无模式。这是必需的,以便 Userform2 可以显示为无模式
  2. 将 Userform2 显示为无模式。这是必需的,以便可以移动 Userform2
  3. 将 Userform2 移动到相关位置

新位置计算:

可以用下图更好地解释

在模块中:

Option Explicit

Sub Sample()
    UserForm1.Show vbModeless
End Sub

Userform1代码区:

Option Explicit

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Private Declare Function ClientToScreen Lib "user32" _
(ByVal hwnd As Long, lpPoint As POINTAPI) As Long

Private Declare Function GetDeviceCaps Lib "Gdi32" _
(ByVal hDC As Long, ByVal nIndex As Long) As Long

Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long

Private Declare Function ReleaseDC Lib "user32" _
(ByVal hwnd As Long, ByVal hDC As Long) As Long

Private Declare Function SetWindowPos Lib "user32" ( _
ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
ByVal x As Long, ByVal y As Long, ByVal cx As Long, _
ByVal cy As Long, ByVal wFlags As Long) As Long

Private Const LOGPIXELSX = 88
Private Const LOGPIXELSY = 90

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Const HWND_TOP = 0
Private Const SWP_NOSIZE = &H1

Private Sub CommandButton1_Click()
    RepositionForm UserForm2, CommandButton1
End Sub

Public Sub RepositionForm(f As Object, c As Object)
    Dim P As POINTAPI
    Dim meHwnd As Long, hwnd As Long

    meHwnd = FindWindow(vbNullString, Me.Caption)

    P.x = (c.Left - (f.Width / 4)) / PointsPerPixelX
    P.y = (c.Top + c.Height) / PointsPerPixelY

    '~~> The ClientToScreen function converts the client coordinates
    '~~> of a specified point to screen coordinates.
    ClientToScreen meHwnd, P

    UserForm2.Show vbModeless

    '~~> Get Handle of Userform2
    hwnd = FindWindow("ThunderDFrame", "UserForm2")

    '~~> Move the form to relevant location
    SetWindowPos hwnd, HWND_TOP, P.x, P.y, 0, 0, SWP_NOSIZE
End Sub

Private Function PointsPerPixelX() As Double
    Dim hDC As Long
    hDC = GetDC(0)
    PointsPerPixelX = 72 / GetDeviceCaps(hDC, LOGPIXELSX)
    ReleaseDC 0, hDC
End Function

Public Function PointsPerPixelY() As Double
    Dim hDC As Long
    hDC = GetDC(0)
    PointsPerPixelY = 72 / GetDeviceCaps(hDC, LOGPIXELSY)
    ReleaseDC 0, hDC
End Function

截图

【讨论】:

  • 放置 userform2 的好解决方案!谢谢你。你的代码让我找到了计算用户窗体边框大小的正确方法,不管它的控件:关键是 ClientToScreen API 函数,它替换了我之前代码中使用的“框架”,以在用户表单。相关代码见下方回复。
【解决方案2】:

阅读 Siddharth Rout 的代码后,我现在可以回答我自己的问题了。关键是使用 ClientToScreen API 函数找到客户端窗口(用户窗体)左上角的“屏幕”坐标。

我将代码留在这里,以防有人需要知道用户窗体的边框大小。

在普通模块中:

Option Explicit
'
'API Declarations
'
#If VBA7 Then
    Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnD As Long) As Long
    Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hWnD As Long, ByVal hDC As Long) As Long
    Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal Index As Long) As Long
    Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hWnD As Long, ByRef lpRect As udtRECT) As Long
    Declare PtrSafe Function GetClientRect Lib "user32" (ByVal hWnD As Long, ByRef lpRect As udtRECT) As Long
    Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
    Declare PtrSafe Function ClientToScreen Lib "user32" (ByVal hWnD As Long, ByRef lpPoint As PointAPI) As Long
#Else
    Declare Function GetDC Lib "user32" (ByVal hWnD As Long) As Long
    Declare Function ReleaseDC Lib "user32" (ByVal hWnD As Long, ByVal hDC As Long) As Long
    Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal Index As Long) As Long
    Declare Function GetWindowRect Lib "user32" (ByVal hWnD As Long, ByRef lpRect As udtRECT) As Long
    Declare Function GetClientRect Lib "user32" (ByVal hWnD As Long, ByRef lpRect As udtRECT) As Long
    Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
    Declare Function ClientToScreen Lib "user32" (ByVal hWnD As Long, ByRef lpPoint As PointAPI) As Long
#End If
'
Public Type udtRECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type
'
Public Type PointAPI
    x As Long
    y As Long
End Type
'
Public Type BorderSize
    TopHeight As Single
    LeftWidth As Single
    BottomHeight As Single
    RightWidth As Single
End Type
'
' To determine the sizes of the borders
'
Public Function FormBorders(ByVal FormHandler As Long) As BorderSize
'
' Credits to Siddharth Rout for the usage of ClientToScreen API function in this context.
'
    Dim rectWindow As udtRECT
    Dim rectClient As udtRECT
    Dim P As PointAPI
    Dim VerBorders As Single
    Dim HorBorders As Single
    Dim Trash As Long
'
    Trash = GetWindowRect(FormHandler, rectWindow)
    Trash = GetClientRect(FormHandler, rectClient)
'
'   Sets the upper left corner of the "client" window...
    P.x = 0
    P.y = 0
    Trash = ClientToScreen(FormHandler, P)      '...and gets its screen coordinates.
'
'   Total dimensions of the borders in points, after converting pixels to points:
    VerBorders = ConvertPixelsToPoints((rectWindow.Right - rectWindow.Left) - (rectClient.Right - rectClient.Left), "X")
    HorBorders = ConvertPixelsToPoints((rectWindow.Bottom - rectWindow.Top) - (rectClient.Bottom - rectClient.Top), "Y")
'
'   Now the individual borders, one by one, in points:
    FormBorders.TopHeight = ConvertPixelsToPoints(P.y - rectWindow.Top, "Y")
    FormBorders.BottomHeight = HorBorders - FormBorders.TopHeight
    FormBorders.LeftWidth = ConvertPixelsToPoints(P.x - rectWindow.Left, "X")
    FormBorders.RightWidth = VerBorders - FormBorders.LeftWidth
'
    Debug.Print FormBorders.TopHeight, FormBorders.LeftWidth, FormBorders.BottomHeight, FormBorders.RightWidth
'
End Function
'
'To convert pixels to points
'
Public Function ConvertPixelsToPoints(ByVal sngPixels As Single, ByVal sXorY As String) As Single
'
'Credits to: https://bettersolutions.com/vba/userforms/positioning.htm
'
    Dim hDC As Long
'
    hDC = GetDC(0)
    If sXorY = "X" Then
        ConvertPixelsToPoints = sngPixels * (72 / GetDeviceCaps(hDC, 88))
    End If
'
    If sXorY = "Y" Then
        ConvertPixelsToPoints = sngPixels * (72 / GetDeviceCaps(hDC, 90))
    End If
    Call ReleaseDC(0, hDC)
'
End Function

在用户表单的代码表中:

选项显式

Private Sub UserForm_Initialize()
'
    Dim MeBorders As BorderSize

    MeBorders = FormBorders(FindWindow(vbNullString, Me.Caption))

    Debug.Print MeBorders.TopHeight, MeBorders.LeftWidth, MeBorders.BottomHeight, MeBorders.RightWidth

End Sub

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-21
    • 2016-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多