Win32アプリでのダークモード対応覚え書き


各ウィンドウのタイトルバーをライト・ダークモードの配色に変更する関数
DwmSetWindowAttribute
        HMODULE ExDwmApi = NULL;
        HRESULT (__stdcall *ExDwmSetWindowAttribute)(HWND hwnd, DWORD dwAttribute, __in_bcount(cbAttribute) LPCVOID pvAttribute, DWORD cbAttribute) = NULL;

        ExDwmApi = LoadLibrary(_T("dwmapi.dll"));
        ExDwmSetWindowAttribute = (HRESULT (__stdcall *)(HWND hwnd, DWORD dwAttribute, __in_bcount(cbAttribute) LPCVOID pvAttribute, DWORD cbAttribute))GetProcAddress(ExDwmApi, "DwmSetWindowAttribute");

        #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
        DWORD dwAttribute = 1;
        ExDwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dwAttribute, sizeof(dwAttribute));
OSのバージョン、マイナー、ビルドを取得する非公開関数
RtlGetNtVersionNumbers
ポップアップメニューをダークモード対応にする非公開関数
AllowDarkModeForApp
各ウィンドウのテーマを設定する関数
SetWindowTheme

AllowDarkModeForApp関数は、便利で唯一ポップアップメニューの配色を変更出来るがOSのビルド番号に依存するのでチェックが必要で将来的に怪しいので注意
        HMODULE ExNtDll = NULL;
        VOID (__stdcall *ExRtlGetNtVersionNumbers)(LPDWORD major, LPDWORD minor, LPDWORD build) = NULL;

        ExNtDll = LoadLibrary(_T("ntdll.dll"));
        ExRtlGetNtVersionNumbers = (VOID (__stdcall *)(LPDWORD major, LPDWORD minor, LPDWORD build))GetProcAddress(ExNtDll, "RtlGetNtVersionNumbers");

        HMODULE ExUxThemeDll = NULL;
        BOOL (__stdcall *AllowDarkModeForApp)(int mode) = NULL;
        HRESULT (__stdcall *ExSetWindowTheme)(HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList) = NULL;

        ExUxThemeDll = LoadLibrary(_T("uxtheme.dll"));
        AllowDarkModeForApp = (BOOL (__stdcall *)(int mode))GetProcAddress(ExUxThemeDll, MAKEINTRESOURCEA(135));
        ExSetWindowTheme = (HRESULT (__stdcall *)(HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList))GetProcAddress(ExUxThemeDll, "SetWindowTheme");

        DWORD major = 0, minor = 0, build = 0;
        ExRtlGetNtVersionNumbers(&major, &minor, &build); build &= 0x0FFFFFFF;
        if ( major == 10 && minor == 0 && build >= 17763 ) {    // // Windows 10 1809 (10.0.17763)
                AllowDarkModeForApp(1);
                bDarkModeSupport = TRUE;
        }
ダークモードのチェックは、ダイレクトにレジストリを見るほうが非公開関数でチェックするよりマシ
        HKEY_CURRENT_USER, "Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme"
メニューバーの描画に関する非公開メッセージ
adzm / win32-custom-menubar-aero-theme

これでメニューバーを自由に描画出来るようになるが非公開なのが怪しい・・・
        #define WM_UAHDRAWMENU         0x0091   // lParam is UAHMENU
        #define WM_UAHDRAWMENUITEM     0x0092   // lParam is UAHDRAWMENUITEM

        ON_MESSAGE(WM_UAHDRAWMENU, OnUahDrawMenu)
        ON_MESSAGE(WM_UAHDRAWMENUITEM, OnUahDrawMenuItem)

