Pizzor2000 06-26-2008, 10:24 AM I have a form on which the user clicks a button and another non-modal form, owned by the first form, shows. After the user brings up the new form, however, I want the original form to stay in focus until the user actually clicks into the new form.
What is the best way to show a form in the background while the calling form retains keyboard focus?
Sorry if this is a stupid question, but doesn't SetFocus work?
Pizzor2000 06-26-2008, 01:17 PM Sometimes. I put Me.SetFocus on the calling form immediately after showing the other form, which works sometimes, but not always.
Actually, it seems the first time I press the button, the original form has focus, but each subsequent time I close the second form and press the button again, the new form gets focus.
Roger_Wgnr 06-26-2008, 01:44 PM In the Form Load event of the second form set the focus back to the mainform.
Private Sub Form_Load()
MainForm.SetFocus
End Sub
I think this will do what you want If not you may want to try the form Initialize event.
If it still does not work the way you want post the code you are using to call the second form and the code you are using to exit the second form.
It may be an issue with those portions of code. With out seeing how you are doing things we are just speculating.
Pizzor2000 06-26-2008, 01:58 PM Thanks for the idea, Roger, but with your code, the second form can never be active. I still want the user to be able to activate and use the second form if they click into it.
I wouldn't use the Initialize or Load events for this, and I certainly wouldn't put it in the calling form's code, because (as you saw) you can't rely on either of those occuring before the owned form gains focus.
Instead, I would have the owned form raise a custom event in the owner in its Activate event. That way, you can be sure where the focus is at that time, and you don't need an object reference for the owner. The owner must then decide what to do with the event - in this case, you'd want to have a flag to keep track of whether it's the first time it fires or not. If it is, re-gain the focus, if it's not, do nothing.
If for some reason that I'm overlooking it isn't as simple as all that, you can use API to show the owned form without activating it, but I wouldn't choose that solution if a native one works.
Pizzor2000 06-26-2008, 03:24 PM If it still does not work the way you want post the code you are using to call the second form and the code you are using to exit the second form.
It may be an issue with those portions of code. With out seeing how you are doing things we are just speculating.
The actual application I am working on is far too complex to post here. I have created a new project to try this from scratch, but I'm not having the same problems. I'll post some of that code anyway:
This is code in the form I want to appear (Form2):
Public Sub ShowForm(pfrmParentForm As Form)
Me.Show , pfrmParentForm
Me.Move 8000, 2000
End Sub
Private Sub cmdClose_Click()
Me.Visible = False
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
Set Form2 = Nothing
End Sub
This is the code I used on the button in the parent form:
Private Sub cmdShowForm_Click()
Form2.ShowForm Me
Me.SetFocus
End Sub
Of course, in the actual application, quite a few ActiveX components are initialized in the Form_Load and ShowForm procedures. It may be one of these components is interfering with what I'm trying to do.
Actually, it seems the first time I press the button, the original form has focus, but each subsequent time I close the second form and press the button again, the new form gets focus.
Your code explains this observation to an extent. The first time the owned form is shown, it has to be loaded first, the following times it only has to be shown, if it's been closed via the cmdClose button, because it isn't being unloaded.
Using the Activate event as I suggested should work independently of that.
Pizzor2000 06-26-2008, 04:04 PM I have modified my code to use an Activate event.
Additions to Form2:
Event Activate()
Private Sub Form_Activate()
RaiseEvent Activate
End Sub
Code in the parent form:
Private mbooFormActivated As Boolean
Private WithEvents mfrmForm2 As Form2
Private Sub cmdShowForm_Click()
Set mfrmForm2 = New Form2
mbooFormActivated = False
mfrmForm2.ShowForm Me
End Sub
Private Sub mfrmForm2_Activate()
If Not (mbooFormActivated) Then
Me.SetFocus
mbooFormActivated = True
End If
End Sub
Is this what you had in mind, Cas? It seems to work in my stub program, but I haven't tried it in the actual application yet.
Indeed, that's precisely what I meant. Nice work! :cool:
Roger_Wgnr 06-26-2008, 05:56 PM Ahh, Well some days I'm just not seeing the forest for the trees.
I jumped in with out really looking at the implications.
I like your solution Cas.
Thanks. :)
I used to avoid the Activate event whenever possible until recently, because I was never sure if I should expect it to fire when a form lost and regained focus or only when it was hidden and shown again. But certain things can only be done from it, and after acquainting myself with the underlying window objects and their different levels of activation states lately, I feel more comfortable with the VB event now...
Pizzor2000 06-27-2008, 08:22 AM Cas, your method does not work on my main application either! :mad:
OK. One thing I forgot to mention is that the secondary form is part of a separate DLL. This means its Activate event will not trigger when I switch between it and the forms on the main application, correct?
Ick.
I would have thought that it doesn't matter if the windows belong to different app instances (DLLs), as long as they're on the same thread. But with the additional ownership relation, the situation is becoming a bit too convoluted for me to trust my own speculations here... :)
That part should be easy to figure out experimentally though, shouldn't it?
I was going to post the API way here, but I just realized that it's a bit less straightforward than I thought - again because of the ownership relation, which I'm not qure how to incorporate. If the Activate really doesn't fire, I'll investigate further, and if I don't come up with anything, there's always a timer-based solution to fall back on, but I'd hate to have to resort to that.
Pizzor2000 06-27-2008, 10:34 AM I created another new project in which I use a separate DLL for the form to show. I have the same problem with it.
On both my live application and this new DLL project, I put a breakpoint in the Form_Activate procedure of the extra form. On the new project, the breakpoint was only reached the first time I loaded the form, and on the application, it was reached the first time I clicked a control within the form. In either case, the breakpoint was never reached if I switched back and forth between the windows or when I closed and re-opened the new window.
Since you mentioned the API, Cas, doesn't the ShowWindow API allow you to show the window maximized, minimized, not active, etc.? I had that in mind when I started this thread, but I didn't remember if there was already a way to do this built into VB.
In either case, the breakpoint was never reached if I switched back and forth between the windows
Well, that's pretty conclusive.
or when I closed and re-opened the new window.
That doesn't make any sense to me. Hiding and showing a window should always raise an Activate, what's the point otherwise? Just when I thought I understood the event... argh!
Since you mentioned the API, Cas, doesn't the ShowWindow API allow you to show the window maximized, minimized, not active, etc.?
Yes, both ShowWindow and SetWindowPos provide a NoActivate flag. The problem with combining these with VB functionality, however, is that you can't set ownerships at that point. Instead, that would be done at window creation. I'm not clear at the moment on how it is even possible for VB to change window ownership during its lifetime, unless it destroys the old window and creates a new one. Doesn't seem likely, though. :confused:
Yesss, I got it. Wrapped in a nice little Show method. :) You can take out the last part, it's just fluff, but I needed it for testing.
Private Enum GWLFlags
GWL_HWNDPARENT = -8
End Enum
Private Declare Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As GWLFlags, ByVal dwNewLong As Long) As Long
Private Enum SWFlags
SW_SHOW = 5
SW_SHOWNOACTIVATE = 4
End Enum
Private Declare Function ShowWindow Lib "user32.dll" _
(ByVal hWnd As Long, ByVal nCmdShow As SWFlags) As Long
Private Enum GWFlags
GW_OWNER = 4
End Enum
Private Declare Function GetWindow Lib "user32.dll" _
(ByVal hWnd As Long, ByVal wCmd As Long) As Long
Private Declare Function GetWindowText Lib "user32.dll" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Public Sub fShow(Optional whichHWndOwner As Long = -1, Optional doActivate As Boolean = True)
Load Me
If Not (whichHWndOwner = -1) Then Call SetWindowLong(Me.hWnd, GWL_HWNDPARENT, whichHWndOwner)
If doActivate Then Call ShowWindow(Me.hWnd, SW_SHOW) _
Else Call ShowWindow(Me.hWnd, SW_SHOWNOACTIVATE)
Dim strOwnerCaption As String
If whichHWndOwner = -1 Then
Me.Caption = "Not owned"
Else
strOwnerCaption = VBA.String$(Not CByte(0), vbNullChar)
Call GetWindowText(whichHWndOwner, strOwnerCaption, Not CByte(0))
strOwnerCaption = VBA.Left$(strOwnerCaption, InStr(1, strOwnerCaption, vbNullChar) - 1)
Me.Caption = "Owned by " & strOwnerCaption
End If
End Sub
The odd thing is that this way of setting owner windows isn't so much undocumented as ANTI-documented by Microsoft.
Owned Windows (http://msdn.microsoft.com/en-us/library/ms632599(VS.85).aspx#owned_windows):
After creating an owned window, an application cannot transfer ownership of the window to another window.
SetWindowLong (http://msdn.microsoft.com/en-us/library/ms633591.aspx):
You must not call SetWindowLong with the GWL_HWNDPARENT index to change the parent of a child window. Instead, use the SetParent function.
GetParent (http://msdn.microsoft.com/en-us/library/ms633510(VS.85).aspx):
Note that, despite its name, this function can return an owner window instead of a parent window.
SetParent (http://msdn.microsoft.com/en-us/library/ms633541(VS.85).aspx): no mention of owned windows altogether.
And yet, VB routinely allows us to switch owners just by hiding and showing a window. What in the world?!
Pizzor2000 06-27-2008, 03:57 PM I played around with a few possible API solutions, with no luck on the full application.
Of course, after looking through the code on the popup form of my original application, I found a SetFocus statement to one of the controls, which appears to have been executing after the form displays. :whoops:
(I didn't create this form originally, so I have no idea what most of the code does anyway)
After removing that line, my original logic (Me.SetFocus immediately following the line to show the form) seems to work properly (knock on wood).
I do, however, appreciate all your help, Cas. Thank you. :D
I found a SetFocus statement to one of the controls, which appears to have been executing after the form displays. :whoops:
D'uh. So the focus went forth and back and forth again, huh? Guess we should have tried more debugging first, that wouldn't have been too hard to detect.
I do, however, appreciate all your help, Cas. Thank you. :D
No worries. The API way to work with owned forms posted above is something that I'm going to be using myself, now that I know about it, and I may well not have been persistent enough to ferret it out if it hadn't been for this thread, so I'm actually quite pleased. :)
And, as it happens, someone just asked a very similar question in the Excel forum, and with VBA not providing SetFocus methods for forms at all, the API way is the only feasible approach, so the work certainly isn't going to waste. :cool:
|