简体   繁体   English

Excel中从右到左的用户表单-VBA

[英]Right to left userforms in excel - VBA

Please Look at the code below and test it: 请查看下面的代码并进行测试:

Private Sub CommandButton1_Click()
   MsgBox "This window converted Right to Left!", vbMsgBoxRtlReading
End Sub

This code convert the message window from right to left. 此代码将消息窗口从右到左转换。 As the close button moves to the left of the window. 当关闭按钮移至窗口左侧时。 How do I do this for userforms? 我该如何针对用户表单? (Hope TM, Mathieu Guindon and ... does not say: " Your question is amiss. Please read the links ...." ) (希望TM,Mathieu Guindon和...不会说:“ 您的问题不对。请阅读链接....”

Like the picture below (Of course photo is photoshop!): 就像下面的图片(当然照片是photoshop!):

在此处输入图片说明

Simulate Right To Left display as in MsgBox MsgBox一样模拟从右到左的显示

It'll be necessary to use some API *) functions to get the wanted layout independant from language settings using right to left functionality by default. 必须使用一些API *)函数来获得所需的布局,而默认情况下,使用右至左功能可以使其与语言设置无关

  1. Identify the Userform's handle to get access to further API methods 确定用户窗体的句柄以访问其他API方法
  2. Remove the Userform's title bar 删除用户窗体的标题栏
  3. Replace it eg with a Label control displaying the caption and give it drag functionality to move the UserForm (here: Label1 ). 例如,将其替换为显示标题的Label控件,并为其提供拖动功能以移动UserForm(此处为Label1 )。
  4. Use another control (here: Label2 ) to simulate the system escape "x". 使用另一个控件(这里: Label2 )来模拟系统转义Label2 “ x”。

    *) API - Application Programming Interface *) API-应用程序编程接口

A simple UserForm code example 一个简单的UserForm代码示例

All you need is to provide for 2 Label controls where Label1 replaces the title bar and receives the UserForm's caption and Label2 simulates the system Escape "x". 您需要提供2个Label控件,其中Label1替换标题栏并接收UserForm的标题,而Label2模拟系统转义Label2 “ x”。 Furthermore this example uses a Type declaration for easy disposal of the UserForm handle for several event procedures needing it for further API actions. 此外,此示例使用Type声明来轻松处理UserForm 句柄 ,以处理多个事件过程,需要使用它进行进一步的API操作。

► Note to 2nd edit as of 10/22 2018 ►截至2018年10月22日第2次编辑的注意事项

As a window handle is declared as LongPtr in Office 2010 or higher and as Long in versions before, it was necessary to differentiate between the different versions by conditional compile constants (eg #If VBA7 Then ... #Else ... #End If ; cf. section II. using also the Win64 constant to identify actually installed 64bit Office systems - note that frequently Office is installed as 32bit by default). 由于在Office 2010或更高版本LongPtr窗口句柄声明为LongPtr ,而在以前的版本中则将其声明为Long ,因此有必要通过条件编译常量来区分不同版本(例如#If VBA7 Then ... #Else ... #End If ;参见第二节,也使用Win64常量来标识实际安装的64位Office系统-注意,默认情况下,经常将Office安装为32位。

Option Explicit                 ' declaration head of userform code module

#If VBA7 Then                   ' compile constant for Office 2010 and higher
    Private Type TThis          ' Type declaratation
        frmHandle As LongPtr    ' receives form window handle 64bit to identify this userform
    End Type
#Else                           ' older versions
    Private Type TThis          ' Type declaratation
        frmHandle As Long       ' receives form window handle 32bit to identify this userform
    End Type
#End If
Dim this As TThis               ' this - used by all procedures within this module

Private Sub UserForm_Initialize()
' ~~~~~~~~~~~~~~~~~~~~~~~
' [1] get Form Handle
' ~~~~~~~~~~~~~~~~~~~~~~~
  this.frmHandle = Identify(Me) ' get UserForm handle via API call (Long)
' ~~~~~~~~~~~~~~~~~~~~~~~
' [2] remove System Title Bar
' ~~~~~~~~~~~~~~~~~~~~~~~
  HideTitleBar (this.frmHandle) ' hide title bar via API call
End Sub

Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
' Purpose: Replaces System Title Bar (after removal via API) and receives dragging functionality
   ' ~~~~~~~~~~~~~~~~~~~~~~~~~~
   ' [3] allow to move UserForm
   ' ~~~~~~~~~~~~~~~~~~~~~~~~~~
     If Button = 1 Then DragForm this.frmHandle
End Sub

Private Sub Label2_Click()
' Purpose:  Label "x" replaces System Escape (after removal in step [2])and hides UserForm
' ~~~~~~~~~~~~~~~~~
' [4] hide UserForm
' ~~~~~~~~~~~~~~~~~
  Me.Hide
End Sub

Private Sub UserForm_Layout()
  Me.RightToLeft = True
' Simulated Escape Icon
  Me.Label2.Caption = " x"
  Me.Label2.BackColor = vbWhite
  Me.Label2.Top = 0
  Me.Label2.Left = 0
  Me.Label2.Width = 18: Me.Label2.Height = 18
' Simulated UserForm Caption
  Me.Label1.Caption = Me.Caption
  Me.Label1.TextAlign = fmTextAlignRight    ' <~~ assign right to left property
  Me.Label1.BackColor = vbWhite
  Me.Label1.Top = 0: Me.Label1.Left = Me.Label2.Width: Me.Label1.Height = Me.Label2.Height
  Me.Label1.Width = Me.Width - Me.Label2.Width - 4
End Sub

II. II。 Separate code module for API functions API函数的独立代码模块

a) Declaration head with constants and special API declarations a)具有常量和特殊API声明的声明头