LRESULT CMainFrame::OnUahDrawMenu(WPARAM wParam, LPARAM lParam)
{
        UAHMENU *pUahMenu = (UAHMENU *)lParam;
        CDC *pDC = CDC::FromHandle(pUahMenu->hdc);
        MENUBARINFO mbi;
        CRect rect, rcWindow;

        ASSERT(pUahMenu != NULL && pDC != NULL);

        ZeroMemory(&mbi, sizeof(mbi));
        mbi.cbSize = sizeof(mbi);
        GetMenuBarInfo(OBJID_MENU, 0, &mbi);

        GetWindowRect(rcWindow);
        rect = mbi.rcBar;
        rect.OffsetRect(-rcWindow.left, -rcWindow.top);

        pDC->FillSolidRect(rect, GetAppColor(APPCOL_MENUFACE));

        return TRUE;
}
LRESULT CMainFrame::OnUahDrawMenuItem(WPARAM wParam, LPARAM lParam)
{
        UAHDRAWMENUITEM *pUahDrawMenuItem = (UAHDRAWMENUITEM *)lParam;
        CMenu *pMenu = CMenu::FromHandle(pUahDrawMenuItem->um.hmenu);
        int npos = pUahDrawMenuItem->umi.iPosition;
        UINT state = pUahDrawMenuItem->dis.itemState;
        CDC *pDC = CDC::FromHandle(pUahDrawMenuItem->dis.hDC);
        CRect rect = pUahDrawMenuItem->dis.rcItem;
        DWORD dwFlags = DT_SINGLELINE | DT_VCENTER | DT_CENTER;
        CString title;
        COLORREF TextColor = GetAppColor(APPCOL_MENUTEXT);
        COLORREF BackColor = GetAppColor(APPCOL_MENUFACE);
        int OldBkMode = pDC->SetBkMode(TRANSPARENT);

        ASSERT(pUahDrawMenuItem != NULL && pDC != NULL && pMenu != NULL);
        
        pMenu->GetMenuString(npos, title, MF_BYPOSITION);

        if ( (state & ODS_NOACCEL) != 0 )
                dwFlags |= DT_HIDEPREFIX;

        if ( (state & (ODS_INACTIVE | ODS_GRAYED | ODS_DISABLED)) != 0 )
                TextColor = GetAppColor(COLOR_GRAYTEXT);

        if ( (state & (ODS_HOTLIGHT | ODS_SELECTED)) != 0 )
                BackColor = GetAppColor(APPCOL_MENUHIGH);

        TextColor = pDC->SetTextColor(TextColor);
        pDC->FillSolidRect(rect, BackColor);
        pDC->DrawText(title, rect, dwFlags);

        pDC->SetTextColor(TextColor);
        pDC->SetBkMode(OldBkMode);

        return TRUE;
}
ダイアログの配色は、基本WM_CTLCOLORで処理出来るがコントールの色設定が出来ない
        ON_WM_CTLCOLOR()

HBRUSH CDialogExt::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
        HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

        switch(nCtlColor) {
        case CTLCOLOR_MSGBOX:           // Message box
        case CTLCOLOR_EDIT:             // Edit control
        case CTLCOLOR_LISTBOX:          // List-box control
        case CTLCOLOR_BTN:              // Button control
                break;

        case CTLCOLOR_DLG:              // Dialog box
        case CTLCOLOR_SCROLLBAR:
        case CTLCOLOR_STATIC:           // Static control
                hbr = GetAppColorBrush(APPCOL_DLGFACE);
                pDC->SetTextColor(GetAppColor(APPCOL_DLGTEXT));
                pDC->SetBkMode(TRANSPARENT);
                break;
        }

        return hbr;
}
各コントロール別にテーマの変更をWM_SETTINGCHANGEで行う事で対処
ボタンコントールは、スタイルにより以下のテーマを選択する必要あり
SetWindowTheme(hWnd, L"Explorer", NULL); ライトモードに設定 コントロールの種類によりかなり違うので注意
Button,Edit,ScrollBar,SysListView32,
SysTreeView32などは、変化するようです
SetWindowTheme(hWnd, L"DarkMode_Explorer", NULL); ダークモードに設定
SetWindowTheme(hWnd, NULL, NULL); デフォルトに戻す
SetWindowTheme(hWnd, L"", L""); テーマ設定を解除 デザインがかなり古いがWM_CTLCOLORの
設定が有効になるようです
        ON_WM_SETTINGCHANGE()

static BOOL CALLBACK EnumSetThemeProc(HWND hWnd , LPARAM lParam)
{
        CWnd *pWnd = CWnd::FromHandle(hWnd);
        TCHAR name[256];

        GetClassName(hWnd, name, 256);

        if ( _tcscmp(name, _T("Button")) == 0 ) {
                if ( (pWnd->GetStyle() & BS_TYPEMASK) <= BS_DEFPUSHBUTTON )
                        ExSetWindowTheme(hWnd, (bDarkMode ? L"DarkMode_Explorer" : L"Explorer"), NULL);
                else if ( pParent->m_bDarkMode )
                        ExSetWindowTheme(hWnd, L"", L"");
                else
                        ExSetWindowTheme(hWnd, NULL, NULL);
                SendMessageW(hWnd, WM_THEMECHANGED, 0, 0);
        }

        return TRUE;
}
void CDialogExt::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
        if ( lpszSection != NULL && _tcscmp(lpszSection, _T("ImmersiveColorSet")) == 0 ) {
                EnumChildWindows(GetSafeHwnd(), EnumSetThemeProc, (LPARAM)this);
                RedrawWindow(NULL, NULL, RDW_ALLCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW | RDW_FRAME | RDW_ERASE);
        }

        CDialog::OnSettingChange(uFlags, lpszSection);
}