如何获取嵌套 VB6 UserControl 的父窗体

How to get parent form of nested VB6 UserControl

有一个类似的问题,不幸的是它对我没有帮助。当我调用 UserControl.Parent 时,可以返回 Form 或另一个 UserControl。如果返回一个表格,我就有了我想要的。但是,如果返回 UserControl,我就无法向上迭代链,因为 UserControl 是基础 class 名称,而我无权访问基础 class 控件实现之外的名称。

从技术上讲,我可能可以通过在应用程序中的每个 UserControl 上公开父级 属性 来解决这个问题,但我真的很想避免这样做(我们有成千上万个) .

我的最终目标是获取对托管控件的父窗体的引用,以便控件可以订阅 Form_Unload 事件。此处控件将删除并清理托管的 .NET 互操作控件,该控件阻止 VB6 UserControl 引发其 UserControl_Terminated 事件,从而泄漏 GDI 对象和内存。

到目前为止,我已尝试在 UserControl_Initialize 和 [=23= 中调用 USER32.dll 中的 GetParent()GetWindow()GetAncestor() 函数] 事件,然后与 Forms 集合中窗体上的 hWnd 交叉引用,但这两个事件似乎都是在 UserControl 位于其主机窗体上之前引发的。

我生疏了,不过Container属性不就是你要的吗?

请注意,在与 dot net 互操作时,dot net 具有非常不同的垃圾收集。仅仅因为 VB6 对象已经发布并不意味着点网对象已经发布。它们可能仍然潜伏在内存中,并且 - 在极少数情况下 - 可能仍然会在计时器上触发事件。

因此请确保您的点网控件正在正确地自我清理,并且有一个 VB6 可以触发的方法来强制终止发生。

参见:https://msdn.microsoft.com/en-us/library/aa445702(v=vs.60).aspx

我做了一些摆弄,这应该会给你一些继续下去的东西。我刚刚创建了两个用户控件,分别称为内部和外部。内部有一个命令按钮,外部有一个框架。我在框架内放置了一个内部实例。然后我将一个 External 的实例放在一个表单上,我刚刚在 Form1 上留下了它的名字。

所以,我在窗体上的控件中有一个控件。问题是从内部控件的上下文中找到对Form的引用。

首先,我在表单上有一个名为 Test 的方法,所以:

Public Sub Test
    MsgBox "Test Succeeded"
End Sub

现在,在内部控件的命令按钮的 Click 事件处理程序中:

Private Sub cmdTest_Click()
    UserControl.ParentControls(0).Parent.Test
End Sub

运行Form项目点击命令按钮成功调用Form的Test方法。 (呵呵。)

因此,要区分嵌套控件和直接在窗体上的控件,您可以按照以下方式进行操作:

Private Sub cmdTest_Click()
    If TypeOf UserControl.Parent is Form Then
        UserControl.Parent.Test
    Else
        UserControl.ParentControls(0).Parent.Test
    End If
End Sub

然后我尝试将外部控件嵌套在另一个控件 (External2) 中,并将 External2 的实例放在窗体上。在调试 window 中,我这样做了:

? typename(usercontrol.ParentControls(0))
External
? typename(usercontrol.ParentControls(0).Parent)
External2

这就是我得到的。尝试这样的事情:

? typename(usercontrol.ParentControls(0).Parent.ParentControls(0))

或类似的东西,出现 "Object doesn't support this property or method" 错误。

如果您需要评估嵌套不止一层的控件(您没有确定,但 "iterate up the chain" 建议您这样做),这可能超出了该技术的能力范围。您也可以弄乱 Controls 和 Name 属性,并可能想出一些办法。但看起来 ParentControls 属性 不会扩展到本身就是控件的控件的父控件,当然除非您不厌其烦地使用不同的 属性 名称来公开它们。至少你可以用这个在链上再迭代一个link。

一般来说,它看起来并不像 Parent 属性 或其派生对象包含对父对象的实际引用,而是它们属性和方法的某种子集。例如,我可以获取 UserControl 的 hWnd 属性,但不能获取 UserControl.Parent。即使通过名称专门引用父级(实际控件实例的名称是 External1,所以 ? External1.hWnd)也无​​法获得 hWnd 属性,抛出 "Object Required" 错误。这似乎会使使用 API 作为解决方案的可能性复杂化。

总之,就留给你玩吧。如果你比我更进一步,我很想看看你的结果。

我能够通过使用 HWND 和 win32 API 遍历 parent/child 关系找到父表单。我的代码大致如下:

