Question

I am trying to put together a gui in Python for a school project, but I am getting an error and don't really understand why.

self.prompt.addAnswer(i, self.ansControls[i].GetValue()) File "C:\Python27\lib\site-packages\wx-3.0-msw\wx_core.py", line 16712, in getattr raise PyDeadObjectError(self.attrStr % self._name) wx._core.PyDeadObjectError: The C++ part of the TextCtrl object has been deleted, attribute access no longer allowed.

I understand what the error means, The TextCtrl object no longer exists so I can't access it. I do not understand why the TextCtrl object does not exist anymore. Here is how the flow goes:

The frame appears with labels, TextCtrls, and buttons. The user enters in data and hits next. Everything goes smoothly. A different instance of the same PromptFrame class is then created, and the same thing happens. This time however, when the user hits next, I get the aforementioned error. Here is the code:

The service in the background running the show:

class AppService(object):

    prompts = [Prompt_1, Prompt_2, Prompt_3, Prompt_4, Prompt_5, Prompt_6, Prompt_7,
            Prompt_8, Prompt_9, Prompt_10, Prompt_11, Prompt_12, Prompt_13, Prompt_14,
             Prompt_15, Prompt_16, Prompt_17, Prompt_18, Prompt_19]

    skippedPromptIndices = []

    def __init__(self):
        print "Service Started"
        PromptFrame(self, self.prompts[0], 0, len(self.prompts))

    def doBack(self, curIndex, curPrompt):
        if curIndex >= 0:
            self.prompts[curIndex] = curPrompt
            PromptFrame(self, self.prompts[curIndex - 1], curIndex - 1, len(self.prompts))
        else:
            posCurIndex = (curIndex * -1) - 1
            self.prompts[posCurIndex] = curPrompt

            backIndex = self.skippedPromptIndices.index(curIndex) - 1
            nextPromptIndex = 0
            if backIndex < 0:
                nextPromptIndex = len(self.prompts) - 1
            else:
                nextPromptIndex = self.skippedPromptIndices[backIndex]

            PromptFrame(self, self.prompts[(nextPromptIndex * -1) - 1], nextPromptIndex, len(self.prompts))

    def doSkip(self, curIndex, curPrompt):
        skipIndex = (curIndex + 1) * -1
        if self.skippedPromptIndices.count(skipIndex) > 0:
            self.skippedPromptIndices.remove(skipIndex)

        self.skippedPromptIndices.append(skipIndex)
        self.doNext(curIndex, curPrompt)

    def doNext(self, curIndex, curPrompt):
        if curIndex >= 0:
            self.prompts[curIndex] = curPrompt
        else:
            self.prompts[(curIndex * -1) - 1] = curPrompt


        if (curIndex >= 0 and curIndex < (len(self.prompts) - 1)):
            PromptFrame(self, self.prompts[curIndex + 1], curIndex + 1, len(self.prompts))
        elif len(self.skippedPromptIndices) > 0:
            skipIndex = self.skippedPromptIndices.pop(0)
            nextIndex = (skipIndex * -1) - 1
            PromptFrame(self, self.prompts[nextIndex], skipIndex, len(self.prompts))
        else:
            dlg = wx.MessageDialog(self, "Done!", "Message", wx.OK)
            dlg.ShowModal() # Shows it
            dlg.Destroy() # finally destroy it when finished.

and here is the PromptFrame Class:

