|
Same problem here from a beginners point of view
|
|
|
|
|
OK, my problem with the XPTabCtrl + MadButch patch was that it doesn't allow for owner-drawn tabs. However, the fix for this is not too difficult. The problem is that the DrawTabItem function assumes the standard case (text/image list). So we need to test for the owner-draw flag, and if activated, call DrawItem with the correct info, thusly:
void CXPTabCtrl::DrawTabItem(CDC* pDC, int ixItem, const CRect& rcItemC, UINT uiFlags)<br />
{<br />
if (GetStyle() & TCS_OWNERDRAWFIXED)<br />
{<br />
DrawOwnerDrawTabItem(pDC, ixItem, rcItemC, uiFlags);<br />
}<br />
else<br />
{<br />
}<br />
}<br />
<br />
void CXPTabCtrl::DrawOwnerDrawTabItem(CDC* pDC,int ixItem,const CRect& rcItemC,UINT uiFlags)<br />
{<br />
DRAWITEMSTRUCT dis;<br />
TCITEM tcitem;<br />
<br />
ASSERT(pDC);<br />
if (pDC)<br />
{<br />
dis.CtlID = GetDlgCtrlID();<br />
dis.CtlType = ODT_TAB;<br />
dis.hDC = pDC->m_hDC;<br />
dis.hwndItem = GetSafeHwnd();<br />
dis.itemAction = ODA_DRAWENTIRE;<br />
<br />
VERIFY(TabCtrl_GetItem(GetSafeHwnd(), ixItem, &tcitem));<br />
dis.itemData = tcitem.lParam;<br />
<br />
dis.itemID = ixItem;<br />
dis.itemState = ((uiFlags&2)? ODS_SELECTED:0); <br />
dis.rcItem = rcItemC;<br />
<br />
DrawItem(&dis);<br />
}<br />
}<br />
I hope I got the DRAWITEMSTRUCT stuff right. There may be some minor detail I missed.
This fixes the problem...almost. If your custom drawing in DrawItem is done via the DC passed via the DRAWITEMSTRUCT, you're fine. However, I have sinned because I don't do this. I create some custom controls on the tabs and they don't get passed the DC, they just repaint themselves. This means that they get overpainted by the code at the end of DrawThemesXpTabItem, which dumps the content of the MemDC into the window DC. So I have come up with an ugly patch for this. Improvements welcome.
What I do is simply flag the owner-draw case and delay the call to DrawTabItem to the end. I have to save the ixItem variable because it's changed afterwards and I didn't want to inquire too closely as to why.
void CXPTabCtrl::DrawThemesXpTabItem(CDC* pDC, int ixItem, const CRect& rcItem, UINT uiFlag) <br />
{
<br />
BOOL bBody =(uiFlag& 1)?TRUE:FALSE;<br />
BOOL bSel =(uiFlag& 2)?TRUE:FALSE;<br />
BOOL bHot =(uiFlag& 4)?TRUE:FALSE;<br />
BOOL bBottom=(uiFlag& 8)?TRUE:FALSE;
BOOL bVertic=(uiFlag&16)?TRUE:FALSE;
BOOL bLeftTab=!bBottom && bVertic && !bBody;<br />
<br />
bool bDelayDrawItem = ((GetStyle() & TCS_OWNERDRAWFIXED) != 0);<br />
int ixDelayItem = -1;<br />
<br />
CSize szBmp=rcItem.Size();<br />
if(bVertic) SwapVars(szBmp.cx,szBmp.cy);<br />
<br />
...<br />
<br />
if(bVertic)<br />
{ if(bBody || !bBottom) bihOut.biHeight=-szBmp.cy;<br />
if(!bBottom && !bBody && ixItem>=0)
{ if(bSel) rcMem.bottom--;<br />
<br />
if (!bDelayDrawItem)<br />
{<br />
DrawTabItem(&dcMem, ixItem, rcMem, uiFlag);<br />
}<br />
else<br />
{<br />
ixDelayItem = ixItem;<br />
}<br />
ixItem=-1;<br />
} }
...<br />
<br />
if(!bBody && ixItem>=0)
{ if(bSel) rcMem.bottom--;<br />
<br />
if (!bDelayDrawItem)<br />
{<br />
DrawTabItem(&dcMem, ixItem, rcMem, uiFlag);<br />
}<br />
else<br />
{<br />
ixDelayItem = ixItem;<br />
}<br />
<br />
if(bVertic)
{ bihOut.biHeight=-szBmp.cy;<br />
GetDIBits(*pDC, bmpMem.operator HBITMAP(),nStart,szBmp.cy-nLenSub,pcImg,&biOut,DIB_RGB_COLORS);<br />
} }<br />
<br />
...<br />
<br />
pDC->BitBlt(rcItem.left,rcItem.top,szBmp.cx,szBmp.cy,&dcMem,0,0,SRCCOPY);
dcMem.SelectObject(pBmpOld);<br />
<br />
if (bDelayDrawItem && ixDelayItem != -1)<br />
{<br />
DrawTabItem(&dcMem, ixDelayItem, rcMem, uiFlag);<br />
}<br />
}<br />
Well, it's not pretty but it's working for me at the moment. I'll keep you posted.
|
|
|
|
|
Alas, all is not well with my fix. I've discovered that it can cause nasty random crashes. Well, not random really. "Random" means "whose cause I do not yet understand".
I'm looking into it.
|
|
|
|
|
OK, I think I fixed it. Apart from the prudent addition of a call to ZeroMemory, which I shouldn't have omitted, the only change is to omit passing lParam from TCITEM to itemData in DRAWITEMSTRUCT. It now seems clear that this is *NOT* what you are supposed to do. Apparently you just use the lParam data for your purposes in other tab control functions. But I haven't been able to dig up much information on the use of itemData either; the documentation only mentions its use in the case of combos and lists.
All that's needed is to replace my original version of DrawOwnerDrawTabItem with this one:
<br />
void CXPTabCtrl::DrawOwnerDrawTabItem(CDC* pDC,int ixItem,const CRect& rcItemC,UINT uiFlags)<br />
{<br />
DRAWITEMSTRUCT dis;<br />
<br />
ASSERT(pDC);<br />
if (pDC)<br />
{<br />
ZeroMemory( (void *)&dis, sizeof(DRAWITEMSTRUCT) ); <br />
dis.CtlID = GetDlgCtrlID();<br />
dis.CtlType = ODT_TAB;<br />
dis.hDC = pDC->m_hDC;<br />
dis.hwndItem = GetSafeHwnd();<br />
dis.itemAction = ODA_DRAWENTIRE;<br />
dis.itemData = 0;<br />
dis.itemID = ixItem;<br />
dis.itemState = ((uiFlags&2)? ODS_SELECTED:0); <br />
dis.rcItem = rcItemC;<br />
<br />
DrawItem(&dis);<br />
}<br />
}<br />
Have fun.
|
|
|
|
|
When resizing the tab, ghosting appears in the part of the screen where labels could be drawn (but are not). Let's say you have a tab control of 400 pixels wide, with 2 tabs under it, both 50 pixels wide. You would then have an area to the right (300 pixels wide) that would not be redrawn on resizing.
I wrote an "as flicker free as possible" solution for it, and here is the code:
(this is old code , and this is new code )
<br />
void CXPTabCtrl::OnPaint() <br />
{<br />
if(!IsExtendedTabTheamedXP())
{ Default(); return; } <br />
CPaintDC dc(this);
<br />
CRect rcClip; rcClip.SetRectEmpty();<br />
dc.GetClipBox(rcClip);<br />
<br />
CRect rcPage,rcItem,rcClient;<br />
GetClientRect(&rcPage);<br />
rcClient=rcPage;<br />
AdjustRect(FALSE,rcPage);<br />
<br />
switch(m_eTabOrientation)<br />
{ case e_tabTop: rcClient.top =rcPage.top -2; break;<br />
case e_tabBottom: rcClient.bottom=rcPage.bottom+3; break;<br />
case e_tabLeft: rcClient.left =rcPage.left -1; break;<br />
case e_tabRight: rcClient.right =rcPage.right +3; break;<br />
default: ASSERT(FALSE); return;<br />
}<br />
UINT uiVertBottm;<br />
uiVertBottm =(m_eTabOrientation&1)? 8:0;
uiVertBottm|=(m_eTabOrientation&2)?16:0;
UINT uiFlags=1|uiVertBottm;
DrawThemesXpTabItem(&dc, -1, rcClient,uiFlags);
<br />
int nTab=GetItemCount();
if(!nTab) return;
<br />
<br />
CRgn rgnItem;
CRgn rgnRepaintArea;
CRect rcRepaintArea;
<br />
VERIFY( GetItemRect( 0, &rcItem) );<br />
if( m_eTabOrientation == e_tabTop )<br />
{<br />
rcRepaintArea.top = rcItem.top - 2;
rcRepaintArea.bottom = rcItem.bottom;<br />
rcRepaintArea.left = rcClient.left;<br />
rcRepaintArea.right = rcClient.right;<br />
}<br />
else if( m_eTabOrientation == e_tabBottom )<br />
{<br />
rcRepaintArea.top = rcItem.top;<br />
rcRepaintArea.bottom = rcItem.bottom + 2;
rcRepaintArea.left = rcClient.left;<br />
rcRepaintArea.right = rcClient.right;<br />
}<br />
else if( m_eTabOrientation == e_tabLeft )<br />
{<br />
rcRepaintArea.top = rcClient.top;<br />
rcRepaintArea.bottom = rcClient.bottom;<br />
rcRepaintArea.left = rcItem.left - 2;
rcRepaintArea.right = rcClient.left;
}<br />
else if( m_eTabOrientation == e_tabRight )<br />
{<br />
rcRepaintArea.top = rcClient.top;<br />
rcRepaintArea.bottom = rcClient.bottom;<br />
rcRepaintArea.left = rcItem.left;<br />
rcRepaintArea.right = rcItem.right + 2;
}<br />
<br />
rgnRepaintArea.CreateRectRgnIndirect( rcRepaintArea );<br />
rgnItem.CreateRectRgn( 0, 0, 0, 0 );<br />
<br />
<br />
TCHITTESTINFO hti; hti.flags=0;<br />
::GetCursorPos(&hti.pt); ScreenToClient(&hti.pt);<br />
int ixHot=HitTest(&hti);<br />
int ixSel=GetCurSel();<br />
<br />
for(int ixTab=0; ixTab<nTab; ixTab++)<br />
{ if(ixTab==ixSel)<br />
continue;<br />
VERIFY(GetItemRect(ixTab, &rcItem));<br />
if(m_eTabOrientation==e_tabLeft) rcItem.right++;<br />
uiFlags=uiVertBottm|(ixTab==ixHot?4:0);
DrawThemesXpTabItem(&dc,ixTab,rcItem,uiFlags);<br />
rgnItem.SetRectRgn( rcItem );<br />
rgnRepaintArea.CombineRgn( &rgnRepaintArea, &rgnItem, RGN_DIFF );<br />
}<br />
VERIFY(GetItemRect(ixSel, &rcItem));
rcItem.InflateRect(2,2);<br />
if(m_eTabOrientation==e_tabTop) rcItem.bottom--;<br />
uiFlags=uiVertBottm|2;
DrawThemesXpTabItem(&dc, ixSel, rcItem,uiFlags);<br />
rgnItem.SetRectRgn( rcItem );<br />
rgnRepaintArea.CombineRgn( &rgnRepaintArea, &rgnItem, RGN_DIFF );<br />
<br />
HBRUSH hBrBack = (HBRUSH) ::GetClassLong( GetParent()->GetSafeHwnd(), GCL_HBRBACKGROUND );<br />
if( hBrBack == 0 )<br />
{<br />
hBrBack = ::GetSysColorBrush( COLOR_BTNFACE );<br />
}<br />
dc.FillRgn( &rgnRepaintArea, CBrush::FromHandle( hBrBack ) );<br />
}<br />
|
|
|
|
|
Hi
Nice work. Just one thing - it seems like you forgot to finish the left/right orientation bit. You've left two lines commented:
and it looks as though you realised that the code before it would be valid for all orientations, but then forgot to remove the previous if statement:
if( ( m_eTabOrientation == e_tabTop ) ||<br />
( m_eTabOrientation == e_tabBottom ) )<br />
As a result, if you have left or right tabs, there's a very nasty crash indeed .
I think the solution is to remove that condition and then add code to subtract 2 from the left or right sides, depending on the orientation. I'm not sure whether there's anything else missing. I have to do some more tests.
Also, I'm not sure what I'll have to do to get this working with my tab control class, because I've overridden the DrawItem function to place controls on each tab, and it seems like this implementation just takes over the drawing completely. Anybody got any hints? I'll look at it tomorrow in any case.
|
|
|
|
|
Oops
I left it open since I wasn't using left or right aligned tabs anywhere, but I didn't mean for it to crash. And I was fixing this during crunch time, you know how work pressure can be
I will see if I can throw a look at it tomorow.
Sorry
|
|
|
|
|
It's pretty simple - if I remember correctly (I don't have the code in front of me), there is a rect which is uninitialised in the case of left/right, and later that's used to create a region, which fails (the BOOL is not checked though) and then that invalid region is used somewhere, and bad things happen.
If you don't fix it first I'll send my corrections, which seem to have fixed it OK.
Meanwhile let me know if you happen to be the world's leading expert in how the work is divided up between DrawItem and OnPaint in the tab control, because I think I'll have to dive into that tomorrow
Cheers
|
|
|
|
|
I modified the original post, so it includes the fix. Plus I cleaned the code up some more, with better variable names and comments.
What I do notice is that in my example code, the tab labels (with just text on them) are not drawn correctly, because the text on them isn't rotated. Not sure why
And I'm unfortunately not the worlds leading expert on DrawItem and Repaint I'm not sure what modifications you have, but you could make the DrawTabItem function of the CXPTabCtrl store the rectangles/settings for each Tab label, and then ask those regions from the CXPTabCtrl and do your own drawing inside them.
good luck
|
|
|
|
|
Good stuff, that certainly fixes the problem. Thanks!
OK, now I will try to do battle with DrawItem...
|
|
|
|
|
I tried to use CXPTabCtrl in my dialog and now it is not receiving TCN_SELCHANGE message sent by the control when one clicks/changes the tab.
Looking at CXpTabCtrl.cpp, I saw TCN_SELCHANGE is handled in ON_NOTIFY_REFLECT. I tried to change it to ON_NOTIFY_REFLECT_EX and returns TRUE. According to MSDN, this should allow the parent to handle TCN_SELCHANGE as well, but it didn't work.
Does anyone have any suggestions? All I need is to let the parent handle tab changes.
|
|
|
|
|
You should return FALSE.
From MSDN:
If you use ON_NOTIFY_REFLECT_EX() in your message map, your message handler may or may not allow the parent window to handle the message. If the handler returns FALSE, the message will be handled by the parent as well, while a call that returns TRUE does not allow the parent to handle it.
|
|
|
|
|
I can't get this working at all. I was quite excited to see this fix, but it's useless to me if I can't catch the TCN_SELCHANGE message.
I use this on my window:
ON_NOTIFY(TCN_SELCHANGE, IDC_TAB_CONTROLLER, OnSelChangeTabController)
The OnSelChangeTabController is never called. I tried adding the "fixes" mentioned above, but to no abvail.
Is there anyone who got this working?
|
|
|
|
|
It works fine for me. Change the message map in XPTabCtrl.cpp so that it looks like this:
BEGIN_MESSAGE_MAP(CXPTabCtrl, CTabCtrl)<br />
ON_WM_PAINT()<br />
ON_NOTIFY_REFLECT(TCN_SELCHANGING,OnTabSelChanging)<br />
ON_NOTIFY_REFLECT_EX(TCN_SELCHANGE, OnTabSelChanged)
END_MESSAGE_MAP()
Then change to declaration of OnTabSelChanged so that it looks lie this:
afx_msg BOOL OnTabSelChanged (NMHDR* pNMHDR, LRESULT* pResult); //<--- change returntype to BOOL
And the implementation to:
BOOL CXPTabCtrl::OnTabSelChanged(NMHDR* pNMHDR, LRESULT* pResult)
{<br />
UNUSED_ALWAYS(pNMHDR);<br />
if(m_ixSelOld>=0 && m_ixSelOld!=GetCurSel() && IsExtendedTabTheamedXP())
{ CWnd* pWndParent=GetParent();<br />
if(pWndParent)<br />
{ CRect rcOldSel; GetItemRect(m_ixSelOld, rcOldSel); rcOldSel.InflateRect(2,2);<br />
ClientToScreen(&rcOldSel); pWndParent->ScreenToClient(&rcOldSel);<br />
pWndParent->InvalidateRect(rcOldSel);<br />
} }<br />
*pResult=1L;<br />
return FALSE;
}
Does it work now? Otherwise I can e-mail you my source
|
|
|
|
|
Thanks... that worked great.
Which of course leaves me wondering what the heck i did wrong in the first place
|
|
|
|
|
Admittedly using a "non-standard" theme, the left-most (or bottom-most) tab is drawn with it's edge cut-off. I haven't had time to investigate further.
--
Joel Lucsy
|
|
|
|
|
It looks great.
But I find a bug(maybe):
1,first dock the tabctrl on the top.
2,click the first tab
3,then dock the tabctrl on the left
4,click the forth tab(which is partly visible)
then you can see the tab doesn't draw correctly.
thanks
Benben
|
|
|
|
|
Nice work.
One issue I noticed immediately was the height of the
tabs, which was a bit too small to accomodate the 16
pixel bitmaps.
Is the height of the tab control determined by a system
metric, or did you hard code that?
|
|
|
|
|
I am not sure (I have not tested it before), but I think that tab height is defined by the height of default UI Font at the time of creating the tab.
Maybe you can try using e.g. CTabCtrl::SetPadding(CSize(0,6)) - or message TCM_SETPADDING - with only vertical increase of padding.
I read in MSDN that CTabCtrl::SetItemSize (or message TCM_SETITEMSIZE) sets the width and height of tabs BUT ONLY for a fixed-width or owner-drawn tab control.
Adi
|
|
|
|
|
Hi,
I've run across this problem some month ago and was realy anoyed. Top Tab-Ctrls. only was not an option, and I started thinking about writung my own Tag-Ctrl. After some time I stoped that because I had some big trouble and concerns, and then fall back to supporting Themed Tabs only for the Top-Position case.
One of my problems was about correct positioning of the Tabs, another problem was, that not all Tabs are alike. The leftmost, for instance has a little different design, which better suits its placement on the left border. A third point is, that the pane (the inner rectangle) has a gradient which schould be rotated too. (I think you have skipped that one.)
There is however a real concern. That is, your workaround only works for the current themes. Although I've heard that microsoft won't come up with new, any other theme will break it and look probably terrible.
Anyway, you've done a good job and i will rate it like that.
Thanks and Greating.
|
|
|
|
|
Thanks for suggestions.
The intention of this article was the solution for a particular problem - drawing tabs on XP with XP Themes. The intention was not a generic solution for other tab problems, so this solution is supposed to be included to the other solutions.
There are certain standards for writing XP Themes. If you use themes from producers who followe the standards (XP themes requirements), all parts from the new theme should work fine for all other applications.
In this sample I followed the next: use the Tab theme background, which ever is currently switched on (dimensions taken from system tab control), use current metrics, which ever is on offer (depends on the current theme and the current font) and draw text and icon on the top of the background. If this does not work in certain cases, than it should be properly adapted.
The metrics used for drawing text in the sample is in some way fixed, but should work fine for standard fonts and not too much height changes. Text and icon are just slightly shifted only to look as close as possible to the original XP Theme in the top orientation. If you want to draw tabs for all GUI font heights such as font with sizes 48 points and higher, than metrics should be more flexible.
But again, that was not intention of this article.
|
|
|
|
|
Hi, first i would like to say that your tab control looks and works really well. I am a beginner programmer and i am not familiar with c++, could you please tell me how i could incorporate this contol into a vb.net project?
|
|
|
|
|
Hi, I am not using VB so I will not be able to help you, maybe some other readers who are more familiar to tansfer C++ project into VB ...
|
|
|
|
|
In Win2000 if i choose left for tab - font is not vertical !!!!!!
Hi, all
|
|
|
|
|
Please read the answer for the question bellow "Problems on Win 98" (the article intention was to correct XP Themes tab problems, not particularly problems with vertical tabs, even though if you run the example on XP, it also includes that solution).
The generic solution for vertical tabs will be included in the another article, which I will send in few days.
Thanks,
Adi
|
|
|
|
|