ThemePageControl.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. //---------------------------------------------------------------------------
  2. #include <vcl.h>
  3. #pragma hdrstop
  4. #include <Common.h>
  5. #include <vsstyle.h>
  6. #include <memory>
  7. #include "ThemePageControl.h"
  8. //---------------------------------------------------------------------------
  9. #pragma package(smart_init)
  10. //---------------------------------------------------------------------------
  11. // Based on
  12. // https://www.codeproject.com/Articles/6355/XP-Themes-Tab-Control-in-any-orientation
  13. //---------------------------------------------------------------------------
  14. //#define USE_DEFAULT_XP_TOPTAB // XP top tab is drawn only for test purpose. To use default, uncoment this line
  15. //---------------------------------------------------------------------------
  16. // constant string definitions here (or you can put it into resource string table)
  17. #define IDS_UTIL_TAB L"TAB"
  18. #define IDS_UTIL_UXTHEME L"UxTheme.dll"
  19. #define IDS_UTIL_THEMEACT "IsThemeActive"
  20. #define IDS_UTIL_THEMEOPN "OpenThemeData"
  21. #define IDS_UTIL_THEMEBCKG "DrawThemeBackground"
  22. //---------------------------------------------------------------------------
  23. static inline void ValidCtrCheck(TThemePageControl *)
  24. {
  25. new TThemePageControl(NULL);
  26. }
  27. //---------------------------------------------------------------------------
  28. namespace Themepagecontrol
  29. {
  30. void __fastcall PACKAGE Register()
  31. {
  32. TComponentClass classes[1] = {__classid(TThemePageControl)};
  33. RegisterComponents(L"Scp", classes, 0);
  34. }
  35. }
  36. //----------------------------------------------------------------------------------------------------------
  37. __fastcall TThemeTabSheet::TThemeTabSheet(TComponent * Owner) :
  38. TTabSheet(Owner)
  39. {
  40. FShadowed = false;
  41. }
  42. //----------------------------------------------------------------------------------------------------------
  43. void __fastcall TThemeTabSheet::SetShadowed(bool Value)
  44. {
  45. if (Shadowed != Value)
  46. {
  47. FShadowed = Value;
  48. TThemePageControl * ThemePageControl = dynamic_cast<TThemePageControl *>(Parent);
  49. if (DebugAlwaysTrue(ThemePageControl != NULL))
  50. {
  51. ThemePageControl->InvalidateTab(TabIndex);
  52. }
  53. else
  54. {
  55. Parent->Invalidate();
  56. }
  57. }
  58. }
  59. //----------------------------------------------------------------------------------------------------------
  60. //----------------------------------------------------------------------------------------------------------
  61. __fastcall TThemePageControl::TThemePageControl(TComponent * Owner) :
  62. TPageControl(Owner)
  63. {
  64. FOldTabIndex = -1;
  65. }
  66. //----------------------------------------------------------------------------------------------------------
  67. int __fastcall TThemePageControl::GetTabsHeight()
  68. {
  69. // Calculated height includes tab/contents separator line on Windows 7/8,
  70. // but not on Windows XP
  71. TRect Rect = GetClientRect();
  72. ::SendMessage(Handle, TCM_ADJUSTRECT, FALSE, (LPARAM)&Rect);
  73. int Result = Rect.Top - 1;
  74. // Two different ways to calculate the same, not sure which one is more reliable,
  75. // so we want to know in case they differ.
  76. if (DebugAlwaysTrue(PageCount >= 0))
  77. {
  78. TRect Rect = TabRect(0);
  79. int Result2 = Rect.Bottom + 1;
  80. if (Result != Result2)
  81. {
  82. // On Windows 10 with 200% scaling, the first is 40, the second is 42.
  83. // The correct size is probably 41. Will wait for final release before settling on solution.
  84. if (DebugAlwaysTrue(IsWin10() && (Result - 2) == Result2))
  85. {
  86. Result--;
  87. }
  88. else
  89. {
  90. Result = Result2;
  91. }
  92. }
  93. }
  94. return Result;
  95. }
  96. //----------------------------------------------------------------------------------------------------------
  97. void __fastcall TThemePageControl::PaintWindow(HDC DC)
  98. {
  99. // Themes not enabled, give up
  100. if (!UseThemes())
  101. {
  102. TPageControl::PaintWindow(DC);
  103. return;
  104. }
  105. // TODO use GetClipBox
  106. TRect PageRect = GetClientRect();
  107. // 1st paint the tab body
  108. TRect ClientRect = PageRect;
  109. ::SendMessage(Handle, TCM_ADJUSTRECT, FALSE, (LPARAM)&PageRect);
  110. ClientRect.Top = PageRect.Top - 2;
  111. DrawThemesXpTabItem(DC, -1, ClientRect, true, 0);
  112. // 2nd paint the inactive tabs
  113. TPoint Point = ScreenToClient(Mouse->CursorPos);
  114. int HotIndex = IndexOfTabAt(Point.X, Point.Y);
  115. int SelectedIndex = TabIndex;
  116. for (int Tab = 0; Tab < PageCount; Tab++)
  117. {
  118. if (Tab != SelectedIndex)
  119. {
  120. TThemeTabSheet * ThemeTabSheet = dynamic_cast<TThemeTabSheet *>(Pages[Tab]);
  121. bool Shadowed = (ThemeTabSheet != NULL) ? ThemeTabSheet->Shadowed : false;
  122. TRect Rect = TabRect(Tab);
  123. int State = (Tab == HotIndex ? TIS_HOT : (Shadowed ? TIS_DISABLED : TIS_NORMAL));
  124. DrawThemesXpTabItem(DC, Tab, Rect, false, State);
  125. }
  126. }
  127. if (SelectedIndex >= 0)
  128. {
  129. // 3rd paint the active selected tab
  130. TRect Rect = TabRect(SelectedIndex);
  131. Rect.Inflate(2, 2);
  132. Rect.Bottom--;
  133. DrawThemesXpTabItem(DC, SelectedIndex, Rect, false, TIS_SELECTED);
  134. }
  135. }
  136. //----------------------------------------------------------------------------------------------------------
  137. // This function draws Themes Tab control parts: a) Tab-Body and b) Tab-tabs
  138. void __fastcall TThemePageControl::DrawThemesXpTabItem(HDC DC, int Item,
  139. const TRect & Rect, bool Body, int State)
  140. {
  141. TSize Size = Rect.Size;
  142. // Draw background
  143. HDC DCMem = CreateCompatibleDC(DC);
  144. HBITMAP BitmapMem = CreateCompatibleBitmap(DC, Size.Width, Size.Height);
  145. HBITMAP BitmapOld = (HBITMAP)SelectObject(DCMem, BitmapMem);
  146. TRect RectMem(0, 0, Size.Width, Size.Height);
  147. if (!Body && (State == TIS_SELECTED))
  148. {
  149. RectMem.Bottom++;
  150. }
  151. if (Body)
  152. {
  153. DrawThemesPart(DCMem, TABP_PANE, State, IDS_UTIL_TAB, &RectMem);
  154. }
  155. else
  156. {
  157. DrawThemesPart(DCMem, TABP_TABITEM, State, IDS_UTIL_TAB, &RectMem);
  158. }
  159. // Init some extra parameters
  160. BITMAPINFO BitmapInfo;
  161. // Fill local pixel arrays
  162. ZeroMemory(&BitmapInfo, sizeof(BITMAPINFO));
  163. BITMAPINFOHEADER & BitmapInfoHeader = BitmapInfo.bmiHeader;
  164. BitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
  165. BitmapInfoHeader.biCompression = BI_RGB;
  166. BitmapInfoHeader.biPlanes = 1;
  167. // force as RGB: 3 bytes,24 bits -> good for rotating bitmap in any resolution
  168. BitmapInfoHeader.biBitCount = 24;
  169. BitmapInfoHeader.biWidth = Size.Width;
  170. BitmapInfoHeader.biHeight = Size.Height;
  171. if (!Body && (Item >= 0))
  172. {
  173. if ((State == TIS_SELECTED))
  174. {
  175. RectMem.Bottom--;
  176. }
  177. DrawTabItem(DCMem, Item, RectMem, (State == TIS_SELECTED), (State == TIS_DISABLED));
  178. }
  179. // Blit image to the screen
  180. BitBlt(DC, Rect.Left, Rect.Top, Size.Width, Size.Height, DCMem, 0, 0, SRCCOPY);
  181. SelectObject(DCMem, BitmapOld);
  182. DeleteObject(BitmapMem);
  183. DeleteDC(DCMem);
  184. }
  185. //----------------------------------------------------------------------------------------------------------
  186. // draw tab item context: possible icon and text
  187. void __fastcall TThemePageControl::DrawTabItem(HDC DC, int Item, TRect Rect,
  188. bool Selected, bool Shadowed)
  189. {
  190. if (Selected)
  191. {
  192. Rect.Bottom -= 1;
  193. }
  194. else
  195. {
  196. Rect.Bottom += 2;
  197. }
  198. Rect.Left += 6;
  199. Rect.Top += 2 + (Selected ? 1 : 3);
  200. UnicodeString Text = Pages[Item]->Caption;
  201. if ((Images != NULL) && (Pages[Item]->ImageIndex >= 0))
  202. {
  203. int Left;
  204. if (!Text.IsEmpty())
  205. {
  206. Left = Rect.Left + (Selected ? 2 : 0);
  207. }
  208. else
  209. {
  210. Left = (Rect.Right - Images->Width - Rect.Left) / 2;
  211. }
  212. int Y = ((Rect.Top + Rect.Bottom - Images->Height) / 2) - 1 + (Selected ? 0 : -2);
  213. std::unique_ptr<TCanvas> Canvas(new TCanvas());
  214. Canvas->Handle = DC;
  215. Images->Draw(Canvas.get(), Left, Y, Pages[Item]->ImageIndex, !Shadowed);
  216. Rect.Left += Images->Width + 3;
  217. }
  218. else
  219. {
  220. Rect.Left -= 2;
  221. }
  222. int OldMode = SetBkMode(DC, TRANSPARENT);
  223. if (!Text.IsEmpty())
  224. {
  225. HFONT OldFont = (HFONT)SelectObject(DC, Font->Handle);
  226. Rect.Right -= 3;
  227. wchar_t * Buf = new wchar_t[Text.Length() + 1 + 4];
  228. wcscpy(Buf, Text.c_str());
  229. TRect TextRect(0, 0, Rect.Right - Rect.Left, 20);
  230. ::DrawText(DC, Buf, -1, &TextRect, DT_CALCRECT | DT_SINGLELINE | DT_MODIFYSTRING | DT_END_ELLIPSIS);
  231. OffsetRect(&Rect, 0, (Selected ? 0 : -2));
  232. DrawText(DC, Buf, -1, &Rect, DT_NOPREFIX | DT_CENTER);
  233. delete[] Buf;
  234. SelectObject(DC, OldFont);
  235. }
  236. SetBkMode(DC, OldMode);
  237. }
  238. //----------------------------------------------------------------------------------------------------------
  239. void __fastcall TThemePageControl::DrawThemesPart(HDC DC, int PartId,
  240. int StateId, LPCWSTR PartNameID, LPRECT Rect)
  241. {
  242. HTHEME Theme = OpenThemeData(NULL, PartNameID);
  243. if (Theme != 0)
  244. {
  245. DrawThemeBackground(Theme, DC, PartId, StateId, Rect, NULL);
  246. CloseThemeData(Theme);
  247. }
  248. }
  249. //==========================================================================================================
  250. // these two messages are necessary only to properly redraw deselected tab background, because
  251. bool __fastcall TThemePageControl::CanChange()
  252. {
  253. FOldTabIndex = ActivePageIndex;
  254. return TPageControl::CanChange();
  255. }
  256. //----------------------------------------------------------------------------------------------------------
  257. void __fastcall TThemePageControl::InvalidateTab(int Index)
  258. {
  259. if (HandleAllocated())
  260. {
  261. TRect Rect = TabRect(Index);
  262. if (Index == TabIndex)
  263. {
  264. Rect.Inflate(2, 2);
  265. }
  266. // Original code was invalidating range against parent window
  267. // (recalculating coordinates first)
  268. InvalidateRect(Handle, &Rect, true);
  269. }
  270. }
  271. //----------------------------------------------------------------------------------------------------------
  272. void __fastcall TThemePageControl::Change()
  273. {
  274. // note that TabIndex yields correct value already here,
  275. // while ActivePageIndex is not updated yet
  276. if ((FOldTabIndex >= 0) && (FOldTabIndex != TabIndex) && UseThemes())
  277. {
  278. InvalidateTab(FOldTabIndex);
  279. }
  280. TPageControl::Change();
  281. }
  282. //----------------------------------------------------------------------------------------------------------
  283. #ifdef _DEBUG
  284. void __fastcall TThemePageControl::RequestAlign()
  285. {
  286. TPageControl::RequestAlign();
  287. }
  288. #endif
  289. //----------------------------------------------------------------------------------------------------------