void UIWizardPreferences::InitDarkMode() {
// There is a bug when using dark mode and picture control is icon.
// In dark mode the icon size is doubled, in light mode the icon has original size.
// This is a workaround to ignore applying dark mode control to the icon picture control.
// m_dark.AddDialogWithControls(*this); // Problematic
// m_dark.AddControls(*this); // Problematic
// m_dark.AddDialog(*this); // OK
m_dark.AddDialog(*this);
auto hWndChild = GetWindow(GW_CHILD);
auto hWndIcon = GetDlgItem(IDC_CUSTOM_ICON_IMG);
while (hWndChild) {
if (hWndChild != hWndIcon) {
m_dark.AddCtrlAuto(hWndChild);
}
hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT);
}
}
void UIWizardPreferences::InitCustomIcon() {
UIWHGraphics::SetIcon(m_hWndMain, m_hWnd, UIWizardSettings::customIcon, UIWizardSettings::customIconPath.c_str());
UIWHDialog::SetCheckBox(m_hWnd, IDC_CUSTOM_ICON, UIWizardSettings::customIcon);
auto customIconPath = std::wstring(pfc::stringcvt::string_wide_from_utf8(UIWizardSettings::customIconPath.c_str()).get_ptr());
UIWHDialog::SetInputFieldText(m_hWnd, IDC_CUSTOM_ICON_PATH, customIconPath);
UIWHDialog::SetCheckBox(m_hWnd, IDC_TASKBAR_ICON, UIWizardSettings::hideTaskbarIcon);
}
BOOL UIWizardPreferences::OnInitDialog(CWindow, LPARAM) {
m_initDialog = true;
InitDarkMode();
InitCustomIcon();
// other methods...
m_initDialog = false;
return TRUE;
}
And here are my helpers used in the code snippets:
#include <algorithm>
#include <format>
#include <map>
#include <optional>
////////////////////////
// * DIALOG HELPERS * //
////////////////////////
namespace UIWHDialog {
bool CreateCustomFont(CFont& font, int height, int weight, const TCHAR* faceName) {
LOGFONT lf = { 0 };
lf.lfHeight = height;
lf.lfWeight = weight;
_tcscpy_s(lf.lfFaceName, LF_FACESIZE, faceName);
if (!font.CreateFontIndirect(&lf)) { // Fallback to system font
_tcscpy_s(lf.lfFaceName, LF_FACESIZE, _T("MS Shell Dlg"));
HDC hdc = GetDC(nullptr);
lf.lfHeight = -MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72);
ReleaseDC(nullptr, hdc);
return font.CreateFontIndirect(&lf);
}
return true;
}
bool GetCheckBoxState(HWND hWnd, int id) {
return IsDlgButtonChecked(hWnd, id) == BST_CHECKED;
}
void SetCheckBox(HWND hWnd, int id, bool checked) {
CheckDlgButton(hWnd, id, checked ? BST_CHECKED : BST_UNCHECKED);
}
void SetControlEnableState(HWND hWnd, const std::vector<int>& controlIDs, bool enabled) {
for (int id : controlIDs) {
::EnableWindow(GetDlgItem(hWnd, id), enabled);
}
}
int GetDropDownIndex(HWND hWnd, int id) {
LRESULT selectedIndex = SendDlgItemMessage(hWnd, id, CB_GETCURSEL, 0, 0);
return selectedIndex == CB_ERR ? -1 : static_cast<int>(selectedIndex);
}
void SetDropDownMenu(HWND hWnd, int id, const std::vector<std::wstring>& items, int selectedItem) {
SendDlgItemMessage(hWnd, id, CB_RESETCONTENT, 0, 0);
for (const auto& item : items) {
SendDlgItemMessage(hWnd, id, CB_ADDSTRING, 0, (LPARAM)item.c_str());
}
SendDlgItemMessage(hWnd, id, CB_SETCURSEL, (WPARAM)selectedItem, 0);
}
int GetInputFieldNumber(HWND hWnd, int id, BOOL* pSuccess, BOOL signedValue) {
return GetDlgItemInt(hWnd, id, pSuccess, signedValue);
}
void SetInputFieldNumber(HWND hWnd, int id, int value, BOOL signedValue) {
SetDlgItemInt(hWnd, id, static_cast<UINT>(value), signedValue);
}
pfc::string8 GetInputFieldText(HWND hWnd, int id) {
int length = ::GetWindowTextLength(GetDlgItem(hWnd, id));
if (length <= 0) return pfc::string8();
std::wstring tempStr(static_cast<size_t>(length) + 1, L'\0');
GetDlgItemTextW(hWnd, id, &tempStr[0], length + 1);
tempStr.resize(length);
return pfc::stringcvt::string_utf8_from_wide(tempStr.c_str()).get_ptr();
}
void SetInputFieldText(HWND hWnd, int id, const std::wstring& text) {
SetDlgItemText(hWnd, id, text.c_str());
}
void SetSpinControlRange(HWND hWnd, int id, int minVal, int maxVal) {
SendDlgItemMessage(hWnd, id, UDM_SETRANGE32, static_cast<WPARAM>(minVal), static_cast<LPARAM>(maxVal));
}
LRESULT CALLBACK SpinControlProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch (message) { // Only need to subclass the spin controls because of flicker issues, do double buffering here
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = ::BeginPaint(hWnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbm = CreateCompatibleBitmap(hdc, ps.rcPaint.right, ps.rcPaint.bottom);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hdcMem, hbm);
DefSubclassProc(hWnd, WM_ERASEBKGND, (WPARAM)hdcMem, 0);
DefSubclassProc(hWnd, WM_PRINTCLIENT, (WPARAM)hdcMem, PRF_CLIENT);
BitBlt(hdc, 0, 0, ps.rcPaint.right, ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hOldBmp);
DeleteObject(hbm);
DeleteDC(hdcMem);
::EndPaint(hWnd, &ps);
return 0;
}
case WM_ERASEBKGND: {
return 1;
}
case WM_NCDESTROY: {
RemoveWindowSubclass(hWnd, SpinControlProc, uIdSubclass);
break;
}
default: {
return DefSubclassProc(hWnd, message, wParam, lParam);
}
}
return DefSubclassProc(hWnd, message, wParam, lParam);
}
void SpinControlSubclass(HWND hWnd) {
SetWindowSubclass(hWnd, SpinControlProc, 0, 0);
}
}
//////////////////////////
// * GRAPHICS HELPERS * //
//////////////////////////
namespace UIWHGraphics {
HICON GetCustomIcon(const std::string& path) {
if (path.empty()) {
return nullptr;
}
pfc::stringcvt::string_wide_from_utf8 widePath(path.c_str());
return static_cast<HICON>(LoadImage(
nullptr, widePath.get_ptr(), IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
LR_LOADFROMFILE | LR_SHARED
));
}
HICON GetDefaultIcon() {
DWORD copied = 0;
DWORD bufferSize = MAX_PATH;
std::vector<wchar_t> fb2kExePath(bufferSize, L'\0');
while (true) {
copied = GetModuleFileNameW(nullptr, fb2kExePath.data(), bufferSize);
if (copied < bufferSize - 1 || copied == 0) break;
bufferSize *= 2;
fb2kExePath.resize(bufferSize);
}
if (copied == 0) return nullptr;
HICON hIcon = nullptr;
if (ExtractIconExW(fb2kExePath.data(), 0, nullptr, &hIcon, 1) > 0 && hIcon) {
return hIcon;
}
return nullptr;
}
void SetIcon(HWND hWndMain, HWND hWndDialog, bool customIcon, const std::string& customIconPath) {
if (!hWndMain && !hWndDialog) return;
HICON hIcon = customIcon && !customIconPath.empty() ? GetCustomIcon(customIconPath) : GetDefaultIcon();
if (hWndMain) {
SetClassLongPtr(hWndMain, GCLP_HICON, reinterpret_cast<LONG_PTR>(hIcon));
}
if (hWndDialog) {
SendDlgItemMessage(hWndDialog, IDC_CUSTOM_ICON_IMG, STM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
pfc::stringcvt::string_wide_from_utf8 widePath(customIconPath.c_str());
SetDlgItemText(hWndDialog, IDC_CUSTOM_ICON_PATH, widePath.get_ptr());
}
}
void WindowRepaint(HWND hWnd) {
RedrawWindow(hWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW | RDW_ALLCHILDREN);
}
}
Minimal resource.rc:
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_PREFERENCES DIALOGEX 0, 0, 332, 288
STYLE DS_SETFONT | WS_CHILD | WS_CLIPCHILDREN
FONT 8, "Tahoma", 0, 0, 0x1
BEGIN
CONTROL " Custom icon",IDC_CUSTOM_ICON,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,202,57,10
EDITTEXT IDC_CUSTOM_ICON_PATH,6,217,107,12,ES_AUTOHSCROLL | ES_READONLY
PUSHBUTTON "...",IDC_CUSTOM_ICON_BUTTON,122,217,17,12,0,WS_EX_ACCEPTFILES
ICON "", IDC_CUSTOM_ICON_IMG, 145, 218, 16, 16
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_PREFERENCES, DIALOG
BEGIN
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//
IDD_PREFERENCES AFX_DIALOG_LAYOUT
BEGIN
0
END
/////////////////////////////////////////////////////////////////////////////
//
// TYPELIB
//
1 TYPELIB "MyCOM.tlb"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED