diff --git a/include/wx/msw/private/dpiaware.h b/include/wx/msw/private/dpiaware.h index 89165d00b4..eb5df72484 100644 --- a/include/wx/msw/private/dpiaware.h +++ b/include/wx/msw/private/dpiaware.h @@ -15,6 +15,8 @@ #endif #include "wx/dynlib.h" +#include "wx/display.h" +#include "wx/sysopt.h" namespace wxMSWImpl { @@ -35,6 +37,9 @@ public: AutoSystemDpiAware() : m_prevContext(WXDPI_AWARENESS_CONTEXT_UNAWARE) { + if ( !Needed() ) + return; + if ( ms_pfnSetThreadDpiAwarenessContext == (SetThreadDpiAwarenessContext_t)-1) { wxLoadedDLL dllUser32("user32.dll"); @@ -62,6 +67,20 @@ public: } } + static bool Needed() + { + // use system-dpi-aware context when: + // - the user did not set an option to force per-monitor context + // - there are displays with different DPI + if ( wxSystemOptions::GetOptionInt("msw.native-dialogs-pmdpi") == 1 ) + return false; + + bool diferentDPI = false; + for ( unsigned i = 1; i < wxDisplay::GetCount() && !diferentDPI; ++i ) + diferentDPI = wxDisplay(0u).GetPPI() != wxDisplay(i).GetPPI(); + return diferentDPI; + } + private: WXDPI_AWARENESS_CONTEXT m_prevContext; diff --git a/interface/wx/sysopt.h b/interface/wx/sysopt.h index c19865d0b8..2fc3bdd2dc 100644 --- a/interface/wx/sysopt.h +++ b/interface/wx/sysopt.h @@ -81,6 +81,12 @@ using it, i.e. this has the same effect as calling wxApp::MSWEnableDarkMode(). If set to 2, use dark mode unconditionally, as if this function were called with wxApp::DarkMode_Always argument. + @flag{msw.native-dialogs-pmdpi} + Some native win32 dialogs (like the font and colour pickers) are not + per-monitor DPI aware, and wxWidgets will forcefully show them as + system DPI aware when there are monitors with different DPI connected. + If set to 1, these dialogs will always be shown as per-monitor DPI + aware (when enabled in the manifest). @endFlagTable diff --git a/src/msw/fontdlg.cpp b/src/msw/fontdlg.cpp index 37ab62ffc1..4d439043e6 100644 --- a/src/msw/fontdlg.cpp +++ b/src/msw/fontdlg.cpp @@ -126,21 +126,22 @@ int wxFontDialog::ShowModal() chooseFontStruct.lpfnHook = wxFontDialogHookProc; } + // The native font dialog does not support moving between displays with + // different DPIs. Check if it will be shown system-dpi-aware. + const bool useSystemDPI = wxMSWImpl::AutoSystemDpiAware::Needed(); + + // When the font dialog is system-dpi-aware, it expects the font at 96DPI/100% scaling. + // When the font dialog is per-monitor-dpi-aware, it expects a font with the system DPI. + const int fontdlgDPI = useSystemDPI ? wxDisplay::GetStdPPIValue() : wxGetDPIofHDC(ScreenHDC()).y; + if ( m_fontData.m_initialFont.IsOk() ) { flags |= CF_INITTOLOGFONTSTRUCT; logFont = m_fontData.m_initialFont.GetNativeFontInfo()->lf; - // The standard dialog seems to always use the default DPI for - // converting LOGFONT height to the value in points shown in the - // dialog (and this happens even when not using AutoSystemDpiAware), - // so we need to convert it to standard (not even system, because the - // dialog doesn't take it into account either) DPI. - logFont.lfHeight = wxNativeFontInfo::GetLogFontHeightAtPPI - ( - m_fontData.m_initialFont.GetFractionalPointSize(), - wxDisplay::GetStdPPIValue() - ); + // Convert the DPI of the font to the DPI of the font dialog. + const double fPointSize = m_fontData.m_initialFont.GetFractionalPointSize(); + logFont.lfHeight = wxNativeFontInfo::GetLogFontHeightAtPPI(fPointSize, fontdlgDPI); } if ( m_fontData.m_fontColour.IsOk() ) @@ -180,33 +181,21 @@ int wxFontDialog::ShowModal() // Don't trust the LOGFONT height returned by the native dialog because // it doesn't use the correct DPI. // - // Note that we must use our parent and not this window itself, as it - // doesn't have any valid HWND and so its DPI can't be determined. - if ( parent ) - { - // We can't just adjust lfHeight directly to the correct DPI here - // as doing this would introduce rounding problems, e.g. 8pt font - // corresponds to lfHeight == 11px and scaling this up for 150% DPI - // would result in 17px height which would then map to 8.5pt at - // 150% DPI and end up being rounded to 9pt, which would be wrong. - // - // So find the point size itself first: - const int pointSize = wxRound(wxNativeFontInfo::GetPointSizeAtPPI - ( - logFont.lfHeight, - wxDisplay::GetStdPPIValue() - )); + // We can't just adjust lfHeight directly to the correct DPI here + // as doing this would introduce rounding problems, e.g. 8pt font + // corresponds to lfHeight == 11px and scaling this up for 150% DPI + // would result in 17px height which would then map to 8.5pt at + // 150% DPI and end up being rounded to 9pt, which would be wrong. + // + // Convert from the DPI of the font dialog to the DPI the + // wxNativeFontInfo constructor will use to determine the font size. + const double fPointSize = wxNativeFontInfo::GetPointSizeAtPPI(logFont.lfHeight, fontdlgDPI); + const int fontDPI = wxGetDPIofHDC(ScreenHDC()).y; + logFont.lfHeight = wxNativeFontInfo::GetLogFontHeightAtPPI(wxRound(fPointSize), fontDPI); - // And then compute the pixel height that results in this point - // size at the actual DPI being used. - logFont.lfHeight = wxNativeFontInfo::GetLogFontHeightAtPPI - ( - pointSize, - parent->GetDPI().y - ); - } - - wxFont f(wxNativeFontInfo(logFont, parent)); + // Use nullptr, so the pointSize calculation in wxNativeFontInfo will + // use the same fontDPI as is used above for lfHeight. + wxFont f(wxNativeFontInfo(logFont, nullptr)); // The native dialog allows selecting only integer font sizes in // points, but converting them to pixel height loses precision and so