I am using QTableWidget to creating something like excel. One of the QTableWidget column is able to let user update note with multiple line. Before use QTextEdit, user need to manually add " \n" to achieve multiple line, but this is not friendly user. I found out I can set QTextEdit into QTableWidget. By using QTextEdit, I able to type multiple line by press "Enter" or "Shift+Enter". However, I want when "Shift+Enter" is pressed, it go to next line but when "Enter" is pressed, it run self.update_MySQL function.
Below is my sample code
import sys, itertools, sip
sip.setapi('QVariant',2)
from PyQt4 import QtCore, QtGui
class CustomTextEditDelegate(QtGui.QItemDelegate):
def createEditor(self, parent, option, index):
editor = QtGui.QTextEdit(parent)
return editor
def setEditorData(self, editor, index):
editor.setText(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.toPlainText())
class PIX_DATABASE_UI(QtGui.QTableWidget):
def __init__(self, parent=None):
super(PIX_DATABASE_UI, self).__init__(parent)
### signal
self.update_tableWidget()
self.itemEntered.connect(self.update_MySQL)
# -----------------------------------------------------------------------------------------------------------------#
def update_MySQL(self):
print "MySQL Updated"
def update_tableWidget(self):
self.filter_columns = [u'remark']
self.setColumnCount(len(self.filter_columns))
self.setHorizontalHeaderLabels(self.filter_columns)
self.setRowCount(5)
for row, col in itertools.product(range(5), range(len(self.filter_columns))):
if self.filter_columns[col] == "remark":
width = self.sizeHint().width()
self.setColumnWidth(col, width * 0.75)
self.setItem(row, col, QtGui.QTableWidgetItem(str("ABC")))
self.setItemDelegateForColumn(col, CustomTextEditDelegate(self))
self.verticalHeader().setResizeMode(row, QtGui.QHeaderView.ResizeToContents)
# -----------------------------------------------------------------------------------------------------------------#
# -----------------------------------------------------------------------------------------------------------------#
if __name__ == '__main__':
global ui
try:
ui.close()
except:
pass
app = QtGui.QApplication(sys.argv)
app.setStyle(QtGui.QStyleFactory.create("Plastique"))
# print QtGui.QStyleFactory.keys()
ui = PIX_DATABASE_UI()
ui.show()
sys.exit(app.exec_())
Conclusion:
Thanks eyllanesc, the code is help me to achieve what I want to achieve with slightly modify to the keyPressEvent. Original code is still emit even I press "Shift + Enter".
Below code is what I modified.
def keyPressEvent(self, event):
modifiers = QtGui.QApplication.keyboardModifiers()
if modifiers != QtCore.Qt.ShiftModifier and event.key() == QtCore.Qt.Key_Return:
self.enter.emit()
# If you do not want a new line uncomment the following
# return
super(TextEdit, self).keyPressEvent(event)
So now, after edit in textEdit and press "Enter", it will run self.update_MySQL and when press "Shift + Enter", it will go to next line.
What you can do is that the data of the model is updated and for this the commitData signal calling the setModelData() method must be emited.
By doing this you can use the signal itemChanged() because the data of the item is modified.
import sys, itertools, sip
sip.setapi('QVariant',2)
from PyQt4 import QtCore, QtGui
class TextEdit(QtGui.QTextEdit):
pressed = QtCore.pyqtSignal()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
self.pressed.emit()
# If you do not want a new line uncomment the following
# return
super(TextEdit, self).keyPressEvent(event)
class CustomTextEditDelegate(QtGui.QItemDelegate):
def createEditor(self, parent, option, index):
editor = TextEdit(parent)
editor.pressed.connect(self.commitAndCloseEditor)
return editor
def setEditorData(self, editor, index):
editor.setText(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.toPlainText())
def commitAndCloseEditor(self):
editor = self.sender()
self.commitData.emit(editor)
# if you want to close the editor uncomment the following
# self.closeEditor.emit(editor, QtGui.QAbstractItemDelegate.NoHint)
class PIX_DATABASE_UI(QtGui.QTableWidget):
def __init__(self, parent=None):
super(PIX_DATABASE_UI, self).__init__(parent)
### signal
self.update_tableWidget()
self.itemEntered.connect(self.update_MySQL)
self.itemChanged.connect(self.update_MySQL)
# -----------------------------------------------------------------------------------------------------------------#
def update_MySQL(self, it):
print("MySQL Updated", it.text())
def update_tableWidget(self):
self.filter_columns = [u'remark']
self.setColumnCount(len(self.filter_columns))
self.setHorizontalHeaderLabels(self.filter_columns)
self.setRowCount(5)
for row, col in itertools.product(range(5), range(len(self.filter_columns))):
if self.filter_columns[col] == "remark":
width = self.sizeHint().width()
self.setColumnWidth(col, width * 0.75)
self.setItem(row, col, QtGui.QTableWidgetItem(str("ABC")))
self.setItemDelegateForColumn(col, CustomTextEditDelegate(self))
self.verticalHeader().setResizeMode(row, QtGui.QHeaderView.ResizeToContents)
# -----------------------------------------------------------------------------------------------------------------#
# -----------------------------------------------------------------------------------------------------------------#
if __name__ == '__main__':
global ui
try:
ui.close()
except:
pass
app = QtGui.QApplication(sys.argv)
app.setStyle(QtGui.QStyleFactory.create("Plastique"))
# print QtGui.QStyleFactory.keys()
ui = PIX_DATABASE_UI()
ui.show()
sys.exit(app.exec_())
Related
Grееtings аll. I am new to this site, so go easy on me.
I am building a program in python using PyQt5 for the interface. I currently have, among other things, a QListWidget (list window) into which I insert a number of QWidgets (list items) through a function during the running of the program. I have implemented an eventFilter by subclassing QObject, and I use it to differentiate between left and right click, and from that I send to the controller class to handle one or the other click accordingly.
I have made it so that when right-clicked on a list item, a context menu appears. However, I also need a context menu to appear when the list window is clicked (not on a list item). The problem which occurs is that when right-clicked on a list item, both the list item context menu and list window context menu appear. This must be because the event filter recognises the click as occurring within the list window, because it is occurring on a list item, which is within the list window. What I need is that when right-clicked on a list item, only its context menu appears, and similarly for the list window, when right-clicked outside the list items.
I have tried checking if source equals the widget where the event appeared, but it seems to recognise both widgets' events independently and if I gate the call to the handler with an if condition, one of the handlers never receives a call. I have searched around the web and this site, but I have found nothing of use. Perhaps it is due to me not being a native speaker and so not being able to phrase things correctly (you can see how clunky my phrasing is).
Below follows some code extracted from my program. Note that anything irrelevant has been cut away to make for a minimal example. For your convenience, I have also merged the GUI files into one and did the same for the control files. I have tested this minimal example and it reproduces the problem. It could not get smaller, so if you deem the code listed below too long, notify me and I can reupload it to GitHub if it is allowed to show the minimal example that way instead of putting code into the question directly.
custom_classes.py:
from PyQt5.QtCore import Qt, QEvent, QObject
class MyFilter(QObject):
def __init__(self, parent, ctrl):
super().__init__(parent)
self._parent = parent
self.ctrl = ctrl
self.parent.installEventFilter(self)
#property
def parent(self):
return self._parent
def eventFilter(self, source, event):
if event.type() == QEvent.MouseButtonPress:
if event.button() == Qt.LeftButton:
self.ctrl.handle_left_click()
elif event.button() == Qt.RightButton:
self.ctrl.handle_right_click(event)
return super().eventFilter(source, event)
gui.py:
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QListWidget
from PyQt5.QtWidgets import QScrollArea
from PyQt5.QtCore import Qt
class MainFrame(QWidget):
def __init__(self):
super().__init__()
self.main_layout = QHBoxLayout()
self.setLayout(self.main_layout)
class ListItemFrame(QWidget):
def __init__(self):
super().__init__()
self.main = QHBoxLayout()
self.main.setContentsMargins(0,0,0,0)
self.name_layout = QHBoxLayout()
self.name_layout.setContentsMargins(0,0,0,0)
self.main.addLayout(self.name_layout)
self.name = QLabel("")
self.name.setMaximumHeight(20)
self.name_layout.addWidget(self.name)
self.setLayout(self.main)
class ListFrame(QListWidget):
def __init__(self):
super().__init__()
self.main = QVBoxLayout()
self.scroll_widget = QScrollArea()
self.scroll_widget.setWidgetResizable(True)
self.scroll_layout = QVBoxLayout()
self.scroll_layout.setAlignment(Qt.AlignTop)
self.scroll_layout_widget = QWidget()
self.scroll_layout_widget.setLayout(self.scroll_layout)
self.scroll_widget.setWidget(self.scroll_layout_widget)
self.main.addWidget(self.scroll_widget)
self.setLayout(self.main)
ctrl.py:
from PyQt5.QtWidgets import QMenu
from gui import ListFrame, ListItemFrame
from custom_classes import MyFilter
class Controller:
def __init__(self, ui, app):
self.ui = ui
self.app = app
self.list_ = ListControl(self)
class ListControl:
def __init__(self, ctrl):
self.ctrl = ctrl
self.ui = ListFrame()
self.the_list = self.get_list() #list of stuff
self.item_list = [] #list of list items
self.ctrl.ui.main_page.main_layout.addWidget(self.ui)
self.index = self.ctrl.ui.main_page.main_layout.count() - 1
self.filter = MyFilter(self.ui, self)
self.show_list()
def handle_left_click(self):
pass #other irrelevant function
def handle_right_click(self, event):
self.show_options(event)
def show_options(self, event):
menu = QMenu()
one_action = menu.addAction("Something!")
quit_action = menu.addAction("Quit")
action = menu.exec_(self.ui.mapToGlobal(event.pos()))
if action == quit_action:
self.ctrl.ui.close()
elif action == one_action:
self.something()
def something(self):
print("Something!")
def show_list(self):
for info in self.the_list:
item = ListItem(self, info)
self.item_list.append(item)
def get_list(self):
return [x for x in "qwertzuiopasdfghjklyxcvbnm"]
class ListItem:
def __init__(self, main, info):
self.main = main
self.info = info*10
self.ui = ListItemFrame()
self.filter = MyFilter(self.ui, self)
self.set_ui()
self.add_to_ui()
self.main.ui.scroll_layout.addWidget(self.ui)
def handle_left_click(self):
pass #other irrelevant function
def handle_right_click(self, event):
self.show_options(event)
def show_options(self, event):
menu = QMenu()
item_action = menu.addAction("Hello!")
quit_action = menu.addAction("Quit")
action = menu.exec_(self.ui.mapToGlobal(event.pos()))
if action == quit_action:
self.main.ctrl.ui.close()
elif action == item_action:
self.hello()
def hello(self):
print(f"Hello! I am {self.info}")
def set_ui(self):
self.ui.name.setText(self.info)
def add_to_ui(self):
self.main.ui.scroll_layout.insertWidget(
self.main.ui.scroll_layout.count() - 1, self.ui
)
main.py:
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QStackedLayout
from PyQt5.QtWidgets import QWidget
from gui import MainFrame
from ctrl import Controller
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("minimal example")
self.stacked = QStackedLayout()
self.main_page = MainFrame()
self.stacked.addWidget(self.main_page)
self.setLayout(self.stacked)
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = Window()
window.show()
c = Controller(window, app)
sys.exit(app.exec())
To reiterate, the context menu appears for both the list item and the list window when a list item is right-clicked. What I need is for it to appear only for the list item if a list item is right-clicked.
Edit: seems the site bit off a part of my introduction. Readded it!
this is probably not the best way to do it but it works. You just create a global variable, for example list_element_clicked and when you click "hello" (of course not quit because you are going to exit the window and there is no point) you set that variable to True. If that variable is False, you show the ListControl context menu, and if not, you set that variable to True, so next time if you click on your ListControl it will appear, and if you click on ListItem it will not.
Finally there is an extra case, if you don't click anywhere after clicking on ListItem, nothing will happen (the ListControl is not shown and the variable is not changed) so everything will work perfectly next time.
So here is the code:
ctrl.py:
from PyQt5.QtWidgets import QMenu
from gui import ListFrame, ListItemFrame
from custom_classes import MyFilter
list_element_clicked = False
class Controller:
def __init__(self, ui, app):
self.ui = ui
self.app = app
self.list_ = ListControl(self)
class ListControl:
def __init__(self, ctrl):
self.ctrl = ctrl
self.ui = ListFrame()
self.the_list = self.get_list() #list of stuff
self.item_list = [] #list of list items
self.ctrl.ui.main_page.main_layout.addWidget(self.ui)
self.index = self.ctrl.ui.main_page.main_layout.count() - 1
self.filter = MyFilter(self.ui, self)
self.show_list()
def handle_left_click(self):
pass #other irrelevant function
def handle_right_click(self, event):
global list_element_clicked
if(list_element_clicked == False):
self.show_options(event)
else:
list_element_clicked = False
def show_options(self, event):
menu = QMenu()
one_action = menu.addAction("Something!")
quit_action = menu.addAction("Quit")
action = menu.exec_(self.ui.mapToGlobal(event.pos()))
if action == quit_action:
self.ctrl.ui.close()
elif action == one_action:
self.something()
def something(self):
print("Something!")
def show_list(self):
for info in self.the_list:
item = ListItem(self, info)
self.item_list.append(item)
def get_list(self):
return [x for x in "qwertzuiopasdfghjklyxcvbnm"]
class ListItem:
def __init__(self, main, info):
self.main = main
self.info = info*10
self.ui = ListItemFrame()
self.filter = MyFilter(self.ui, self)
self.set_ui()
self.add_to_ui()
self.main.ui.scroll_layout.addWidget(self.ui)
def handle_left_click(self):
pass #other irrelevant function
def handle_right_click(self, event):
self.show_options(event)
def show_options(self, event):
menu = QMenu()
item_action = menu.addAction("Hello!")
quit_action = menu.addAction("Quit")
action = menu.exec_(self.ui.mapToGlobal(event.pos()))
if action == quit_action:
self.main.ctrl.ui.close()
elif action == item_action:
global list_element_clicked
list_element_clicked = True
self.hello()
def hello(self):
print(f"Hello! I am {self.info}")
def set_ui(self):
self.ui.name.setText(self.info)
def add_to_ui(self):
self.main.ui.scroll_layout.insertWidget(
self.main.ui.scroll_layout.count() - 1, self.ui
)
In the default behavior of editing a cell in a QtableView, when the user clicks away either to another widget or closes the form, the edits are lost. After a lot of googling, I have found a way to save the edits if the user selects another widget in the form, but if the form is closed, the edits are still lost. The blog post is here.
I attempted to call the closeEditor method from the forms closeEvent, but it requires two parameters: the editor and hint. I can provide QAbstractItemDelegate.NoHint but the editor is expecting the QlineEdit object where the editing is taking place. I am lost on how to provide this for the cell currently being edited.
Here is a gif of the current behaviour:
My question is how do I provide the QlineEdit of the cell being edited?
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5.QtWidgets import *
from phones import *
class Main(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.resize(490, 998)
self.layoutWidget = QWidget(self)
self.layoutWidget.setObjectName("layoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.new_phone = QtWidgets.QPushButton(self.layoutWidget)
self.new_phone.setObjectName("new_phone")
self.new_phone.setText("New Phone")
self.horizontalLayout_7.addWidget(self.new_phone)
self.delete_phone = QtWidgets.QPushButton(self.layoutWidget)
self.delete_phone.setObjectName("delete_phone")
self.delete_phone.setText("Delete phone")
self.horizontalLayout_7.addWidget(self.delete_phone)
self.verticalLayout.addLayout(self.horizontalLayout_7)
self.phone_view = Syn_tableview()
self.verticalLayout.addWidget(self.phone_view)
self.cont_id = '9'
self.setCentralWidget(self.layoutWidget)
self.new_phone.clicked.connect(self.add_phone)
self.populate_phones()
def populate_phones(self):
self.phone_model = QSqlTableModel(self)
self.phone_model.setTable("contact_phones")
self.phone_model.setFilter("contact_id='{0}'".format(self.cont_id))
self.phone_model.select()
self.phone_view.setModel(self.phone_model)
self.phone_view.resizeColumnsToContents()
def add_phone(self):
self.phone_model.submitAll()
self.phone_model.setEditStrategy(QSqlTableModel.OnManualSubmit)
row = self.phone_model.rowCount()
record = self.phone_model.record()
record.setGenerated('id', False) #primary key
record.setValue('contact_id', self.cont_id) #foreign key
self.phone_model.insertRecord(row, record)
phone_index_edit = self.phone_model.index(row, self.phone_model.fieldIndex('phone_number'))
self.phone_view.edit(phone_index_edit)
def closeEvent(self, event):
submit = self.phone_model.submitAll()
#This is the problem
self.phone_view.closeEditor("QLineEdit", QAbstractItemDelegate.NoHint)
class Syn_tableview(QTableView):
def __init__(self, *args, **kwargs):
QTableView.__init__(self, *args, **kwargs)
def closeEditor(self, editor, hint):
if hint == QAbstractItemDelegate.NoHint:
QTableView.closeEditor(self, editor,
QAbstractItemDelegate.SubmitModelCache)
if __name__=="__main__":
app=QApplication(sys.argv)
db = QSqlDatabase.addDatabase("QPSQL");
db.setHostName(server)
db.setDatabaseName(database)
db.setUserName(user)
db.setPassword(pword)
myapp = Main()
myapp.show()
sys.exit(app.exec_())
The delegate editors are children of the QTableView so you can use findChildren to get them, to make sure they are not other children you can set an objectName that allows you to filter them:
import sys
from PyQt5 import QtCore, QtSql, QtWidgets
def create_connection():
db = QtSql.QSqlDatabase.addDatabase("QPSQL")
# FIXME
db.setHostName("server")
db.setDatabaseName("database")
db.setUserName("user")
db.setPassword("pword")
if not db.open():
print(db.lastError().text())
return False
return True
class Syn_Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = super(Syn_Delegate, self).createEditor(parent, option, index)
if isinstance(editor, QtWidgets.QWidget):
editor.setObjectName("syn_editor")
return editor
class Syn_Tableview(QtWidgets.QTableView):
def closeEditor(self, editor, hint):
if hint == QtWidgets.QAbstractItemDelegate.NoHint:
hint = QtWidgets.QAbstractItemDelegate.SubmitModelCache
super(Syn_Tableview, self).closeEditor(editor, hint)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.new_phone = QtWidgets.QPushButton(self.tr("New Phone"))
self.delete_phone = QtWidgets.QPushButton(self.tr("Delete phone"))
self.phone_view = Syn_Tableview()
self.phone_model = QtSql.QSqlTableModel()
self.phone_model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
self.phone_view.setModel(self.phone_model)
self.phone_view.resizeColumnsToContents()
delegate = Syn_Delegate(self)
self.phone_view.setItemDelegate(delegate)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QGridLayout(central_widget)
lay.addWidget(self.new_phone, 0, 0)
lay.addWidget(self.delete_phone, 0, 1)
lay.addWidget(self.phone_view, 1, 0, 1, 2)
self._contact_id = "9"
self.populate_phones()
self.new_phone.clicked.connect(self.add_phone)
#property
def contact_id(self):
return self._contact_id
def populate_phones(self):
self.phone_model.setTable("contact_phones")
self.phone_model.setFilter("contact_id='{0}'".format(self.contact_id))
self.phone_model.select()
#QtCore.pyqtSlot()
def add_phone(self):
self.phone_model.submitAll()
row = self.phone_model.rowCount()
record = self.phone_model.record()
record.setGenerated("id", False) # primary key
record.setValue("contact_id", self.contact_id) # foreign key
self.phone_model.insertRecord(row, record)
phone_index_edit = self.phone_model.index(
row, self.phone_model.fieldIndex("phone_number")
)
if phone_index_edit.isValid():
self.phone_view.edit(phone_index_edit)
def closeEvent(self, event):
for editor in self.phone_view.findChildren(QtWidgets.QWidget, "syn_editor"):
self.phone_view.commitData(editor)
submit = self.phone_model.submitAll()
super().closeEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
if not create_connection():
sys.exit(-1)
w = MainWindow()
w.show()
w.resize(640, 480)
ret = sys.exit(app.exec_())
sys.exit(ret)
if __name__ == "__main__":
main()
Is there a signal/event I can make use of for the QMenu tear-off?
I have a QMenu subclass in which it has .setTearOffEnabled(True), but I would like to set this tear-off to always be on the top if the user clicks on the tear-off 'bar'.
I am unable to utilise QtCore.Qt.WindowStaysOnTopHint, as this will result in my menu already being in the tear-off state.
For example: if my main tool area is bigger than the tear-off, and I click on my main tool, the tear-off window will be behind it.
In the following code the clicked signal is emitted when the tear off (dotted lines) is pressed:
import sys
from PyQt5 import QtCore, QtWidgets
class Menu(QtWidgets.QMenu):
clicked = QtCore.pyqtSignal()
def mouseReleaseEvent(self, event):
if self.isTearOffEnabled():
tearRect = QtCore.QRect(
0,
0,
self.width(),
self.style().pixelMetric(
QtWidgets.QStyle.PM_MenuTearoffHeight, None, self
),
)
if tearRect.contains(event.pos()):
self.clicked.emit()
QtCore.QTimer.singleShot(0, self.after_clicked)
super(Menu, self).mouseReleaseEvent(event)
#QtCore.pyqtSlot()
def after_clicked(self):
tornPopup = None
for tl in QtWidgets.QApplication.topLevelWidgets():
if tl.metaObject().className() == "QTornOffMenu":
tornPopup = tl
break
if tornPopup is not None:
print("This is the tornPopup: ", tornPopup)
tornPopup.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QMainWindow(parent=None)
menu = Menu("Menu", tearOffEnabled=True)
menu.clicked.connect(lambda: print("clicked"))
w.menuBar().addMenu(menu)
for i in range(5):
action = QtWidgets.QAction("action{}".format(i), w)
menu.addAction(action)
w.show()
sys.exit(app.exec_())
When a menu is torn off, it is hidden and Qt replaces it with a copy created from an internal subclass of QMenu. So to set the WindowStaysOnTopHint on a torn off menu, you would first need to find a way to get a reference to it. One way to do this would be to set an event-filter on the application object and watch for child events of the right type:
class MenuWatcher(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
QtWidgets.qApp.installEventFilter(self)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.ChildAdded and
event.child().metaObject().className() == 'QTornOffMenu'):
event.child().setWindowFlag(QtCore.Qt.WindowStaysOnTopHint)
return super().eventFilter(source, event)
This class will operate on all torn-off menus created in the application.
However, if the event-filtering was done by the source menu class, its own torn-off menu could be identified by comparing menu-items:
class Menu(QtWidgets.QMenu):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTearOffEnabled(True)
QtWidgets.qApp.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.ChildAdded:
child = event.child()
if (child.metaObject().className() == 'QTornOffMenu' and
all(a is b for a, b in zip(child.actions(), self.actions()))):
child.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint)
return super().eventFilter(source, event)
I made a ui for work using PyQt5 and Python3. Additionally to clicking the buttons, I want to execute specific actions by pressing specific keys on my keyboard e.g. the space bar. I used the following to do so:
def keyPressEvent(self, event):
key = event.key()
if key == Qt.Key_Space:
print('space')
elif key == Qt.Key_W:
print('w')
Instead of printing 'space', it presses the focused button in the ui, when I'm pressing the space bar. Just like I hit return. Although pressing the 'W'-key prints 'w' as expected.
I already searched here at stackoverflow and other where in the web as well, but I found nothing really helpfull. The best I got was this one here. But it's using PyQt4.3 and copy and paste the code to my editor just brought me some errors. I thought the approach was a good one, but I were not able to transfer this to PyQt5.
Try it:
#from PyQt4.QtCore import *
#from PyQt4.QtGui import *
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
# create objects
self.la = QLabel("Press Space or `w` in this box:")
self.le = MyLineEdit()
self.la2 = QLabel("\nLook here:")
self.le2 = QLineEdit()
self.btnSpace = QPushButton("Button1")
self.btnSpace.setEnabled(False)
self.btnSpace.clicked.connect(self.onBtnSpace)
self.btnW = QPushButton("Button2")
self.btnW.setEnabled(False)
self.btnW.clicked.connect(self.onBtnW)
# layout
layout = QVBoxLayout()
layout.addWidget(self.la)
layout.addWidget(self.le)
layout.addWidget(self.la2)
layout.addWidget(self.le2)
layout.addWidget(self.btnSpace)
layout.addWidget(self.btnW)
self.setLayout(layout)
# connections
#self.connect(self.le, SIGNAL("tabPressed"), self.update)
self.le.signalTabPressed[str].connect(self.update)
def update(self, keyPressed):
newtext = str(self.le2.text()) + str(keyPressed) #"tab pressed "
self.le2.setText(newtext)
if keyPressed == "Key Space; ":
self.btnSpace.setEnabled(True)
self.btnSpace.setText(str(keyPressed))
if keyPressed == "Key W; ":
self.btnW.setEnabled(True)
self.btnW.setText(str(keyPressed))
def onBtnSpace(self):
print("keyPressed -> btnSpace")
def onBtnW(self):
print("keyPressed -> btnW")
class MyLineEdit(QLineEdit):
signalTabPressed = pyqtSignal(str) # +++
def __init__(self, *args):
QLineEdit.__init__(self, *args)
def event(self, event):
#if (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_Tab):
if (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_Space):
self.signalTabPressed.emit("Key Space; ")
return True
if (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_W):
self.signalTabPressed.emit("Key W; ")
return True
return QLineEdit.event(self, event)
def main():
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
With S. Nick's very useful answer I were able to come to a solution that really fits my needs.
class MyPushButton(QPushButton):
def __init__(self, *args):
QPushButton.__init__(self, *args)
def event(self, event):
if (event.type() == QEvent.KeyPress) and (event.key() == Qt.Key_Space):
print(foo)
return True
return QPushButton.event(self, event)
So, that's it. Whenever I press the space key on my keyboard now it just prints foo and nothing else. Where print(foo) is just a place-holder for whatever I wish to do, obviously.
Using PyQt 4.8 and Python 3.3
I'm using a modified version of this example: whereas this example emits a signal on tab press and adds arbitrary text to the second QLineEdit, I want my script to emit a signal on any keypress, add arbitrary signal text to the 2nd QLineEdit, and add the typed character to the 1st QLineEdit (assuming it's a valid ASCII character).
Whenever I try to use any keypress as a signal, I can no longer grab that text to input into QLineEdit. Here's what I have so far and where I'm stuck:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
####################################################################
def main():
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
####################################################################
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
# create objects
self.la = QLabel("Type in this box:")
self.le = MyLineEdit()
self.la2 = QLabel("\nLook here:")
self.le2 = QLineEdit()
self.char = MyLineEdit.char # HOW CAN I GET THIS WORKING?
# layout
layout = QVBoxLayout()
layout.addWidget(self.la)
layout.addWidget(self.le)
layout.addWidget(self.la2)
layout.addWidget(self.le2)
self.setLayout(layout)
# connections
self.connect(self.le, SIGNAL("keyPressed"),
self.update)
def update(self):
newtext1 = self.le.text() + self.char
newtext2 = self.le2.text() + "kP "
self.le.setText(newtext1)
self.le2.setText(newtext2)
####################################################################
class MyLineEdit(QLineEdit):
def __init__(self, *args):
QLineEdit.__init__(self, *args)
def event(self, event):
if (event.type() == QEvent.KeyPress):
self.emit(SIGNAL("keyPressed"))
self.char = "%c" % (event.key())
return True
return QLineEdit.event(self, event)
####################################################################
if __name__ == "__main__":
main()
Any and all help is greatly appreciated. Is there something within PyQt4 that allows me to use a keypress as both a signal and input text, or is my Python off?
Problem1: you are emitting the signal before setting self.char:
class MyLineEdit(QLineEdit):
def __init__(self, *args):
QLineEdit.__init__(self, *args)
self.char = ""
def event(self, event):
if (event.type() == QEvent.KeyPress):
self.char = "%c" % (event.key()) #this line above the next
self.emit(SIGNAL("keyPressed"))
return True
return QLineEdit.event(self, event)
Problem 2: use the char value in your MyLineEdit object:
def update(self):
newtext1 = self.le.text() + self.le.char
newtext2 = self.le2.text() + "kP "
self.le.setText(newtext1)
self.le2.setText(newtext2)
Finally you don't need self.char on MyWindow