It's necessary to provide for different application versions as the code declarations differ in some arguments (eg PtrSafe). 由于某些参数的代码声明不同(例如PtrSafe),因此有必要提供不同的应用程序版本。 64 bit declarations start as follows: Private Declare PtrSafe ... 64位声明开始如下: Private Declare PtrSafe ...

Take also care of the correct declarations via #If , #Else and #End If allowing version dependant compilation. 就拿也不在乎通过正确的声明#If#Else#End If允许的版本依赖编译。

The prefix &H used in constants stands for hexadecimal values. 常量中使用的前缀&H代表十六进制值。

Option Explicit

Private Const WM_NCLBUTTONDOWN = &HA1&
Private Const HTCAPTION = 2&
Private Const GWL_STYLE = (-16)
Private Const WS_BORDER = &H800000
Private Const WS_DLGFRAME = &H400000
Private Const WS_CAPTION = WS_BORDER Or WS_DLGFRAME

#If VBA7 Then                                               ' True if you're using Office 2010 or higher
    ' [0] ReleaseCapture
    Private Declare PtrSafe Sub ReleaseCapture Lib "User32" ()
    ' [1] SendMessage
    Private Declare PtrSafe Function SendMessage Lib "User32" _
      Alias "SendMessageA" _
      (ByVal hWnd As LongPtr, ByVal wMsg As Long, _
      ByVal wParam As LongPtr, lParam As Any) As LongPtr    ' << arg's hWnd, wParam + function type: LongPtr
    ' [2] FindWindow
    Private Declare PtrSafe Function FindWindow Lib "User32" _
            Alias "FindWindowA" _
           (ByVal lpClassName As String, _
            ByVal lpWindowName As String) As LongPtr        ' << function type: LongPtr
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ' Two API functions requiring the Win64 compile constant for 64bit Office installations
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #If Win64 Then                                          ' true if Office explicitly installed as 64bit
      ' [3a] Note that GetWindowLong has been replaced by GetWindowLongPtr
        Private Declare PtrSafe Function GetWindowLongPtr Lib "User32" _
            Alias "GetWindowLongPtrA" _
           (ByVal hWnd As LongPtr, _
            ByVal nIndex As Long) As LongPtr
      ' [3b] Note that GetWindowLong has been replaced by GetWindowLongPtr
      '      Changes an attribute of the specified window.
      '      The function also sets a value at the specified offset in the extra window memory.
        Private Declare PtrSafe Function SetWindowLongPtr Lib "User32" _
            Alias "SetWindowLongPtrA" _
           (ByVal hWnd As LongPtr, _
            ByVal nIndex As Long, _
            ByVal dwNewLong As LongPtr) As LongPtr
    #Else                                                   ' true if Office install defaults 32bit
      ' [3aa] Note that GetWindowLong has been replaced by GetWindowLongPtr Alias GetWindowLongA !
        Private Declare PtrSafe Function GetWindowLongPtr Lib "User32" _
            Alias "GetWindowLongA" _
           (ByVal hWnd As LongPtr, _
            ByVal nIndex As Long) As LongPtr
      ' [3bb] Note that GetWindowLong has been replaced by GetWindowLongPtr Alias SetWindowLongA !
        Private Declare PtrSafe Function SetWindowLongPtr Lib "User32" _
            Alias "SetWindowLongA" _
           (ByVal hWnd As LongPtr, _
            ByVal nIndex As Long, _
            ByVal dwNewLong As LongPtr) As LongPtr

    #End If

    ' [4] DrawMenuBar
    Private Declare PtrSafe Function DrawMenuBar Lib "User32" _
           (ByVal hWnd As LongPtr) As Long                  ' << arg hWnd: LongPtr

#Else                                                       ' True if you're using Office before 2010 ('97)

    Private Declare Sub ReleaseCapture Lib "User32" ()
    Private Declare Function SendMessage Lib "User32" _
          Alias "SendMessageA" _
          (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

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


    Private Declare Function GetWindowLong Lib "User32" _
            Alias "GetWindowLongA" _
           (ByVal hWnd As Long, _
            ByVal nIndex As Long) As Long

    Private Declare Function SetWindowLong Lib "User32" _
            Alias "SetWindowLongA" _
           (ByVal hWnd As Long, _
            ByVal nIndex As Long, _
            ByVal dwNewLong As Long) As Long

    Private Declare Function DrawMenuBar Lib "User32" _
           (ByVal hWnd As Long) As Long
#End If

b) Following Procedures (after section a) b)遵循程序(在a节之后)

