I am trying to build a simple GUI using PyQT5, with 3 buttons to open file browsers and one more to run processing with the selected files, but I can't get my buttons to connect to the functions needed to carry this out.
In the Ctrl class, the _connect_signals function doesn't seem to be calling _input_select. Can anyone help me figure out why?
import sys
# Import QApplication and the required widgets from PyQt5.QtWidgets
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QFileDialog
# Create a subclass of QMainWindow to setup the calculator's GUI
class UI(QMainWindow):
"""App's View (GUI)."""
def __init__(self):
"""View initializer."""
super().__init__()
# Set some main window's properties
self.setFixedSize(300, 150)
# Set the central widget and the general layout
self.generalLayout = QVBoxLayout()
self._centralWidget = QWidget(self)
self.setCentralWidget(self._centralWidget)
self._centralWidget.setLayout(self.generalLayout)
# Create the buttons
self._create_buttons()
def _create_buttons(self):
"""Create the buttons."""
self.buttons = {}
buttons_layout = QVBoxLayout()
# Button text | position on the QVBoxLayout
buttons = {
"Select input file...": 0,
"Select config file...": 1,
"Select output file...": 2,
"Run": 3,
}
# Create the buttons and add them to the grid layout
for btnText, pos in buttons.items():
self.buttons[btnText] = QPushButton(btnText)
buttons_layout.addWidget(self.buttons[btnText], pos)
# Add buttons_layout to the general layout
self.generalLayout.addLayout(buttons_layout)
# Create a Controller class to connect the GUI and the model
class Ctrl:
"""App's Controller."""
def __init__(self, setup, view):
"""Controller initializer."""
self._view = view
self._setup = setup
# Connect signals and slots
self._connect_signals()
def _input_select(self): # Not being called
print("input selection")
options = QFileDialog.Options()
file_select, _ = QFileDialog.getOpenFileNames(
self,
'Select Input File...',
'',
'CSV Files (*.csv);;All Files (*)',
options=options
)
if file_select:
self._setup["input"] = file_select
def _connect_signals(self):
"""Connect signals and slots."""
self._view.buttons["Select input file..."].clicked.connect(self._input_select) # Not working!
# Client code
def main():
"""Main function."""
# Create an instance of `QApplication`
app = QApplication(sys.argv)
# Show the app's GUI
view = UI()
view.show()
setup = {}
# Create instance of the controller
Ctrl(setup=setup, view=view)
# Execute app's main loop
sys.exit(app.exec_())
if __name__ == "__main__":
main()
In case it helps, I started out by butchering this example code from a Real Python tutorial, but must have broken it along the way.
The problem is that you are not keeping any persistent reference to the Ctrl() instance you are creating. This results in python garbage collecting it as soon as the instance is created.
To solve the issue, just assign it to a variable:
def main():
"""Main function."""
# Create an instance of `QApplication`
app = QApplication(sys.argv)
# Show the app's GUI
view = UI()
view.show()
setup = {}
# Create instance of the controller
ctrl = Ctrl(setup=setup, view=view)
# Execute app's main loop
sys.exit(app.exec_())
Some considerations:
while separating logic from interface is usually good practice, it's a concept that needs to be used with care, as sometimes it only makes things more complex than they should be. Most of the times (especially with simple programs), it only makes a bigger codebase without giving any actual benefit: it's harder to read and to debug, and you'll probably end up continuously switching from the logic parts and the ui parts of your code;
your code shows one of the drawback of that concept: when you create the file dialog, you're using self, but in that case it refers to the Ctrl instance, while the argument should be the UI instance instead (which will result in a crash, as Qt will get an unexpected argument type); you can use self._view instead, but, as said, the whole separation in this case just makes things unnecessarily complex;
using strings for dictionary keys that refer to internal objects is rarely a good idea (especially when using long descriptive strings like you did);
when importing more than one element from a module, it's usually better to group them instead of using single line imports: it makes the code tidier and easier to read and inspect: from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QFileDialog)
Related
So I have 2 windows I want to be able to switch between, Login and MainWindow, each one is QWidget in its own seperate file, loginUI.py and MainUI.py respectively.
I can easily switch from login to main upon correct authentification by creating a new instance of MainWindow. But in MainWindow I want to have a 'Disconnect' button that shows the Login screen.
Since both are in different files, importing in this scenario raises a circular import error in python.
Other approaches I tried are:
Using signals and handling them in an intermediate file. This is fine but as I add more buttons/ windows the file started to become a bit of a mess.
Passing the instance of Login to MainWindow.__init__(self, login), and just using self.login.show(). Which seems like a good way, but again as I add more and more windows, I'm scared it might affect performance as so many instances are just running in the background.
Is any of these the correcte way or am I missing an easier way
Edit:
login.py
from PyQt5.QtWidgets import QWidget, QPushButton, QLineEdit
from PyQt5.QtCore import QSize
from mainmenu import MainWindow
from PyQt5 import QtWidgets
import sys
class Login(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setMinimumSize(QSize(300, 200))
self.setWindowTitle("Log in")
self.username = QLineEdit(self)
self.username.move(50, 10)
self.password = QLineEdit(self)
self.password.move(50, 40)
self.connect_button = QPushButton('Connect', self)
self.connect_button.move(50, 100)
self.connect_button.clicked.connect(self.handleConnexion)
def handleConnexion(self):
if self.username.text() == "admin" and self.password.text()=="1":
self.mw = MainWindow()
self.mw.show()
self.close()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = Login()
mainWin.show()
sys.exit( app.exec_() )
mainmenu.py
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(300, 200))
self.setWindowTitle("Main menu")
disconnect_button = QPushButton('Disconnect', self)
disconnect_button.clicked.connect(self.handeDC)
def handeDC(self):
pass
# here I either send a signal to handle it somewhere else
# or
# if i pass the login instance in __init__, just do login.show()
Note: since this is a common question, I'll provide a more broad answer that better reflects the object hierarchy.
Since the "main" window is, as the name suggests, the main one, the script containing it should be the main one, while the login window should be imported and eventually shown as required.
The hierarchy is important: you don't have to consider the order in which the windows are shown, but their relevance.
Considering this, the main script will:
create the main window;
show the login if required;
show the main window if the login is successful;
show again the login if the user disconnects;
clear the contents if the user has changed (*see below);
The above also shows why it's rarely a good idea to continuously create new instances of windows.
The login window should also be a QDialog, which makes things easier: the exec() method is "blocking" (for the function execution, not for the event loop), and waits until the dialog is accepted or *rejected.
main.py
from PyQt5.QtWidgets import *
from login import Login
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(300, 200)
self.setWindowTitle('Main menu')
disconnect_button = QPushButton('Disconnect')
self.setCentralWidget(disconnect_button)
# only for a *persistent* login dialog (*see below)
self.login = Login()
disconnect_button.clicked.connect(self.disconnect)
def start(self):
# put here some function that might check for a 'previous' logged in
# state, possibly stored using QSettings.
# in this case, we just assume that the user has never previously
# logged in, so we automatically show the login window; if the above
# function returns True instead, we can safely show the main window
logged = False
if logged:
self.show()
else:
self.showLogin()
def disconnect(self):
self.hide()
self.showLogin()
def showLogin(self):
if self.login.exec():
self.show()
# alternatively (*see below):
login = Login()
if login.exec():
self.show()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.start()
sys.exit(app.exec())
login.py
from PyQt5.QtWidgets import *
class Login(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(300, 200)
self.setWindowTitle('Log in')
self.username = QLineEdit()
self.password = QLineEdit()
self.password.setEchoMode(self.password.Password)
self.connect_button = QPushButton('Connect', enabled=False)
layout = QGridLayout(self)
layout.addWidget(QLabel('Username:'), 0, 0)
layout.addWidget(self.username, 0, 1)
layout.addWidget(QLabel('Password:'), 1, 0)
layout.addWidget(self.password, 1, 1)
layout.addWidget(self.connect_button, 2, 0, 1, 2)
self.connect_button.clicked.connect(self.handleConnexion)
self.username.textChanged.connect(self.checkFields)
self.password.textChanged.connect(self.checkFields)
def checkFields(self):
if self.username.text() and self.password.text():
self.connect_button.setEnabled(True)
else:
self.connect_button.setEnabled(False)
def handleConnexion(self):
if self.username.text() == 'admin' and self.password.text() == '1':
self.accept()
else:
QMessageBox.warning(self, 'Error',
'Invalid username or password!')
Notes:
the above code will always show the existing Login instance, so the username and password fields will "remember" the previous entries; if you don't want that, you can always call clear() on those fields by overriding the exec() (but remember to call the base implementation and return its result!); alternatively, don't create the self.login and always create a new, local instance of Login() in showLogin();
you shall always use layout managers, and never rely on fixed geometries;
QMainWindow should always have a central widget, creating children of a main window using it as the parent is discouraged (unless you really know what you're doing); if you need more widgets, use a basic QWidget, set a layout for it, add the children, and finally call setCentralWidget();
more complex hierarchies can require a "controller" (read more about the MVC pattern) to better organize the whole program and respect the OOP patterns; this is normally achieved by a basic class or by subclassing the QApplication;
about the last (*) point in my initial list, and related to what explained above: a "controller" could/should completely delete the previous "main window" (it would be both easier and safer) and eventually show a new instance whenever the user has disconnected;
Instead of closing the Login widget when creating the MainWindow widget, you could just hide the widget, which will save the overhead of creating a new instance and also keep the connected slots intact.
Then on your MainWindow you can create a diconnected signal that should be emited when the user clicks the disconnect button.
The login window can listen for the signal and call it's show method.
I made inline comments where I made changes in the example below:
mainwindow.py
from PyQt5.QtCore import pyqtSignal # added this
class MainWindow(QMainWindow):
disconnected = pyqtSignal() # added this
def __init__(self):
QMainWindow.__init__(self)
...
disconnect_button = QPushButton('Disconnect', self)
disconnect_button.clicked.connect(self.handeDC)
def handeDC(self):
# ... do some stuff
self.disconnected.emit() # added this
login.py
class Login(QWidget):
def __init__(self):
QWidget.__init__(self)
...
def handleConnexion(self):
if self.username.text() == "admin" and self.password.text()=="1":
self.mw = MainWindow()
self.mw.disconnected.connect(self.show) # added this
self.mw.show()
self.hide() # changed this
...
I'm currently doing a tutorial on how to create GUI apps from a book called "Modern PyQt" by Joshua Willman and I'm stuck right out of the gate:
I've copy pasted a basic code snippet from the book and tried to tweak it bit by bit instead of reading a bunch of text without any practical trial and error. I can display a window and adjust it's size and properties, but when I try to attach other widgets to the main window, they just don't show up.
Here's what I've got so far:
# First practical example from the book: Pomodoro time manager
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtGui import QIcon
class Pomodoro(QWidget):
def __init__(self): # Create default constructor
super().__init__()
self.initializeUI()
def initializeUI(self):
"""Initialize the window and display its contents to the screen."""
self.setGeometry(int((SCREEN_WIDTH-WINDOW_WIDTH)/2),int((SCREEN_HEIGHT-WINDOW_HEIGHT)/2),WINDOW_WIDTH,WINDOW_HEIGHT) # x,y,w,h
self.setWindowTitle('Bortism')
# self.setWindowIcon(QIcon("Borticon.png"))
self.button1 = QPushButton()
self.button1.setText('Button')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
WINDOW_WIDTH, WINDOW_HEIGHT = 1000,600
SCREEN_X, SCREEN_Y, SCREEN_WIDTH, SCREEN_HEIGHT = app.desktop().screenGeometry().getRect()
window = Pomodoro()
sys.exit(app.exec_())
For a widget to be shown as part of another then the requirement is that the first is the child of the second. This can be done by passing the parent or by using a layout (which is also passed to it by the parent). So in your case you must change to:
self.button1 = QPushButton(self)
I want to get the submenu That is QuitProgram <- This is name of submenu but it is in UI file.
How can I get it in a variable set its action to quit program?
File menu object name is 'actionQuit_FromProgram' <- This is a submenu.
Python File:
from PyQt5 import QtWidgets, uic
from PyQt5.QtWidgets import QFileDialog, QMenuBar
from PyQt5.QtGui import *
import sys
import os
import qdarkgraystyle
path = os.path.abspath(os.getcwd())
import qrcode
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('main.ui', self)
#self.show()
self.button = self.findChild(QtWidgets.QPushButton, 'qrgenerator')
self.button.clicked.connect(self.QrCodeGenerator) # Remember to pass the definition/method, not the return value!
self.input = self.findChild(QtWidgets.QLineEdit, 'qredit')
self.button1 = self.findChild(QtWidgets.QPushButton, 'qropen')
self.button1.clicked.connect(self.OpenQRCode)
self.menu1 = self.findChild(QMenuBar,'actionQuit_FromProgram')
print(self.menu1)
#self.menu1.triggered.connect(qApp.quit)
self.show()
def QrCodeGenerator(self):
# Generate QR code
self.url = qrcode.make(self.qredit.text())
if self.qredit.text() == '':
QMessageBox.warning(self, "Error", "Please Type In Something To Generate Qr Code")
else:
self.url.save("filename.png","PNG")
def OpenQRCode(self):
fname = QFileDialog.getOpenFileName(self, 'Open file',
path,"Image files (*.jpg *.gif *.png *.svg)")[0]
self.label_2.setPixmap(QPixmap(fname))
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(qdarkgraystyle.load_stylesheet())
window = Ui()
app.exec_()
If you're using loadUi (or setupUi if you're using files generated by pyuic), all elements in the object inspector (the tree view that lists all widgets on your UI) become available as instance attributes, according to their object name.
So, if your action is called actionQuit_FromProgram in the inspector, you can directly access it using self.actionQuit_FromProgram.
This also means that:
all those findChild are absolutely useless: you already have access to self.qrgenerator, self.qredit, etc;
in any case, findChild should be used with the correct class of the object you're looking for: I sincerely doubt that actionQuit_FromProgram is a QMenuBar (so, using findChild(QMenuBar, ...) won't work at all; if it is an action, use findChild(QAction, ...); if it's a submenu, use findChild(Qmenu, ...);
there's usually just one menu bar for each QMainWindow, and it is easily accessible using self.menuBar();
I'm trying to create a small database app to keep all clients inside. I would like to write GUI using PyQt5. I have a problem with understanding how app structure should looks like.
I'd like to have a main class which starts the app and I want to seperate GUI, DB and Main classes in different files.
You can see my code snippets bellow. It don't work because some variables are not recognized and accually I don't understand why.
My thoughts:
1. Window, tab1 objects will be created in main class init function
2. When window, tab1 instances were created, the methods inside it's init will be called
3. I have window, tab1 objects and it's variables are available for themselves
window.gbT1Main.setLayout(T1LayMain) is not defined for TabNewClient class. Why ? How should I change my code to achieve above requirements? Please explain me how should I connect my classes :(
Window and TabNewClient class (window, tab1)
from PyQt5.QtWidgets import QApplication, QDialog, QTabWidget, QGroupBox, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFormLayout, QLineEdit, QDateEdit, QTextEdit, QRadioButton, QGridLayout
import sys
import datetime
class Window(QDialog):
def __init__(self):
super().__init__()
self.InitWindow()
def InitWindow(self):
# create tab widget
self.tab = QTabWidget()
# create MainWindow groupbox
self.gbMainWindow = QGroupBox()
# TAB groupBoxes
self.gbT1Main = QGroupBox()
self.gbT2Main = QGroupBox("Main2")
self.gbT3Main = QGroupBox("Main3")
# Adding tabs
self.tab.addTab(self.gbT1Main, "Dodaj klienta")
self.tab.addTab(self.gbT2Main, "Wyszukaj")
self.tab.addTab(self.gbT3Main, "Statystki")
# Setting MainWindow title
self.setWindowTitle("MEDIKAP - gabinet medycyny pracy")
# Main Window Layout
self.layMainWindow = QHBoxLayout()
# Set MainWindow Layout
self.layMainWindow.addWidget(self.tab)
self.gbMainWindow.setLayout(self.layMainWindow)
# set MainWindow layout visible
self.setLayout(self.layMainWindow)
#show window
self.show()
class TabNewClient:
def __init__(self):
self.CreateTab1Layout()
def CreateTab1Layout(self):
self.gbAddClient = QGroupBox("Dane klienta")
self.gbRodzajBadania = QGroupBox("Podstawa prawna")
self.gbDane = QGroupBox()
self.gbComment = QGroupBox("Komentarz")
self.gbButtons = QGroupBox()
# TAB1 - layouts
T1LayMain = QVBoxLayout()
layDane = QHBoxLayout()
# TAB1
layDane.addWidget(self.gbAddClient)
layDane.addWidget(self.gbRodzajBadania)
self.gbDane.setLayout(layDane)
# TAB1 - set layout to Main
T1LayMain.addWidget(self.gbDane)
T1LayMain.addWidget(self.gbComment)
T1LayMain.addWidget(self.gbButtons)
window.gbT1Main.setLayout(T1LayMain)
Main class:
from PyQt5.QtWidgets import QApplication
import sys
from guiv3 import Window, TabNewClient
class Main:
def __init__(self):
window = Window()
tab1 = TabNewClient()
if __name__ == "__main__":
app = QApplication(sys.argv)
main = Main()
app.exec_()
error:
window.gbT1Main.setLayout(T1LayMain)
NameError: name 'window' is not defined
To answer the question of why you get an error about 'window' not being defined, I believe it's because there is no window variable in the TabNewClient class. It looks like you're attempting to refer to the window defined in Main, but that won't work since that variable isn't in the scope of the TabNewClient class. My guess is that you're going to run into the same issue with gbT1Main, since that is outside the scope of the TabNewClient class as well.
Edit: I think I understand what you're attempting here. You want the gbT1Main related tab in window to hold the layout from tab1. To do that, you will need to set window's layout in Main:
from PyQt5.QtWidgets import QApplication
import sys
from guiv3 import Window, TabNewClient
class Main:
def __init__(self):
window = Window()
tab1 = TabNewClient()
window.gbT1Main.setLayout(tab1.T1LayMain)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = Main()
app.exec_()
That's most likely what you're looking for.
Important: this also requires that T1LayMain be an attribute of the TabNewClient class, so use self.T1LayMain inside that class for it to be accessible outside it.
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.
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 :-)