Showing Form in Background

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?

Cas
06-26-2008, 11:41 AM
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.

Cas
06-26-2008, 03:07 PM
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.

Cas
06-26-2008, 03:44 PM
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.

Cas
06-26-2008, 04:37 PM
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.

Cas
06-26-2008, 06:27 PM
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?

Cas
06-27-2008, 10:02 AM
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.

Cas
06-27-2008, 10:46 AM
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:

Cas
06-27-2008, 03:10 PM
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

Cas
06-27-2008, 03:22 PM
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

Cas
06-27-2008, 04:11 PM
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:

EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum