Auga
Auga

Reputation: 25

Destroying and Recreating a Panel wx.Python

This is the bane of my life:

Traceback (most recent call last): File "C:\Users\User\Documents\PYTHON\GAME FILES\simple.py", line 18, in OnDestroy self.Destroy() File "C:\Python26\lib\site-packages\wx-2.8-msw-ansi\wx_core.py", line 14610, in getattr_ raise PyDeadObjectError(self.attrStr % self._name) wx._core.PyDeadObjectError: The C++ part of the MyPanels object has been deleted, attribute > access no longer allowed.

My Goal: To have two panels, with the left panel killing and "recreating" the right panel/an identical copy via buttons, simulating how a user can make a choice in the left hand panel about what they want to see and have the right panel display this. That's what I'm trying to do in my more complicated programme, but that was failing, so am trying to learn how to destroy and recreate a blank panel here. Still same TERRIFYING error.

Code:

import wx
import sys
import traceback


def show_error():
    message = ''.join(traceback.format_exception(*sys.exc_info()))
    dialog = wx.MessageDialog(None, message, 'Error!', wx.OK|wx.ICON_ERROR)
    dialog.ShowModal()

class MyPanels(wx.Panel):

 def __init__(self, parent, id):
        wx.Panel.__init__(self, parent)
        self.parent = parent

 def OnDestroy(self, event):

    self.Destroy()

 def OnTest(self, event):
    print "Hello"

class MyFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(1000, 480))
        self.parent = parent

        self.panel = MyPanels(self, -1)
        self.panel.SetBackgroundColour("grey")

        self.panel.leftpanel = MyPanels(self.panel, 1)
        self.panel.rightpanel = MyPanels(self.panel, 1)
        self.panel.leftpanel.SetBackgroundColour("red")
        self.panel.rightpanel.SetBackgroundColour("green")

        self.panel.basicsizer = wx.BoxSizer(wx.HORIZONTAL)
        self.panel.basicsizer.Add(self.panel.leftpanel, 1, wx.EXPAND)
        self.panel.basicsizer.Add(self.panel.rightpanel, 1, wx.EXPAND)
        self.panel.SetSizer(self.panel.basicsizer)

        button =  wx.Button(self.panel.leftpanel, 1, 'DIE DIE DIE', (50, 130))
        buttonres = wx.Button(self.panel.leftpanel, 2, 'Resurrect', (50, 230))
        buttonextra = wx.Button(self.panel.leftpanel, 3, 'Test', (50, 330))

        self.Bind(wx.EVT_BUTTON, self.panel.rightpanel.OnDestroy, id = 1)
        self.Bind(wx.EVT_BUTTON, self.CreateNewPanel, id = 2)
        self.Bind(wx.EVT_BUTTON, self.panel.rightpanel.OnTest, id = 3)




    def CreateNewPanel(self, event):
        self.panel.rightpanel = MyPanels(self.panel, 1)
        self.panel.rightpanel.SetBackgroundColour("green")

        self.panel.basicsizer.Add(self.panel.rightpanel, 1, wx.EXPAND)
        self.panel.SetSizer(self.panel.basicsizer)

        self.panel.rightpanel.Refresh()
        self.panel.leftpanel.Refresh()
        self.panel.leftpanel.Layout()
        self.panel.leftpanel.Update()
        self.panel.Layout()
        self.Update()


        self.Show(True)
        self.Centre()



def main():
    app = wx.App()
    try:
        frame = MyFrame(None, -1, 'Die.py')
        frame.Show()
        app.MainLoop()
    except:
        show_error()

if __name__ == '__main__':
    main()

Essentially, it seems like I've (finally) got the Resurrect Button to work, at least in that the 'new' panel fills the appropriate space. But if I try to Kill it again, that's when the scary error pops up.

I've been researching the error message, and it seems I'm getting this because the self.Destroy() destroyed something that is still needed by something in the programme. I'm finding this difficult to understand, because I thought I was creating a perfect identical copy, down to the same name.

The only thing I think it can be is the Kill button itself. It's as if it requires the original rightpanel to destroy again, that it is locked into its unique class reference, rather than its name of rightpanel. I added a third button to test it. This third button has almost identical code, it just calls a different Method in the MyPanels class. And...it still works after the Kill and Resurrect. But pressing Kill again does not work. The only difference between them is that the OnDestroy method uses 'self'.

Somehow I guess I have got to stop the Kill button's event binding itself to the 'self' of the original right panel. I am utterly lost. I've tried hopelessly telling the left panel to refresh/update itself. Nothing. Have spent the last two days trying to work this out. I'm new to python and wxpython and am utterly in over my head here, but please, please help me out.

EDITED-

ok, had a mini-brainwave just now. I think once an event is bound with the Bind command, it is bound to that widget forever, unless it is changed. That is why it is still clinging to a dead panel. So if I unbind it and then rebind it, is that the solution?

I've changed the button name to self.button. And then in the def CreateNewPanel() method, I've put an Unbind command in...and then immediately put copy pasted the Bind command. It seems to be working now!!!!

