Open a second window in PyQt - python

I'm trying to use pyqt to show a custom QDialog window when a button on a QMainWindow is clicked. I keep getting the following error:
$ python main.py
DEBUG: Launch edit window
Traceback (most recent call last):
File "/home/james/Dropbox/Database/qt/ui_med.py", line 23, in launchEditWindow
dialog = Ui_Dialog(c)
File "/home/james/Dropbox/Database/qt/ui_edit.py", line 15, in __init__
QtGui.QDialog.__init__(self)
TypeError: descriptor '__init__' requires a 'sip.simplewrapper' object but received a 'Ui_Dialog'
I've gone over several online tutorials, but most of them stop just short of showing how to use a non built-in dialog window. I generated the code for both the main window and the dialog using pyuic4. What I think should be the relevant code is below. What am I missing here?
class Ui_Dialog(object):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class Ui_MainWindow(object):
def __init__(self, dbConnection):
global c
c = dbConnection
def launchEditWindow(self):
print "DEBUG: Launch edit window"
dialog = QtGui.QDialog()
dialogui = Ui_Dialog(c)
dialogui = setupUi(dialog)
dialogui.show()
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
conn = sqlite3.connect('meds.sqlite')
c = conn.cursor()
self.ui = Ui_MainWindow(c)
self.ui.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
program = StartQT4()
program.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Bonus question: since it looks like you can't pass arguments in pyqt function callbacks, is setting something which would otherwise be passed as an argument (the poorly named "c") to be global the best way to get information into those functions?

I've done like this in the past, and i can tell it works.
assuming your button is called "Button"
class Main(QtGui.QMainWindow):
''' some stuff '''
def on_Button_clicked(self, checked=None):
if checked==None: return
dialog = QDialog()
dialog.ui = Ui_MyDialog()
dialog.ui.setupUi(dialog)
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dialog.exec_()
This works for my application, and I believe it should work with yours as well. hope it'll help, it should be pretty straight forward to do the few changes needed to apply it to your case.
have a good day everybody.

Ui_Dialog should inherent from QtGui.QDialog, not object.
class Ui_Dialog(QtGui.QDialog):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection

class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
Why QtGui.QWidget.__init___ ???
Use insted:
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
You must call __init__ methon from base class (name in parenthesis '()')
QDialog have two useful routins:
exec_()
show()
First wait for closing dialog and then you can access any field form dialog. Second show dialog but don't wait, so to work properly you must set some slot/signals connections to respond for dialog actions.
eg. for exec_():
class Dialog(QDialog):
def __init__(self, parent):
QDialog.__init__(parent)
line_edit = QLineEdit()
...
dialog = Dialog()
if dialog.exec_(): # here dialog will be shown and main script will wait for its closing (with no errors)
data = dialog.line_edit.text()
Small tip: can you change your ui classes into widgets (with layouts). And perhaps problem is that your __init__ should be __init__(self, parent=None, dbConnection)
Because when you create new widget in existing one PyQt may try to set it as children of existing one. (So change all init to have additional parent param (must be on second position)).

Related

Transfer QMessageBox setting to a separate class in Python

I've used QtDesigner to make ui files that I then use to make classes, like
class MyPopup1(MyBaseClass, MyClass):
def __init__(self, parent=None):
super(MyPopup1, self).__init__(parent)
self.setupUi(self)
...
Granted I used some tutorial for this so I'm not actually sure what all of that does. But now I have written code that generates a popup that uses the QMessageBox class and I would like to move this code to a separate class so I can call it from multiple places.
How do I move this code to make a MyPopup2 class?
MyPopup2 = QtWidgets.QMessageBox(parent = self.central_widget)
MyPopup2.setWindowTitle("My Popup 2")
MyPopup2.setText("Some text")
MyPopup2.setIcon(QtWidgets.QMessageBox.Question)
MyPopup2.addButton("Btn1", QtWidgets.QMessageBox.RejectRole)
MyPopup2.addButton("Btn2", QtWidgets.QMessageBox.ActionRole)
choice = MyPopup2.exec_()
I know I probably need to connect the button signals to functions and use self.done() to send the result back to a call.
I am mostly confused on what to put as MyBaseClass and MyClass for the second popup.
Qt Designer provides a class that serves to fill a widget, so a recommended way is to inherit a widget and inherit from the generated class of Qt Designer, for example the structure that Qt Designer provides has the following structure:
class MyClass(object):
def setupUi(self, AAA):
...
self.retranslateUi(AAA)
QtCore.QMetaObject.connectSlotsByName(AAA)
def retranslateUi(self, AAA):
...
Then depending on the template you should choose as MyBaseClass to QMainWindow, QDialog or QWidget and call setupUi() which is the method that you add the other widget to the window as you point out:
class MyPopup1(MyBaseClass, MyClass):
def __init__(self, parent=None):
super(MyPopup1, self).__init__(parent)
self.setupUi(self)
...
But in the case that you are going to create the widget, MyClass is not necessary, so in your case the solution is the following:
from PyQt5 import QtWidgets
class MyPopup2(QtWidgets.QMessageBox):
def __init__(self, parent=None):
super(MyPopup2, self).__init__(parent)
self.setWindowTitle("My Popup 2")
self.setText("Some text")
self.setIcon(QtWidgets.QMessageBox.Question)
self.addButton("Btn1", QtWidgets.QMessageBox.RejectRole)
self.addButton("Btn2", QtWidgets.QMessageBox.ActionRole)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
popup = MyPopup2()
if popup.exec_() == QtWidgets.QMessageBox.Accepted:
print("Btn2")
else:
print("Btn1")
The full solution for the example is
class MyPopup2(QtWidgets.QMessageBox):
def __init__(self, parent=None):
super(NoMatch, self).__init__(parent)
self.setWindowTitle("My Popup 2")
self.setText("Some text")
self.setIcon(QtWidgets.QMessageBox.Question)
self.Btn1 = self.addButton("Btn1", QtWidgets.QMessageBox.RejectRole)
self.Btn2 = self.addButton("Btn2", QtWidgets.QMessageBox.ActionRole)
self.Btn1.clicked.connect(lambda: self.done(QtWidgets.QMessageBox.RejectRole))
self.Btn2.clicked.connect(lambda: self.done(QtWidgets.QMessageBox.ActionRole))
Which can be called with choice = MyPopup2.exec_() from anywhere

