PyQt: how to receive keyboard events in a subclassed QWidget? - python

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.

Related

PySide make QDialog appear in main window

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.

How to accept close event of MainWindow when loading it with QUiLoader()?

How to receive close event in following code?
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.view = QUiLoader().load("sample.ui", self)
self.view.show()
def closeEvent(self, e):
print "close event recieved"
def main():
app = QApplication(sys.argv)
a=Main()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
If I convert sample.ui to sample.py using pyside-uic and importing this into main.py then I was able to receive close event.
from sample import Ui_MainWindow
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
def closeEvent(self, e):
print "close event recieved"
app = QApplication(sys.argv)
a=Main()
a.show()
sys.exit(app.exec_())
The second example works because it effectively becomes a subclass of the top-level class from Qt Designer. By contrast, the first example uses composition rather than subclassing, which puts all the gui elements inside an internal namespace. The Main class is just a container that acts as the parent of the view widget, and is never actually shown (which in turn means it doesn't receive any close events).
In PyQt, the uic module has several funtions which allow you to work around these issues, but there is currently nothing like that in PySide. Instead, you have to roll your own function. See this answer for an explanation of how to do that.
Alternatively, you could change the top-level class in Qt Designer to a QWidget, and then make view the central widget of your Main class. This is a lot less flexible than the above method, though.

ESC key in wx.Dialog doesn't trigger button with ID_CANCEL unless it has focus

According to the docs, dialogs created in wxPython and shown with ShowModal should handle the escape (ESC) key by searching for a button with ID_CANCEL and simulating a click (i.e. triggering its EVT_BUTTON event).
This was working in all but one of my dialogs. After a lot of debugging, I discovered that the cancel button - or any other button! - should have focus. In other words, as long as I call .SetFocus() on one of the buttons I create, the ESC key works fine.
Does anybody know what's going on here? Do dialogs displayed with ShowModal() not automatically get focus? Should they? Or am I misunderstanding something?
In the example code below, uncomment the line b.SetFocus() to see the difference:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent):
super(MainWindow, self).__init__(parent)
self.Show()
d = SomeDialog(self)
d.ShowModal()
self.Destroy()
class SomeDialog(wx.Dialog):
def __init__(self, parent):
super(SomeDialog, self).__init__(parent)
button = wx.Button(self, wx.ID_CANCEL, 'Cancel')
button.Bind(wx.EVT_BUTTON, self.action_cancel)
#button.SetFocus()
def action_cancel(self, e):
self.EndModal(wx.ID_CANCEL)
if __name__ == '__main__':
app = wx.App(False)
frame = MainWindow(None)
app.MainLoop()
Update: This happens when running on linux (Fedora 20, Gnome)
You can call the dialog's SetFocus() method to set focus on itself:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent):
super(MainWindow, self).__init__(parent)
self.Show()
d = SomeDialog(self)
d.ShowModal()
self.Destroy()
class SomeDialog(wx.Dialog):
def __init__(self, parent):
super(SomeDialog, self).__init__(parent)
button = wx.Button(self, wx.ID_CANCEL, 'Cancel')
button.Bind(wx.EVT_BUTTON, self.action_cancel)
self.SetFocus()
def action_cancel(self, e):
self.EndModal(wx.ID_CANCEL)
if __name__ == '__main__':
app = wx.App(False)
frame = MainWindow(None)
app.MainLoop()
This works on Kubuntu 14.04 with wxPython 2.8.12.1 and Python 2.7.6. However, I suspect that when you set focus to the dialog, it is probably passing the focus to its first child just as a wx.Panel will do. I don't know why Linux is behaving in this manner, but I would agree with #nepix32 and #VZ that this should work without the SetFocus(). You can call it anyway as a workaround, but you should probably report it as a bug. There's a link on the wxPython website where you can submit your bug report.

Good way of implementing more windows in PyQt

How to open a new window after the user clicks a button is described here:
https://stackoverflow.com/a/21414775/1898982
and here:
https://stackoverflow.com/a/13519181/1898982
class Form1(QtGui.QWidget, Ui_Form1):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
self.button1.clicked.connect(self.handleButton)
self.window2 = None
def handleButton(self):
if self.window2 is None:
self.window2 = Form2(self)
self.window2.show()
class Form2(QtGui.QWidget, Ui_Form2):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
I want to develop a GUI application that consists of several steps. Once the user clicks next, the current window closes and another window opens. Technically I can do this like it is described above: Each window opens a new one. After a few steps this is pretty much nested.
Is there a better way to do this?
I would like to have the control flow in my main. Something like this:
main()
window1 = win2()
window1.show()
wait until button in window1 is clicked, then
window1.close()
window2 = win2()
window2.show()
wait until button in window2 is clicked, then
window1.close()
....
I would recommend to use QWizard or QStackedWidget class to perform this task. You can easily switch between widgets or windows using either of these two classes. Refer to QWizard and QStackedWidget docs.
Qt provides special widget for such cases, which called QWidget. Look at it. It's also available in Qt4.

PyQt4 - closing a dialog window, exec_() not working

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()

Categories

Resources