//--------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include "ThemePageControl.h" //--------------------------------------------------------------------------- #pragma package(smart_init) //--------------------------------------------------------------------------- // Based on // https://www.codeproject.com/Articles/6355/XP-Themes-Tab-Control-in-any-orientation //--------------------------------------------------------------------------- //#define USE_DEFAULT_XP_TOPTAB // XP top tab is drawn only for test purpose. To use default, uncoment this line //--------------------------------------------------------------------------- // constant string definitions here (or you can put it into resource string table) #define IDS_UTIL_TAB L"TAB" #define IDS_UTIL_UXTHEME L"UxTheme.dll" #define IDS_UTIL_THEMEACT "IsThemeActive" #define IDS_UTIL_THEMEOPN "OpenThemeData" #define IDS_UTIL_THEMEBCKG "DrawThemeBackground" //--------------------------------------------------------------------------- static inline void ValidCtrCheck(TThemePageControl *) { new TThemePageControl(NULL); } //--------------------------------------------------------------------------- namespace Themepagecontrol { void __fastcall PACKAGE Register() { TComponentClass classes[1] = {__classid(TThemePageControl)}; RegisterComponents(L"Scp", classes, 0); } } //---------------------------------------------------------------------------------------------------------- __fastcall TThemeTabSheet::TThemeTabSheet(TComponent * Owner) : TTabSheet(Owner) { FShadowed = false; } //---------------------------------------------------------------------------------------------------------- void __fastcall TThemeTabSheet::SetShadowed(bool Value) { if (Shadowed != Value) { FShadowed = Value; TThemePageControl * ThemePageControl = dynamic_cast(Parent); if (DebugAlwaysTrue(ThemePageControl != NULL)) { ThemePageControl->InvalidateTab(TabIndex); } else { Parent->Invalidate(); } } } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- __fastcall TThemePageControl::TThemePageControl(TComponent * Owner) : TPageControl(Owner) { FOldTabIndex = -1; } //---------------------------------------------------------------------------------------------------------- int __fastcall TThemePageControl::GetTabsHeight() { // Calculated height includes tab/contents separator line on Windows 7/8, // but not on Windows XP TRect Rect = GetClientRect(); ::SendMessage(Handle, TCM_ADJUSTRECT, FALSE, (LPARAM)&Rect); int Result = Rect.Top - 1; // Two different ways to calculate the same, not sure which one is more reliable, // so we want to know in case they differ. if (DebugAlwaysTrue(PageCount >= 0)) { TRect Rect = TabRect(0); int Result2 = Rect.Bottom + 1; // On Windows 10 with 200% scaling, the first is 40, the second is 42. // With 250% scaling its 50 vs 53. // Using the larger. if (Result2 > Result) { DebugAssert(IsWin10()); Result = Result2; } } return Result; } //---------------------------------------------------------------------------------------------------------- void __fastcall TThemePageControl::PaintWindow(HDC DC) { // Themes not enabled, give up if (!UseThemes()) { TPageControl::PaintWindow(DC); return; } // TODO use GetClipBox TRect PageRect = GetClientRect(); // 1st paint the tab body TRect ClientRect = PageRect; ::SendMessage(Handle, TCM_ADJUSTRECT, FALSE, (LPARAM)&PageRect); ClientRect.Top = PageRect.Top - 2; DrawThemesXpTabItem(DC, -1, ClientRect, true, 0); // 2nd paint the inactive tabs TPoint Point = ScreenToClient(Mouse->CursorPos); int HotIndex = IndexOfTabAt(Point.X, Point.Y); int SelectedIndex = TabIndex; for (int Tab = 0; Tab < PageCount; Tab++) { if (Tab != SelectedIndex) { TThemeTabSheet * ThemeTabSheet = dynamic_cast(Pages[Tab]); bool Shadowed = (ThemeTabSheet != NULL) ? ThemeTabSheet->Shadowed : false; TRect Rect = TabRect(Tab); int State = (Tab == HotIndex ? TIS_HOT : (Shadowed ? TIS_DISABLED : TIS_NORMAL)); DrawThemesXpTabItem(DC, Tab, Rect, false, State); } } if (SelectedIndex >= 0) { // 3rd paint the active selected tab TRect Rect = TabRect(SelectedIndex); Rect.Inflate(2, 2); Rect.Bottom--; DrawThemesXpTabItem(DC, SelectedIndex, Rect, false, TIS_SELECTED); } } //---------------------------------------------------------------------------------------------------------- // This function draws Themes Tab control parts: a) Tab-Body and b) Tab-tabs void __fastcall TThemePageControl::DrawThemesXpTabItem(HDC DC, int Item, const TRect & Rect, bool Body, int State) { TSize Size = Rect.Size; // Draw background HDC DCMem = CreateCompatibleDC(DC); HBITMAP BitmapMem = CreateCompatibleBitmap(DC, Size.Width, Size.Height); HBITMAP BitmapOld = (HBITMAP)SelectObject(DCMem, BitmapMem); TRect RectMem(0, 0, Size.Width, Size.Height); if (!Body && (State == TIS_SELECTED)) { RectMem.Bottom++; } if (Body) { DrawThemesPart(DCMem, TABP_PANE, State, IDS_UTIL_TAB, &RectMem); } else { DrawThemesPart(DCMem, TABP_TABITEM, State, IDS_UTIL_TAB, &RectMem); } // Init some extra parameters BITMAPINFO BitmapInfo; // Fill local pixel arrays ZeroMemory(&BitmapInfo, sizeof(BITMAPINFO)); BITMAPINFOHEADER & BitmapInfoHeader = BitmapInfo.bmiHeader; BitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER); BitmapInfoHeader.biCompression = BI_RGB; BitmapInfoHeader.biPlanes = 1; // force as RGB: 3 bytes,24 bits -> good for rotating bitmap in any resolution BitmapInfoHeader.biBitCount = 24; BitmapInfoHeader.biWidth = Size.Width; BitmapInfoHeader.biHeight = Size.Height; if (!Body && (Item >= 0)) { if ((State == TIS_SELECTED)) { RectMem.Bottom--; } DrawTabItem(DCMem, Item, RectMem, (State == TIS_SELECTED), (State == TIS_DISABLED)); } // Blit image to the screen BitBlt(DC, Rect.Left, Rect.Top, Size.Width, Size.Height, DCMem, 0, 0, SRCCOPY); SelectObject(DCMem, BitmapOld); DeleteObject(BitmapMem); DeleteDC(DCMem); } //---------------------------------------------------------------------------------------------------------- // draw tab item context: possible icon and text void __fastcall TThemePageControl::DrawTabItem(HDC DC, int Item, TRect Rect, bool Selected, bool Shadowed) { if (Selected) { Rect.Bottom -= 1; } else { Rect.Bottom += 2; } Rect.Left += 6; Rect.Top += 2 + (Selected ? 1 : 3); UnicodeString Text = Pages[Item]->Caption; if ((Images != NULL) && (Pages[Item]->ImageIndex >= 0)) { int Left; if (!Text.IsEmpty()) { Left = Rect.Left + (Selected ? 2 : 0); } else { Left = (Rect.Right - Images->Width - Rect.Left) / 2; } int Y = ((Rect.Top + Rect.Bottom - Images->Height) / 2) - 1 + (Selected ? 0 : -2); std::unique_ptr Canvas(new TCanvas()); Canvas->Handle = DC; Images->Draw(Canvas.get(), Left, Y, Pages[Item]->ImageIndex, !Shadowed); Rect.Left += Images->Width + 3; } else { Rect.Left -= 2; } int OldMode = SetBkMode(DC, TRANSPARENT); if (!Text.IsEmpty()) { HFONT OldFont = (HFONT)SelectObject(DC, Font->Handle); Rect.Right -= 3; wchar_t * Buf = new wchar_t[Text.Length() + 1 + 4]; wcscpy(Buf, Text.c_str()); TRect TextRect(0, 0, Rect.Right - Rect.Left, 20); ::DrawText(DC, Buf, -1, &TextRect, DT_CALCRECT | DT_SINGLELINE | DT_MODIFYSTRING | DT_END_ELLIPSIS); OffsetRect(&Rect, 0, (Selected ? 0 : -2)); DrawText(DC, Buf, -1, &Rect, DT_NOPREFIX | DT_CENTER); delete[] Buf; SelectObject(DC, OldFont); } SetBkMode(DC, OldMode); } //---------------------------------------------------------------------------------------------------------- void __fastcall TThemePageControl::DrawThemesPart(HDC DC, int PartId, int StateId, LPCWSTR PartNameID, LPRECT Rect) { HTHEME Theme = OpenThemeData(NULL, PartNameID); if (Theme != 0) { DrawThemeBackground(Theme, DC, PartId, StateId, Rect, NULL); CloseThemeData(Theme); } } //========================================================================================================== // these two messages are necessary only to properly redraw deselected tab background, because bool __fastcall TThemePageControl::CanChange() { FOldTabIndex = ActivePageIndex; return TPageControl::CanChange(); } //---------------------------------------------------------------------------------------------------------- void __fastcall TThemePageControl::InvalidateTab(int Index) { if (HandleAllocated()) { TRect Rect = TabRect(Index); if (Index == TabIndex) { Rect.Inflate(2, 2); } // Original code was invalidating range against parent window // (recalculating coordinates first) InvalidateRect(Handle, &Rect, true); } } //---------------------------------------------------------------------------------------------------------- void __fastcall TThemePageControl::Change() { // note that TabIndex yields correct value already here, // while ActivePageIndex is not updated yet if ((FOldTabIndex >= 0) && (FOldTabIndex != TabIndex) && UseThemes()) { InvalidateTab(FOldTabIndex); } TPageControl::Change(); } //---------------------------------------------------------------------------------------------------------- #ifdef _DEBUG void __fastcall TThemePageControl::RequestAlign() { TPageControl::RequestAlign(); } #endif //----------------------------------------------------------------------------------------------------------