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.
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.