[Git][wxwidgets/wxwidgets][master] 11 commits: Save DPI in wxPersistentTLW to handle DPI change correctly

5 views
Skip to first unread message

Vadim Zeitlin (@_VZ_)

unread,
Sep 15, 2026, 7:14:51 PMSep 15
to wx-commi...@googlegroups.com

Vadim Zeitlin pushed to branch master at wxWidgets / wxWidgets

Commits:

  • 3cdb10d4
    by Vadim Zeitlin at 2026-09-14T02:39:45+02:00
    Save DPI in wxPersistentTLW to handle DPI change correctly
    
    Add support at wxPersistentWindow level for saving the DPI at which the
    window coordinates were saved to allow rescaling the values by the ratio
    of this DPI to the current one if the DPI has changed between saving and
    restoring.
    
    This allows keeping the same proportional size of the window after DPI
    change on the platforms not using DPI-independent pixels (such as wxMSW)
    too by just using the new {Save,Restore}Coord() functions instead of
    {Save,Restore}Value() for any quantities that need to be scaled.
    
    Do this for wxPersistentTLW to update the window size to remain
    proportionally the same after DPI change.
    
  • 2a992cfa
    by Vadim Zeitlin at 2026-09-14T02:39:45+02:00
    Factor out SavePersistenceTestDVC() in wxPersistDVC unit test
    
    Make the code creating a wxDVC and saving it available in a reusable
    function to allow calling it from another place in the upcoming commit.
    
    No real changes, this is a pure refactoring.
    
  • c2d78e95
    by Vadim Zeitlin at 2026-09-14T14:03:23+02:00
    Account for DPI in wxPersistentDataViewCtrl too
    
    Just use {Save,Restore}Coord() to restore the columns widths instead of
    {Save,Restore}Value() to rescale the saved values if necessary.
    
    Add a unit test checking that this works as expected.
    
  • a02e4dac
    by Vadim Zeitlin at 2026-09-14T14:03:23+02:00
    Fix restoring wrong last split position in wxPersistentSplitter
    
    This was broken since f24b3d5483 (Save last wxSplitterWindow position
    before it was unsplit, 2024-02-12) which accidentally used the wrong
    lastSplitPos component when restoring.
    
  • 8a786560
    by Vadim Zeitlin at 2026-09-14T14:03:23+02:00
    Account for DPI in wxPersistentSplitter too
    
    Rescale the saved splitter position if the DPI has changed when
    restoring it by using {Save,Restore}Coord().
    
  • 5a2a673d
    by Vadim Zeitlin at 2026-09-14T14:03:23+02:00
    Fix updating saved splitter position on DPI change
    
    While the current and minimum positions were updated, the saved last
    split position was not, so unsplitting the splitter, changing the DPI
    and splitting it again wouldn't use the correct position for the new
    DPI.
    
    Also rescale m_requestedSashPosition if it's valid even if it's not
    clear whether this can happen or not.
    
  • 9d2352e1
    by Steve Cornett at 2026-09-14T14:04:22+02:00
    Fix checkbox drawing for empty label in wxMSW dark mode
    
    For a checkbox with an empty label, do not draw the focus rectangle with
    size 0x0.
    
    Fixes #26778.
    
    Closes #27003.
    
  • b425edc7
    by Steve Cornett at 2026-09-14T14:06:42+02:00
    Fix header button dark mode theme in wxMSW
    
    Do not use the theme Explorer::HEADER because the rendering loses this
    theme when switching from dark mode to light. Instead, specify separate
    themes for light and dark mode, choosing the best dark mode theme
    available. Do this the same way in wxRendererXP and wxMSWHeaderCtrl.
    
    See #26790.
    
    Closes #27005.
    
  • 8eea7beb
    by Marc Aldorasi at 2026-09-15T01:30:47+02:00
    Provide wxrc as an imported target
    
    This lets users use the target wx::wxrc directly as an executable in
    add_custom_command(), instead of needing to guess its path.
    
    Closes #27008.
    
  • e9d032d5
    by Steve Cornett at 2026-09-16T00:42:50+02:00
    Fix wxStaticBox for dark mode with double buffering
    
    Set custom painting mode for dark mode in MSWSetDarkOrLightMode()
    because custom painting mode is not set in Create() when double
    buffering.
    
    Fixes #27004.
    
    Closes #27007.
    
  • 21a11df9
    by Vadim Zeitlin at 2026-09-16T00:43:14+02:00
    Merge branch 'persist-dpi'
    
    Handle restoring settings saved by persistent objects after DPI change.
    
    Closes #18752.
    
    See #27006.
    

17 changed files:

