2929#include < vsstyle.h>
3030
3131#include < array>
32+ #include < cstdint>
3233#include < string>
3334
3435#include " DmlibColor.h"
@@ -142,13 +143,24 @@ int DarkMode::getLibInfo(int libInfoType)
142143 * - `light`: Follow system mode; apply light theme when system is in light mode.
143144 * - `classic`: Follow system mode; apply classic style when system is in light mode.
144145 */
145- enum class WinMode : unsigned char
146+ enum class WinMode : std:: uint8_t
146147{
147148 disabled, // /< Manual - system mode is ignored.
148149 light, // /< Use light theme if system is in light mode.
149150 classic // /< Use classic style if system is in light mode.
150151};
151152
153+ // / Types of list and tree views checkbox styles
154+ enum class ViewCheckbox : std::uint8_t
155+ {
156+ listView, // /< List view with LVS_EX_CHECKBOXES extendend style.
157+ tvSimple, // /< Tree view with TVS_CHECKBOXES style.
158+
159+ // / Tree view with any combination of TVS_EX_PARTIALCHECKBOXES, TVS_EX_EXCLUSIONCHECKBOXES,
160+ // / TVS_EX_DIMMEDCHECKBOXES extendend styles.
161+ tvExtended
162+ };
163+
152164/* *
153165 * @struct DarkModeParams
154166 * @brief Defines theming and subclassing parameters for child controls.
@@ -882,8 +894,9 @@ DWORD DarkMode::getWindowsBuildNumber()
882894 return dmlib_win32api::GetWindowsBuildNumber ();
883895}
884896
885- // / Check if OS is at leaast Windows 11 version 25H2 build 26200.6899.
886- static bool isAtLeastWin11Ver25H2 () noexcept
897+ // / Check if OS is Windows 11 version 24H2 build 26100 rev 6899+,
898+ // / Windows 11 version 25H2 build 26200 rev 6899+, or later builds.
899+ static bool doesWin11SupportDarkThemeStyle () noexcept
887900{
888901 static const DWORD win11Revision = []()
889902 {
@@ -899,10 +912,12 @@ static bool isAtLeastWin11Ver25H2() noexcept
899912 return 0UL ;
900913 }();
901914
915+ static constexpr DWORD win11Build24H2 = 26100 ;
902916 static constexpr DWORD win11Build25H2 = 26200 ;
903- static constexpr DWORD minWin11Build25H2Revision = 6899 ;
904- return (dmlib_win32api::GetWindowsBuildNumber () == win11Build25H2 && win11Revision >= minWin11Build25H2Revision)
905- || dmlib_win32api::GetWindowsBuildNumber () > win11Build25H2;
917+ static constexpr DWORD minWin11Revision = 6899 ;
918+ return dmlib_win32api::GetWindowsBuildNumber () > win11Build25H2
919+ || (dmlib_win32api::GetWindowsBuildNumber () == win11Build25H2 && win11Revision >= minWin11Revision)
920+ || (dmlib_win32api::GetWindowsBuildNumber () == win11Build24H2 && win11Revision >= minWin11Revision);
906921}
907922
908923/* *
@@ -1307,7 +1322,7 @@ static void setTabCtrlSubclassAndTheme(HWND hWnd, DarkModeParams p) noexcept
13071322 if (p.m_theme )
13081323 {
13091324 DarkMode::setDarkTooltips (hWnd, static_cast <int >(DarkMode::ToolTipsType::tabbar));
1310- if (isAtLeastWin11Ver25H2 () && dmlib_subclass::isThemePrefered ())
1325+ if (doesWin11SupportDarkThemeStyle () && dmlib_subclass::isThemePrefered ())
13111326 {
13121327 DarkMode::setDarkThemeTheme (hWnd);
13131328 }
@@ -1376,7 +1391,7 @@ static void setCustomBorderForListBoxOrEditCtrlSubclassAndTheme(HWND hWnd, DarkM
13761391 && !isListBox
13771392 && !hasScrollBar)
13781393 {
1379- if (isAtLeastWin11Ver25H2 ())
1394+ if (doesWin11SupportDarkThemeStyle ())
13801395 {
13811396 DarkMode::setDarkThemeTheme (hWnd);
13821397 }
@@ -1510,7 +1525,7 @@ static void setComboBoxCtrlSubclassAndTheme(HWND hWnd, DarkModeParams p)
15101525
15111526 if (p.m_theme ) // for light dropdown arrow in dark mode
15121527 {
1513- if (isAtLeastWin11Ver25H2 ())
1528+ if (doesWin11SupportDarkThemeStyle ())
15141529 {
15151530 DarkMode::setDarkThemeTheme (hWnd);
15161531 }
@@ -1640,7 +1655,7 @@ static void setListViewCtrlSubclassAndTheme(HWND hWnd, DarkModeParams p) noexcep
16401655
16411656 if (dmlib_subclass::isThemePrefered ())
16421657 {
1643- if (isAtLeastWin11Ver25H2 ())
1658+ if (doesWin11SupportDarkThemeStyle ())
16441659 {
16451660 DarkMode::setDarkThemeTheme (hHeader);
16461661 }
@@ -1746,7 +1761,7 @@ static void setStatusBarCtrlSubclass(HWND hWnd, DarkModeParams p) noexcept
17461761{
17471762 if (p.m_theme
17481763 && dmlib_subclass::isThemePrefered ()
1749- && isAtLeastWin11Ver25H2 ())
1764+ && doesWin11SupportDarkThemeStyle ())
17501765 {
17511766 DarkMode::setDarkThemeTheme (hWnd);
17521767 }
@@ -1814,7 +1829,7 @@ static void setProgressBarCtrlSubclass(HWND hWnd, DarkModeParams p) noexcept
18141829 {
18151830 DarkMode::setProgressBarClassicTheme (hWnd);
18161831 }
1817- else if (isAtLeastWin11Ver25H2 ())
1832+ else if (doesWin11SupportDarkThemeStyle ())
18181833 {
18191834 ::SetWindowTheme (hWnd, DarkMode::isExperimentalActive() ? L"DarkMode_CopyEngine" : nullptr, nullptr);
18201835 }
@@ -1993,6 +2008,7 @@ static void setHotKeyCtrlSubclass(HWND hWnd, DarkModeParams p) noexcept
19932008 * @param[in] hWnd Handle to the tree view control.
19942009 * @param[in] p Parameters controlling whether to apply theming.
19952010 *
2011+ * @see DarkMode::setDarkTreeViewCheckboxes(hWnd);
19962012 * @see DarkMode::setTreeViewWindowTheme()
19972013 * @see DarkMode::setDarkTooltips()
19982014 */
@@ -2004,6 +2020,7 @@ static void setTreeViewCtrlTheme(HWND hWnd, DarkModeParams p) noexcept
20042020 TreeView_SetBkColor (hWnd, DarkMode::getViewBackgroundColor ());
20052021
20062022 DarkMode::setTreeViewWindowThemeEx (hWnd, p.m_theme );
2023+ DarkMode::setDarkTreeViewCheckboxes (hWnd);
20072024 DarkMode::setDarkTooltips (hWnd, static_cast <int >(DarkMode::ToolTipsType::treeview));
20082025 }
20092026}
@@ -2654,10 +2671,12 @@ void DarkMode::setDarkTitleBar(HWND hWnd)
26542671 * @return "DarkMode_DarkTheme" on Windows 11 25H2+
26552672 * @return "DarkMode_Explorer" on Windows 10+
26562673 * @return nullptr if not on supported OS
2674+ *
2675+ * @see doesWin11SupportDarkThemeStyle()
26572676 */
26582677const wchar_t * DarkMode::getDarkModeThemeName ()
26592678{
2660- if (isAtLeastWin11Ver25H2 ())
2679+ if (doesWin11SupportDarkThemeStyle ())
26612680 {
26622681 return L" DarkMode_DarkTheme" ;
26632682 }
@@ -2816,10 +2835,12 @@ void DarkMode::setDarkTooltips(HWND hWnd, int tooltipType)
28162835/* *
28172836 * @brief Applies "DarkMode_DarkTheme" visual style if supported and experimental mode is active.
28182837 *
2819- * If OS is at least Windows 11 version 25H2 applies "DarkMode_DarkTheme" visual style,
2838+ * Applies "DarkMode_DarkTheme" visual style if supported ,
28202839 * else applies "DarkMode_Explorer" visual style.
28212840 *
28222841 * @param[in] hWnd Handle to the control or window to theme.
2842+ *
2843+ * @see DarkMode::getDarkModeThemeName()
28232844 */
28242845void DarkMode::setDarkThemeTheme (HWND hWnd)
28252846{
@@ -2869,33 +2890,26 @@ void DarkMode::setDarkListView(HWND hWnd)
28692890 DarkMode::setDarkThemeExperimental (hWnd);
28702891}
28712892
2893+
2894+
28722895/* *
2873- * @brief Replaces default list view checkboxes with themed dark-mode versions on Windows 11.
2874- *
2875- * If the list view uses `LVS_EX_CHECKBOXES` and is running on Windows 11 or later,
2876- * this function manually draws the unchecked and checked checkbox visuals using
2877- * themed drawing APIs, then inserts the resulting icons into the state image list.
2896+ * @brief Replaces list view or tree view image list checkbox state images with themed dark mode versions on Windows 11.
28782897 *
28792898 * Uses `"DarkMode_Explorer::Button"` as the theme class if experimental dark mode is active;
28802899 * otherwise falls back to `VSCLASS_BUTTON`.
28812900 *
2882- * @param[in] hWnd Handle to the list view control with extended checkbox style.
2901+ * @param[in] hWnd Handle to the control to change checkbox style.
2902+ * @param[in] hImgList Handle to the image list of control containing checkbox state images.
28832903 *
2884- * @note Does nothing on pre-Windows 11 systems or if checkboxes are not enabled .
2904+ * @note Does nothing on pre-Windows 11 systems.
28852905 */
2886- void DarkMode::setDarkListViewCheckboxes (HWND hWnd)
2906+ static void setDarkCheckboxes (HWND hWnd, HIMAGELIST hImgList, ViewCheckbox viewCheckbox )
28872907{
28882908 if (!DarkMode::isAtLeastWindows11 ())
28892909 {
28902910 return ;
28912911 }
28922912
2893- if (const auto lvExStyle = ListView_GetExtendedListViewStyle (hWnd);
2894- (lvExStyle & LVS_EX_CHECKBOXES ) != LVS_EX_CHECKBOXES )
2895- {
2896- return ;
2897- }
2898-
28992913 HDC hdc = ::GetDC (nullptr );
29002914
29012915 const bool useDark = DarkMode::isExperimentalActive () && DarkMode::isThemeDark ();
@@ -2906,14 +2920,12 @@ void DarkMode::setDarkListViewCheckboxes(HWND hWnd)
29062920
29072921 const RECT rcBox{ 0 , 0 , szBox.cx , szBox.cy };
29082922
2909- auto hImgList = ListView_GetImageList (hWnd, LVSIL_STATE );
29102923 if (hImgList == nullptr )
29112924 {
29122925 ::CloseThemeData (hTheme);
29132926 ::ReleaseDC (nullptr , hdc);
29142927 return ;
29152928 }
2916- ::ImageList_RemoveAll (hImgList);
29172929
29182930 HDC hBoxDC = ::CreateCompatibleDC (hdc);
29192931 HBITMAP hBoxBmp = ::CreateCompatibleBitmap (hdc, szBox.cx , szBox.cy );
@@ -2927,23 +2939,55 @@ void DarkMode::setDarkListViewCheckboxes(HWND hWnd)
29272939 ii.hbmColor = hBoxBmp;
29282940 ii.hbmMask = hMaskBmp;
29292941
2942+ int idx = (viewCheckbox == ViewCheckbox::listView) ? 0 : 1 ; // tree view state images start from index 1
2943+
29302944 HICON hIcon = ::CreateIconIndirect (&ii);
2931- if (hIcon != nullptr )
2945+
2946+ auto addIcon = [&hImgList, &idx, &hIcon]()
29322947 {
2933- ::ImageList_AddIcon (hImgList, hIcon);
2934- ::DestroyIcon (hIcon);
2935- hIcon = nullptr ;
2936- }
2948+ if (hIcon != nullptr )
2949+ {
2950+ ::ImageList_ReplaceIcon (hImgList, idx, hIcon);
2951+ ::DestroyIcon (hIcon);
2952+ hIcon = nullptr ;
2953+ ++idx;
2954+ }
2955+ };
29372956
2938- ::DrawThemeBackground (hTheme, hBoxDC, BP_CHECKBOX , CBS_CHECKEDNORMAL , &rcBox, nullptr );
2939- ii.hbmColor = hBoxBmp;
2957+ addIcon (); // unchecked state
29402958
2941- hIcon = ::CreateIconIndirect (&ii);
2942- if (hIcon != nullptr )
2959+ auto addIconState = [&](int iStateId)
29432960 {
2944- ::ImageList_AddIcon (hImgList, hIcon);
2945- ::DestroyIcon (hIcon);
2946- hIcon = nullptr ;
2961+ ::DrawThemeBackground (hTheme, hBoxDC, BP_CHECKBOX , iStateId, &rcBox, nullptr );
2962+ ii.hbmColor = hBoxBmp;
2963+
2964+ hIcon = ::CreateIconIndirect (&ii);
2965+ addIcon ();
2966+ };
2967+
2968+ addIconState (CBS_CHECKEDNORMAL );
2969+
2970+ if (viewCheckbox == ViewCheckbox::tvExtended)
2971+ {
2972+ const auto tvExStyle = TreeView_GetExtendedStyle (hWnd);
2973+
2974+ // Tree view check boxes: The extended check box states
2975+ // https://devblogs.microsoft.com/oldnewthing/20171205-00/?p=97525
2976+ // The image list states are added in the order: Partial, then dimmed, then exclusion.
2977+ if ((tvExStyle & TVS_EX_PARTIALCHECKBOXES ) != 0 )
2978+ {
2979+ addIconState (CBS_MIXEDNORMAL );
2980+ }
2981+
2982+ if ((tvExStyle & TVS_EX_DIMMEDCHECKBOXES ) != 0 )
2983+ {
2984+ addIconState (CBS_IMPLICITPRESSED ); // make it different from CBS_CHECKEDNORMAL
2985+ }
2986+
2987+ if ((tvExStyle & TVS_EX_EXCLUSIONCHECKBOXES ) != 0 )
2988+ {
2989+ addIconState (CBS_EXCLUDEDNORMAL );
2990+ }
29472991 }
29482992
29492993 ::SelectObject (hBoxDC, holdBmp);
@@ -2954,6 +2998,71 @@ void DarkMode::setDarkListViewCheckboxes(HWND hWnd)
29542998 ::ReleaseDC (nullptr , hdc);
29552999}
29563000
3001+ /* *
3002+ * @brief Replaces default list view checkboxes with themed dark-mode versions on Windows 11.
3003+ *
3004+ * If the list view uses `LVS_EX_CHECKBOXES` and is running on Windows 11 or later,
3005+ * this function then manually draws the unchecked and checked checkbox visuals using
3006+ * themed drawing APIs, then inserts the resulting icons into the state image list.
3007+ *
3008+ * Uses `"DarkMode_Explorer::Button"` as the theme class if experimental dark mode is active;
3009+ * otherwise falls back to `VSCLASS_BUTTON`.
3010+ *
3011+ * @param[in] hWnd Handle to the list view control with extended checkbox style.
3012+ *
3013+ * @see setDarkCheckboxes()
3014+ *
3015+ * @note Does nothing on pre-Windows 11 systems or if checkboxes are not enabled.
3016+ */
3017+ void DarkMode::setDarkListViewCheckboxes (HWND hWnd)
3018+ {
3019+ if (const auto lvExStyle = ListView_GetExtendedListViewStyle (hWnd);
3020+ (lvExStyle & LVS_EX_CHECKBOXES ) != LVS_EX_CHECKBOXES )
3021+ {
3022+ return ;
3023+ }
3024+
3025+ setDarkCheckboxes (hWnd, ListView_GetImageList (hWnd, LVSIL_STATE ), ViewCheckbox::listView);
3026+ }
3027+
3028+ /* *
3029+ * @brief Replaces default tree view checkboxes with themed dark-mode versions on Windows 11.
3030+ *
3031+ * If the tree view uses `TVS_CHECKBOXES` or any combination of `TVS_EX_PARTIALCHECKBOXES`,
3032+ * `TVS_EX_EXCLUSIONCHECKBOXES`, `TVS_EX_DIMMEDCHECKBOXES` extended styles and is running on
3033+ * Windows 11 or later, this function then manually draws the checkbox state visuals using
3034+ * themed drawing APIs, then inserts the resulting icons into the state image list.
3035+ *
3036+ * Uses `"DarkMode_Explorer::Button"` as the theme class if experimental dark mode is active;
3037+ * otherwise falls back to `VSCLASS_BUTTON`.
3038+ *
3039+ * @param[in] hWnd Handle to the tree view control with normal or extended checkbox style.
3040+ *
3041+ * @see setDarkCheckboxes()
3042+ *
3043+ * @note Does nothing on pre-Windows 11 systems or if checkboxes are not enabled.
3044+ */
3045+ void DarkMode::setDarkTreeViewCheckboxes (HWND hWnd)
3046+ {
3047+ ViewCheckbox tvType = ViewCheckbox::tvSimple;
3048+ if (const auto tvStyle = ::GetWindowLongPtr (hWnd, GWL_STYLE );
3049+ (tvStyle & TVS_CHECKBOXES ) == TVS_CHECKBOXES )
3050+ {
3051+ // tvType = ViewCheckbox::tvSimple;
3052+ }
3053+ else if (const auto tvExStyle = TreeView_GetExtendedStyle (hWnd);
3054+ (tvExStyle & (TVS_EX_PARTIALCHECKBOXES | TVS_EX_EXCLUSIONCHECKBOXES | TVS_EX_DIMMEDCHECKBOXES )) != 0 )
3055+ {
3056+ tvType = ViewCheckbox::tvExtended;
3057+ }
3058+ else
3059+ {
3060+ return ;
3061+ }
3062+
3063+ setDarkCheckboxes (hWnd, TreeView_GetImageList (hWnd, TVSIL_STATE ), tvType);
3064+ }
3065+
29573066/* *
29583067 * @brief Sets colors and edges for a RichEdit control.
29593068 *
@@ -3525,7 +3634,7 @@ void DarkMode::setProgressBarClassicTheme(HWND hWnd)
35253634 static constexpr COLORREF greenLight = dmlib_color::HEXRGB (0x06B025 );
35263635 static constexpr COLORREF greenDark = dmlib_color::HEXRGB (0x0F7B0F );
35273636 static constexpr COLORREF azureDark = dmlib_color::HEXRGB (0x60CDFF );
3528- static const auto clrDark = isAtLeastWin11Ver25H2 () ? azureDark : greenDark;
3637+ static const auto clrDark = doesWin11SupportDarkThemeStyle () ? azureDark : greenDark;
35293638 ::SendMessage (hWnd, PBM_SETBARCOLOR , 0 , static_cast <LPARAM >(DarkMode::isExperimentalActive() ? clrDark : greenLight));
35303639 }
35313640}
0 commit comments