// XPTabCtrl.cpp : ±¸Çö ÆÄÀÏÀÔ´Ï´Ù. // #include "stdafx.h" #include "XPTabCtrl.h" // CXPTabCtrl /***********************************************************************************************************/ // constant string definitions here (or you can put it into resource string table) #define IDS_UTIL_TAB _T("TAB") #define IDS_UTIL_UXTHEME _T("UxTheme.dll") #define IDS_UTIL_THEMEACT _T("IsThemeActive") #define IDS_UTIL_THEMEOPN _T("OpenThemeData") #define IDS_UTIL_THEMEBCKG _T("DrawThemeBackground") /***********************************************************************************************************/ IMPLEMENT_DYNAMIC(CXPTabCtrl, CTabCtrl) CXPTabCtrl::CXPTabCtrl() { m_bTabExtended=TRUE; // default is to use extended tab (if it is Themes XP) m_eTabOrientation=e_tabBottom; // default initial orientation is: bottom m_ixSelOld=-1; } CXPTabCtrl::~CXPTabCtrl() { } void CXPTabCtrl::InitImageList(UINT nBitmapID) { if(!::IsWindow(GetSafeHwnd()) || m_ilTabs.operator HIMAGELIST()) { ASSERT(FALSE); return; } // tab control has to be created first and image list can be created only once if(m_ilTabs.Create(nBitmapID, 16, 1, RGB(0xFF,0,0xFF))) // add an images list with appropriate background (transparent) color SetImageList(&m_ilTabs); } BEGIN_MESSAGE_MAP(CXPTabCtrl, CTabCtrl) ON_WM_PAINT() ON_NOTIFY_REFLECT(TCN_SELCHANGING,OnTabSelChanging) ON_NOTIFY_REFLECT(TCN_SELCHANGE, OnTabSelChanged) END_MESSAGE_MAP() // CXPTabCtrl ¸Þ½ÃÁö 󸮱âÀÔ´Ï´Ù. //---------------------------------------------------------------------------------------------------------- void CXPTabCtrl::OnPaint() { if(!IsExtendedTabTheamedXP()) // if it is not XP or it is not Themes, behave as default { Default(); return; } CPaintDC dc(this); // device context for painting CRect rcClip; rcClip.SetRectEmpty(); dc.GetClipBox(rcClip); // 1st paint the tab body CRect rcPage,rcItem,rcClient; GetClientRect(&rcPage); rcClient=rcPage; AdjustRect(FALSE,rcPage); switch(m_eTabOrientation) { case e_tabTop: rcClient.top =rcPage.top -2; break; case e_tabBottom: rcClient.bottom=rcPage.bottom+3; break; case e_tabLeft: rcClient.left =rcPage.left -1; break; case e_tabRight: rcClient.right =rcPage.right +3; break; default: ASSERT(FALSE); return; } UINT uiVertBottm; uiVertBottm =(m_eTabOrientation&1)? 8:0; // 8=bottom uiVertBottm|=(m_eTabOrientation&2)?16:0; // 16=vertical UINT uiFlags=1|uiVertBottm; // 1=body DrawThemesXpTabItem(&dc, -1, rcClient,uiFlags); // TABP_PANE=9,0,'TAB' int nTab=GetItemCount(); // paint the tabs first and then the borders if(!nTab) return; // no tab pages added // 2nd paint the inactive tabs CRect ; TCHITTESTINFO hti; hti.flags=0; ::GetCursorPos(&hti.pt); ScreenToClient(&hti.pt); int ixHot=HitTest(&hti); int ixSel=GetCurSel(); for(int ixTab=0; ixTab good for rotating bitmap in any resolution bihOut.biWidth =szBmp.cx; bihOut.biHeight=szBmp.cy; int nBmpWdtPS=DWordAlign(szBmp.cx*3); int nSzBuffPS=((nBmpWdtPS*szBmp.cy)/8+2)*8; LPBYTE pcImg=NULL; if(bBottom || bVertic) { pcImg=new BYTE[nSzBuffPS]; ASSERT(pcImg); } int nStart=0,nLenSub=0; if(bBody && bBottom && !bVertic) nStart=3,nLenSub=4; // if bottom oriented flip the body contest only (no shadows were flipped) // 3rd if it is left oriented tab, draw tab context before mirroring or rotating (before GetDIBits) if(bVertic) { if(bBody || !bBottom) bihOut.biHeight=-szBmp.cy; if(!bBottom && !bBody && ixItem>=0) // { if(bSel) rcMem.bottom--; DrawTabItem(&dcMem, ixItem, rcMem, uiFlag); ixItem=-1; } } // rotate or mirror // 4th get bits (for rotate) and mirror: body=(all except top) tab=(all except top) if(bVertic || bBottom) // get bits: { GetDIBits(*pDC, bmpMem.operator HBITMAP(),nStart,szBmp.cy-nLenSub,pcImg,&biOut,DIB_RGB_COLORS); if(bBottom) // mirror: body=(bottom and right) tab=(bottom and right) { bihOut.biHeight=-szBmp.cy; // to mirror bitmap is eough to use negative height between Get/SetDIBits SetDIBits(*pDC, bmpMem.operator HBITMAP(),nStart,szBmp.cy-nLenSub,pcImg,&biOut,DIB_RGB_COLORS); if(bBody && bVertic) // when it is right oriented body -> flip twice, first flip border shadows, than flip shaded inside body again { nStart=2; nLenSub=4; bihOut.biHeight=szBmp.cy; GetDIBits(*pDC, bmpMem.operator HBITMAP(),nStart,szBmp.cy-nLenSub,pcImg,&biOut,DIB_RGB_COLORS); bihOut.biHeight=-szBmp.cy; // to mirror bitmap is eough to use negative height between Get/SetDIBits SetDIBits(*pDC, bmpMem.operator HBITMAP(),nStart,szBmp.cy-nLenSub,pcImg,&biOut,DIB_RGB_COLORS); } } } // 5th if it is bottom or right oriented tab, draw after mirroring background (do GetDIBits again) if(!bBody && ixItem>=0) // { if(bSel) rcMem.bottom--; DrawTabItem(&dcMem, ixItem, rcMem, uiFlag); if(bVertic) // if it is right tab, do GetDIBits again { bihOut.biHeight=-szBmp.cy; GetDIBits(*pDC, bmpMem.operator HBITMAP(),nStart,szBmp.cy-nLenSub,pcImg,&biOut,DIB_RGB_COLORS); } } // 6th: do rotate now, finaly if(bVertic) // force rotating bitmap as RGB -> good for any resolution { SwapVars(szBmp.cx,szBmp.cy); int nBmpWdtPD=DWordAlign(szBmp.cx*3); int nPadD=nBmpWdtPD-szBmp.cx*3; int nSzBuffPD=((nBmpWdtPD*szBmp.cy)/8+2)*8; LPBYTE pcImgRotate=new BYTE[nSzBuffPD]; ASSERT(pcImgRotate); int nWidth,nHeight=szBmp.cy,nHeight1=nHeight-1; //==================================== //------------------------------------ // here is the example how to speed up lengthy repeatetive processing by using inline assembler // #define __USE_MASM__ // the same processing is in C and in asm. To use C -> comment the beginning of this line // Do the actual whole RGB bitmap rotating in C or assembler #ifndef __USE_MASM__ LPBYTE pcImgS=pcImg; LPBYTE pcImgD=pcImgRotate; int ixHeight=0; BOOL bLast=FALSE; while(ixHeightcould not read/write DWORD)!! else { ixHeight++; pcImgD+=nPadD; // destination bitmap horizontal padding to DWORD pcImgS=pcImg+(ixHeight*3); // reset the source to the begining of the next vertical line } } #else // __USE_MASM__ nBmpWdtPS-=4; // adjust esi increment (due to esi self-incrementing by movsd) nWidth=szBmp.cx; __asm { mov esi, pcImg // source index mov edi, pcImgRotate // destination index xor ebx, ebx // vertical counter loop_height: mov ecx, nWidth // horizontal counter cmp ebx, nHeight1 // check is it the last line jne loop_width dec ecx // if it is decremnt for the last pixel loop_width: movsd // copies 4 bytes and increments source and destination by 4 (we need only 3 bytes copied 'one pixel' RGB triplet) dec edi // adjust edi to 'as incremented by 3' add esi,nBmpWdtPS // adjust esi to the next source line loop loop_width // loop one hotizontal destination line cmp ebx, nHeight1 // check is it the last line je do_last // if not last, do incrementing here inc ebx // increment vertical counter add edi, nPadD // adjust destination index by possible padding to DWORD mov esi, ebx // reset the source index: add vertical counter * 3 shl esi, 1 // (is the same as * 2 +1*) add esi, ebx // +1* add esi, pcImg // add to the beginning of the source jmp loop_height // loop whole height do_last: // the last pixel is done by movsw // moving first two bytes movsb // and than by moving the very last byte } #endif // __USE_MASM__ dcMem.SelectObject(pBmpOld); bmpMem.DeleteObject(); // recreate rotated bitmap bmpMem.CreateCompatibleBitmap(pDC,szBmp.cx,szBmp.cy); dcMem.SelectObject(&bmpMem); bihOut.biWidth =szBmp.cx; bihOut.biHeight=bBody?-szBmp.cy:szBmp.cy; SetDIBits(*pDC, bmpMem.operator HBITMAP(),0,szBmp.cy,pcImgRotate,&biOut,DIB_RGB_COLORS); // set rotated bitmap bits delete pcImgRotate; } if(pcImg) delete pcImg; // 6th blit mirrored/rotated image to the screen pDC->BitBlt(rcItem.left,rcItem.top,szBmp.cx,szBmp.cy,&dcMem,0,0,SRCCOPY); // dcMem.SelectObject(pBmpOld); } //---------------------------------------------------------------------------------------------------------- // draw tab item context: possible icon and text void CXPTabCtrl::DrawTabItem(CDC* pDC, int ixItem, const CRect& rcItemC, UINT uiFlags) { TC_ITEM tci; CString sText; tci.mask =TCIF_TEXT | TCIF_IMAGE; tci.pszText =sText.GetBuffer(128); tci.cchTextMax=127; GetItem(ixItem,&tci); sText.ReleaseBuffer(); BOOL bSel =(uiFlags&2)?TRUE:FALSE; BOOL bBottom=(uiFlags&8)?TRUE:FALSE; CRect rcItem=rcItemC; if(bSel) rcItem.bottom -= 1; else rcItem.bottom += 2; rcItem.left+=(bBottom?3:6); // text & icon rcItem.top +=(bBottom?3:2) + (bSel ? 1 : (bBottom?0:3)); int nOldMode=pDC->SetBkMode(TRANSPARENT); HIMAGELIST hilTabs = (HIMAGELIST)TabCtrl_GetImageList(GetSafeHwnd()); // icon if(hilTabs && tci.iImage>=0) { ImageList_Draw(hilTabs, tci.iImage, *pDC, rcItem.left+(bSel?2:0), rcItem.top+(bSel?0:-2), ILD_TRANSPARENT); rcItem.left+=19; } else rcItem.OffsetRect(-2,0); if(sText.GetLength()) { CFont* pOldFont=pDC->SelectObject(GetFont()); // prepare dc rcItem.right-=3; // text CRect rect(0,0,rcItem.Width(),20); ::DrawText(pDC->GetSafeHdc(),sText.GetBuffer(sText.GetLength()+4),-1,rect,DT_CALCRECT|DT_SINGLELINE|DT_MODIFYSTRING|DT_END_ELLIPSIS); sText.ReleaseBuffer(); rcItem.OffsetRect((bBottom?-1:0),(bSel?1:-1)); pDC->DrawText(sText, rcItem, DT_NOPREFIX|DT_CENTER); pDC->SelectObject(pOldFont); } pDC->SetBkMode(nOldMode); } //---------------------------------------------------------------------------------------------------------- BOOL CXPTabCtrl::IsExtendedTabTheamedXP() { if(!m_bTabExtended || !::IsWindow(GetSafeHwnd())) return FALSE; DWORD dwStyle=GetStyle(); m_eTabOrientation=(dwStyle&TCS_BOTTOM)?e_tabBottom:e_tabTop; if(dwStyle&TCS_VERTICAL) m_eTabOrientation=(m_eTabOrientation==e_tabTop)?e_tabLeft:e_tabRight; #ifdef USE_DEFAULT_XP_TOPTAB if(m_eTabOrientation==e_tabTop) return FALSE; #endif return IsThemeActiveXP()?TRUE:FALSE; } //========================================================================================================== // these two messages are necessary only to properly redraw deselected tab background, because void CXPTabCtrl::OnTabSelChanging(NMHDR* pNMHDR, LRESULT* pResult) // selected rect was inflated by 2 points { UNUSED_ALWAYS(pNMHDR); m_ixSelOld=GetCurSel(); *pResult=0L; } //---------------------------------------------------------------------------------------------------------- void CXPTabCtrl::OnTabSelChanged(NMHDR* pNMHDR, LRESULT* pResult) { UNUSED_ALWAYS(pNMHDR); if(m_ixSelOld>=0 && m_ixSelOld!=GetCurSel() && IsExtendedTabTheamedXP()) // else { CWnd* pWndParent=GetParent(); if(pWndParent) { CRect rcOldSel; GetItemRect(m_ixSelOld, rcOldSel); rcOldSel.InflateRect(2,2); ClientToScreen(&rcOldSel); pWndParent->ScreenToClient(&rcOldSel); pWndParent->InvalidateRect(rcOldSel); } } *pResult=1L; } /***********************************************************************************************************/ // Helper functions /***********************************************************************************************************/ int DWordAlign(int n) { const int rem=n%4; if(rem) n+=(4-rem); return n; } //---------------------------------------------------------------------------------------------------------- BOOL IsThemeActiveEx() { // check theme activity always (could change during application running) USES_CONVERSION; HINSTANCE hDll=LoadLibrary(CString((LPCTSTR)IDS_UTIL_UXTHEME)); // 'UxTheme.dll' if(hDll==NULL) return FALSE; // the DLL won't be available on anything except Windows XP UINT (PASCAL *pfnIsThemeActive)(); #ifdef X (FARPROC&)pfnIsThemeActive=GetProcAddress(hDll,W2A(IDS_UTIL_THEMEACT)); // 'IsThemeActive' #else (FARPROC&)pfnIsThemeActive=GetProcAddress(hDll,IDS_UTIL_THEMEACT); // 'IsThemeActive' #endif UINT uiThemeActive=0; if(pfnIsThemeActive) uiThemeActive=pfnIsThemeActive(); FreeLibrary(hDll); return uiThemeActive?TRUE:FALSE; } //---------------------------------------------------------------------------------------------------------- #define PACKVERSION(major,minor) MAKELONG(minor,major) DWORD GetWinVersion() { static DWORD c_dwWinVers=0; // check win version only once (will not change during application) if(!c_dwWinVers) { OSVERSIONINFO osvi; ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); // Initialize the OSVERSIONINFO structure. osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osvi); c_dwWinVers=PACKVERSION(osvi.dwMajorVersion,osvi.dwMinorVersion); } return c_dwWinVers; } //---------------------------------------------------------------------------------------------------------- BOOL IsWinXP() { return GetWinVersion()>=PACKVERSION(5,1)?TRUE:FALSE; } //---------------------------------------------------------------------------------------------------------- BOOL IsThemeActiveXP() { return (IsWinXP() && IsThemeActiveEx())?TRUE:FALSE; } //---------------------------------------------------------------------------------------------------------- #define WPART_NAME_SZ 128 HRESULT DrawThemesPart(HDC hDC, int iPartId, int iStateId, LPCSTR uiPartNameID, LPRECT prcBox) { USES_CONVERSION; if(!IsWinXP()) return E_FAIL; HINSTANCE hDll=LoadLibrary(CString((LPCTSTR)IDS_UTIL_UXTHEME)); // 'UxTheme.dll' if(!hDll) return E_FAIL; BOOL (PASCAL* pfnIsThemeActive)(); UINT hTheme=0; #ifdef X (FARPROC&)pfnIsThemeActive=GetProcAddress(hDll,W2A(IDS_UTIL_THEMEACT)); // 'IsThemeActive' #else (FARPROC&)pfnIsThemeActive=GetProcAddress(hDll,IDS_UTIL_THEMEACT); // 'IsThemeActive' #endif HRESULT hResult=E_FAIL; if(pfnIsThemeActive && pfnIsThemeActive()) { CString sPartName(uiPartNameID); if(sPartName.GetLength()>0) { WCHAR swPartName[WPART_NAME_SZ]; #ifdef X MultiByteToWideChar(CP_ACP,0,W2A(sPartName),-1,swPartName,sizeof(swPartName)/sizeof(WCHAR)); #else MultiByteToWideChar(CP_ACP,0,sPartName,-1,swPartName,sizeof(swPartName)/sizeof(WCHAR)); #endif UINT (PASCAL* pfnOpenThemeData)(HWND hwnd, LPCWSTR pszClassList); #ifdef X (FARPROC&)pfnOpenThemeData=GetProcAddress(hDll,W2A(IDS_UTIL_THEMEOPN));// 'OpenThemeData' #else (FARPROC&)pfnOpenThemeData=GetProcAddress(hDll,IDS_UTIL_THEMEOPN);// 'OpenThemeData' #endif if(pfnOpenThemeData && (hTheme=pfnOpenThemeData(NULL, swPartName))!=0) { UINT (PASCAL* pfnDrawThemeBackground)(UINT htheme,HDC hdc,int iPartID,int iStateID,const RECT* prcBx,const RECT* prcClip); #ifdef X (FARPROC&)pfnDrawThemeBackground=GetProcAddress(hDll,W2A(IDS_UTIL_THEMEBCKG)); // 'DrawThemeBackground' #else (FARPROC&)pfnDrawThemeBackground=GetProcAddress(hDll,IDS_UTIL_THEMEBCKG); // 'DrawThemeBackground' #endif if(pfnDrawThemeBackground) hResult=pfnDrawThemeBackground(hTheme, hDC, iPartId, iStateId, prcBox, NULL); } } } FreeLibrary(hDll); return hResult; } //----------------------------------------------------------------------------------------------------------