Trying to build a user interface using PyQt4. Got a dialog window that pops up, and I want it to do something then close, when 'Ok' is pressed. Unfortunately, I can't seem to get it working - tried all sorts of combinations of Dialog.exec_(), Dialog.close(), self.exec_(), self.close(), emitting an 'accepted' signal to Dialog.accept, etc. So far, nothing has worked, and I'm not quite sure why. Here's the code for it:
Dialog window initialised as such;
def begin_grab(self):
self.GrabIm=qtg.QDialog(self)
self.GrabIm.ui=Ui_Dialog()
self.GrabIm.ui.setupUi(self.GrabIm)
self.GrabIm.show()
Dialog window;
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName(_fromUtf8("Dialog"))
...
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), self.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def accept(self):
if self.radioButton.isChecked()==True: #assume it is true
#Call continuous grabber
print "Grabbing continuously"
Dialog.exec_() #Close it here
else:
#Call trigger server
print "Grabbing triggered"
self.exec_()
The main thing that keeps happening is either a message saying 'Dialog' is an unknown variable, in the accept() function, or if I use self.exec_() or similar it says exec_() is not a known attribute. If I try doing accept(self, Dialog), and put self.accept(Dialog) in the connect statement, it also crashes.
Any and all help would be appreciated.
You are doing it pretty wrong. You shouldn't modify the Qt Designer generated code (Ui_Dialog). You should subclass QDialog and define the accept there:
class MyDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
# use new style signals
self.ui.buttonBox.accepted.connect(self.accept)
self.ui.buttonBox.rejected.connect(self.reject)
def accept(self):
if self.ui.radioButton.isChecked(): # no need to do ==True
#Call continuous grabber
print "Grabbing continuously"
else:
#Call trigger server
print "Grabbing triggered"
super(MyDialog, self).accept() # call the accept method of QDialog.
# super is needed
# since we just override the accept method
Then you initialize it as:
def begin_grab(self):
self.GrabIm=MyDialog(self)
self.GrabIm.exec_() # exec_() for modal dialog
# show() for non-modal dialog
But looking at your code, I wouldn't do it that way. Let the dialog return with accept/reject and then do your conditional stuff in the caller (i.e. Main window):
class MyDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
# use new style signals
self.ui.buttonBox.accepted.connect(self.accept)
self.ui.buttonBox.rejected.connect(self.reject)
and the caller code:
def begin_grab(self):
self.GrabIm=MyDialog(self)
if self.GrabIm.exec_(): # this will be True if dialog is 'accept'ed, False otherwise
if self.GrabIm.ui.radioButton.isChecked():
#Call continuous grabber
print "Grabbing continuously"
else:
#Call trigger server
print "Grabbing triggered"
You can re implement closeEvent which will help you to do some process before Dialog exit
def closeEvent(self,event):
print "I am here"
event.accept()
Related
I have a pyqt app where I want a dialog to display when I click a menu item. If the dialog loses focus and the menu item is clicked again, it brings the dialog to the front. This is working fine so far.
The problem is that when the dialog is opened and then closed, clicking the menu item doesnt create/display a new dialog. I think I know why, but can't figure out a solution
Heres the code:
from ui import mainWindow, aboutDialog
class ReadingList(QtGui.QMainWindow, mainWindow.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.about = None
self.actionAbout.triggered.connect(self.showAbout)
def showAbout(self):
# If the about dialog does not exist, create one
if self.about is None:
self.about = AboutDialog(self)
self.about.show()
# If about dialog exists, bring it to the front
else:
self.about.activateWindow()
self.about.raise_()
class AboutDialog(QtGui.QDialog, aboutDialog.Ui_Dialog):
def __init__(self, parent=None):
super(self.__class__, self).__init__()
self.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
readingList = ReadingList()
readingList.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The problem lies in the fact that when the dialog is created the first time, self.about is no longer None. This is good because the conditional in showAbout() allows me to bring the dialog to the front instead of creating a new one (the else condition)
However, when the dialog is closed, self.about is no longer None due to the previous dialog creation, which means it doesn't create a new one and just jumps to the else condition
How can I make it so that dialogs can be created after the first?
I thought about overriding the closeEvent method in the AboutDialog class but I'm not sure how to get a reference to readingList to send a message back saying the dialog has been closed. Or maybe I'm overthinking it, maybe the return from self.about.show() can be used somehow?
(I know I can probably avoid all of this using modal dialogs but want to try to figure this out)
There are probably several ways to do this, but here's one possibility:
class ReadingList(QtGui.QMainWindow, mainWindow.Ui_MainWindow):
def __init__(self):
super(ReadingList, self).__init__()
self.setupUi(self)
self.actionAbout.triggered.connect(self.showAbout)
self.about = None
def showAbout(self):
if self.about is None:
self.about = AboutDialog(self)
self.about.show()
self.about.finished.connect(
lambda: setattr(self, 'about', None))
else:
self.about.activateWindow()
self.about.raise_()
class AboutDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(AboutDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
(NB: you should never use self.__class__ with super as it can lead to an infinite recursion under certain circumstances. Always pass in the subclass as the first argument - unless you're using Python 3, in which case you can omit all the arguments).
I'm trying (and researching) with little success to emit a signal from a working Qthread to the main window. I don't seem to understand how I should go about this in the new syntax.
Here's a simple example.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
class Dialog(QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
button = QPushButton("Test me!")
layout = QVBoxLayout()
layout.addWidget(button)
self.setLayout(layout)
#self.button.clicked.connect(self.test) ----> 'Dialog' object has no attribute 'button'
self.connect(button, SIGNAL('clicked()'), self.test)
self.workerThread = WorkerThread()
def test(self):
self.workerThread.start()
QMessageBox.information(self, 'Done!', 'Done.')
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
time.sleep(5)
print "Thread done!"
app = QApplication(sys.argv)
dialog = Dialog()
dialog.show()
app.exec_()
I understand that if I didn't have another thread I'd create the signal inside the Dialog class and connect it in the __init__ but how can I create a custom signal that can be emitted from WorkerThread and be used test()?
As a side question. You can see it commented out of the code that the new syntax for connecting the signal errors out. Is it something in my configurations?
I'm on OsX El Capitan, Python 2.7
Any help is highly appreciated! Thanks a lot
TL:DR: I'd like to emmit a signal from the WorkerThread after 5 seconds so that the test function displays the QMessageBox only after WorkingThread is done using the new syntax.
Ok, it's been a long day trying to figure this out. My main resource was this: http://www.matteomattei.com/pyside-signals-and-slots-with-qthread-example/
In the new syntax, in order to handle signals from different threads, you have to create a class for your signal like so:
class WorkerThreadSignal(QObject):
workerThreadDone = Signal()
This is how the WorkerThread end up looking like:
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
self.workerThreadSignal = WorkerThreadSignal()
def run(self):
time.sleep(3)
self.workerThreadSignal.workerThreadDone.emit()
And for the connections on the Dialog class:
self.workerThread = WorkerThread()
self.buttonn.clicked.connect(self.test)
and:
self.workerThreadSignal = WorkerThreadSignal()
self.workerThread.workerThreadSignal.workerThreadDone.connect(self.success)
def success(self):
QMessageBox.warning(self, 'Warning!', 'Thread executed to completion!')
So the success method is called once the signal is emitted.
What took me the longest to figure out was this last line of code. I originally thought I could connect directly to the WorkerThreadSignal class but, at least in this case, it only worked once I backtracked it's location. From the Dialog init to WorkerThread init back to the WorkerThreadSignal. I took this hint from the website mentioned above.
I find strange that I have to create the same local variables on both classes, maybe there's a way to create one global variable I can refer to instead all the current solution but it works for now.
I hope this helps someone also stuck in this process!
PS: The syntax problem for the connection was also solved. So everything is written with the new syntax, which is great.
I've created an app which has an main window and the possibility to open an dialog (question, error and so on). I'm not using QMessageBox.warning() or QMessageBox.question() and so on because I wanted to customize the dialogs a bit.
But every time I open a new Dialog, in the Windows task bar (I'm working on Windows 10) a new 'tab' is opened, which is a little bit annoying.
My code (shortened):
from PySide import QtCore, QtGui
import sys
class MessageBox:
def __init__(self, title, message):
msg = QtGui.QMessageBox()
flags = QtCore.Qt.Dialog
flags |= QtCore.Qt.CustomizeWindowHint
flags |= QtCore.Qt.WindowTitleHint
msg.setWindowFlags(flags)
msg.setWindowTitle(title)
msg.setText(message)
msg.exec_()
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.show()
MessageBox("Title", "My message here")
if __name__ == "__main__":
app = QtGui.QApplication([])
window = MainWindow()
sys.exit(app.exec_())
Note: Normally, the dialog is called from an menu or button.
Question: How can I make the dialog appear in the main window without creating a new 'task bar tab'?
The solution was quite simple: Passing an reference of QMainWindow to the constructor of QDialog will do the job, e.g:
class MessageBox(QtGui.QDialog):
def __init__(self, parent, title, message, icon="info"):
super(MessageBox, self).__init__(parent)
...
and then calling the dialog from an class that inherits from QMainWindow:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
#connect button with function, e.g.:
mybutton.clicked.connect(self.open_dialog)
def open_dialog(self):
MessageBox(self)
Maybe this helps anyone!
If you set the parent of the QDialog to the window, it will only show as one item on the task bar. This is generally the first argument to QMessageBox.
class MessageBox:
def __init__(self, parent, title, message):
msg = QtGui.QMessageBox(parent)
Also, if you really want to create a custom dialog, you might as well just subclass from QDialog.
I have an issue with a QSystemTrayIcon application I'm working on.
Any dialog box that I make with the SystemTrayIcon as parent or grandparent will terminate the entire application when closed, even when I override the "reject" method.
Here is a simplified example with no icon. When you run it (Windows 7 here) you should have a blank tray icon application on the task bar. If you left click it, an empty Dialog box will pop up. Clicking the "X" to close the dialog will also completely terminate the python process.
from PySide import QtGui, QtCore
class RestartDialog(QtGui.QDialog):
def __init__(self, parent):
super(RestartDialog, self).__init__()
def reject(self):
self.hide()
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, parent=None):
QtGui.QSystemTrayIcon.__init__(self, parent)
self.activated.connect(self.click_trap)
def click_trap(self, value):
''' Left click tray function '''
if value == self.Trigger: #left click!
self.dialog = RestartDialog(self)
self.dialog.show()
def show(self):
QtGui.QSystemTrayIcon.show(self)
if __name__ == "__main__":
proc = QtGui.QApplication([])
APP = SystemTrayIcon()
APP.show()
proc.exec_()
Try adding this after creating your QApplication:
proc.setQuitOnLastWindowClosed(False)
It is true by default, so your eventloop will terminate after you close the dialog.
Maybe this is been asked many times, but i can't find a solution.
I have a dialog:
class PostDialog(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.ui = Ui_Dialog() #code from designer!!
self.ui.setupUi(self)
self.ui.plainTextEdit = ContentEditor()
This dialog has a QPlainTextEdit from the designer.
I need to override keyPress and keyRelease of that QPlainTextEdit.
So i have subclassed it:
class ContentEditor(QtGui.QPlainTextEdit):
def __init__(self, parent=None):
QtGui.QPlainTextEdit.__init__(self, parent)
def keyPressEvent(self, event):
print "do something"
but ContentEditor.keyPressEvent is never called! Why?
I recommend using installEventFilter for this purpose:
This would look like:
class PostDialog(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.ui = Ui_Dialog() #code from designer!!
self.ui.setupUi(self)
self.ui.plainTextEdit.installEventFilter(self)
def eventFilter(self, event):
if event.type() == QtCore.QEvent.KeyPress:
# do some stuff ...
return True # means stop event propagation
else:
return QtGui.QDialog.eventFilter(self, event)
What you're trying to accomplish is better done by promoting in Qt Designer the QPlainTextEdit widget to your subclass ContentEditor.
Qt documentation
In the "Promoted Widgets" Dialog:
"Promote class name": ContentEditor
"Header file": your_python_module_name.h
May be you need to call method setFocusPolicyof QWidget to receive a KeyPress Event.
From API docs of QWidget for the method keyPressEvent:
This event handler, for event event, can be reimplemented in a subclass
to receive key press events for the widget. A widget must call setFocusPolicy()
to accept focus initially and have focus in order to receive a key press event.
You'll probably just need to swap the following two lines:
self.ui.setupUi(self)
self.ui.plainTextEdit = ContentEditor()
If you write it like this:
self.ui.plainTextEdit = ContentEditor()
self.ui.setupUi(self)
you make sure your custom widget gets bound before the UI gets setup. Otherwise you're just replacing a reference to an already initialised object.