Private Declare Function GetParent Lib "USER32" (ByVal Hwnd As Long) As Long
Private Declare Function GetClassName Lib "USER32" Alias "GetClassNameA" _
    (ByVal Hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long

Public Function FindParentForm(Hwnd As Long) As Form
    Dim ParentHwnd As Long
    Dim CandidateForm As Form
    Dim strTmp As String * 255
    Dim lngLngth As Long
    Dim strTitle As String
    ParentHwnd = Hwnd

    Do While (Not ParentHwnd = 0)
        ParentHwnd = GetParent(ParentHwnd)
        lngLngth = GetClassName(ParentHwnd, strTmp, 255)
        strTitle = Left(strTmp, lngLngth)
        'ThunderFormDC is the Debug version of a VB6 form, and ThunderRT6FormDC is the Release/Runtime version of a VB6 form
        If strTitle = "ThunderFormDC" Or strTitle = "ThunderRT6FormDC" Then
            Exit Do
        End If
    Loop

    For Each CandidateForm In Forms
        If CandidateForm.Hwnd = ParentHwnd Then
            Set FindParentForm2 = CandidateForm
            Exit Function
        End If
    Next
    'Didn't find the parent form somehow
    Set FindParentForm2 = Nothing
End Function

正如我在问题中提到的,此解决方案的问题是在 UserControl_Initialize 和 UserControl_Resize 事件期间,尚未在 HWND 之间建立 parent/child 关系。如果你尝试遍历 parent/child 关系,你会发现 usercontrol 的父级是一个名为 "Static".

的 class

我能够通过在用户控件的手动 Init() 过程中搜索父表单来解决这个问题。但是,许多表单不会调用 Init() 过程,除非它所在的选项卡被单击(试图获得某种在 VB6 中实现的延迟加载)。我能够通过将控件重构为在调用 Init() 过程之前不动态 create/add .NET 互操作控件来解决此问题。至此,parent/child关系似乎已经建立起来。

延迟加载问题的另一种解决方案是挂钩 WndProc 过程,并侦听 WM_CHILDACTIVATED 消息。但是,只有当子控件更改父控件时才会发送此消息。它不会传播给孙子。但是,应该可以将另一个 WndProc 挂接到新的父控件并监听它自己的 WM_CHILDACTIVATED 消息等等,直到子控件成为 ThunderForm 的父级为止。但是,由于 WndProc 只能在静态模块中实现,我不想跟踪 parent/child 关系。

我使用 ReadProperty 中的 UserControl.Parent 解决了我的引用问题。 我试过 GetParent() 但没有成功。还在 Initialize 中尝试了 UserControl.Parent。 问题是在 Initialize 期间对象不存在。它正在 link 之前构建以形成 (Parent)。
ReadProperty(或调整大小)中它已经存在,您将能够使用 UserControl.Parent.
比如我用UserControl.Parent.name 来保存一些信息到window register.

这与此主题相关,因此我 post 将它放在这里是为了在特殊情况下必要时使用。它不能深入到嵌套中,但在我的案例中是一种获取信息的快速方法,其他人可能会发现它有用。

我有一个控件 (cmdPageDown1) 放在用户控件上,用户控件放在用户控件 (xMCGridNew1) 上,用户控件 (xMCGridNew1) 放在窗体 (frmMC) 上。

正如其他人所指出的,我无法通过任何快速的单行代码获取表单名称,但是通过这些简单的代码行,我可以获取其他 2 个嵌套用户控件和较低级别的名称控制.

例如:

        'Debug.Print '[Roadblock Here]         'Top Level  frm MC    (Parent)
        Debug.Print Screen.ActiveControl.Name 'SubLevel 1 xMCGridNew1 (Child)
        Debug.Print UserControl.Name          'SubLevel 2 xpage (GrandChild)
        Debug.Print ActiveControl.Name        'SubLevel 3 cmdPageDown1 (Great Grandchild)

这将立即产生对应于三个嵌套级别的window。

xMCGridNew1
xPage  
cmdPageDown1

正如另一个 poster(原始发帖人)在此线程中提到的,关于尝试使用容器 属性:

The Container property has two issues. First, UserControl's do not have a >Container property. You must get it off of the UserControl.Extender >property. Second, the Container may be another UserControl. Since the >UserControl.Extender property is a base class property, it is not accessible >outside of that UserControl's specific implementation. I.e. UserControl1 >cannot access UserControl2.UserControl.Extender.Container. Thus the >Container property has the same problems as the Parent property - I would >need to modify every user control in our application (too many). – Taedrin >Jan 15 '16 at 15:08