Search code examples
vb6

How to get parent form of nested VB6 UserControl


There is a similar question here, which unfortunately does not help me. When I make a call to UserControl.Parent, either a Form or another UserControl can be returned. If a Form is returned, I have what I want. But if a UserControl is returned, I have no way of iterating up the chain, since UserControl is the base class name, and I do not have access to the base class name outside of the control's implementation.

Technically, I could probably get around this by exposing the Parent property on every single UserControl in the application, but I would really like to avoid doing this (we have thousands of them).

My ultimate goal is to get a reference to the parent form which is hosting the control, so that the control can subscribe to the Form_Unload event. Here the control will remove and clean up a hosted .NET interopped control which is preventing the VB6 UserControl from raising its UserControl_Terminated event, thus leaking GDI objects and memory.

So far I have tried to make calls to GetParent(), GetWindow() and GetAncestor() functions in USER32.dll in the UserControl_Initialize and UserControl_Resize events, and then cross referencing with the hWnds on the forms in the Forms collection, but both of these events seem to be raised before the UserControl has been sited on its host form.


Solution

  • I was able to find the parent form by traversing parent/child relationships using HWNDs and the win32 API. My code is roughly as follows:

    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
    

    As I mentioned in the question, the problem with this solution is that during the UserControl_Initialize and UserControl_Resize events, the parent/child relationships have not been setup between HWNDs yet. If you try to traverse the parent/child relationships, you will find that the parent of the usercontrol is a class named "Static".

    I was able to work around this issue by searching for the parent form in a manual Init() procedure for my user control. However, many forms do not call the Init() procedure unless the tab it is on is clicked on (in an attempt to get some sort of lazy loading implemented in VB6). I was able to work around this by refactoring the control to not dynamically create/add the .NET interopped control until the Init() procedure was called. By this time, the parent/child relationships seem to be set up.

    An alternative solution to the lazy loading problem is to hook a WndProc procedure, and listen for the WM_CHILDACTIVATED message. However, this message only gets sent when the child control changes parents. It does not propogate to grandchildren. It should, however, be possible to hook another WndProc to the new parent control and listen for it's own WM_CHILDACTIVATED message and so on until a child control gets parented to a ThunderForm. However, since WndProc can only be implemented in a static module, I didn't feel like keeping track of parent/child relationships.