class PromptFrame(wx.Frame):

    appService = None
    prompt = None
    promptIndex = 0
    numPrompts = 0
    ansControls = []

    def __init__(self, appService, prompt=testPrompt, promptIndex=0, numPrompts=0):
        print "New Prompt Frame!"
        self.appService = appService
        self.prompt = prompt
        self.promptIndex = promptIndex
        self.numPrompts = numPrompts

        wx.Frame.__init__(self, None, wx.ID_ANY, title=prompt.title)
        self.panel = wx.Panel(self, wx.ID_ANY)
        self.panel.SetBackgroundColour('#2FC0E0') #0013E3, #66DFFA, #2FC0E0
        self.Maximize()
        self.CreateStatusBar() # A Statusbar in the bottom of the window


        # Setting up the menu.
        filemenu= wx.Menu()
        menuAbout= filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
        menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")

        # Creating the menubar.
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
        self.SetMenuBar(menuBar)  # Adding the MenuBar to the Frame content.

        # Creating fonts for the controls
        titleFont = wx.Font(24, wx.DECORATIVE, wx.ITALIC, wx.BOLD)
        qFont = wx.Font(12, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)

        #Creating layout
        vertSizer = wx.BoxSizer(wx.VERTICAL)

        lblTitle = wx.StaticText(self.panel, wx.ID_ANY, prompt.title)
        lblTitle.SetFont(titleFont)

        vertSizer.Add(lblTitle, 0, wx.ALIGN_CENTER | wx.TOP, border=30)
        vertSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY), 0, wx.EXPAND)
        vertSizer.AddStretchSpacer(2)

        for i in range(len(prompt.questions)):
            if prompt.qTypes[i] == 0:
                lbl = wx.StaticText(self.panel, wx.ID_ANY, prompt.questions[i], size=(200, -1))
                lbl.SetFont(qFont)
                lbl.Wrap(200)                                    
                ans = wx.TextCtrl(self.panel, wx.ID_ANY, size=(200,-1))
                if not prompt.answers[i] == None:
                    ans.SetValue(prompt.answers[i])
                self.ansControls.append(ans)

                horizSizer = wx.BoxSizer(wx.HORIZONTAL)
                horizSizer.Add((30, -1), 0)
                horizSizer.Add(lbl, 0)
                horizSizer.Add((20, -1), 0)
                horizSizer.Add(self.ansControls[len(self.ansControls) - 1], 0)

                vertSizer.Add(horizSizer, 0)
                vertSizer.AddStretchSpacer(1)

        print self.ansControls

        vertSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY), 0, wx.EXPAND)

        self.btnBack = wx.Button(self.panel, wx.ID_ANY, "Back")
        self.btnSkip = wx.Button(self.panel, wx.ID_ANY, "Skip")
        self.btnNext = wx.Button(self.panel, wx.ID_ANY, "Next")

        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        btnSizer.Add(self.btnBack, 0, wx.RIGHT, border=30)
        btnSizer.Add(self.btnSkip, 0)
        btnSizer.Add(self.btnNext, 0, wx.LEFT, border=30)
        btnSizer.AddStretchSpacer(1)

        vertSizer.AddStretchSpacer(2)
        vertSizer.Add(btnSizer, 0, wx.ALIGN_CENTER)        
        vertSizer.AddStretchSpacer(2)

        lblPage = wx.StaticText(self.panel, wx.ID_ANY, "Page: " + str(self.promptIndex) + "/" + str(self.numPrompts))
        vertSizer.Add(lblPage, 0, wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT | wx.ALL, border=20)

        self.panel.SetSizer(vertSizer)

        # Events.
        self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
        self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
        self.Bind(wx.EVT_BUTTON, self.OnBack, self.btnBack)
        self.Bind(wx.EVT_BUTTON, self.OnSkip, self.btnSkip)
        self.Bind(wx.EVT_BUTTON, self.OnNext, self.btnNext)

        self.Show()

    def OnAbout(self,e):
        # Create a message dialog box
        aboutString = "This program was designed by students of Worcester Polytechnic Institute to aid water supply " \
                  "officials apply the World Health Organization's Water Safety Plans."
        dlg = wx.MessageDialog(self, aboutString, "About Water Safety Plan Helper", wx.OK)
        dlg.ShowModal() # Shows it
        dlg.Destroy() # finally destroy it when finished.

    def OnExit(self,e):
        self.Close(True)  # Close the frame.

    def OnBack(self, e):
        if (self.promptIndex > 0):
            self.SaveAns()
            self.appService.doBack(self.promptIndex, self.prompt)
            self.Close(True)
        else:
            errorString = "There are no previous pages to go back to"
            dlg = wx.MessageDialog(self, errorString, "Error", wx.OK)
            dlg.ShowModal() # Shows it
            dlg.Destroy() # finally destroy it when finished.

    def OnSkip(self, e):
        self.SaveAns()
        self.appService.doSkip(self.promptIndex, self.prompt)
        self.Close(True)

    def OnNext(self, e):
        self.SaveAns()
        self.appService.doNext(self.promptIndex, self.prompt)
        self.Close(True)

    def SaveAns(self):
        print self.ansControls
        for i in range(len(self.ansControls)):
            if self.prompt.qTypes[i] == 0:
                self.prompt.addAnswer(i, self.ansControls[i].GetValue())    

Thanks for your help guys!

EDIT: Here is my init.py file:

from MainFrame import MainFrame
import wx

app = wx.App(False)
frame = MainFrame(None, "My App")
app.MainLoop() 
Était-ce utile?

La solution

ansControls is currently defined as a class variable. Which means that any control defined in any window gets added to it.

You create a control in the first instance, it is added to the class, but the window belongs to the instance. So when you destroy the class, the instance gets destroyed, but the python object pointing to it still exists.

Then you open your second window, add more controls, and then hit the loop where you loop over them. The first ones in your loop will not have a valid C++ object below it anymore, and will fail.

Not sure why they were defined as class variables, but you either need to keep a pointer to the root window as well, delete class controls as the parent windows get deleted, or (simpler) just make ansControls an instance variable instead of a class variable...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top