XRCCTRL issue after reloading the same xrc dialog
Operating system: Windows 7 x64 wxPython version: 4.0.0b1 Stock or custom build: Stock Python version: 3.6.2 Stock or custom build: Stock
When loading a dialog from XRC twice (create -> destroy -> re-create), controls references returned by XRCCTRL are wrong. I'm joining a small test case. To expose the issue :
- Run the script.
- Click on "Open dialog" button
- Close the dialog.
- Click on "Open dialog" button again --> Crash
Note 1 : Enabling the line 112 seems to fix the problem but it should not be mandatory. Note 2 : The same code running on Linux + GTK 2 fail too, but after repeating around 10x create/close sequence. Note 3 : This issue is not specific to wxDialog, I was able to reproduce it on wxPanel too.
I've spent some time on this and although I don't have a definitive answer yet it seems to be a good idea to record some findings here so I don't forget about them when I come back to it again one day.
I found it to be inconsistent on all platforms, not just non-Windows. It definitely seems to be related to timing and/or environmental interaction whether the failure occurs or not.
Not holding a reference to the dialog after it's has been Destroy()ed does seem to prevent the problem, although I'm not completely sure why yet. Although the C++ objects will be gone by the time the dialog is created again, the Python proxy objects will still exist until just after the next instance of the dialog is created. So perhaps there is some interaction with the old proxy objects that is confusing things.
Although XRCCTRL is giving the wrong type, it appears that the widgets on the dialog appear to be correct.
Just to keep you informed: I came across this problem in a more complex code. This time, releasing the reference on the previous dialog was not enough to solve the problem because the new dialog was created immediately after the destruction of the previous one (in the same event handler). Here is the sequence that I have used to workaround the issue this time:
1 - Destroy the previous dialog. 2 - Release the reference on the previous dialog. 3 - Delay the creation of the new dialog with a CallLater 10ms (CallAfter is not enough)
So, I confirm the "timing" influence. Forcing a garbage collection after releasing the reference didn't help.
Update:
To me it is looking like SIP is erroneously equating a dead widget and a new widget, because they were allocated to the same memory address.
I added some code to spit out memory addresses of widgets upon creation and this is what I got.
New Dialog populated:
btn-><wx._core.Button object at 0x7f3b32ace8b8>
tc-><wx._core.TextCtrl object at 0x7f3b32ace948>
sc-><wx._core.SpinCtrl object at 0x7f3b32ace9d8>
sb-><wx._core.StaticBitmap object at 0x7f3b32acea68>
choice-><wx._core.Choice object at 0x7f3b32aceaf8>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
New Dialog populated:
btn-><wx._core.Button object at 0x7f3b32acec18>
tc-><wx._core.TextCtrl object at 0x7f3b32aceca8>
sc-><wx._core.SpinCtrl object at 0x7f3b32aced38>
sb-><wx._core.StaticBitmap object at 0x7f3b32acedc8>
choice-><wx._core.Choice object at 0x7f3b32acee58>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
New Dialog populated:
btn-><wx._core.Button object at 0x7f3b32acec18>
tc-><wx._core.TextCtrl object at 0x7f3b32ace8b8>
sc-><wx._core.SpinCtrl object at 0x7f3b32ace9d8>
sb-><wx._core.SpinCtrl object at 0x7f3b32aced38>
choice-><wx._core.Choice object at 0x7f3b32ace948>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expect wx.Button - got : <class 'wx._core.Button'> as <wx._core.Button object at 0x7f3b32acec18>
Expect wx.TextCtrl - got : <class 'wx._core.TextCtrl'> as <wx._core.TextCtrl object at 0x7f3b32ace8b8>
Expect wx.SpinCtrl - got : <class 'wx._core.SpinCtrl'> as <wx._core.SpinCtrl object at 0x7f3b32ace9d8>
Expect wx.StaticBitmap - got : <class 'wx._core.SpinCtrl'> as <wx._core.SpinCtrl object at 0x7f3b32aced38>
Expect wx.Choice - got : <class 'wx._core.Choice'> as <wx._core.Choice object at 0x7f3b32ace948>
Traceback (most recent call last):
File "xrcissue.py", line 113, in onDialog
self._dlg = testDialog(self)
File "xrcissue.py", line 93, in __init__
assert( isinstance(self._sb, wx.StaticBitmap) )
AssertionError
In this run, I created the dialog three times, and crashed on the third attempt. Looking at the memory addresses, the third static bitmap was allocated to the same section of memory as the previous SpinCtrl.
you could easily get varying errors if it was different widgets 'overlapping'
I'll need to come up with some more direct tests to be sure I'm correct.
Found a fix that worked for me: At initialization, instead of declaring the XmlResource global (self), work locally and inside an infinite loop until all controls are correctly instantiated.
Instead of:
def frameInit(self):
self.res = xrc.XmlResource("myFrame.xrc")
self.frameMain = self.res.LoadFrame(None, "frameMain")
self.myComboBox1 = xrc.XRCTRL(self.frameMain, "myComboBox1")
self.myComboBox2 = xrc.XRCTRL(self.frameMain, "myComboBox2")
Approach this way:
def frameInit(self):
while True:
allOk = True
res = xrc.XmlResource("myFrame.xrc")
frameMain = res.LoadFrame(None, "frameMain")
self.myComboBox1 = xrc.XRCCTRL(frameMain, "myComboBox1")
self.myComboBox2 = xrc.XRCCTRL(frameMain, "myComboBox2")
# Check for correct type. Change string value acording the control.
if "ComboBox" not in str(type(self.myComboBox1)):
allOk = False
if "ComboBox" not in str(type(self.myComboBox2)):
allOk = False
if allOk == True:
self.frameMain = frameMain
break
Rude but save my day...
I am encountering this same issue. I'm not certain what I can do to provide more information. If there's no current workaround I'll have to abandon XRC.
This issue still exists in wxPython v4.1.0 + Python 3.7
Is there really nothing to do about this? XRC makes window creation code far more clearer !
This issue still exists in wxPython v4.2.0 + Python 3.7/3.8/3.10 (win64)
For people still concerned by this bug, I've found a workaround. Registering a custom XML handler for all kind of resources found in your XRC file do the trick.