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で行う事で対処 ボタンコントールは、スタイルにより以下のテーマを選択する必要あり
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); } |