Fix wrong destruction of wxBitmap in wxMSW wxStaticBitmap Due to a regression introduced in 3e32a9abe1 (Don't bother resetting wxStaticBitmap image when destroying it, 2025-06-12), which was part of #25518, the handle of wxBitmap passed to wxStaticBitmap::SetBitmap() was destroyed when another bitmap was passed to SetBitmap() later. Fix this by ensuring that we don't overwrite the value of the current handle (to be deleted) prematurely in DoUpdateImage(). Add a simple unit test for wxStaticBitmap checking that the problem is really fixed and, under MSW, also that it doesn't leak bitmap handles. Closes #26106.
Make wxXmlInitResourceModule() safer to use This function unconditionally added a new wxXmlResourceModule object to the modules list which could result in problems if it had been already initialized. Ensure that we do this only if necessary by calling the new function added to wxModule which checks if the module is already known and adds and initializes only this module if it is not. See #26111. Closes #26039.
Improve documentation of null wxDialog parent Mention wxDIALOG_NO_PARENT in the documentation of the parent parameter as people who don't know about this style are not going to see this style documentation either. See #23560.
| ... | ... | @@ -54,6 +54,10 @@ public: |
| 54 | 54 | |
| 55 | 55 | static void UnregisterModule(wxModule *module);
|
| 56 | 56 | |
| 57 | + // Initialize the module with the given type information if it's not
|
|
| 58 | + // already initialized. This is for internal use only currently.
|
|
| 59 | + static void AddModuleIfNecessary(const wxClassInfo *classInfo);
|
|
| 60 | + |
|
| 57 | 61 | protected:
|
| 58 | 62 | static wxModuleList ms_modules;
|
| 59 | 63 |
| ... | ... | @@ -166,7 +166,10 @@ public: |
| 166 | 166 | Constructor.
|
| 167 | 167 | |
| 168 | 168 | @param parent
|
| 169 | - Can be @NULL, a frame or another dialog box.
|
|
| 169 | + Can be @NULL, a frame or another dialog box. Please note that when
|
|
| 170 | + the parent is @NULL, the dialog will be owned by the application's
|
|
| 171 | + top window, if any. Use ::wxDIALOG_NO_PARENT style to really make
|
|
| 172 | + dialog not owned by any window.
|
|
| 170 | 173 | @param id
|
| 171 | 174 | An identifier for the dialog. A value of -1 is taken to mean a
|
| 172 | 175 | default.
|
| ... | ... | @@ -18,6 +18,8 @@ |
| 18 | 18 | #include "wx/log.h"
|
| 19 | 19 | #endif
|
| 20 | 20 | |
| 21 | +#include <memory>
|
|
| 22 | + |
|
| 21 | 23 | #define TRACE_MODULE wxT("module")
|
| 22 | 24 | |
| 23 | 25 | wxIMPLEMENT_ABSTRACT_CLASS(wxModule, wxObject);
|
| ... | ... | @@ -148,6 +150,43 @@ bool wxModule::DoInitializeModule(wxModule *module, |
| 148 | 150 | return true;
|
| 149 | 151 | }
|
| 150 | 152 | |
| 153 | +void wxModule::AddModuleIfNecessary(const wxClassInfo *classInfo)
|
|
| 154 | +{
|
|
| 155 | + wxCHECK_RET( classInfo, wxS("Valid class info must be provided") );
|
|
| 156 | + wxCHECK_RET( classInfo->IsKindOf(wxCLASSINFO(wxModule)),
|
|
| 157 | + wxS("Class info must be for wxModule-derived class") );
|
|
| 158 | + |
|
| 159 | + const wxString className(classInfo->GetClassName());
|
|
| 160 | + for ( wxModuleList::const_iterator it = ms_modules.begin();
|
|
| 161 | + it != ms_modules.end();
|
|
| 162 | + ++it )
|
|
| 163 | + {
|
|
| 164 | + if ( (*it)->GetClassInfo()->GetClassName() == className )
|
|
| 165 | + {
|
|
| 166 | + // Already initialized or at least registered and will be
|
|
| 167 | + // initialized later.
|
|
| 168 | + return;
|
|
| 169 | + }
|
|
| 170 | + }
|
|
| 171 | + |
|
| 172 | + std::unique_ptr<wxModule>
|
|
| 173 | + module{static_cast<wxModule*>(classInfo->CreateObject())};
|
|
| 174 | + |
|
| 175 | + // Do not call RegisterModule() here as it would add it to ms_modules which
|
|
| 176 | + // would result in it being added twice as DoInitializeModule() would do it
|
|
| 177 | + // too on success.
|
|
| 178 | + module->m_state = State_Registered;
|
|
| 179 | + |
|
| 180 | + if ( !DoInitializeModule(module.get(), ms_modules) )
|
|
| 181 | + {
|
|
| 182 | + // Error is already given by DoInitializeModule().
|
|
| 183 | + return;
|
|
| 184 | + }
|
|
| 185 | + |
|
| 186 | + // Module is now owned by ms_modules.
|
|
| 187 | + module.release();
|
|
| 188 | +}
|
|
| 189 | + |
|
| 151 | 190 | // Initialize user-defined modules
|
| 152 | 191 | bool wxModule::InitializeModules()
|
| 153 | 192 | {
|
| ... | ... | @@ -231,6 +231,7 @@ void wxStaticBitmap::DoUpdateImage(const wxSize& sizeOld, bool wasIcon) |
| 231 | 231 | |
| 232 | 232 | // For the icons we just use its HICON directly, but for bitmaps we create
|
| 233 | 233 | // our own temporary bitmap and need to delete its handle manually later.
|
| 234 | + WXHANDLE currentHandle = 0;
|
|
| 234 | 235 | if ( !m_icon.IsOk() )
|
| 235 | 236 | {
|
| 236 | 237 | wxBitmap bitmap = m_bitmapBundle.GetBitmapFor(this);
|
| ... | ... | @@ -244,8 +245,8 @@ void wxStaticBitmap::DoUpdateImage(const wxSize& sizeOld, bool wasIcon) |
| 244 | 245 | {
|
| 245 | 246 | // For bitmap with alpha channel create temporary DIB with
|
| 246 | 247 | // not-premultiplied alpha values.
|
| 247 | - m_currentHandle = wxDIB(bitmap.ConvertToImage(),
|
|
| 248 | - wxDIB::PixelFormat_NotPreMultiplied)
|
|
| 248 | + currentHandle = wxDIB(bitmap.ConvertToImage(),
|
|
| 249 | + wxDIB::PixelFormat_NotPreMultiplied)
|
|
| 249 | 250 | .Detach();
|
| 250 | 251 | m_ownsCurrentHandle = true;
|
| 251 | 252 | }
|
| ... | ... | @@ -266,14 +267,14 @@ void wxStaticBitmap::DoUpdateImage(const wxSize& sizeOld, bool wasIcon) |
| 266 | 267 | // Just use the HBITMAP as is, but also make a copy of the bitmap
|
| 267 | 268 | // to ensure that HBITMAP remains valid for as long as we need it
|
| 268 | 269 | m_bitmap = bitmap;
|
| 269 | - m_currentHandle = bitmap.GetHandle();
|
|
| 270 | + currentHandle = bitmap.GetHandle();
|
|
| 270 | 271 | }
|
| 271 | 272 | }
|
| 272 | 273 | |
| 273 | 274 | const bool isIcon = m_icon.IsOk();
|
| 274 | 275 | if ( isIcon )
|
| 275 | 276 | {
|
| 276 | - m_currentHandle = m_icon.GetHandle();
|
|
| 277 | + currentHandle = m_icon.GetHandle();
|
|
| 277 | 278 | }
|
| 278 | 279 | |
| 279 | 280 | if ( isIcon != wasIcon )
|
| ... | ... | @@ -287,18 +288,22 @@ void wxStaticBitmap::DoUpdateImage(const wxSize& sizeOld, bool wasIcon) |
| 287 | 288 | // Update the handle used by the native control.
|
| 288 | 289 | const WPARAM imageType = m_icon.IsOk() ? IMAGE_ICON : IMAGE_BITMAP;
|
| 289 | 290 | |
| 290 | - const HGDIOBJ currentHandle = (HGDIOBJ)m_currentHandle;
|
|
| 291 | 291 | const HGDIOBJ oldHandle = (HGDIOBJ)
|
| 292 | 292 | ::SendMessage(GetHwnd(), STM_SETIMAGE, imageType, (LPARAM)currentHandle);
|
| 293 | 293 | |
| 294 | 294 | // detect if this is still the handle we passed before or
|
| 295 | 295 | // if the static-control made a copy of the bitmap!
|
| 296 | - if ( oldHandle != 0 && oldHandle != currentHandle )
|
|
| 296 | + if ( oldHandle != 0 && oldHandle != m_currentHandle )
|
|
| 297 | 297 | {
|
| 298 | 298 | // the static control made a copy and we are responsible for deleting it
|
| 299 | 299 | ::DeleteObject(oldHandle);
|
| 300 | 300 | }
|
| 301 | 301 | |
| 302 | + // Note that m_currentHandle must be changed after comparing its previous
|
|
| 303 | + // value with oldHandle above and before possibly freeing it in the call to
|
|
| 304 | + // Free() below.
|
|
| 305 | + m_currentHandle = currentHandle;
|
|
| 306 | + |
|
| 302 | 307 | // Also check if we need to keep our current handle, it may be unnecessary
|
| 303 | 308 | // if the native control doesn't actually use it.
|
| 304 | 309 | const HGDIOBJ newHandle = (HGDIOBJ)
|
| ... | ... | @@ -3255,9 +3255,17 @@ wxIMPLEMENT_DYNAMIC_CLASS(wxXmlResourceModule, wxModule); |
| 3255 | 3255 | // then the built-in module system won't pick this one up. Add it manually.
|
| 3256 | 3256 | void wxXmlInitResourceModule()
|
| 3257 | 3257 | {
|
| 3258 | - wxModule* module = new wxXmlResourceModule;
|
|
| 3259 | - wxModule::RegisterModule(module);
|
|
| 3260 | - wxModule::InitializeModules();
|
|
| 3258 | + // Only add the module if the module system is already initialized,
|
|
| 3259 | + // otherwise it will be added automatically when it is done as it will be
|
|
| 3260 | + // known to the module system at that time (this happens as soon as the
|
|
| 3261 | + // code containing this function is loaded from a shared library and if
|
|
| 3262 | + // this function is running, it must have been already loaded!).
|
|
| 3263 | + if ( wxModule::AreInitialized() )
|
|
| 3264 | + {
|
|
| 3265 | + // Still check that this module is not already registered, as could
|
|
| 3266 | + // happen if this function is called multiple times, for example.
|
|
| 3267 | + wxModule::AddModuleIfNecessary(wxCLASSINFO(wxXmlResourceModule));
|
|
| 3268 | + }
|
|
| 3261 | 3269 | }
|
| 3262 | 3270 | |
| 3263 | 3271 | #endif // wxUSE_XRC |
| ... | ... | @@ -239,6 +239,7 @@ TEST_GUI_OBJECTS = \ |
| 239 | 239 | test_gui_slidertest.o \
|
| 240 | 240 | test_gui_spinctrldbltest.o \
|
| 241 | 241 | test_gui_spinctrltest.o \
|
| 242 | + test_gui_statbmptest.o \
|
|
| 242 | 243 | test_gui_styledtextctrltest.o \
|
| 243 | 244 | test_gui_textctrltest.o \
|
| 244 | 245 | test_gui_textentrytest.o \
|
| ... | ... | @@ -1138,6 +1139,9 @@ test_gui_spinctrldbltest.o: $(srcdir)/controls/spinctrldbltest.cpp $(TEST_GUI_OD |
| 1138 | 1139 | test_gui_spinctrltest.o: $(srcdir)/controls/spinctrltest.cpp $(TEST_GUI_ODEP)
|
| 1139 | 1140 | $(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/controls/spinctrltest.cpp
|
| 1140 | 1141 | |
| 1142 | +test_gui_statbmptest.o: $(srcdir)/controls/statbmptest.cpp $(TEST_GUI_ODEP)
|
|
| 1143 | + $(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/controls/statbmptest.cpp
|
|
| 1144 | + |
|
| 1141 | 1145 | test_gui_styledtextctrltest.o: $(srcdir)/controls/styledtextctrltest.cpp $(TEST_GUI_ODEP)
|
| 1142 | 1146 | $(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/controls/styledtextctrltest.cpp
|
| 1143 | 1147 |
| 1 | +///////////////////////////////////////////////////////////////////////////////
|
|
| 2 | +// Name: tests/controls/statbmptest.cpp
|
|
| 3 | +// Purpose: wxStaticBitmap unit test
|
|
| 4 | +// Author: Vadim Zeitlin
|
|
| 5 | +// Created: 2026-01-22
|
|
| 6 | +// Copyright: (c) 2026 Vadim Zeitlin
|
|
| 7 | +///////////////////////////////////////////////////////////////////////////////
|
|
| 8 | + |
|
| 9 | +#include "testprec.h"
|
|
| 10 | + |
|
| 11 | +#if wxUSE_STATBMP
|
|
| 12 | + |
|
| 13 | +#ifndef WX_PRECOMP
|
|
| 14 | + #include "wx/app.h"
|
|
| 15 | +#endif // WX_PRECOMP
|
|
| 16 | + |
|
| 17 | +#include "wx/statbmp.h"
|
|
| 18 | + |
|
| 19 | +#include "wx/dcmemory.h"
|
|
| 20 | + |
|
| 21 | +#ifdef __WINDOWS__
|
|
| 22 | + #include "wx/msw/private/resource_usage.h"
|
|
| 23 | +#endif // __WINDOWS__
|
|
| 24 | + |
|
| 25 | +static void TestStaticBitmap(wxWindow* window, double scale)
|
|
| 26 | +{
|
|
| 27 | + // Create the bitmaps using the window scale factor to make sure they
|
|
| 28 | + // are used directly by wxStaticBitmap instead, without any rescaling
|
|
| 29 | + // (which would create temporary bitmaps and hide the manifestation of
|
|
| 30 | + // the bug that this test was written to trigger, see #26106).
|
|
| 31 | + wxBitmap bmp1;
|
|
| 32 | + bmp1.CreateWithDIPSize(16, 16, scale);
|
|
| 33 | + wxBitmap bmp2;
|
|
| 34 | + bmp2.CreateWithDIPSize(32, 32, scale);
|
|
| 35 | + |
|
| 36 | + wxIcon icon;
|
|
| 37 | + icon.CopyFromBitmap(bmp1);
|
|
| 38 | + |
|
| 39 | + wxStaticBitmap statbmp(window, wxID_ANY, bmp1);
|
|
| 40 | + REQUIRE( statbmp.GetBitmap().IsOk() );
|
|
| 41 | + |
|
| 42 | + statbmp.SetBitmap(bmp2);
|
|
| 43 | + REQUIRE( statbmp.GetBitmap().IsOk() );
|
|
| 44 | + |
|
| 45 | + // Check that the bitmap is valid by selecting it into wxMemoryDC:
|
|
| 46 | + // unlike IsOk() check, this would fail if it had been destroyed.
|
|
| 47 | + REQUIRE_NOTHROW( wxMemoryDC(bmp1) );
|
|
| 48 | + |
|
| 49 | + statbmp.SetIcon(icon);
|
|
| 50 | + REQUIRE_NOTHROW( wxMemoryDC(bmp2) );
|
|
| 51 | + |
|
| 52 | + statbmp.SetBitmap(bmp2);
|
|
| 53 | + REQUIRE_NOTHROW( wxMemoryDC(bmp1) );
|
|
| 54 | +}
|
|
| 55 | + |
|
| 56 | +TEST_CASE("wxStaticBitmap::Set", "[wxStaticBitmap][bitmap]")
|
|
| 57 | +{
|
|
| 58 | + auto* const window = wxTheApp->GetTopWindow();
|
|
| 59 | + |
|
| 60 | + TestStaticBitmap(window, window->GetDPIScaleFactor());
|
|
| 61 | +}
|
|
| 62 | + |
|
| 63 | +#ifdef __WINDOWS__
|
|
| 64 | + |
|
| 65 | +TEST_CASE("wxStaticBitmap::ResourceLeak", "[wxStaticBitmap]")
|
|
| 66 | +{
|
|
| 67 | + auto* const window = wxTheApp->GetTopWindow();
|
|
| 68 | + auto const scale = window->GetDPIScaleFactor();
|
|
| 69 | + |
|
| 70 | + wxGUIObjectUsage usageBefore;
|
|
| 71 | + for ( int n = 0; n < 100; ++n )
|
|
| 72 | + {
|
|
| 73 | + TestStaticBitmap(window, scale);
|
|
| 74 | + |
|
| 75 | + // First use of GDI allocates some resources, so consider the state
|
|
| 76 | + // after the first iteration as the baseline: if there are any leaks,
|
|
| 77 | + // we'll see them in the subsequent iterations too and the test below
|
|
| 78 | + // will fail.
|
|
| 79 | + if ( n == 0 )
|
|
| 80 | + usageBefore = wxGetCurrentlyUsedResources();
|
|
| 81 | + }
|
|
| 82 | + |
|
| 83 | + auto const usageAfter = wxGetCurrentlyUsedResources();
|
|
| 84 | + CHECK( usageAfter.numGDI == usageBefore.numGDI );
|
|
| 85 | +}
|
|
| 86 | + |
|
| 87 | +#endif // __WINDOWS__
|
|
| 88 | + |
|
| 89 | +#endif // wxUSE_STATBMP |
| ... | ... | @@ -212,6 +212,7 @@ TEST_GUI_OBJECTS = \ |
| 212 | 212 | $(OBJS)\test_gui_slidertest.o \
|
| 213 | 213 | $(OBJS)\test_gui_spinctrldbltest.o \
|
| 214 | 214 | $(OBJS)\test_gui_spinctrltest.o \
|
| 215 | + $(OBJS)\test_gui_statbmptest.o \
|
|
| 215 | 216 | $(OBJS)\test_gui_styledtextctrltest.o \
|
| 216 | 217 | $(OBJS)\test_gui_textctrltest.o \
|
| 217 | 218 | $(OBJS)\test_gui_textentrytest.o \
|
| ... | ... | @@ -1085,6 +1086,9 @@ $(OBJS)\test_gui_spinctrldbltest.o: ./controls/spinctrldbltest.cpp |
| 1085 | 1086 | $(OBJS)\test_gui_spinctrltest.o: ./controls/spinctrltest.cpp
|
| 1086 | 1087 | $(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
|
| 1087 | 1088 | |
| 1089 | +$(OBJS)\test_gui_statbmptest.o: ./controls/statbmptest.cpp
|
|
| 1090 | + $(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
|
|
| 1091 | + |
|
| 1088 | 1092 | $(OBJS)\test_gui_styledtextctrltest.o: ./controls/styledtextctrltest.cpp
|
| 1089 | 1093 | $(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
|
| 1090 | 1094 |
| ... | ... | @@ -227,6 +227,7 @@ TEST_GUI_OBJECTS = \ |
| 227 | 227 | $(OBJS)\test_gui_slidertest.obj \
|
| 228 | 228 | $(OBJS)\test_gui_spinctrldbltest.obj \
|
| 229 | 229 | $(OBJS)\test_gui_spinctrltest.obj \
|
| 230 | + $(OBJS)\test_gui_statbmptest.obj \
|
|
| 230 | 231 | $(OBJS)\test_gui_styledtextctrltest.obj \
|
| 231 | 232 | $(OBJS)\test_gui_textctrltest.obj \
|
| 232 | 233 | $(OBJS)\test_gui_textentrytest.obj \
|
| ... | ... | @@ -1384,6 +1385,9 @@ $(OBJS)\test_gui_spinctrldbltest.obj: .\controls\spinctrldbltest.cpp |
| 1384 | 1385 | $(OBJS)\test_gui_spinctrltest.obj: .\controls\spinctrltest.cpp
|
| 1385 | 1386 | $(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\controls\spinctrltest.cpp
|
| 1386 | 1387 | |
| 1388 | +$(OBJS)\test_gui_statbmptest.obj: .\controls\statbmptest.cpp
|
|
| 1389 | + $(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\controls\statbmptest.cpp
|
|
| 1390 | + |
|
| 1387 | 1391 | $(OBJS)\test_gui_styledtextctrltest.obj: .\controls\styledtextctrltest.cpp
|
| 1388 | 1392 | $(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\controls\styledtextctrltest.cpp
|
| 1389 | 1393 |
| ... | ... | @@ -252,6 +252,7 @@ |
| 252 | 252 | controls/slidertest.cpp
|
| 253 | 253 | controls/spinctrldbltest.cpp
|
| 254 | 254 | controls/spinctrltest.cpp
|
| 255 | + controls/statbmptest.cpp
|
|
| 255 | 256 | controls/styledtextctrltest.cpp
|
| 256 | 257 | controls/textctrltest.cpp
|
| 257 | 258 | controls/textentrytest.cpp
|
| ... | ... | @@ -954,6 +954,7 @@ |
| 954 | 954 | <ClCompile Include="controls\slidertest.cpp" />
|
| 955 | 955 | <ClCompile Include="controls\spinctrldbltest.cpp" />
|
| 956 | 956 | <ClCompile Include="controls\spinctrltest.cpp" />
|
| 957 | + <ClCompile Include="controls\statbmptest.cpp" />
|
|
| 957 | 958 | <ClCompile Include="controls\styledtextctrltest.cpp" />
|
| 958 | 959 | <ClCompile Include="controls\textctrltest.cpp" />
|
| 959 | 960 | <ClCompile Include="controls\textentrytest.cpp" />
|
| ... | ... | @@ -248,6 +248,9 @@ |
| 248 | 248 | <ClCompile Include="controls\spinctrltest.cpp">
|
| 249 | 249 | <Filter>Source Files</Filter>
|
| 250 | 250 | </ClCompile>
|
| 251 | + <ClCompile Include="controls\statbmptest.cpp">
|
|
| 252 | + <Filter>Source Files</Filter>
|
|
| 253 | + </ClCompile>
|
|
| 251 | 254 | <ClCompile Include="controls\styledtextctrltest.cpp">
|
| 252 | 255 | <Filter>Source Files</Filter>
|
| 253 | 256 | </ClCompile>
|
—
View it on GitLab.
You're receiving this email because of your account on gitlab.com. Manage all notifications · Help