But... can someone tell me: 1) is this the right solution or just some cumbersome workaround that I've come up with and 2) Why why why does the whole frame jump half an inch across my screen when I call self.panel.rightpanel.Refresh()?

** Edit -

Now that I seem to be able to destroy and recreate RightPanel based on LeftPanel's buttons, I've tried to manage this on the complex programme. The complex programme is designed to show information (bio + pic) of a character in the RightPanel, based on user selection of a listctrl of names on LeftPanel.

So it's very similar to this: instead of having an empty RightPanel, it has tons of widgets.

And it does work...to an extent. If I click to see Character X, RightPanel destroys itself and then recreates itself with Character Y's widgets. Yay. But it also temporarily flashes a tiny little square to the top left corner of RightPanel that clearly contains copies of all the widgets.

This square vanishes almost immediately, but it's clearly visible everytime you click to change RightPanel's contents. It's pretty ugly. I just can't get rid of it! I've tried dotting in .Layout() everywhere and to everything, nope. Can't work out why it would appear and then disappear! Is it a programme delay that I can't get rid of? Or some error I'm making with my sizers? Must stress - the widgets lay out perfectly as I intended...so the sizers are surely working fine...right? **

Upvotes: 2

Views: 8048

Answers (2)

Mike Driscoll
Mike Driscoll

Reputation: 33111

I think the problem here is that you are tying everything together in a very odd manner. You don't normally create nested panels by doing MyPanel.panel = wx.Panel(). Instead, you create them the normal way you create any panel:

panelOne = wx.Panel(self)
panelTwo = wx.Panel(self)
panelThree = MyPanels(parent, id)

I would also avoid creating sizers the way you do. I use something like

mySizer = wx.BoxSizer()

I ended up cleaning out a bunch of the weird junk to make this work. I don't see a reason to destroy the panel, so I just hid it instead:

import wx
import sys
import traceback

def show_error():
    message = ''.join(traceback.format_exception(*sys.exc_info()))
    dialog = wx.MessageDialog(None, message, 'Error!', wx.OK|wx.ICON_ERROR)
    dialog.ShowModal()

class MyPanels(wx.Panel):

    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent)
        self.parent = parent

class MyFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(1000, 480))
        self.parent = parent

        self.panel = wx.Panel(self, -1)
        self.panel.SetBackgroundColour("grey")

        self.leftpanel = MyPanels(self.panel, 1)
        self.rightpanel = MyPanels(self.panel, 1)
        self.leftpanel.SetBackgroundColour("red")
        self.rightpanel.SetBackgroundColour("green")

        self.basicsizer = wx.BoxSizer(wx.HORIZONTAL)
        self.basicsizer.Add(self.leftpanel, 1, wx.EXPAND)
        self.basicsizer.Add(self.rightpanel, 1, wx.EXPAND)
        self.panel.SetSizer(self.basicsizer)

        button =  wx.Button(self.leftpanel, 1, 'DIE DIE DIE', (50, 130))
        buttonres = wx.Button(self.leftpanel, 2, 'Resurrect', (50, 230))
        buttonextra = wx.Button(self.leftpanel, 3, 'Test', (50, 330))

        self.Bind(wx.EVT_BUTTON, self.destroyPanel, button)
        self.Bind(wx.EVT_BUTTON, self.CreateNewPanel, buttonres)

    def CreateNewPanel(self, event):
        self.rightpanel = MyPanels(self.panel, 1)
        self.rightpanel.SetBackgroundColour("green")
        self.basicsizer.Add(self.rightpanel, 1, wx.EXPAND)
        self.panel.Layout()

        self.Show(True)
        self.Centre()

    def destroyPanel(self, event):
        self.rightpanel.Hide()
        self.panel.Layout()

def main():
    app = wx.App()
    try:
        frame = MyFrame(None, -1, 'Die.py')
        frame.Show()
        app.MainLoop()
    except:
        show_error()

if __name__ == '__main__':
    main()

This exercise reminded me of my panel switching article from a couple years ago: http://www.blog.pythonlibrary.org/2010/06/16/wxpython-how-to-switch-between-panels/

Maybe that will help you too.

Upvotes: 3

Andrey Sobolev
Andrey Sobolev

Reputation: 12713

  1. Yes, that's the right solution, although more pythonic way would be to Unbind the Die button at the same time you destroy the panel so that pressing that button multiple times would not throw an exception. Something like this should do:

    self.Bind(wx.EVT_BUTTON, self.OnPanelDestroy, id = 1)
    
    def OnPanelDestroy(self, event):
        self.Unbind(wx.EVT_BUTTON, id = 1)
        self.panel.rightpanel.OnDestroy(event)
    
    def CreateNewPanel(self, event):
        self.panel.rightpanel = MyPanels(self.panel, 1)
        self.panel.rightpanel.SetBackgroundColour("green")
        self.Bind(wx.EVT_BUTTON, self.OnPanelDestroy, id = 1)
    
  2. If I understood you correctly, that's because of calling self.Centre() rather than self.panel.rightpanel.Refresh(). If you comment it out, the frame wouldn't center on the screen, but stay put.

Upvotes: 1

Related Questions