Python – How to open a child window from the main window

childwindowpyqtpythonwindow

My question is tough to explain but I am trying my best. Please help me in this regard.

I designed a gui in QtDesigner and converted .ui files into .py e.g. main_window.py. Now in order to avoid changing in main_window.py I created another class for listeners.

class Main():    
    window = None
    app = None

    def __init__(self):
        self.launch()
        self.attach_listener()
        self.execute()

    ''' Launch GUI '''
    def launch(self):
        self.app = QtGui.QApplication(sys.argv)
        self.window = Ui_MainWindow()
        self.window.show()

    ''' Execute Window '''
    def execute(self):
        sys.exit(self.app.exec_())

    ''' Attach Listeners '''
    def attach_listener(self):
        self.window.add_button.clicked.connect(self.add_listener)
        self.window.delete_button.clicked.connect(self.delete_listener)
        self.window.update_button.clicked.connect(self.update_listener)
        self.window.connect(self.window.combo_box, QtCore.SIGNAL('activated(QString)'), self.logout_listener)

I have another child_window.py with same structure, but I can't open that window from this one because of QApplication. I searched for the answer but couldn't apply on my code. Those answers were applicable when class was extending from QtGui.QMainWindow or QtGui.QWidget, but my scenario is different.

Best Answer

You are mixing up the Ui_MainWindow object with the actual window object (QMainWindow, QDialog,QWidget etc.) self.window = Ui_MainWindow() doesn't do anything because the class you are attaching it to is not a Window. You need to create a window and apply the Ui_MainWindow to it.

Apparently you can make this work, but it doesn't look pretty. You need to access your widgets via findChild. The only benefit I can see is that you don't run pyside-uic after changing a form in the designer, and that's pretty easy.

The easier way

When you use pyuic / pyside-uic it converts the .ui files to .py files. You shouldn't edit the .py's as they will be overwritten the next time you use QtDesigner. You need to create a window class and apply the UI class to it.

Setting up a new form

  • Produce the file mainWinui.ui in QtDesigner - Class name MainWindow
  • Convert with pyside-uic -o mainWinui.py mainWinui.ui. mainWinui.py is never edited by hand
  • Create mainWinClass.py to load the ui.py and do all the custom UI work
  • Declare signals and slots, use .connect etc. in the mainWinClass.py

Some of these module names might seem a bit awkward, but I've settled on them because in the past I've had trouble with name clashes between modules and classes; caused by me not understanding how Qt Designer was going to handle it's naming.

If you look at the file that pyside-uic created, the top of it contains the correct class and method names that you need to use in your mainWinClass.py

mainWinui.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'mainWinui.ui'
#
# Created: Sun Feb  7 14:22:09 2016
#      by: pyside-uic 0.2.15 running on PySide 1.2.4
#
# WARNING! All changes made in this file will be lost!

from PySide import QtCore, QtGui

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")

Create a new mainwinClass.py and copy the correct imports and class names to it, plus a bit of boilerplate to load the .ui.

It looks like this:

mainWinClass.py

from mainWinui import Ui_MainWindow
from PySide import QtGui


