The new vbAccelerator Site - more VB and .NET Code and Controls
Source Code
3 Code Libraries &nbsp

Keep Form Titlebars in Focus when ToolWindows are shown.

Allow your VB app to behave like Office Applications when ToolWindows and Find/Replace Dialogs are in view.


NOTE: this code has been superceded by the version at the new site.



&nbsp

Even when the ToolWindow has the focus, the main MDI form keeps the focus.

Download the TitleBar Focus code and demonstration (20kb)

&nbsp Before you Begin &nbsp
&nbsp This project requires the SSubTmr.DLL component. Make sure you have loaded and registered this before trying the project. &nbsp

Introduction
The ToolWindow style, introduced with VB4, allowing you to easily create pop-up tool windows with small captions. However, there is (and has always been) a problem with them: whenever the form or a control on it gets the focus, it appears that the main form of your application looses focus. Whilst maybe this isn't the worst user-interface crime in the world, it is annoying and makes your application look unprofessional compared to the smoother behaviour in Word, Excel, DevStudio etc.

This subclassing sample demonstrates how to fix the title bar problem with a working but rather slimy hack. The code here is the basis for the code used in the vbAccelerator Drop-Down Form Control and is also incorporated directly into the vbAccelerator Toolbar, Rebar and CoolMenu control.

Note: This technique is probably not suitable for an application that contains multiple main form windows.

How It Works
Changing the appearance of a VB form's title bar is one of the things VB makes it very difficult to do. Whenever a Window's title bar needs to change state from active to inactive, Windows sends a WM_NCACTIVATE message to the window. In a simple C Windows application you could simply intercept this message and consume it rather than sending it to the DefWindowProc.

In VB, it isn't so easy. VB appears to use the WM_NCACTIVATE message for its own purposes. If you eat this message, your VB form either stops responding to the mouse or keyboard, or the program goes into a continous loop and can only be stopped with Ctrl-Alt-Del.

Another possible alternative is intercepting the WM_NCPAINT message. Windows sends this whenever any part of the non-client area needs to be repainted (including the form border, the titlebar, the menu bar and so on). If you consume this message you can write all the non-client area drawing code yourself - and that means you can paint the titlebar focused when it would normally be non-focused. Whilst I played with this solution for quite a while, my experiences with indicate that either Windows does not play fair with the WM_NCPAINT message or that VB redraws parts of the non-client area at other points for its own reasons. You can download a sample which shows how it it is almost but not quite reliable enough to draw the entire non-client area yourself. (BTW: there is a bug in this sample - the client drawing appears to draws one pixel too deep on the title bar, causing the menu bar to flicker. However, if you remove the pixel from the drawing, you end up with an non-drawn area. Can anyone fix it? If so, please mail me!)

Another solution is required. It turns out there is a simple one, but it is a bit of a slimy hack, as you're going to see..

The Code
This technique involves subclassing for three messages: WM_NCACTIVATE, WM_ACTIVATEAPP and WM_ACTIVATE. When a WM_NCACTIVATE is received, the code first calls LockWindowUpdate to prevent any changes being shown to the user.

Tip Successful LockWindowUpdate
LockWindowUpdate is an excellent call in that it completely stops window repainting and thereby speeds things up and stops distracting flicker. But you have to be careful when using it. If you cover or expose any area of another window whilst LockWindowUpdate is on, as soon as you turn it off again the entire desktop repaints, this is very slow, and causes probably worse flickering than if it wasn't turned on in the first place! So when using LockWindowUpdate, ensure you apply it to a window that does not change size or move. Often the parent window is a better choice than the window being updated.

With window updating locked, it then calls the VB window procedure with the as received WM_NCACTIVATE parameters. Normally this would redraw the title bar inactive, but because updates are switched off, the display is not updated. The code then calls WM_NCACTIVATE again, but with the parameters set to indicate that the window should be active. This sets the title bar back to the right display but enables the form to keep working correctly. Finally we turn LockWindowUpdate back off again to make the display correct.

That ensures that the titlebar keeps an active appearance when a non-modal form is displayed, but we need to make a few more modifications to hold that appearance when you display a modal form. Showing a modal form can be detected because Windows will repeatedly try to call WM_NCACTIVATE until the title bar state is changed correctly. Normally directly after a WM_NCACTIVATE call there is a WM_ACTIVATE message. So to detect a modal form being shown, and to allow the title bar to update, we can use a static variable to count the number of times the WM_NCACTIVATE message has been sent between WM_ACTIVATE messages. If this exceeds 2 we assume a modal form is being displayed and allow the default processing to occur.

The final processing is to detect the WM_ACTIVATEAPP message. This tells the code whether the entire app (all forms) has gained or lost the focus, and since it fires after the WM_NCACTIVATE message we can update the titlebar in response.

Private Property Get ISubclass_MsgResponse() As SSubTimer.EMsgResponse
Select Case CurrentMessage
Case WM_NCACTIVATE
ISubclass_MsgResponse = emrConsume
Case Else
ISubclass_MsgResponse = emrPostProcess
End Select
End Property

Private Function ISubclass_WindowProc(ByVal hWnd As Long, ByVal iMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Static iRefCount As Long

Select Case iMsg
Case WM_NCACTIVATE
If wParam = 0 Then
iRefCount = iRefCount + 1
If iRefCount < 3 Then
LockWindowUpdate hWnd
ISubclass_WindowProc = CallOldWindowProc(hWnd, iMsg, wParam, lParam)
CallOldWindowProc m_hWnd, WM_NCACTIVATE, 1, 0
LockWindowUpdate 0
Else
ISubclass_WindowProc = CallOldWindowProc(hWnd, iMsg, wParam, lParam)
End If
Else
ISubclass_WindowProc = CallOldWindowProc(hWnd, iMsg, wParam, lParam)
End If

Case WM_ACTIVATEAPP
If (wParam = 0) Then
iRefCount = 0
' app being deactivated
CallOldWindowProc m_hWnd, WM_NCACTIVATE, 0, 0
Else
' app being activated
' if not the active form then we should repaint
' the title bar
CallOldWindowProc m_hWnd, WM_NCACTIVATE, 1, 0
End If

Case WM_ACTIVATE
If wParam = 0 Then
iRefCount = 0
' deactivating the window, lParam is the window that is being activated.
End If

Case WM_DESTROY
' In case the user does not set the class
' to nothing before the owning form is
' closed:
Detach
End Select

End Function



As ever, using this code is somewhat more simple that writing it. Just reference SSubTmr.DLL, include the cActiveTitleBar class and call it like this:


Option Explicit
Private m_cATBar As cActiveTitleBar

Private Sub Form_Load()
m_cATBar.Attach Me.hWnd
End Sub


That's it! Just add the code to all non-modal forms and you are done.



TopBack to top
Source Code LibraryBack to Code Libraries
Source Code - What We're About!Back to Source Code

&nbsp

AboutContributeSend FeedbackPrivacy

Copyright 1998-1999, Steve McMahon ( steve@vbaccelerator.com). All Rights Reserved.
Last updated: 25 August 1999