Changes:

  • build/cmake/utils/CMakeLists.txt
    ... ... @@ -27,6 +27,7 @@ if(wxUSE_XRC)
    27 27
     
    
    28 28
         wx_get_install_dir(runtime)
    
    29 29
         wx_install(TARGETS wxrc
    
    30
    +        EXPORT wxWidgetsTargets
    
    30 31
             RUNTIME DESTINATION "${runtime_dir}"
    
    31 32
             BUNDLE DESTINATION "${runtime_dir}"
    
    32 33
             )
    

  • docs/doxygen/overviews/persistence.h
    ... ... @@ -137,6 +137,13 @@ support for your custom class @c MyWidget you just need to:
    137 137
     -# Define wxCreatePersistentObject() overload taking `MyWidget*` and
    
    138 138
        returning a new `MyPersistentWidget` object.
    
    139 139
     
    
    140
    +If any of the saved values is a size or a position in pixels, use
    
    141
    +wxPersistentWindow::SaveCoord() and wxPersistentWindow::RestoreCoord()
    
    142
    +instead of the generic functions: they also store the DPI at which the value
    
    143
    +was saved and rescale it if it changes before the value is restored, which is
    
    144
    +important for the applications which may be used with different DPI values,
    
    145
    +e.g. when a display is replaced or its scaling factor is changed.
    
    146
    +
    
    140 147
     If you want to add persistence support for a class not deriving from wxWindow,
    
    141 148
     you need to derive `MyPersistentWidget` directly from wxPersistentObject and
    
    142 149
     so implement its pure virtual wxPersistentObject::GetName() method too.
    

  • include/wx/msw/private/tlwgeom.h
    ... ... @@ -68,6 +68,10 @@ public:
    68 68
                  !store.SaveValue(wxPERSIST_TLW_MAX_Y, pt.y) )
    
    69 69
                 return false;
    
    70 70
     
    
    71
    +        // Finally save the DPI at which all the values above were taken.
    
    72
    +        if ( !SaveDPI(store) )
    
    73
    +            return false;
    
    74
    +
    
    71 75
             // We don't currently save the minimized window position, it doesn't
    
    72 76
             // seem useful for anything and is probably just a left over from
    
    73 77
             // Windows 3.1 days, when icons were positioned on the desktop instead
    
    ... ... @@ -115,6 +119,8 @@ public:
    115 119
     
    
    116 120
             m_offScreen = store.RestoreValue(wxPERSIST_TLW_OFF_SCREEN, &tmp) && tmp;
    
    117 121
     
    
    122
    +        RestoreDPI(store);
    
    123
    +
    
    118 124
             return true;
    
    119 125
         }
    
    120 126
     
    
    ... ... @@ -160,11 +166,17 @@ public:
    160 166
     
    
    161 167
             m_offScreen = !IsFullyOnScreen(GetNormalRect());
    
    162 168
     
    
    169
    +        SetDPIFrom(tlw);
    
    170
    +
    
    163 171
             return true;
    
    164 172
         }
    
    165 173
     
    
    166 174
         virtual bool ApplyTo(wxTopLevelWindow* tlw) override
    
    167 175
         {
    
    176
    +        // If the DPI has changed since the geometry was saved, the window size
    
    177
    +        // needs to be adjusted to keep its contents at the same apparent size.
    
    178
    +        RescaleIfNeeded();
    
    179
    +
    
    168 180
             // The saved geometry may not make sense any more, e.g. if the window
    
    169 181
             // had been shown on a monitor which is not connected any longer, and
    
    170 182
             // restoring it as is would leave the window invisible, so adjust it if
    
    ... ... @@ -213,6 +225,31 @@ public:
    213 225
         }
    
    214 226
     
    
    215 227
     private:
    
    228
    +    // Rescale the normal window size if the DPI of the display where it is
    
    229
    +    // going to be shown is different from the one at which it was saved.
    
    230
    +    void RescaleIfNeeded()
    
    231
    +    {
    
    232
    +        wxRect rect = GetNormalRect();
    
    233
    +
    
    234
    +        // Note that we can't use the current DPI of the window here, as it is
    
    235
    +        // going to be moved to the display containing its saved position,
    
    236
    +        // which may well use a different DPI.
    
    237
    +        const int n = wxDisplay::GetFromRect(rect);
    
    238
    +        const wxSize dpi = n == wxNOT_FOUND
    
    239
    +                            ? wxDisplay().GetPPI()
    
    240
    +                            : wxDisplay(static_cast<unsigned>(n)).GetPPI();
    
    241
    +
    
    242
    +        const wxSize size = RescaleSize(rect.GetSize(), dpi);
    
    243
    +        if ( size == rect.GetSize() )
    
    244
    +            return;
    
    245
    +
    
    246
    +        // Note that the position is deliberately not changed here, see comment
    
    247
    +        // before RescaleSize().
    
    248
    +        rect.SetSize(size);
    
    249
    +
    
    250
    +        wxCopyRectToRECT(rect, m_placement.rcNormalPosition);
    
    251
    +    }
    
    252
    +
    
    216 253
         // Return the normal, i.e. neither maximized nor iconized, window rect.
    
    217 254
         wxRect GetNormalRect() const
    
    218 255
         {
    

  • include/wx/msw/statbox.h
    ... ... @@ -78,6 +78,8 @@ public:
    78 78
     protected:
    
    79 79
         virtual wxWindowList GetCompositeWindowParts() const override;
    
    80 80
     
    
    81
    +    virtual void MSWSetDarkOrLightMode(SetMode setmode) override;
    
    82
    +
    
    81 83
         // return the region with all the windows inside this static box excluded
    
    82 84
         WXHRGN MSWGetRegionWithoutChildren();
    
    83 85
     
    

  • include/wx/persist.h
    ... ... @@ -18,6 +18,20 @@
    18 18
     
    
    19 19
     class wxPersistentObject;
    
    20 20
     
    
    21
    +// ----------------------------------------------------------------------------
    
    22
    +// constants
    
    23
    +// ----------------------------------------------------------------------------
    
    24
    +
    
    25
    +// Name of the entry storing the DPI at which the other, pixel-valued, entries
    
    26
    +// of the same object were saved, used to rescale them if the DPI changes
    
    27
    +// between saving and restoring them.
    
    28
    +//
    
    29
    +// Note that the absence of this entry doesn't mean that the values were saved
    
    30
    +// at the standard DPI, but that they were saved by an older version of the
    
    31
    +// library not storing the DPI at all, in which case they can't be rescaled and
    
    32
    +// must be used as is.
    
    33
    +#define wxPERSIST_DPI "DPI"
    
    34
    +
    
    21 35
     // ----------------------------------------------------------------------------
    
    22 36
     // global functions
    
    23 37
     // ----------------------------------------------------------------------------
    

  • include/wx/persist/dataview.h
    ... ... @@ -73,7 +73,7 @@ public:
    73 73
                 // entire control width.
    
    74 74
                 const int width = column->WXGetSpecifiedWidth();
    
    75 75
                 if ( width > 0 )
    
    76
    -                SaveValue(columnPrefix + wxASCII_STR(wxPERSIST_DVC_WIDTH), width);
    
    76
    +                SaveCoord(columnPrefix + wxASCII_STR(wxPERSIST_DVC_WIDTH), width);
    
    77 77
     
    
    78 78
                 // Check if this column is the current sort key.
    
    79 79
                 if ( column->IsSortKey() )
    
    ... ... @@ -115,7 +115,7 @@ public:
    115 115
     
    
    116 116
                 // Restore the column width.
    
    117 117
                 int width;
    
    118
    -            if ( RestoreValue(columnPrefix + wxASCII_STR(wxPERSIST_DVC_WIDTH), &width) )
    
    118
    +            if ( RestoreCoord(columnPrefix + wxASCII_STR(wxPERSIST_DVC_WIDTH), &width) )
    
    119 119
                     column->SetWidth(width);
    
    120 120
     
    
    121 121
                 // Restore the column's view position.
    

  • include/wx/persist/splitter.h
    ... ... @@ -43,21 +43,21 @@ public:
    43 43
             wxSplitterWindow* const splitter = Get();
    
    44 44
     
    
    45 45
             int pos = splitter->IsSplit() ? splitter->GetSashPosition() : -1;
    
    46
    -        SaveValue(wxPERSIST_SPLITTER_POSITION, pos);
    
    46
    +        SaveCoord(wxPERSIST_SPLITTER_POSITION, pos);
    
    47 47
     
    
    48 48
             // Save the previously used position too if we have them.
    
    49 49
             const wxPoint lastSplitPos = splitter->GetLastSplitPosition();
    
    50 50
             if ( lastSplitPos.x || lastSplitPos.y )
    
    51 51
             {
    
    52
    -            SaveValue(wxPERSIST_SPLITTER_DEFAULT_HORIZONTAL, lastSplitPos.y);
    
    53
    -            SaveValue(wxPERSIST_SPLITTER_DEFAULT_VERTICAL, lastSplitPos.x);
    
    52
    +            SaveCoord(wxPERSIST_SPLITTER_DEFAULT_HORIZONTAL, lastSplitPos.y);
    
    53
    +            SaveCoord(wxPERSIST_SPLITTER_DEFAULT_VERTICAL, lastSplitPos.x);
    
    54 54
             }
    
    55 55
         }
    
    56 56
     
    
    57 57
         virtual bool Restore() override
    
    58 58
         {
    
    59 59
             int pos;
    
    60
    -        if ( !RestoreValue(wxPERSIST_SPLITTER_POSITION, &pos) )
    
    60
    +        if ( !RestoreCoord(wxPERSIST_SPLITTER_POSITION, &pos) )
    
    61 61
                 return false;
    
    62 62
     
    
    63 63
             if ( pos == -1 )
    
    ... ... @@ -68,8 +68,8 @@ public:
    68 68
             // Note that it's possible that default position was not stored, in
    
    69 69
             // which case lastSplitPos will just remain as (0, 0) and that's ok.
    
    70 70
             wxPoint lastSplitPos;
    
    71
    -        RestoreValue(wxPERSIST_SPLITTER_DEFAULT_HORIZONTAL, &lastSplitPos.x);
    
    72
    -        RestoreValue(wxPERSIST_SPLITTER_DEFAULT_VERTICAL, &lastSplitPos.y);
    
    71
    +        RestoreCoord(wxPERSIST_SPLITTER_DEFAULT_HORIZONTAL, &lastSplitPos.y);
    
    72
    +        RestoreCoord(wxPERSIST_SPLITTER_DEFAULT_VERTICAL, &lastSplitPos.x);
    
    73 73
     
    
    74 74
             Get()->SetLastSplitPosition(lastSplitPos);
    
    75 75
     
    

  • include/wx/persist/window.h
    ... ... @@ -41,6 +41,77 @@ public:
    41 41
     protected:
    
    42 42
         wxWindow *GetWindow() const { return static_cast<wxWindow *>(GetObject()); }
    
    43 43
     
    
    44
    +    // Save a coordinate, i.e. either a position or a size, in pixels.
    
    45
    +    //
    
    46
    +    // Unlike SaveValue(), this function also saves the DPI at which the
    
    47
    +    // coordinate was computed, which allows RestoreCoord() to rescale it if
    
    48
    +    // the DPI changes in the meanwhile.
    
    49
    +    //
    
    50
    +    // The special values which are not really coordinates, such as -1, may be
    
    51
    +    // passed to this function too, they're just saved as is.
    
    52
    +    bool SaveCoord(const wxString& name, int value) const
    
    53
    +    {
    
    54
    +        if ( !SaveValue(name, value) )
    
    55
    +            return false;
    
    56
    +
    
    57
    +        // Under the platforms using DPI-independent pixels the values don't
    
    58
    +        // change with the DPI, so we don't need to store it at all.
    
    59
    +#ifndef wxHAS_DPI_INDEPENDENT_PIXELS
    
    60
    +        // Don't store the DPI if we haven't saved anything depending on it.
    
    61
    +        if ( value > 0 )
    
    62
    +        {
    
    63
    +            // All the values of the same object are saved at the same DPI, so
    
    64
    +            // write it just once, using the same key for all of them, but do
    
    65
    +            // write it again if it has somehow changed since the last time.
    
    66
    +            const int dpi = GetWindow()->GetDPI().y;
    
    67
    +            if ( dpi != m_dpiSaved )
    
    68
    +            {
    
    69
    +                if ( !SaveValue(wxASCII_STR(wxPERSIST_DPI), dpi) )
    
    70
    +                    return false;
    
    71
    +
    
    72
    +                m_dpiSaved = dpi;
    
    73
    +            }
    
    74
    +        }
    
    75
    +#endif // !wxHAS_DPI_INDEPENDENT_PIXELS
    
    76
    +
    
    77
    +        return true;
    
    78
    +    }
    
    79
    +
    
    80
    +    // Restore a coordinate saved by SaveCoord(), rescaling it if the DPI has
    
    81
    +    // changed since it had been saved.
    
    82
    +    bool RestoreCoord(const wxString& name, int* value)
    
    83
    +    {
    
    84
    +        if ( !RestoreValue(name, value) )
    
    85
    +            return false;
    
    86
    +
    
    87
    +#ifndef wxHAS_DPI_INDEPENDENT_PIXELS
    
    88
    +        // Only the values really expressed in pixels can be rescaled, the
    
    89
    +        // special ones, such as -1, must be preserved as is.
    
    90
    +        if ( *value <= 0 )
    
    91
    +            return true;
    
    92
    +
    
    93
    +        int dpiSaved;
    
    94
    +        if ( !RestoreValue(wxASCII_STR(wxPERSIST_DPI), &dpiSaved) )
    
    95
    +        {
    
    96
    +            // The value was saved by a version of the library which didn't
    
    97
    +            // store the DPI, so we have no choice but to use it as is.
    
    98
    +            return true;
    
    99
    +        }
    
    100
    +
    
    101
    +        if ( dpiSaved <= 0 )
    
    102
    +        {
    
    103
    +            // Don't use invalid value, just ignore the saved DPI in this case.
    
    104
    +            return true;
    
    105
    +        }
    
    106
    +
    
    107
    +        const int dpi = GetWindow()->GetDPI().y;
    
    108
    +        if ( dpi != dpiSaved )
    
    109
    +            *value = wxRound(*value*static_cast<double>(dpi)/dpiSaved);
    
    110
    +#endif // !wxHAS_DPI_INDEPENDENT_PIXELS
    
    111
    +
    
    112
    +        return true;
    
    113
    +    }
    
    114
    +
    
    44 115
     private:
    
    45 116
         void HandleDestroy(wxWindowDestroyEvent& event)
    
    46 117
         {
    
    ... ... @@ -55,6 +126,11 @@ private:
    55 126
             }
    
    56 127
         }
    
    57 128
     
    
    129
    +#ifndef wxHAS_DPI_INDEPENDENT_PIXELS
    
    130
    +    // The last DPI written by SaveCoord() or 0 if none yet.
    
    131
    +    mutable int m_dpiSaved = 0;
    
    132
    +#endif // !wxHAS_DPI_INDEPENDENT_PIXELS
    
    133
    +
    
    58 134
         wxDECLARE_NO_COPY_CLASS(wxPersistentWindowBase);
    
    59 135
     };
    
    60 136
     
    

  • include/wx/private/tlwgeom.h
    ... ... @@ -13,6 +13,12 @@
    13 13
     #include "wx/display.h"
    
    14 14
     #include "wx/toplevel.h"
    
    15 15
     
    
    16
    +#include "wx/private/rescale.h"
    
    17
    +
    
    18
    +// Name of the entry storing the DPI at which the geometry was saved: it's the
    
    19
    +// same as wxPERSIST_DPI in wx/persist.h, but avoid including it just for this.
    
    20
    +#define wxPERSIST_TLW_DPI "DPI"
    
    21
    +
    
    16 22
     // ----------------------------------------------------------------------------
    
    17 23
     // wxTLWGeometryBase: abstract base class for platform-specific classes
    
    18 24
     // ----------------------------------------------------------------------------
    
    ... ... @@ -42,6 +48,86 @@ public:
    42 48
         // writing/reading the values of the different fields of this object.
    
    43 49
         virtual bool Save(Store& store) const = 0;
    
    44 50
         virtual bool Restore(const Store& store) = 0;
    
    51
    +
    
    52
    +protected:
    
    53
    +#ifdef wxHAS_DPI_INDEPENDENT_PIXELS
    
    54
    +    // The geometry doesn't change with the DPI under these platforms, so we
    
    55
    +    // don't need to save DPI at all but still provide stubs for the functions
    
    56
    +    // below to avoid having to use #ifdefs when calling them.
    
    57
    +
    
    58
    +    bool SaveDPI(Store& WXUNUSED(store)) const
    
    59
    +    {
    
    60
    +        return true;
    
    61
    +    }
    
    62
    +
    
    63
    +    void RestoreDPI(const Store& WXUNUSED(store))
    
    64
    +    {
    
    65
    +    }
    
    66
    +
    
    67
    +    void SetDPIFrom(const wxTopLevelWindow* WXUNUSED(tlw))
    
    68
    +    {
    
    69
    +    }
    
    70
    +
    
    71
    +    wxSize RescaleSize(const wxSize& size, const wxSize& WXUNUSED(dpi)) const
    
    72
    +    {
    
    73
    +        return size;
    
    74
    +    }
    
    75
    +#else // !wxHAS_DPI_INDEPENDENT_PIXELS
    
    76
    +    // Helpers for the derived classes Save() and Restore() implementations:
    
    77
    +    // they must be used to save and restore the DPI at which the geometry was
    
    78
    +    // taken, as it is needed by RescaleSize() below.
    
    79
    +    bool SaveDPI(Store& store) const
    
    80
    +    {
    
    81
    +        wxASSERT_MSG( m_dpi != wxDefaultSize, "forgot to call SetDPIFrom()?" );
    
    82
    +
    
    83
    +        return store.SaveValue(wxPERSIST_TLW_DPI, m_dpi.y);
    
    84
    +    }
    
    85
    +
    
    86
    +    void RestoreDPI(const Store& store)
    
    87
    +    {
    
    88
    +        int dpi;
    
    89
    +        if ( store.RestoreValue(wxPERSIST_TLW_DPI, &dpi) )
    
    90
    +        {
    
    91
    +            if ( dpi <= 0 )
    
    92
    +            {
    
    93
    +                // Probably not worth logging this, just ignore.
    
    94
    +                return;
    
    95
    +            }
    
    96
    +
    
    97
    +            m_dpi = wxSize(dpi, dpi);
    
    98
    +        }
    
    99
    +        //else: leave it as wxDefaultSize, which means that we don't know the
    
    100
    +        //      DPI at which the geometry was saved and so shouldn't rescale it.
    
    101
    +    }
    
    102
    +
    
    103
    +    // Return the size to use for a window which had the given size when its
    
    104
    +    // geometry was saved if it is going to be used with the given DPI now.
    
    105
    +    //
    
    106
    +    // Note that this function only deals with the size because the position of
    
    107
    +    // the window doesn't need to be changed, screen resolution doesn't depend
    
    108
    +    // on the DPI on the platforms without DPI-independent pixels, where this
    
    109
    +    // really matters.
    
    110
    +    wxSize RescaleSize(const wxSize& size, const wxSize& dpi) const
    
    111
    +    {
    
    112
    +        if ( m_dpi == wxDefaultSize || dpi == m_dpi || !dpi.x || !dpi.y )
    
    113
    +            return size;
    
    114
    +
    
    115
    +        return wxRescaleCoord(size).From(m_dpi).To(dpi);
    
    116
    +    }
    
    117
    +
    
    118
    +    // Set the DPI to save together with the geometry of the given window, must
    
    119
    +    // be called from GetFrom().
    
    120
    +    void SetDPIFrom(const wxTopLevelWindow* tlw)
    
    121
    +    {
    
    122
    +        m_dpi = tlw->GetDPI();
    
    123
    +    }
    
    124
    +
    
    125
    +private:
    
    126
    +    // The DPI at which the geometry was saved or wxDefaultSize if it is not
    
    127
    +    // known, which is the case when restoring the geometry saved by a previous
    
    128
    +    // version of the library which didn't store it.
    
    129
    +    wxSize m_dpi = wxDefaultSize;
    
    130
    +#endif // wxHAS_DPI_INDEPENDENT_PIXELS
    
    45 131
     };
    
    46 132
     
    
    47 133
     // ----------------------------------------------------------------------------
    
    ... ... @@ -87,6 +173,9 @@ public:
    87 173
             if ( !store.SaveValue(wxPERSIST_TLW_ICONIZED, m_iconized) )
    
    88 174
                 return false;
    
    89 175
     
    
    176
    +        if ( !SaveDPI(store) )
    
    177
    +            return false;
    
    178
    +
    
    90 179
             return true;
    
    91 180
         }
    
    92 181
     
    
    ... ... @@ -105,6 +194,8 @@ public:
    105 194
             if ( store.RestoreValue(wxPERSIST_TLW_ICONIZED, &tmp) )
    
    106 195
                 m_iconized = tmp != 0;
    
    107 196
     
    
    197
    +        RestoreDPI(store);
    
    198
    +
    
    108 199
             // If we restored at least something, return true.
    
    109 200
             return m_hasPos || m_hasSize || m_maximized || m_iconized;
    
    110 201
         }
    
    ... ... @@ -112,6 +203,7 @@ public:
    112 203
         virtual bool GetFrom(const wxTopLevelWindow* tlw) override
    
    113 204
         {
    
    114 205
             m_rectScreen = tlw->GetScreenRect();
    
    206
    +        SetDPIFrom(tlw);
    
    115 207
             m_hasPos =
    
    116 208
             m_hasSize = true;
    
    117 209
             m_iconized = tlw->IsIconized();
    
    ... ... @@ -142,11 +234,13 @@ public:
    142 234
     
    
    143 235
             if ( m_hasSize )
    
    144 236
             {
    
    237
    +            // Adjust the size to the currently used DPI if necessary.
    
    238
    +            wxSize size = RescaleSize(m_rectScreen.GetSize(), tlw->GetDPI());
    
    239
    +
    
    145 240
                 // a previous version of the program could have saved the window
    
    146 241
                 // size which used to be big enough, but which is not big enough
    
    147 242
                 // any more for the new version, so check that the size we restore
    
    148 243
                 // doesn't cut off parts of the window
    
    149
    -            wxSize size = m_rectScreen.GetSize();
    
    150 244
                 size.IncTo(tlw->GetBestSize());
    
    151 245
                 tlw->SetSize(size);
    
    152 246
             }
    

  • interface/wx/persist/window.h
    ... ... @@ -47,4 +47,59 @@ public:
    47 47
             before saving or restoring persistent properties.
    
    48 48
          */
    
    49 49
         virtual wxString GetName() const;
    
    50
    +
    
    51
    +protected:
    
    52
    +    /**
    
    53
    +        Save a coordinate, i.e. either a position or a size, in pixels.
    
    54
    +
    
    55
    +        This function must be used instead of wxPersistentObject::SaveValue()
    
    56
    +        for the values representing a size or a position in pixels, as it also
    
    57
    +        stores the DPI in which the coordinate is expressed, allowing
    
    58
    +        RestoreCoord() to rescale it if the DPI changes when the coordinate
    
    59
    +        is restored later.
    
    60
    +
    
    61
    +        Note that under the platforms using DPI-independent pixels, such as
    
    62
    +        wxGTK and wxOSX, the saved values don't change with the DPI and so no
    
    63
    +        DPI is stored for them at all, meaning that they are never rescaled.
    
    64
    +
    
    65
    +        Also note that the values which are not really expressed in pixels,
    
    66
    +        such as the special value -1 often used to indicate the absence of a
    
    67
    +        value, are not affected by the rescaling and can be saved using this
    
    68
    +        function too.
    
    69
    +
    
    70
    +        @param name
    
    71
    +            The name of the value in the configuration file.
    
    72
    +        @param value
    
    73
    +            The value to save.
    
    74
    +        @return
    
    75
    +            @true if the value was saved or @false if an error occurred.
    
    76
    +
    
    77
    +        @since 3.3.4
    
    78
    +     */
    
    79
    +    bool SaveCoord(const wxString& name, int value) const;
    
    80
    +
    
    81
    +    /**
    
    82
    +        Restore a coordinate saved by SaveCoord().
    
    83
    +
    
    84
    +        If the DPI in which the coordinate is expressed is different from the
    
    85
    +        DPI at which it had been saved, it is rescaled accordingly. If the
    
    86
    +        latter is unknown, e.g. because the value was saved by a version of
    
    87
    +        wxWidgets older than 3.3.4, the coordinate is restored unchanged.
    
    88
    +
    
    89
    +        As explained in SaveCoord() documentation, no DPI is stored under
    
    90
    +        the platforms using DPI-independent pixels, so this function simply
    
    91
    +        behaves as wxPersistentObject::RestoreValue() there.
    
    92
    +
    
    93
    +        @param name
    
    94
    +            The same name as was used by SaveCoord().
    
    95
    +        @param value
    
    96
    +            Non-@NULL pointer to the variable to fill with the saved value if
    
    97
    +            it was read successfully, which is not modified otherwise.
    
    98
    +        @return
    
    99
    +            @true if the value was successfully read or @false if it was not
    
    100
    +            found or an error occurred.
    
    101
    +
    
    102
    +        @since 3.3.4
    
    103
    +     */
    
    104
    +    bool RestoreCoord(const wxString& name, int* value);
    
    50 105
     };

  • src/generic/splitter.cpp
    ... ... @@ -512,6 +512,13 @@ void wxSplitterWindow::OnDPIChanged(wxDPIChangedEvent& event)
    512 512
         m_minimumPaneSize = event.ScaleX(m_minimumPaneSize);
    
    513 513
         m_sashPosition = event.ScaleX(m_sashPosition);
    
    514 514
         m_lastSize = event.Scale(m_lastSize);
    
    515
    +
    
    516
    +    // Any saved or pending positions need to be updated too.
    
    517
    +    m_lastSplitPosition = event.Scale(m_lastSplitPosition);
    
    518
    +
    
    519
    +    // But this one should only be scaled if it is really valid.
    
    520
    +    if ( m_requestedSashPosition != INT_MAX )
    
    521
    +        m_requestedSashPosition = event.ScaleX(m_requestedSashPosition);
    
    515 522
     #endif // !wxHAS_DPI_INDEPENDENT_PIXELS
    
    516 523
     
    
    517 524
         event.Skip();
    

  • src/msw/control.cpp
    ... ... @@ -599,12 +599,20 @@ bool wxMSWOwnerDrawnButtonBase::MSWDrawButton(WXDRAWITEMSTRUCT *item)
    599 599
         {
    
    600 600
             RECT oldLabelRect = rectLabel; // needed if right aligned
    
    601 601
     
    
    602
    -        if ( !::DrawText(hdc, label.t_str(), label.length(), &rectLabel,
    
    602
    +        // If the label is empty, use a space character to avoid a focus
    
    603
    +        // rectangle size 0x0.
    
    604
    +        auto s = label.empty() ? wxString(" ") : label;
    
    605
    +        if ( !::DrawText(hdc, s.t_str(), s.length(), &rectLabel,
    
    603 606
                              fmt | DT_CALCRECT) )
    
    604 607
             {
    
    605 608
                 wxLogLastError(wxT("DrawText(DT_CALCRECT)"));
    
    606 609
             }
    
    607 610
     
    
    611
    +        // For empty label, mimic the native control by extending to the
    
    612
    +        // client width.
    
    613
    +        if ( label.empty() )
    
    614
    +            rectLabel.right = dis->rcItem.right - 1;
    
    615
    +
    
    608 616
             if ( isRightAligned )
    
    609 617
             {
    
    610 618
                 // move the label rect to the right
    

  • src/msw/headerctrl.cpp
    ... ... @@ -238,7 +238,11 @@ WXDWORD wxMSWHeaderCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
    238 238
     
    
    239 239
     void wxMSWHeaderCtrl::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
    
    240 240
     {
    
    241
    -    support.themeName = L"ItemsView";
    
    241
    +    // Use DarkMode_DarkTheme if available as it looks better.
    
    242
    +    if ( wxMSWDarkMode::HasDarkTheme() )
    
    243
    +        support.themeName = L"DarkMode_DarkTheme";
    
    244
    +    else
    
    245
    +        support.themeName = L"ItemsView";
    
    242 246
     }
    
    243 247
     
    
    244 248
     void wxMSWHeaderCtrl::MSWSetDarkOrLightMode(SetMode setmode)
    

  • src/msw/renderer.cpp
    ... ... @@ -636,7 +636,10 @@ wxRendererXP::DrawHeaderButton(wxWindow *win,
    636 636
                                    wxHeaderSortIconType sortArrow,
    
    637 637
                                    wxHeaderButtonParams* params)
    
    638 638
     {
    
    639
    -    wxUxThemeHandle hTheme(win, L"Explorer::HEADER");
    
    639
    +    // Use DarkMode_DarkTheme if available as it looks better.
    
    640
    +    auto dark = wxMSWDarkMode::HasDarkTheme() ?
    
    641
    +        L"DarkMode_DarkTheme::Header" : L"ItemsView::Header";
    
    642
    +    wxUxThemeHandle hTheme(win, L"HEADER", dark);
    
    640 643
         if ( !hTheme )
    
    641 644
         {
    
    642 645
             return m_rendererNative.DrawHeaderButton(win, dc, rect, flags, sortArrow, params);
    

  • src/msw/statbox.cpp
    ... ... @@ -95,6 +95,19 @@ bool wxStaticBox::Create(wxWindow *parent,
    95 95
         return true;
    
    96 96
     }
    
    97 97
     
    
    98
    +void wxStaticBox::MSWSetDarkOrLightMode(SetMode setmode)
    
    99
    +{
    
    100
    +    wxStaticBoxBase::MSWSetDarkOrLightMode(setmode);
    
    101
    +
    
    102
    +    // Set custom painting because the native control does not support dark mode.
    
    103
    +    //
    
    104
    +    // Note that it's not useful to check if we're using dark mode now because
    
    105
    +    // we must either be starting to use it now or had been using it at some
    
    106
    +    // point for this function to be called and once custom painting is
    
    107
    +    // enabled, calling this function doesn't do anything anyhow.
    
    108
    +    UseCustomPaint();
    
    109
    +}
    
    110
    +
    
    98 111
     bool wxStaticBox::ShouldUseCustomPaint() const
    
    99 112
     {
    
    100 113
         // When not using double buffering, we paint the box ourselves by default
    

  • tests/persistence/dataview.cpp
    ... ... @@ -98,6 +98,27 @@ void GTKWaitRealized(wxDataViewCtrl* list)
    98 98
     #endif // __WXGTK__/!__WXGTK__
    
    99 99
     }
    
    100 100
     
    
    101
    +// Save the state of the test control with the given column widths.
    
    102
    +static void SavePersistenceTestDVC(int width1, int width2)
    
    103
    +{
    
    104
    +    wxDataViewCtrl* const list = CreatePersistenceTestDVC();
    
    105
    +
    
    106
    +    list->GetColumn(0)->SetWidth(width1);
    
    107
    +    list->GetColumn(1)->SetWidth(width2);
    
    108
    +    list->GetColumn(1)->SetSortOrder(false);
    
    109
    +
    
    110
    +    CHECK(wxPersistenceManager::Get().Register(list));
    
    111
    +
    
    112
    +    // We need to wait until the window is fully realized and the column
    
    113
    +    // widths are actually set.
    
    114
    +    GTKWaitRealized(list);
    
    115
    +
    
    116
    +    // Deleting the control itself doesn't allow it to save its state as
    
    117
    +    // the wxEVT_DESTROY handler is called too late, so delete its parent
    
    118
    +    // (as would usually be the case) instead.
    
    119
    +    delete list->GetParent();
    
    120
    +}
    
    121
    +
    
    101 122
     // --------------------------------------------------------------------------
    
    102 123
     // tests themselves
    
    103 124
     // --------------------------------------------------------------------------
    
    ... ... @@ -107,23 +128,7 @@ void GTKWaitRealized(wxDataViewCtrl* list)
    107 128
     TEST_CASE_METHOD(PersistenceTests, "wxPersistDVC", "[persist][wxDataViewCtrl]")
    
    108 129
     {
    
    109 130
         {
    
    110
    -        wxDataViewCtrl* const list = CreatePersistenceTestDVC();
    
    111
    -
    
    112
    -        // Adjust the initial settings.
    
    113
    -        list->GetColumn(0)->SetWidth(150);
    
    114
    -        list->GetColumn(1)->SetWidth(250);
    
    115
    -        list->GetColumn(1)->SetSortOrder(false);
    
    116
    -
    
    117
    -        CHECK(wxPersistenceManager::Get().Register(list));
    
    118
    -
    
    119
    -        // We need to wait until the window is fully realized and the column
    
    120
    -        // widths are actually set.
    
    121
    -        GTKWaitRealized(list);
    
    122
    -
    
    123
    -        // Deleting the control itself doesn't allow it to save its state as
    
    124
    -        // the wxEVT_DESTROY handler is called too late, so delete its parent
    
    125
    -        // (as would usually be the case) instead.
    
    126
    -        delete list->GetParent();
    
    131
    +        SavePersistenceTestDVC(150, 250);
    
    127 132
     
    
    128 133
             // Test that the relevant keys have been stored correctly.
    
    129 134
             int val = -1;
    
    ... ... @@ -162,4 +167,63 @@ TEST_CASE_METHOD(PersistenceTests, "wxPersistDVC", "[persist][wxDataViewCtrl]")
    162 167
         }
    
    163 168
     }
    
    164 169
     
    
    170
    +#ifdef __WXMSW__
    
    171
    +
    
    172
    +// Check that the column widths saved at one DPI are rescaled when they are
    
    173
    +// restored at a different one.
    
    174
    +TEST_CASE_METHOD(PersistenceTests, "wxPersistDVC::DPI",
    
    175
    +                 "[persist][wxDataViewCtrl][dpi]")
    
    176
    +{
    
    177
    +    // Use half of the widths used by the other tests, so that the columns have
    
    178
    +    // exactly those widths after being scaled by 2 below.
    
    179
    +    SavePersistenceTestDVC(75, 125);
    
    180
    +
    
    181
    +    const int dpi = wxTheApp->GetTopWindow()->GetDPI().y;
    
    182
    +
    
    183
    +    // All the DPI values used in practice are even, which allows us to use
    
    184
    +    // exact values in the checks below.
    
    185
    +    REQUIRE(dpi % 2 == 0);
    
    186
    +
    
    187
    +    // The DPI must have been saved together with the widths.
    
    188
    +    int dpiSaved = -1;
    
    189
    +    CHECK(GetConfig().Read(DVC_PREFIX "/DPI", &dpiSaved));
    
    190
    +    CHECK(dpiSaved == dpi);
    
    191
    +
    
    192
    +    SECTION("Rescale")
    
    193
    +    {
    
    194
    +        // Pretend that the widths had been saved at half the current DPI, in
    
    195
    +        // which case they must be doubled when restoring them.
    
    196
    +        GetConfig().Write(DVC_PREFIX "/DPI", dpi / 2);
    
    197
    +
    
    198
    +        wxDataViewCtrl* const list = CreatePersistenceTestDVC();
    
    199
    +        CHECK(wxPersistenceManager::Get().RegisterAndRestore(list));
    
    200
    +
    
    201
    +        GTKWaitRealized(list);
    
    202
    +
    
    203
    +        CHECK(list->GetColumn(0)->GetWidth() == 150);
    
    204
    +        CHECK(list->GetColumn(1)->GetWidth() == 250);
    
    205
    +
    
    206
    +        delete list->GetParent();
    
    207
    +    }
    
    208
    +
    
    209
    +    SECTION("Compatibility")
    
    210
    +    {
    
    211
    +        // The values saved by the previous versions of the library don't have
    
    212
    +        // any DPI associated with them and must be restored as is.
    
    213
    +        GetConfig().DeleteEntry(DVC_PREFIX "/DPI");
    
    214
    +
    
    215
    +        wxDataViewCtrl* const list = CreatePersistenceTestDVC();
    
    216
    +        CHECK(wxPersistenceManager::Get().RegisterAndRestore(list));
    
    217
    +
    
    218
    +        GTKWaitRealized(list);
    
    219
    +
    
    220
    +        CHECK(list->GetColumn(0)->GetWidth() == 75);
    
    221
    +        CHECK(list->GetColumn(1)->GetWidth() == 125);
    
    222
    +
    
    223
    +        delete list->GetParent();
    
    224
    +    }
    
    225
    +}
    
    226
    +
    
    227
    +#endif // __WXMSW__
    
    228
    +
    
    165 229
     #endif

  • tests/persistence/tlw.cpp
    ... ... @@ -325,4 +325,61 @@ TEST_CASE_METHOD(PersistenceTests, "wxPersistTLW::OffScreen", "[persist][tlw]")
    325 325
         }
    
    326 326
     }
    
    327 327
     
    
    328
    +// Check that the geometry saved at one DPI is rescaled when it is restored at
    
    329
    +// a different one.
    
    330
    +TEST_CASE_METHOD(PersistenceTests, "wxPersistTLW::DPI", "[persist][tlw][dpi]")
    
    331
    +{
    
    332
    +    const wxPoint pos(100, 150);
    
    333
    +
    
    334
    +    // Use a relatively small size, so that the frame still fits on the screen
    
    335
    +    // after being scaled by 2 below.
    
    336
    +    const wxSize size(225, 175);
    
    337
    +
    
    338
    +    SavePersistenceTestFrame(pos, size);
    
    339
    +
    
    340
    +    // The frame size can be greater than the requested one, e.g. because the
    
    341
    +    // system doesn't allow frames narrower than their caption buttons, so
    
    342
    +    // check the restored size against the size which was really saved and not
    
    343
    +    // the one we had asked for.
    
    344
    +    wxSize sizeSaved;
    
    345
    +    REQUIRE(GetConfig().Read(FRAME_OPTIONS_PREFIX "/w", &sizeSaved.x));
    
    346
    +    REQUIRE(GetConfig().Read(FRAME_OPTIONS_PREFIX "/h", &sizeSaved.y));
    
    347
    +
    
    348
    +    const int dpi = wxTheApp->GetTopWindow()->GetDPI().y;
    
    349
    +
    
    350
    +    // All the DPI values used in practice are even, which allows us to use
    
    351
    +    // exact values in the checks below.
    
    352
    +    REQUIRE(dpi % 2 == 0);
    
    353
    +
    
    354
    +    // The DPI must have been saved together with the geometry.
    
    355
    +    int dpiSaved = -1;
    
    356
    +    CHECK(GetConfig().Read(FRAME_OPTIONS_PREFIX "/DPI", &dpiSaved));
    
    357
    +    CHECK(dpiSaved == dpi);
    
    358
    +
    
    359
    +    SECTION("Rescale")
    
    360
    +    {
    
    361
    +        // Pretend that the geometry had been saved at half the current DPI:
    
    362
    +        // in this case the size must be doubled when restoring it, but not the
    
    363
    +        // position, which doesn't depend on the DPI.
    
    364
    +        GetConfig().Write(FRAME_OPTIONS_PREFIX "/DPI", dpi / 2);
    
    365
    +
    
    366
    +        auto const frame = RestorePersistenceTestFrame();
    
    367
    +
    
    368
    +        CHECK(frame->GetSize() == sizeSaved*2);
    
    369
    +        CHECK(frame->GetPosition() == pos);
    
    370
    +    }
    
    371
    +
    
    372
    +    SECTION("Compatibility")
    
    373
    +    {
    
    374
    +        // The geometry saved by the previous versions of the library doesn't
    
    375
    +        // have any DPI associated with it and must be restored as is.
    
    376
    +        GetConfig().DeleteEntry(FRAME_OPTIONS_PREFIX "/DPI");
    
    377
    +
    
    378
    +        auto const frame = RestorePersistenceTestFrame();
    
    379
    +
    
    380
    +        CHECK(frame->GetSize() == sizeSaved);
    
    381
    +        CHECK(frame->GetPosition() == pos);
    
    382
    +    }
    
    383
    +}
    
    384
    +
    
    328 385
     #endif // __WXMSW__

Reply all
Reply to author
Forward
0 new messages