class MainWin(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setup_signals()

# Is this is the same as your Listeners ??
def setup_signals(self):
    # Signal for updating the lineedits from the grid
    self.ui.tabWidget.currentChanged.connect(self.onTabChanged)
    # Connect the "Add Staff" button to the addStaffMember method
    self.ui.btnAddStaff.clicked.connect(self.addStaffMember)

Then use another file to launch the app itself, and to maintain some non GUI aspects of the app, like updaters or global logging. I've seen code where all the child windows are instantiated in here to, but I don't (normally) do it that way. I keep them in the main form instead. It depends on how you intend to design the app.

appname.py

from PySide import QtGui, QtCore
from mainwinClass import MainWin
import sys


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    mainWin = MainWin()

    mainWin.show()
    sys.exit(app.exec_())

    # Nothing else _needed_ in here

Now for any child windows follow the same again.

Modal forms

In Qt Designer create a new 'Dialog with buttons bottom'. Add widgets as desired and save as dialogAddStaffui.ui.

Run

pyside-uic -o dialogAddStaffui.py dialogAddStaffui.ui.

Create a new, empty text document called dialogAddStaffClass.py and using dialogAddStaffui.ui as a reference for class names etc. edit dialogAddStaffClass.py to look like this:

dialogAddStaffClass

from dialogAddStaffui import Ui_DialogAddStaff
from PySide import QtCore, QtGui


class DialogAddStaff(QtGui.QDialog):
    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        self.ui = Ui_DialogAddStaff()
        self.ui.setupUi(self)
        # Your own init stuff

Those two imports are the only ones needed here. If you are trying to copy this, realise that in the Qt Designer I have

windowModality = ApplicationModal and the form was a "Dialog with Buttons Bottom"

For these simple forms, they have an accept method that checks the validity of data that the user has entered and closes with self.done(1). If you want to see how the validation and close is handled:

dialogAddStaffClass

def validate(self):
    retval = True
    if not self.ui.editLname.text():
        retval = False
        QtGui.QMessageBox.information(self, 'Invalid Last name',
                                      "Last Name must not be blank")
        self.ui.editLname.setFocus()
    return retval

def accept(self):
    if self.validate():
        self.done(1)

With these dialog forms Qt has automatically set the OK button to fire accept. I just overrode that method.

If you want communication between parent and child you can either set a property on the child that references the parent, or read the the properties of the child after it closes, but before it's variable gets garbage collected. There can be issues with creating circular references so be careful.

As the new form is modal, the user can't interact with the Main Form until they have closed the Child Form, and the function that launches the chil d window will halt until the child window is closed, therefore it's ok to use a local variable to hold the child class.

The 'Add Staff' button is connected to the addStaffMember function.

mainWinClass.py

from dialogAddStaffClass import DialogAddStaff


def addStaffMember(self):
    addStaffForm = DialogAddStaff()
        res = addStaffForm.exec_()   # exec_ waits, show would continue
        # Function waits here for the Modal form to close.
        if res:   # child was closed with OK and validated correctly
            print(addStaffForm.ui.editLname.text())
            # Saveing other Staff data happens here

Because the child form is run with exec_, the main form waits untill the child form closes before continuing. When the function exits the addStaffForm variable is garbage collected, so there is no longer any reference to the attributes of the child form. (and probably no form ...)

If you want to open a Long lived form, you would instantiate it somewhere more long lasting.

Non Modal forms

Here is an example of a SisterForm. It is was created in the designer from the 'Main Window' type (It has it's own menu's and Status bar etc.). If you don't need these frills, use a Dialog form but set it's windowModality to NonModal.

  • Create sisterWinui.ui in Qt Designer - set objectName SisterWin
  • pyside-uic -o sisterWinui.py sisterWinui.ui
  • Create a file sisterwinClass.py - set up it's import and init
  • Make a variable to hold it in a long lived place (MainWin itself, use a self. ref attribute)
  • Make a launch button for it and connect it to method in the MainWin Class

sisterWinui.ui

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'sisterWinui.ui'
#
# Created: Mon Feb  8 12:05:37 2016
#      by: pyside-uic 0.2.15 running on PySide 1.2.4
#
# WARNING! All changes made in this file will be lost!

from PySide import QtCore, QtGui

class Ui_SisterWin(object):
    def setupUi(self, SisterWin):
        SisterWin.setObjectName("SisterWin")

Run uic

pyside-uic -o sisterWinui.py sisterWinui.ui

Create a file sisterwinClass.py - set up it's import and init

sisterwinClass.py

from sisterWinui import Ui_SisterWin
from PySide import QtCore, QtGui


class SisterWin(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        self.ui = Ui_SisterWin()
        self.ui.setupUi(self)
        # Your custom stuff after this

In Qt Designer, add a button or whatever to your main form to launch sisterForm. Then make some edits to the mainwinClass. Make a variable to hold it in a long lived place

mainwinClass

from sisterwinClass import SisterWin
# no other new imports needed, just the stuff you had before


class MainWin(QtGui.QMainWindow):
    def __init__(self, parent=None):
        # These three lines were already there
        QtGui.QMainWindow.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        # Add a long lived attribute to hold the class instance
        self.sisterWin = None
        # Next line was already there
        self.setup_signals()

def setup_signals(self):
    # Connect button to openSisterWin
    self.ui.btnSisterwin.clicked.connect(self.openSisterWin)
    # You probably have other connects after this


# This toggles the window
def openSisterWin(self):
    if not self.sisterWin:
        self.sisterWin = SisterWin()
    if self.sisterWin.isVisible():
        print('Hiding')
        self.sisterWin.hide()
        # hide or close, it's your choice
        # self.sisterWin.close()
    else:
        print('Showing')
        self.sisterWin.show()

I hope that covers what you were looking for now ? If are trying to find out how to hide the main window , look here Happy hacking :-)