UCanCode
Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database
workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!
"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"
_TrackMouseEvent, GetWindow,
GetWindowRect, SubclassWindow, GetWindowLong, SetWindowLong,
Drawing, VC++, source code
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.
-
Add a member
variable to that
CPropertySheet -derived
class.
-
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.
|