Environment: The code was
compiled with VC6 SP2, Win98 and tested with Win95 SP2, Win98 and NT4 SP3.
I think that default look of common CTabCtrl is not cool
enough (for the year 2000), when control is used with scrolling tabs:
The CLBTabCtrl class uses my CLBSpinButtonCtrl class
(see "Owner Drawn Spin Control" article) and completely draw
itself to look like this:
Beside that, my CLBTabCtrl class has "autoraising
items" feature. It means, that when mouse is over inactive item, that item
is drawn higher then other inactive items and its right border became darker.
This feature also persist when tab control is used in stacked mode:
How to use CLBTabCtrl
Add LBTabCtrl.h and LBTabCtrl.cpp to your
project.
Create new class derived from CPropertySheet and
add it to project.
Include LBTabCtrl.h file in the header file of just created CPropertySheet-derived class.
#include "LBTabCtrl.h";
Add a member variable to that CPropertySheet-derived class
CLBTabCtrl m_LBTabCtrl;
Subclass the common tab control to
CLBTabCtrl. The good place to do this - in the virtual member function
OnInitDialog() of CPropertySheet-derived class:
After that you can use that CPropertySheet-derived class everywhere as
usual CPropertySheet. For instance:
CLBTabDemoPropSheet sheet("Any Title");
CPropertyPage page1(IDD_PROPPAGE1),page2(IDD_PROPPAGE2),
page3(IDD_PROPPAGE3);
sheet.AddPage(&page1);
sheet.AddPage(&page2);
sheet.AddPage(&page3);
// The following line is required if you wish to switch on
// the scrolling mode,since stacked mode is default
// for tab controls.
sheet.EnableStackedTabs(FALSE);
sheet.DoModal();
Note: Certainly you also can subclass any tab control,
which does not lives within CPropertySheet.
How it works
1. Overview
The CLBTabCtrl is owner drawn tab control. To make its
job this control handles the following messages:
WM_MOUSEMOVE
WM_MOUSELEAVE
TCN_SELCHANGING
TCN_SELCHANGE
TCM_SETCURSEL
WM_HSCROLL
WM_PAINT
WM_ERASEBKGND
WM_KILLFOCUS
WM_SETFOCUS
It also overrides virtual member functions of
CTabCtrl:
PreSubclassWindow
WindowProc
Note: There is a possibility to switch off /on the
"autoraising items" feature of CLBTabCtrl, using its public member
function [bool SetAutoRaising(bool bOn)].
2. How autoraising items work
Only handling of WM_MOUSEMOVE and WM_MOUSELEAVE does
the trick.
When the mouse enters the CLBTabCtrl it receive WM_MOUSEMOVE
message and in that moment I use _TrackMouseEvent API to be notified with
WM_MOUSELEAVE message, when the mouse leaves tab control.
This API,declared in "Commctrl.h", tries to
use the window manager's implementation of TrackMouseEvent if it is present(for
Win98/NT), otherwise it emulates (for Win95).
In WM_MOUSEMOVE handler I invalidate the item which
is under mouse to draw it as raised and also invalidate item which was
raised before as normal (unraised).
In WM_MOUSELEAVE handler I invalidate item which
was raised before mouse left CLBTabCtrl, to draw it as normal (unraised).
3. How CLBSpinButtonCtrl is involved
CLBSpinButtonCtrl substitutes the common up-down control
which used within CTabCtrl in scrolling mode.
It is done in the virtual member function
PreSubclassWindow() of CLBTabCtrl. There I look for msctls_updown32 control and
if it is present, modify its size, position and subclass it to
CLBSpinButtonCtrl, which have autodisabling arrow buttons.
CWnd* pWnd = GetWindow(GW_CHILD);
while(pWnd)
{
char buf[]="msctls_updown32";
intnRet= ::GetClassName(pWnd->m_hWnd,
buf,sizeof(buf)/sizeof(buf[0]));
if(nRet&&strcmp(buf,"msctls_updown32"))
{
pWnd=pWnd->GetWindow(GW_HWNDNEXT);
}
else
{
//The msctls_updown32 control isfound.
//
pWnd->GetWindowRect(&m_rectUpDn);
ScreenToClient(&m_rectUpDn);
//Update size and position of msctls_updown32 control
m_rectUpDn.DeflateRect(3,2);
m_rectUpDn.OffsetRect(3,5);
pWnd->MoveWindow(&m_rectUpDn);
//Subclass common msctls_updown32 control to
//my CLBSpinButtonCtrl, which have autodisabling arrow
buttons.m_Spin.SubclassWindow(pWnd->m_hWnd);
pWnd=0;
}
}
4. Handling of TCN_SELCHANGING and TCN_SELCHANGE
messages
These messages are handled as reflected notification
messages. After my handlers do the job, the parent window also gets a chance to
handle it.
After CLBTabCtrl control (and its parent) handles
TCN_SELCHANGING message, the system will send to it WM_PAINT/WM_ERASEBKGND
messages. Since at that moment I still don't know which item has become active,
and can not properly draw items, I have to avoid these WM_PAINT/WM_ERASEBKGND
messages to get rid of flickering.
Since up-down control do not receive
WM_PAINT/WM_ERASEBKGND messages if it is invisible, so I temporary set the
appropriate visible bits off and the control thinks it is visible even though it
is not.
DWORD dwStyle =
::GetWindowLong(m_hWnd,GWL_STYLE);
if (dwStyle & WS_VISIBLE)
//switch off WS_VISIBLE
::SetWindowLong(m_hWnd, GWL_STYLE,
(dwStyle & ~ WS_VISIBLE));
I set visible bits back on in TCN_SELCHANGE handler,
when it is good time to redraw CLBTabCtrl.
5. Handling of WM_PAINT message
a.) First of all to get rid of flickering I'm drawing to
memory DC (dc). So I have to create compatible memory DC and select bitmap into
it.
// Since we are bypassing WM_ERASEBCKGND, by returning TRUE from its
//handler - let's do its job here
dc.FillSolidRect(&rctPaint,::GetSysColor(COLOR_BTNFACE));
b.) After that I check if current repaint has happened
due to autoraised item. If so, I repaint only raised item.
Otherwise I repaint parts of border of CLBTabCtr which
overlapped by update region. After that I draw all inactive items, which
intersects the update region. And in the last turn the active item is drawn, in
case if it also intersects the update region.
For drawing of items I've used virtual member function
DrawItem of CTabCtrl and virtual member function DrawItemRect, added to
CLBTabCtrl.
c.) At the final step I copy the resulting bitmap from
memory DC to the screen, using BitBlt with SRCCOPY ROP.
Standard Disclaimer
These files may be redistributed unmodified by any means providing it is not sold for profit without the
authors written consent, and providing that the authors name and all copyright
notices remains intact. This code may be used in compiled form in any way you
wish with the following conditions:
If the source code is used in any commercial product
then a statement along the lines of "Portions Copyright (C) 1999 Oleg
Lobach" must be included in the startup banner, "About" box or
printed documentation. The source code may not be compiled into a standalone
library and sold for profit. In any other cases the code is free to whoever
wants it anyway!
This software is provided "as is" without
express or implied warranty. The author accepts no liability for any damages to
your computer or data these products may cause.
Comments
There are no comments yet. Be the first to comment!