excel VBA:更改用户窗体的父窗口

g6ll5ycj  于 2023-05-30  发布在  其他
关注(0)|答案(1)|浏览(216)

转发注解:
·这里讨论的所有代码和UserForm都在Excel Addin中
· Office是64位系统上的365
我的目标是将无模式UserForm的“父”移动到UserForm处于活动状态时激活的任何工作簿(具有给定的名称类型)。我成功地做到了这一点,当一个工作簿被激活时,使用以下方法重新加载UserForm:

''' Code in an EventClassModule:
Private Sub ExcelApp_WorkbookActivate(ByVal wbActivated As Workbook)
 '… Conditional and Supporting Code …
    Unload UD_SearchImageNotes
    UD_SearchImageNotes.Show vbModeless

虽然这样做是可行的,但这意味着必须保存表单字段,并在.Show之后重新填充这些字段(这看起来效率低下且笨拙)
在寻找可能更好(更简单)的解决方案时,我在这里遇到了类似的问题和解决方案:58658670
我已经实现了建议的解决方案(有微小的变化):

''' Code (in an EventClassModule):
Private Sub ExcelApp_WindowActivate(ByVal wbBook As Workbook, ByVal wnWindow As Window)
    Dim lpHWnd As LongPtr, lpHWndActivated As LongPtr

    lpHWndActivated = wnWindow.hwnd
    lpHWnd = FindWindowA(vbNullString, "Search Image Notes")

    If lpHWnd > 0 And wbBook.Name Like "*Image Data*" Then
        If GetParent(lpHWnd) <> lpHWndActivated Then
            SetParent lpHWnd, lpHWndActivated
        End If
    End If
procFinish: Set wbBook = Nothing: Set wnWindow = Nothing
End Sub

''' Supporting Code (in a standard module):
Public Declare PtrSafe Function SetParent Lib "user32" (ByVal hWndChild As LongPtr, ByVal hWndParent As LongPtr) As LongPtr
Public Declare PtrSafe Function GetParent Lib "user32" (ByVal hWndChild As LongPtr) As LongPtr
Public Declare PtrSafe Function FindWindowA Lib "user32" (ByVal stClassName$, ByVal stWindowName$) As LongPtr

但是(不幸的是)上面的方法不起作用。事件处理程序的工作方式与FindWindowA相同,但不是为UserForm分配新的父级,而是关闭UserForm。
我在58658670上读到了开场海报上关于解决方案的评论:

"… as soon as I open another workbook from my code, it closes my Userform" and
"… managed to get it working by creating my new workbook first and then opening the workbook"

听起来他们也看到了我看到的症状,但找到了解决办法。可悲的是,我看不出他们的解决方案如何适用于我的情况(因为我没有创建任何工作簿,只是在现有的书籍之间切换)。
有没有人有任何线索,什么可能导致这一点和/或我可能会尝试?

dpiehjr4

dpiehjr41#

是的,即使10年了,SDI仍然令XLAM开发人员感到沮丧。我也看到了“消失”的用户表单,并做了一些修改,使之工作。我注意到的一件事是,当我在任务栏中的Excel缩略图上悬停时,用户表单会出现在我的第二个显示器的边缘,所以我知道它已经加载,但只是不可见。SetParent的使用在某种程度上工作得很好,问题似乎是关于用户表单定位的。当用户表单有了一个新的父级时,它就不再是真正的无模式的了(如果你将它滑向工作簿的边缘,你会看到它位于窗口中,而不是在窗口上方)。父母的改变也会打乱坐标系,但不是以一种有意义的方式。我希望事情是相对于活动窗口的,但事实并非如此。设置userform.left似乎基本上被忽略了,它报告了一些奇怪的数字(这些数字在这个例子中显示在状态栏中,当你离开主显示器时,你会看到它们超过了你的总水平分辨率)。
这并不完美:positionUserForm在我的主显示器上可靠地工作,尽管即使在主显示器上,位置也不像预期的那样一致。当我将一个工作簿移出主显示器时,由于xCoordinate超出了可见空间,当切换到时,窗体停止显示。我希望进一步的API与设备上下文的争论将需要完全正确。
除了状态栏更新之外,我还在一个标准模块中添加了两个变量(hwndUserForm和objUserForm),用于传递用户表单,这样我就不会在类中使用硬编码字符串,并且我让类进行表单定位,这样您就可以从表单切换窗口,或者只需单击另一个打开的窗口即可。希望这能送你回家。

'''' Class WinActivate
Public WithEvents AppEvents As Application
Private Declare PtrSafe Function SetParent Lib "user32" _
   (ByVal hWndChild As LongPtr, ByVal hWndNewParent As LongPtr) As LongPtr

Private Sub AppEvents_WindowActivate(ByVal wb As Workbook, ByVal Wn As Window)
    'Change precedent object of UserForm when switching windows
    'hwndUserForm is a global that is set when a userform is loaded and set to 0 when it's unloaded
    If Val(Application.Version) >= 15 And hwndUserForm <> 0 Then
        SetParent hwndUserForm, Wn.hwnd
        positionUserForm objUserForm
    End If
End sub
''''Standard Module
Option Explicit
Dim WA As New WinActivate
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
  (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr

Public hwndUserForm   As Long 'handle to userform, used by WinActivate class, set by showModeless, set to 0 where you unload the form
Public objUserForm As Object 'userform object, set where userform is loaded and before calling showModeless, destroyed where you unload the userform

Sub ShowModeless()
    Set WA.AppEvents = Application
    objUserForm.Show 0
End Sub

Sub load_test_form()
    Load frm_test
    Set objUserForm = frm_test
    hwndUserForm = FindWindow("ThunderDFrame", objUserForm.Caption)
    positionUserForm objUserForm
    ShowModeless
End Sub
Public Sub positionUserForm(ByRef thisform As Object)
    If thisform Is Nothing Then Exit Sub
    thisform.Left = 100
    thisform.Top = 100
    Application.StatusBar = UCase(Left(thisform.Caption, 2)) & " fLeft: " & thisform.Left & " | wLeft: " & ActiveWindow.Left & " | " & ActiveWindow.Caption & " " & thisform.Caption
End Sub
''''userform code
Option Explicit
Private initializing As Boolean
Private Sub cbx_file2_Change()
    If initializing Then Exit Sub
    Application.Workbooks(Me.cbx_file2.Text).Activate
End Sub
Private Sub cbx_file1_Change()
    If initializing Then Exit Sub
    Application.Workbooks(Me.cbx_file1.Text).Activate
End Sub
Private Sub UserForm_Initialize()
Dim wb As Workbook
Dim i As Integer
    initializing = True
    If Application.Workbooks.Count = 0 Then
        MsgBox "This requires at least one open workbook", vbInformation, "Workbook Required"
        Exit Sub
    End If
    
    For Each wb In Application.Workbooks
        Me.cbx_file1.AddItem
        Me.cbx_file1.List(Me.cbx_file1.ListCount - 1, 0) = wb.Name
        If cbx_file1.List(Me.cbx_file1.ListCount - 1, 0) = ActiveWorkbook.Name Then
            Me.cbx_file1.ListIndex = Me.cbx_file1.ListCount - 1
        End If
    Next wb
   
    Me.cbx_file2.List = Me.cbx_file1.List
    If Me.cbx_file2.ListCount > 0 Then
        For i = 0 To Me.cbx_file2.ListCount - 1
            If i <> Me.cbx_file1.ListIndex Then
                Me.cbx_file2.ListIndex = i
                Exit For
            End If
        Next i
    End If
    initializing = False
End Sub

相关问题