' ~~~~~~~~~~~~~~~~~~~~~~
' 3 Procedures using API
' ~~~~~~~~~~~~~~~~~~~~~~

#If VBA7 Then                               ' Office 2010 and higher
    Public Function Identify(frm As Object) As LongPtr
    ' Purpose: [1] return window handle of form
    ' Note:    vbNullString instead of ThunderXFrame (97) and class names of later versions
      Identify = FindWindow(vbNullString, frm.Caption)
    End Function

    Public Sub HideTitleBar(hWnd As LongPtr)
    ' Purpose: [2] remove Userform title bar
      SetWindowLongPtr hWnd, GWL_STYLE, GetWindowLongPtr(hWnd, GWL_STYLE) And Not WS_CAPTION
    End Sub
        Public Sub ShowTitleBar(hWnd As LongPtr)
        ' Purpose: show Userform title bar
          SetWindowLongPtr hWnd, GWL_STYLE, GetWindowLongPtr(hWnd, GWL_STYLE) Or WS_CAPTION
        End Sub

    Public Sub DragForm(hWnd As LongPtr)
    ' Purpose: [3] allow to drag & move userform via control (here via e.g.: Label1)
      Call ReleaseCapture
      Call SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&)
    End Sub

#Else                                       ' vers. before Office 2010 (Office '97)
    Public Function Identify(frm As Object) As Long
    ' Purpose: [1] return window handle of form
    ' Note:    vbNullString instead of ThunderXFrame (97) and class names of later versions
      Identify = FindWindow(vbNullString, frm.Caption)
    End Function
    Public Sub HideTitleBar(hWnd As Long)
    ' Purpose: [2] remove Userform title bar
      SetWindowLong hWnd, GWL_STYLE, GetWindowLong(hWnd, GWL_STYLE) And Not WS_CAPTION
    End Sub
    '    Public Sub ShowTitleBar(HWND As Long)
    '    ' Purpose: show Userform title bar
    '      SetWindowLong HWND, GWL_STYLE, GetWindowLong(HWND, GWL_STYLE) Or WS_CAPTION
    '    End Sub

    Public Sub DragForm(hWnd As Long)
    ' Purpose: [3] allow to drag & move userform via control (here via e.g.: Label1)
      Call ReleaseCapture
      Call SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&)
    End Sub


#End If

► Caveat: API declarations not tested for actually installed 64 bit systems in Office 2010 or higher. ►警告: 未经Office 2010或更高版本中实际安装的64位系统测试的API声明。 The 2nd Edit as of 10/22 2018 tries to correct several LongPtr declarations (only for pointers to a → handle or → memory location) and using the current Get/SetWindowLongPtr function differentiating explicitly between Win64 and Win32 ; 截至2018年10月22日第二次Edit尝试更正多个LongPtr声明(仅用于指向→句柄或→内存位置的指针),并使用当前的Get / SetWindowLongPtr函数在Win64Win32之间进行显式区分; cf. 比照 also edited Type declaration in the UserForm code module's declaration head). 还编辑了UserForm代码模块的声明标题中的Type声明)。

See also Compatibility between 32bit and 64bit Versions of Office 2010 and Office 2010 Help Files: Win32API PtrSafe with 64bit Support 另请参阅Office 2010Office 2010帮助文件的 32位和64位版本之间的兼容性 :具有64位支持的Win32API PtrSafe

Additional note 附加说明

UserForms are Windows and can be identified by their window handle . UserForm是Windows,可以通过其窗口句柄进行标识。 The API function used for this purpose is FindWindow disposing of two arguments: 1) A string giving the name of the class of the window it needs to find and 2) a string giving the caption of the window (UserForm) it needs to find. 用于此目的的API函数是FindWindow ,它包含两个参数:1)一个字符串,提供需要查找的窗口的类的名称; 2)字符串,提供需要查找的窗口的标题 (UserForm)。

Therefore frequently one distinguishes between version '97 (UserForm class name "ThunderXFrame") and later versions ("ThunderDFrame"): 因此,经常会在版本'97(用户窗体类名称为“ ThunderXFrame”)和更高版本(“ ThunderDFrame”)之间进行区分:

 If Val(Application.Version) < 9 Then 
    hWnd = FindWindow("ThunderXFrame", frm.Caption)   ' if used within Form: Me.Caption
 Else   ' later versions
    hWnd = FindWindow("ThunderDFrame", frm.Caption)   ' if used within Form: Me.Caption
 End If 

However using vbNullString ( and unique captions!) instead makes coding much easier: 但是,使用vbNullString唯一的字幕!)可以使编码更加容易:

 hWnd = FindWindow(vbNullString, frm.Caption)         ' if used within Form: Me.Caption

Recommended further reading 推荐进一步阅读

UserForm code modules actually are classes and should be used as such. UserForm代码模块实际上是 ,应该这样使用。 So I recommend reading M. Guindon's article UserForm1.Show . 因此,我建议阅读M. Guindon的文章UserForm1.Show - Possibly of some interest, as well is Destroy a modeless UserForm instance properly -可能也会引起一些兴趣, 正确地销毁无模UserForm实例

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM