Download demo project - 56 Kb
Download source files - 37 Kb
I think that the default look of common CTabCtrl
is not cool enough (for the
year 2000) when the control is used with scrolling tabs:
The CLBTabCtrl class uses my CLBSpinButtonCtrl class
(see "Owner drawn spin button control with autodisabling
arrow buttons" article) and completly 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 a 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:
BOOL CLBTabDemoPropSheet::OnInitDialog()
{
BOOL bResult =CPropertySheet::OnInitDialog();
CTabCtrl *pTab =GetTabControl();
m_LBTabCtrl.SubclassWindow(pTab->m_hWnd);
return bResult;
}
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);
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).
TRACKMOUSEEVENT stTRACKMOUSEEVENT;
stTRACKMOUSEEVENT.cbSize = sizeof(stTRACKMOUSEEVENT);
stTRACKMOUSEEVENT.hwndTrack=m_hWnd;
stTRACKMOUSEEVENT.dwFlags=TME_LEAVE;
_TrackMouseEvent(&stTRACKMOUSEEVENT);
After that used approach is straightforward:
- 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 leaved 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
{
pWnd->GetWindowRect(&m_rectUpDn);
ScreenToClient(&m_rectUpDn);
m_rectUpDn.DeflateRect(3,2);
m_rectUpDn.OffsetRect(3,5);
pWnd->MoveWindow(&m_rectUpDn);
buttons.m_Spin.SubclassWindow(pWnd->m_hWnd);
pWnd=0;
}
}
4. Handling the TCN_SELCHANGING and TCN_SELCHANGE messages
These messages are handled as reflected notification messages. After my handlers do the job, the parent window also
get 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)
::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 the WM_PAINT message
- 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.
CPaintDC RealDC(this);
CDC dc;
CBitmap bmpMem,*pOldMemBmp;
rctPnt=dcReal.m_ps.rcPaint;
dc.CreateCompatibleDC(&RealDC);
bmpMem.CreateCompatibleBitmap(&RealDC,
rctPaint.Width(),rctPaint.Height());
pOldMemBmp=dc.SelectObject(&bmpMem);
dc.FillSolidRect(&rctPaint,::GetSysColor(COLOR_BTNFACE));
- 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
CLBTabCtrl
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
.
- 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.