Search code examples
python-3.xbuttonwxpythonframe

How can I avoid my program to keep running when I close my main frame?


I am currently learning wxPython library on my own. I got the idea to create a GUI with one main frame which can open a child one.

I know I can do it by compiling both frame into the same code, but for my project, I need to have them separated.

I succeeded in managing the opening of the child frame and its closing, but unfortunately, it creates a new problem in my parent frame.

Here are my codes :

wx_Practicing.py

import wx
import time
import wx_Practicing_child
import threading
import os
import sys

"""Class which defines my main frame."""

class MainWindow(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Test", wx.DefaultPosition,
        (1000,850), wx.DEFAULT_FRAME_STYLE, wx.FrameNameStr)

        # Click counter
        self.click = 0

        # Init of the opening variable which is set to 1 when a child frame is opened
        self.OpenButtonFlag = 0

        # Init of the frame child invoked by the parent frame
        self.child = wx_Practicing_child.MainWindow_child()
        self.child.label = "child"

        # Sizers
        sizer_hori = wx.BoxSizer(wx.HORIZONTAL)
        sizer_verti = wx.BoxSizer(wx.VERTICAL)

        # Init of the panel

        test_panel = PanelMainWindow(self)
        test_panel.SetSizer(sizer_verti)

        # Buttons declaration
        # Button to quit the main frame
        btn_quit = wx.Button(test_panel, label ="Quit")
        btn_quit.Bind(wx.EVT_BUTTON, self.OnQuit)
        sizer_verti.Add(btn_quit)

        # Button counting number of time you trigger it

        btn_counter = wx.Button(test_panel, label="Click counter")
        sizer_verti.Add(btn_counter)
        btn_counter.Bind(wx.EVT_LEFT_DOWN, self.OnCount)

        # Button opening the child frame

        btn_new_frame = wx.Button(test_panel, label = "Open new frame",
                                    pos=(100,100))

        btn_new_frame.Bind(wx.EVT_LEFT_DOWN, self.OnNewFrame)


        self.Bind(wx.EVT_CLOSE, self.OnClose)

        # Frame displaying
        self.Show()

    def OnClose(self, event):

        self.Destroy(True)

    # Method used to close the parent frame
    def OnQuit(self, event):
        self.Destroy()
        print("closed")


    # Method used to count number of click
    def OnCount(self, event):
        self.click +=1
        print(self.click)

    # Method calling wx_Practicing_child.py to open a child frame
    def OnNewFrame(self, event):
        if self.child.OpenButtonFlag == 0 :
            self.child = wx_Practicing_child.MainWindow_child()
            self.child.label = "child"
            print("Flag before",self.child.OpenButtonFlag)
            self.child.Show()
            print("new Frame opened")
            self.child.OpenButtonFlag = 1
        else :
            print("Frame already launched, close the previous one and retry")
        print("Flag after", self.child.OpenButtonFlag)


"""Class of the panel"""

class PanelMainWindow(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

test = wx.App(False)
frame = MainWindow()

test.MainLoop()

and wx_Practicing_child.py

import wx
import time


"""Classe définissant une frame (i.e la zone globale parente). Elle permet
de faire exister le panel et ses attributs."""

class MainWindow_child(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Test", wx.DefaultPosition,
        (1000,850), wx.DEFAULT_FRAME_STYLE, wx.FrameNameStr)

        self.OpenButtonFlag = 0
        self.label = "Child"

        # Sizers
        sizer_hori = wx.BoxSizer(wx.HORIZONTAL)
        sizer_verti = wx.BoxSizer(wx.VERTICAL)

        # Init of the panel

        test_panel_child = PanelMainWindow_child(self)
        test_panel_child.SetSizer(sizer_verti)

        # Buttons declaration
        # Button to quit the frame
        btn_quit = wx.Button(test_panel_child, label ="Quit")
        btn_quit.Bind(wx.EVT_LEFT_DOWN, self.OnQuit)
        sizer_verti.Add(btn_quit)

    # Method used to quit the frame
    def OnQuit(self, event):
        self.OpenButtonFlag = 0
        self.Destroy()
        print("child print", self.OpenButtonFlag)



"""Class which defines a panel for the child frame"""

class PanelMainWindow_child(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

So my main problem is that when I click on the "Quit" button or the "x" box on the parent frame (wx_practicing.py), the frame is closed, but the program does not shutdown. After few tries, I have noticed that it seems to be caused by the declaration of self.child in MainWindow.

However, I need this declaration to allow MainWindow to open a MainWindow_child. I have tried to add a self.child.Close() in my Onquit() method in my MainWindow class but it has been unsuccessful.

After some research, I have seen that I could maybe use CloseEvent handler, but I did not really understand its purpose and how it works.

I hope I have been clear enough.

NOTE: Both programs are in the same folder.


Solution

  • Welcome to StackOverflow

    The problem with your code is that you are creating two MainWindow_child instances. You can see this by adding print(self.child) below each line like self.child = wx_Practicing_child.MainWindow_child() in file wx_Practicing.py. If you run your code now and creates a child frame you get something like:

    <wx_Practicing_child.MainWindow_child object at 0x102e078b8>
    <wx_Practicing_child.MainWindow_child object at 0x1073373a8>
    Flag before 0
    new Frame opened
    Flag after 1
    

    The first two lines above mean that you have two instances of MainWindow_child and the pointer self.child points only to one of them. So when you use self.child.Destroy() or close the child frame in the normal way (red X button) you are destroying only one instance. The other instance keep living forever because you never show it, so you cannot press the close button to destroy it or use self.child.Destroy() because self.child is pointing to a different object. The living forever child frame is the reason why your program never closes.

    For more clarity, if you change the OnQuit() and OnClose() methods in wx_Practicing.py to:

    def OnClose(self, event):
        try:
            self.child.Destroy()
        except Exception:
            pass
        self.Destroy()
    
    # Method used to close the parent frame
    def OnQuit(self, event):
        try:
            self.child.Destroy()
        except Exception:
            pass
        self.Destroy()
        print("closed")
    

    the program will close if you do not press the Open new frame button because self.child points to the only instance of the child frame. Once you press the button then you have two instance, one of them not shown and without a pointer and the program does not close anymore.