learning_t0_pr0gram
asked on
Make window child to my MDI
Is it possible to make a window (outside of my application) a child to my own MDIForm? Like if I have:
Window = FindWindow("windowClass", vbNullString)
Could I turn Window into a child to my MDI Form?
Window = FindWindow("windowClass", vbNullString)
Could I turn Window into a child to my MDI Form?
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Ahh, I knew it was possible :P
I'm still trying to learn all of the API's.
Thanks IM.
I'm still trying to learn all of the API's.
Thanks IM.
ASKER
Hey IM. If you see this do you know how to tell if the Child window has closed? For some reason it doesn't find the window anymore once it's added as a Child to my MDI form.
Hmm....have to think about that one a little...
When you add it using SetParent() it isn't really an MDI child and isn't really its own form anymore either. We can't subclass it to tell when it has closed because it isn't part of our process. We may have to use some kind of timer and periodically enumerate the child windows of your main form...
I'll see what I can come up with.
When you add it using SetParent() it isn't really an MDI child and isn't really its own form anymore either. We can't subclass it to tell when it has closed because it isn't part of our process. We may have to use some kind of timer and periodically enumerate the child windows of your main form...
I'll see what I can come up with.
ASKER
Anything that works will be good. I see it can find the window using FindWindowEx(Me.hWnd, ....) but i'm still having trouble keeping track of all the windows and looping through etc to see if they're closed or not. I'd like an easier way if there is one. The question is already closed so you don't have to give further help unless you want to.
>> I see it can find the window using FindWindowEx(Me.hWnd, ....)
That is exactly the route I was thinking of doing first. Use a recursive subroutine to descend into the child windows. Each time you open a form, you can use FindWindow() to get the hWnd and then store it in a collection. You can use any value you want as the key for the hWnd. Call the recursive routine from the timer and update your collection accordingly. When you go to open the form, you should first check the collection using the key to see if the form is already open.
Alternatively, we can use various ShellAndWait() algorithms to monitor the processes we have spawned. You can make a boolean value for each child window that you spawn and then toggle it back when you know it is closed. Here is a basic shell and wait algorithm:
Option Explicit
Private Const SYNCHRONIZE = &H100000
Private Const WAIT_TIMEOUT = &H102
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, _
ByVal dwMilliseconds As Long) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Sub Command1_Click()
ShellAndWait "calc.exe"
MsgBox "Done"
End Sub
Private Sub ShellAndWait(ByVal cmd As String)
Dim lPid As Long
Dim lHnd As Long
lPid = Shell(cmd, vbNormalFocus)
If lPid <> 0 Then
lHnd = OpenProcess(SYNCHRONIZE, 0, lPid)
If lHnd <> 0 Then
Do
DoEvents ' keep application responsive
Sleep 50 ' keep CPU usage from ramping to 100%
Loop Until WaitForSingleObject(lHnd, 0) <> WAIT_TIMEOUT
CloseHandle (lHnd)
End If
End If
End Sub
We would need to modify it so it wouldn't be a "holding" routine. Instead, we add the PID from the Shell() command into a collection. At the same time we add the name of a boolean variable into another collection. A Timer control routine can iterate the collection and use OpenProcess() and CloseHandle() on each one checking to see if the process is still open. If it is not, we can use the CallByName() function along with the corresponding variable name from the second collection to toggle the correct variable (I'll show you how to do that if you don't know how).
Let me know if you want to see the modifed shell and wait algorithm.
~IM
That is exactly the route I was thinking of doing first. Use a recursive subroutine to descend into the child windows. Each time you open a form, you can use FindWindow() to get the hWnd and then store it in a collection. You can use any value you want as the key for the hWnd. Call the recursive routine from the timer and update your collection accordingly. When you go to open the form, you should first check the collection using the key to see if the form is already open.
Alternatively, we can use various ShellAndWait() algorithms to monitor the processes we have spawned. You can make a boolean value for each child window that you spawn and then toggle it back when you know it is closed. Here is a basic shell and wait algorithm:
Option Explicit
Private Const SYNCHRONIZE = &H100000
Private Const WAIT_TIMEOUT = &H102
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, _
ByVal dwMilliseconds As Long) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Sub Command1_Click()
ShellAndWait "calc.exe"
MsgBox "Done"
End Sub
Private Sub ShellAndWait(ByVal cmd As String)
Dim lPid As Long
Dim lHnd As Long
lPid = Shell(cmd, vbNormalFocus)
If lPid <> 0 Then
lHnd = OpenProcess(SYNCHRONIZE, 0, lPid)
If lHnd <> 0 Then
Do
DoEvents ' keep application responsive
Sleep 50 ' keep CPU usage from ramping to 100%
Loop Until WaitForSingleObject(lHnd, 0) <> WAIT_TIMEOUT
CloseHandle (lHnd)
End If
End If
End Sub
We would need to modify it so it wouldn't be a "holding" routine. Instead, we add the PID from the Shell() command into a collection. At the same time we add the name of a boolean variable into another collection. A Timer control routine can iterate the collection and use OpenProcess() and CloseHandle() on each one checking to see if the process is still open. If it is not, we can use the CallByName() function along with the corresponding variable name from the second collection to toggle the correct variable (I'll show you how to do that if you don't know how).
Let me know if you want to see the modifed shell and wait algorithm.
~IM
ASKER
Well right now I have this:
Private Sub Get_All_IM_Windows(ByVal Win As Long)
Dim LastWin As Long, a As Long
Win = GetWindow(Win, GW_HWNDFIRST)
LastWin = GetWindow(Win, GW_HWNDLAST)
Do
Win = GetWindow(Win, GW_HWNDNEXT) 'next window
If GetClass(Win) = "AIM_IMessage" Then
AIM_WINDOWS.Add Win
End If
DoEvents
Loop Until Win = LastWin Or Win = 0
End Sub
This adds all open AIM windows's handles to my collection (AIM_WINDOWS). So AIM_WINDOWS already contains each window I have in my MDIForm. When it adds an AIM window to my MDI form, it adds the Username of the person to a ListBox. What I want to do is remove the name from the ListBox when the window isn't found anymore. All Index's should be the same. username with index 0 in the listbox should be the same as index 1 in the collection, 1 in the listbox would be 2 in the collection, etc.
Private Sub Get_All_IM_Windows(ByVal Win As Long)
Dim LastWin As Long, a As Long
Win = GetWindow(Win, GW_HWNDFIRST)
LastWin = GetWindow(Win, GW_HWNDLAST)
Do
Win = GetWindow(Win, GW_HWNDNEXT) 'next window
If GetClass(Win) = "AIM_IMessage" Then
AIM_WINDOWS.Add Win
End If
DoEvents
Loop Until Win = LastWin Or Win = 0
End Sub
This adds all open AIM windows's handles to my collection (AIM_WINDOWS). So AIM_WINDOWS already contains each window I have in my MDIForm. When it adds an AIM window to my MDI form, it adds the Username of the person to a ListBox. What I want to do is remove the name from the ListBox when the window isn't found anymore. All Index's should be the same. username with index 0 in the listbox should be the same as index 1 in the collection, 1 in the listbox would be 2 in the collection, etc.
Then you would need two different collections. One to hold the AIM handles you initially find and another that gets updated each time you call Get_All_IM_Windows() from your polling routine.
Then you can iterate the first collection and see if those values are still present in the updated collection. As you iterate the first collection, if the value is not in the second collection then remove it from the first collection and the listbox (AIM window has been closed). If the value from the first collection is in the second collection then only remove it from the second collection (AIM window still open). When you are done iterating the first collection, anything left in the second collection is a new AIM window that needs to be added to your first collection and your listbox.
Hope that made sense....
Then you can iterate the first collection and see if those values are still present in the updated collection. As you iterate the first collection, if the value is not in the second collection then remove it from the first collection and the listbox (AIM window has been closed). If the value from the first collection is in the second collection then only remove it from the second collection (AIM window still open). When you are done iterating the first collection, anything left in the second collection is a new AIM window that needs to be added to your first collection and your listbox.
Hope that made sense....
ASKER
I do get what you're saying. I appreciate all your help. I will see if I can do what you suggested when I get home. It sounds like it might work.
ASKER
For some reason I can't get it to work. This is the first time i've known how to do something but couldn't make it work right. My coding to do it keeps building up and up and it still won't work. Have a look:
Private Sub Check_All_IM_Windows()
Dim Win As Long, x As Long, y As Long
Win = FindWindowEx(Me.hWnd, 0, "AIM_IMessage", vbNullString)
Set AIM_HANDLES = Nothing
'###### Add all Child Windows to AIM_HANDLES
Do
If GetClass(Win) = "AIM_IMessage" Then
AIM_HANDLES.Add Win
End If
Win = GetWindow(Win, GW_HWNDNEXT) 'next window
DoEvents
Loop Until Win = 0
For x = 1 To AIM_WINDOWS.Count
NextX:
For y = 1 To AIM_HANDLES.Count
If AIM_HANDLES.Item(y) = AIM_WINDOWS.Item(x) Then
AIM_WINDOWS.Remove x
If x <= AIM_WINDOWS.Count Then GoTo NextX Else GoTo Re_Add
End If
Next
Form1.List1.RemoveItem (x - 1)
AIM_WINDOWS.Remove x
Next
Re_Add:
For x = 1 To AIM_HANDLES.Count
AIM_WINDOWS.Add AIM_HANDLES.Item(x)
Next
End Sub
As you can see it got pretty sloppy when I kept trying new things. It's pretty easy to follow. It DOES detect when I close a window, but it keeps removing Index 0 in the Listbox. Can't figure out why. I'm probably just mis-reading something, dunno. Any Idea?
Private Sub Check_All_IM_Windows()
Dim Win As Long, x As Long, y As Long
Win = FindWindowEx(Me.hWnd, 0, "AIM_IMessage", vbNullString)
Set AIM_HANDLES = Nothing
'###### Add all Child Windows to AIM_HANDLES
Do
If GetClass(Win) = "AIM_IMessage" Then
AIM_HANDLES.Add Win
End If
Win = GetWindow(Win, GW_HWNDNEXT) 'next window
DoEvents
Loop Until Win = 0
For x = 1 To AIM_WINDOWS.Count
NextX:
For y = 1 To AIM_HANDLES.Count
If AIM_HANDLES.Item(y) = AIM_WINDOWS.Item(x) Then
AIM_WINDOWS.Remove x
If x <= AIM_WINDOWS.Count Then GoTo NextX Else GoTo Re_Add
End If
Next
Form1.List1.RemoveItem (x - 1)
AIM_WINDOWS.Remove x
Next
Re_Add:
For x = 1 To AIM_HANDLES.Count
AIM_WINDOWS.Add AIM_HANDLES.Item(x)
Next
End Sub
As you can see it got pretty sloppy when I kept trying new things. It's pretty easy to follow. It DOES detect when I close a window, but it keeps removing Index 0 in the Listbox. Can't figure out why. I'm probably just mis-reading something, dunno. Any Idea?
ASKER
Is that a no? :(
Hi learning_t0_pr0gram,
I haven't forgotten you. =)
Just been real busy sorry. I'll see what I can do...
I haven't forgotten you. =)
Just been real busy sorry. I'll see what I can do...
ASKER
Oh ok. I'll get you some points for all your help anyway. I haven't had much time to answer questions on the site so my points are low but i'll answer a few and get you some.
ASKER
Oh man. I kind of gave up on this a few days ago and then decided to give it one more thought. I was going the wrong way about it, it was so simple!
Private Sub Check_All_IM_Windows()
Dim x As Long
For x = 1 To AIM_WINDOWS.Count
If GetClass(AIM_WINDOWS.Item( x)) = "" Then
AIM_WINDOWS.Remove x
Form1.List1.RemoveItem x - 1
x = x - 1
End If
Next
End Sub
Private Sub Check_All_IM_Windows()
Dim x As Long
For x = 1 To AIM_WINDOWS.Count
If GetClass(AIM_WINDOWS.Item(
AIM_WINDOWS.Remove x
Form1.List1.RemoveItem x - 1
x = x - 1
End If
Next
End Sub
ASKER
https://www.experts-exchange.com/questions/21355625/Points-for-Idle-Mind.html
Some points for your help.
Some points for your help.
ASKER
Seek #1, 25
I'll give 10 pts to whoever tells me what it does.