Python program works, but it shows an error. Why?

I am new in programming and I have done my research on this website and others, but I can't find anything helpful for my problem. I am writing a Python program with several PyQt windows opening when different buttons are pressed. This is my program:
import sys, os,
from PyQt4 import QtCore, QtGui, uic
Ui_IntroWindow = uic.loadUiType('introduction.ui')[0]
Ui_ElmWindow = uic.loadUiType('elm.ui')[0]
Ui_ClueWindow = uic.loadUiType('pistaelm.ui') [0]
Ui_ButtonWindow = uic.loadUiType('firtsguibutton.ui')[0]
class IntroWindow(QtGui.QMainWindow, Ui_IntroWindow):
def __init__ (self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.continuar.clicked.connect(self.continuar_clicked)
def continuar_clicked(self):
window = ElmWindow(self)
window.show()
window.exec_()
self.close()
class ElmWindow(QtGui.QMainWindow, Ui_ElmWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.bpista.clicked.connect(self.pista)
self.bcontinuar.clicked.connect(self.continuar)
def pista(self):
pistaelm = ClueWindow(self)
pistaelm.show()
pistaelm.exec_()
def continuar(self):
elemento = str(self.elemento.text())
main = ButtonWindow(self)
if elemento == 'cobalto':
main.show()
main.exec_()
self.close()
class ClueWindow(QtGui.QMainWindow, Ui_ClueWindow):
def __init__ (self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
class ButtonWindow(QtGui.QMainWindow, Ui_ButtonWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
app = QtGui.QApplication(sys.argv)
myWindow = IntroWindow()
myWindow.show()
app.exec_()
I have several sub windows created, for example in:
def continuar_clicked(self):
window = ElmWindow(self)
window.show()
window.exec_()
self.close()
If i don't write
window.exec_()
the window will open, but the buttons won't work. But when i write it I get an error:
Traceback (most recent call last):
File "C:\Users\Work\Desktop\Project\project.py", line 19, in continuar_clicked
window.exec_()
AttributeError: 'ElmWindow' object has no attribute 'exec_'
How can I stop the error?
Objects derived from QMainWindow do not have a method exec_(). This is why you see the exception.
The reason this exception is modifying the behaviour of your program is because when an exception is raised, the rest of the slot currently being executed is not run. So your continuar_clicked method runs until it hits the line with the missing method, and stops.
This points to the fact that not calling self.close() in continuar_clicked keeps your GUI working. Calling self.close() is apparently breaking the program.
SO what does this mean? Well it points to a bad object hierarchy. You are spawning new windows, that are children of an existing window, and then closing the parent window. Quite possibly the parent window is being deleted, depending on whether the Qt.WA_DeleteOnClose attribute is set to true for your windows.
So I would suggest redesigning your program. Perhaps have a parent window that is always open, or write a window managing class which handles the creation/closing of all windows (eg a window object calls a method from your window managing object to close the current window and open a new window).
Ultimately how you structure your code will be up to you as it is difficult to gauge the details of your program from a minimal example

PyQt4 signals and slots - QToolButton

In PyQt4 I have a main window which when the settings button is clicked opens the settings dialog
from PyQt4 import QtCore, QtGui
import ui_Design, ui_Settings_Design
class MainDialog(QtGui.QMainWindow, ui_Design.Ui_arbCrunchUI):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.settingsBtn.clicked.connect(lambda: self.showSettings())
def showSettings(self):
dialog = QtGui.QDialog()
dialog.ui = SettingsDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
The above works and my SettingsDialog is displayed but I cant get the setPageIndex to work
class SettingsDialog(QtGui.QDialog, ui_Settings_Design.Ui_SettingsDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setupUi(self)
self.bookSettingsBtn.clicked.connect(self.setPageIndex)
#QtCore.pyqtSlot()
def setPageIndex(self):
print 'selected'
self.settingsStackedWidget.setCurrentIndex(0)
The bookSettingsBtn is a QToolButton
self.bookSettingsBtn = QtGui.QToolButton(self.navigationFrame)
And the settingsStackedWidget is a QStackedWidget
self.settingsStackedWidget = QtGui.QStackedWidget(SettingsDialog)
At this point I am pretty confused on signals and slots and nothing I have read clears this up - if anyone can point out what I am doing wrong above and also potentially direct me to a good (beginners) guide / tutorial on signals and slots it would be greatly appreciated
I would also like to know how to make setPageIndex work as follows:
def setPageIndex(self, selection):
self.settingsStackedWidget.setCurrentIndex(selection)
I'm not sure why you're doing the following, but that's the issue:
def showSettings(self):
dialog = QtGui.QDialog()
dialog.ui = SettingsDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
SettingsDialog itself is a proper QDialog. You don't need to instantiate another QDialog.
Right now, you're creating an empty QDialog and then populate it with the same ui as SettingsDialog (i.e. setupUi(dialog)), then you show this dialog. But... The signal connection is for SettingsDialog, and the dialog you're showing doesn't have that.
Basically, you don't need that extra QDialog at all. The following should be enough:
def showSettings(self):
dialog = SettingsDialog()
dialog.exec_()
Ok. So here is an example how you pass an argument to a slot
from functools import partial
# here you have a button bookSettingsBtn:
self.bookSettingsBtn = QtGui.QPushButton("settings")
self.bookSettingsBtn.clicked.connect(partial(self.setPageIndex, self.bookSettingsBtn.text()))
#pyqtSlot(str) # this means the function expects 1 string parameter (str, str) 2 string parameters etc.
def setPageIndex(self, selection):
print "you pressed button " + selection
In your case the argument would be an int. Of course you have to get the value from somewhere
and then put it in the partial part as the argument (here I just used the text of the button),
but you can use int, bool etc. Just watch the slot signature.
Here is a tutorial that helped me:
http://zetcode.com/gui/pyqt4/
I hope this helps.
Hey here I have a fully running example (just copy paste it in a python file and run it):
Maybe this helps you. It's a small example but here you see how it works.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from functools import partial
class MyForm(QMainWindow):
def __init__(self, parent=None):
super(MyForm, self).__init__(parent)
button1 = QPushButton('Button 1')
button2 = QPushButton('Button 2')
button1.clicked.connect(partial(self.on_button, button1.text()))
button2.clicked.connect(partial(self.on_button, button1.text()))
layout = QHBoxLayout()
layout.addWidget(button1)
layout.addWidget(button2)
main_frame = QWidget()
main_frame.setLayout(layout)
self.setCentralWidget(main_frame)
#pyqtSlot(str)
def on_button(self, n):
print "Text of button is: " + str(n)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
form = MyForm()
form.show()
app.exec_()
So I dont really understand why but changing the way the settingsDialog is called from the MainWindow has fixed my problem. I guess the parent window needed to be specified??:
class MainDialog(QtGui.QMainWindow, ui_Design.Ui_arbCrunchUI):
....
def showSettings(self):
self.settingsDialog = QtGui.QDialog(self)
self.settingsDialog.ui = SettingsDialog(self)
self.settingsDialog.ui.show()
class SettingsDialog(QtGui.QDialog, ui_Settings_Design.Ui_SettingsDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setupUi(self)
self.bookSettingsBtn.clicked.connect(partial(self.setPageIndex, 1))
#QtCore.pyqtSlot(int)
def setPageIndex(self, selection):
self.settingsStackedWidget.setCurrentIndex(selection)

Configure Widgets in a Sub-Dialog in a pyqt Main Window

I have a GUI App with a Main Dialog and I added a button to it.
Pushing the button adds another "dialog" where the user has to input some values.
Both Ui-files are written with the QTDesigner and "dialog" has a "QtableWidget" with the object name "tableCo" I am not sure why I cannot change the properties of this tableWidget:
from PyQt4 import QtGui, QtCore, Qt
from Main_Window import Ui_Dialog as Dlg
from dialog import Ui_MyDialog
class MainDialog(QtGui.QDialog, Dlg):
def __init__(self):
QtGui.QDialog.__init__(self)
self.setupUi(self)
self.connect(self.buttonOK,
QtCore.SIGNAL("clicked()"), self.onOK)
self.connect(self.buttonAbbrechen,
QtCore.SIGNAL("clicked()"), self.onClose)
self.connect(self.Button,
QtCore.SIGNAL("clicked()"), self.on_Button_clicked)
def on_Button_clicked(self, checked=None):
if checked==None: return
dialog = QtGui.QDialog()
dialog.ui = Ui_MyDialog()
dialog.ui.setupUi(dialog)
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dialog.exec_()
some_list=["A","B","C"]
#a list in another python class from another script that changes so
#the table properties have to be changed dynamically
#here I just take a simple list as an example
#the following two lines do not work (they work if tableCo is an
#object in the Main Dialog
self.tableCo.setColumnCount(len(some_list))
self.tableCo.setHorizontalHeaderLabels(some_list)
def onOK:
...
def onClose:
...
If I push the button i see my "tableCo" widget, but the properties of the header have not changed, and after closing this sub-dialog I get the following error-message
Traceback (most recent call last):
File "C:/gui.py", line 88, in on_Button_clicked
self.tableCo.setColumnCount(len(some_list))
AttributeError: 'MainDialog' object has no attribute 'tableCo'
What do i have to change in my code to configure a Widget in a sub-Dialog?
There are two problems with the code in your on_Button_clicked.
Firstly, you are attempting to call methods after the dialog has closed. When exec_ is called, the dialog enters a blocking loop until the user closes the dialog. When the dialog closes, the following lines will get executed, but the dialog will be immediately garbage-collected after that when the function returns.
Secondly, you are attempting to access methods of the dialog using self, rather than via the local name dialog, which is why you are getting the AttributeError.
You can fix these problems by creating a subclass for the second dialog in the same way that you have for your MainDialog class:
class SubDialog(QtGui.QDialog, Ui_MyDialog):
def __init__(self, some_list, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.tableCo.setColumnCount(len(some_list))
self.tableCo.setHorizontalHeaderLabels(some_list)
class MainDialog(QtGui.QDialog, Dlg):
...
def on_Button_clicked(self, checked=None):
if checked is None: return
dialog = SubQDialog(some_list)
dialog.exec_()
are you sure tableCo has that exact name and it's parented directly to the MainWindow? Seems like the properties are not being updated simply because there is no self.tableCo.

PyQt4 custom widget (uic loaded) added to layout is invisible

I've created a custom widget in pyqt4 that I've worked on and tested and now am ready to load it into my main window. Since it doesn't show up in designer, I need to manually add it to my main window manually.
My widget uses uic to load the ui file instead of converting it to a py file (it's been quicker less hassle so far) so it looks something like this:
class widgetWindow(QtGui.QWidget):
def __init__(self, parent = None):
super(widgetWindow, self).__init__(parent)
self.ui = uic.loadUi("widget.ui")
#everything else
now in my main class (example for brevity) I create the layout, add the widget to the layout and then add it to the main widget
class main(QtGui.QMainWindow):
def __init__(self, parent = None):
super(main, self).__init__(parent)
self.ui = uic.loadUi("testWindow.ui")
mainlayout = QtGui.QVBoxLayout()
window = widgetWindow(self)
mainlayout.addWidget(window)
centerWidget = QtGui.QWidget()
centerWidget.setLayout(mainlayout)
self.ui.setCentralWidget(centerWidget)
There are no errors thrown, and it will make space for the widget, but it simply won't show anything.
adding in the line window.ui.show() will just pop open a new window overtop the space that it should be occupying on the main window. What am I missing?
Doing some more research into the uic loader, there are two ways to load a ui file. The way I'm using it in the question is one way, the other way is with the uic.loadUiType(). This creates both the base class and the form class to be inherited by the class object instead of just the QtGui.QWidget class object.
widgetForm, baseClass= uic.loadUiType("addTilesWidget.ui")
class windowTest(baseClass, widgetForm):
def __init__(self, parent = None):
super(windowTest, self).__init__(parent)
self.setupUi(self)
This way, the widget can be loaded into another form as expected. As for exactly why, I haven't found that answer yet.
Some more info on the different setup types: http://bitesofcode.blogspot.com/2011/10/comparison-of-loading-techniques.html
Try to add the parent argument into the loadUi statements:
self.ui = uic.loadUi("widget.ui",parent)
self.ui = uic.loadUi("testWindow.ui",self)
And try the following line at the end of your main class.
self.setCentralWidget(centerWidget)
You need to specify that 'centerWidget' is the central widget of the main window.
i.e your code for class main should be have a line like:
self.setCentralWidget(centerWidget)
class main(QMainWindow):
def __init__(self, parent = None):
super(main, self).__init__(parent)
....
self.setCentralWidget(centerWidget)

Categories