With PyQt4, I am using a QtableView with more than 10 columns. The user must have the choice of showing/hiding a column.
This is generally done by adding a small button in the top-right of the table's header. The button shows a menu with checked/unchecked Checkboxes allowing to hide/show columns.
This is an example from Sqlite-Manager Table.
So, I wonder how can I do the same with PyQt's QtableView?
Thanks,
Thank you Kitsune Meyoko, it was a great Idea.. ;)
I found another solution pretty much like yours by using QMenu with Checkable QActions instead of a QPushButton: Let's Go:
import sys
import string
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Header(QHeaderView):
def __init__(self, parent=None):
super(Header, self).__init__(Qt.Horizontal, parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.ctxMenu)
self.setup()
#pyqtSlot(bool)
def printID(self, i):
print("id")
if i == False:
self.hideSection(0)
else:
self.showSection(0)
#pyqtSlot(bool)
def printNAME(self, i):
print("name")
if i == False:
self.hideSection(1)
else:
self.showSection(1)
#pyqtSlot(bool)
def printUSERNAME(self, i):
print("username")
if i == False:
self.hideSection(2)
else:
self.showSection(2)
def setup(self):
self.id = QAction("id",self)
self.id.setCheckable(True)
self.id.setChecked(True)
self.connect(self.id, SIGNAL("triggered(bool)"), self, SLOT("printID(bool)"))
self.name = QAction("name",self)
self.name.setCheckable(True)
self.name.setChecked(True)
self.connect(self.name, SIGNAL("triggered(bool)"), self, SLOT("printNAME(bool)"))
self.username = QAction("username",self)
self.username.setCheckable(True)
self.username.setChecked(True)
self.connect(self.username, SIGNAL("triggered(bool)"), self, SLOT("printUSERNAME(bool)"))
def ctxMenu(self, point):
menu = QMenu(self)
self.currentSection = self.logicalIndexAt(point)
menu.addAction(self.id)
menu.addAction(self.name)
menu.addAction(self.username)
menu.exec_(self.mapToGlobal(point))
class Table(QTableWidget):
def __init__(self, parent=None):
super(Table, self).__init__(parent)
self.setHorizontalHeader(Header(self))
self.setColumnCount(3)
self.setHorizontalHeaderLabels(['id', 'name', 'username'])
self.populate()
def populate(self):
self.setRowCount(10)
for i in range(10):
for j,l in enumerate(string.ascii_letters[:3]):
self.setItem(i, j, QTableWidgetItem(l))
if __name__ == '__main__':
app = QApplication(sys.argv)
t = Table()
t.show()
app.exec_()
sys.exit()
In QTableView not have kind of button just like "Sqlite-Manager Table". But your can custom widget by using QtGui.QPushButton and work with QtGui.QMenu together to get column from user. And use QTableView.hideColumn (self, int column) & QTableView.showColumn (self, int column) to hide show your column;
Full example;
import sys
import random
from functools import partial
from PyQt4 import QtGui
class QCustomTableViewWidget (QtGui.QWidget):
def __init__ (self, myQStandardItemModel, *args, **kwargs):
super(QCustomTableViewWidget, self).__init__(*args, **kwargs)
# Layout setup
self.localQTableView = QtGui.QTableView()
self.rightCornerQPushButton = QtGui.QPushButton()
menuQHBoxLayout = QtGui.QHBoxLayout()
menuQHBoxLayout.addStretch(1)
menuQHBoxLayout.addWidget(self.rightCornerQPushButton)
allQVBoxLayout = QtGui.QVBoxLayout()
allQVBoxLayout.addLayout(menuQHBoxLayout)
allQVBoxLayout.addWidget(self.localQTableView)
self.setLayout(allQVBoxLayout)
# Object setup
self.localQTableView.setModel(myQStandardItemModel)
self.rightCornerQPushButton.setText('Show column')
currentQMenu = QtGui.QMenu()
for column in range(myQStandardItemModel.columnCount()):
currentQAction = QtGui.QAction('Column %d' % (column + 1), currentQMenu)
currentQAction.setCheckable(True)
currentQAction.setChecked(True)
currentQAction.toggled.connect(partial(self.setColumnVisible, column))
currentQMenu.addAction(currentQAction)
self.rightCornerQPushButton.setMenu(currentQMenu)
def setColumnVisible (self, column, isChecked):
if isChecked:
self.localQTableView.showColumn(column)
else:
self.localQTableView.hideColumn(column)
def tableView (self):
return self.localQTableView
# Simulate data
myQStandardItemModel = QtGui.QStandardItemModel()
for _ in range(10):
myQStandardItemModel.appendRow([QtGui.QStandardItem('%d' % random.randint(100, 999)), QtGui.QStandardItem('%d' % random.randint(100, 999)), QtGui.QStandardItem('%d' % random.randint(100, 999))])
# Main application
myQApplication = QtGui.QApplication(sys.argv)
myQCustomTableViewWidget = QCustomTableViewWidget(myQStandardItemModel)
myQCustomTableViewWidget.show()
sys.exit(myQApplication.exec_())
Related
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()
I need to create a custom signal on Qmdisubwindow close. In other word, when I closed any subwindow, a signal is emitted with the name of that window being closed. Below is my trail, but seems not right. Error occurs as:
a subwindow already created without calling
add subwindow option is not working
closable action is not working
Hope you can show me how to fix it.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MyMdi(QMdiSubWindow):
sigClosed = pyqtSignal(str)
def __init__(self, parent=None):
super(MyMdi, self).__init__(parent)
def closeEvent(self, event):
"""Get the name of active window about to close
"""
name = name
self.sigClosed.emit('{} is close'.format(name))
QMdiSubWindow.closeEvent(self, event)
class MainWindow(QMainWindow):
count = 0
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
self.mdi = MyMdi()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.triggered[QAction].connect(self.windowaction)
self.setWindowTitle("MDI demo")
# my signal
self.mdi.sigClosed.connect(self.windowclosed)
#pyqtSlot(str)
def windowclosed(self, text):
print(text)
def windowaction(self, q):
if q.text() == "New":
MainWindow.count = MainWindow.count+1
sub = QMdiSubWindow()
sub.setWidget(QTextEdit())
sub.setWindowTitle("subwindow"+str(MainWindow.count))
self.mdi.addSubWindow(sub)
sub.show()
def main():
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You have an initial error: a QMdiSubWindow must be inside a QMdiArea but there is none in your code.
On the other hand, the idea of subclassing is good but you have several drawbacks:
You are not using it initially since there is no QMdiArea, if you execute the QAction then your application will be closed because a QMdiSubWindow does not have any method called addSubWindow.
The QMdiSubWindow does not have an attribute called name, you must use windowTitle.
class MdiSubWindow(QMdiSubWindow):
sigClosed = pyqtSignal(str)
def closeEvent(self, event):
"""Get the name of active window about to close
"""
self.sigClosed.emit(self.windowTitle())
QMdiSubWindow.closeEvent(self, event)
class MainWindow(QMainWindow):
count = 0
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.triggered[QAction].connect(self.windowaction)
self.setWindowTitle("MDI demo")
#pyqtSlot(str)
def windowclosed(self, text):
print(text)
def windowaction(self, q):
if q.text() == "New":
MainWindow.count = MainWindow.count + 1
sub = MdiSubWindow()
sub.setWidget(QTextEdit())
sub.setAttribute(Qt.WA_DeleteOnClose)
sub.setWindowTitle("subwindow" + str(MainWindow.count))
sub.sigClosed.connect(self.windowclosed)
self.mdi.addSubWindow(sub)
sub.show()
Forgive me if the question has already been asked, but I couldn't find the answer anywhere.
I am trying to test a small gui that contains a QListWidget and a QTreeWidget.
More specifically, I want to test the drag and drop behavior from one of the QListWidgetItem of the QListWidget to the QTreeWidget.
The ui works as intented but the problem comes when testing the drag and drop behavior as I am attempting to use QTest.mousePress() on the item, as this method only takes a QWidget as an input, and not a QListWidgetItem.
import sys
from PySide2 import QtWidgets, QtGui, QtCore
def startup():
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
class MainWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
main_layout = QtWidgets.QHBoxLayout(self)
self.source = SourceList()
main_layout.addWidget(self.source)
self.destination = DestinationTree()
main_layout.addWidget(self.destination)
self.add_items(self.source)
def add_items(self, list_widget):
items = ['apple', 'banana']
for item in items:
list_widget_item = QtWidgets.QListWidgetItem(item)
list_widget.addItem(list_widget_item)
def item_count(self):
number_of_items = 0
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
number_of_items += 1
iterator += 1
return number_of_items
class SourceList(QtWidgets.QListWidget):
def __init__(self, parent=None):
super(SourceList, self).__init__(parent)
self.setViewMode(QtWidgets.QListWidget.IconMode)
self.setDragEnabled(True)
def startDrag(self, supportedActions):
items = self.selectedItems()
name = items[0].text()
drag = QtGui.QDrag(self)
ba = bytearray(name, 'utf-8')
drag_mime_data = QtCore.QMimeData()
drag_mime_data.setData('MoveQComponentItem', QtCore.QByteArray(ba))
drag.setMimeData(drag_mime_data)
drag.exec_(QtCore.Qt.MoveAction)
class DestinationTree(QtWidgets.QTreeWidget):
def __init__(self, parent=None):
super(DestinationTree, self).__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, drag_event):
mime_data = drag_event.mimeData()
if mime_data.hasFormat('MoveQComponentItem'):
drag_event.acceptProposedAction()
def dragMoveEvent(self, drag_event):
return
def dragLeaveEvent(self, drag_event):
return
def dropEvent(self, drop_event):
print('Entering Drop event')
byte_array = drop_event.mimeData().data('MoveQComponentItem')
name = byte_array.data().decode('utf8')
print(name)
item = QtWidgets.QTreeWidgetItem()
item.setText(0, name)
self.addTopLevelItem(item)
def item_count(self):
number_of_items = 0
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
number_of_items += 1
iterator += 1
return number_of_items
if __name__ == '__main__':
startup()
I'd like to test this with something similar to this :
def test_shouldAddOneWidgetToTheTree_whenDragingFromListItemToTree(self):
my_q_list_widget_item = main_window.source.item(0)
tree_widget = main_window.destination
QtTest.QTest.mousePress(my_q_list_widget_item, QtCore.Qt.LeftButton)
QtTest.QTest.mouseMove(tree_widget)
QtTest.QTest.mouseRelease(tree_widget, QtCore.Qt.LeftButton)
count = tree_widget.item_count()
assert count == 1
Ideally I'd need a solution that works both on python 2 and 3, also if that helps, I'm using pytest.
Any idea would be greatly appreciated :)
I need auto completion in a table. So far, I could make it work that I get the same list for the entire table.
However, I need a dynamic list for each cell. How can I get update the list when I move to a new position in the cell?
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class mainWindow(QMainWindow):
def __init__(self, parent = None):
super(mainWindow, self).__init__(parent)
self.initUI()
def initUI(self):
self.center_window = centerWindow(parent=self)
self.setCentralWidget(self.center_window)
class centerWindow(QWidget):
def __init__(self, parent=None):
super(centerWindow, self).__init__(parent)
table = QTableWidget()
table.setItemDelegate(TableItemCompleter())
table.setRowCount(5)
table.setColumnCount(1)
vbox = QVBoxLayout(self)
vbox.addWidget(table)
self.setLayout(vbox)
class TableItemCompleter(QStyledItemDelegate):
def __init__(self, parent = None):
super(TableItemCompleter, self).__init__(parent)
def createEditor(self, parent, styleOption, index):
editor = QLineEdit(parent)
completion_ls = ['aaa', 'bbb', 'ccc']
autoComplete = QCompleter(completion_ls)
editor.setCompleter(autoComplete)
return editor
if __name__ == '__main__':
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
else:
print('QApplication instance already exists: %s' % str(app))
ex = mainWindow()
ex.show()
sys.exit(app.exec_())
To make it more clear. Instead of having the completion_ls list in the TableItemCompleter, I'd like to add something like this:
table.setItem(row, column) #add my new auto completion list
QCompleter can be established a model that uses as a source for the autocomplete, we could pass the QModelIndex model that provides the method createEditor(self, parent, option, index) through index.model() but the problem is that you can only take a column and is not what is desired.
Then we must do a conversion of the tablemodel to a listmodel, and then we must filter the repeated elements so that the completer shows unique elements, one way to do them is through proxies, classes that inherit from QAbstractProxyModel, the scheme is as follows:
TableModel ----> ReadTable2ListProxyModel ----> DuplicateFilterProxyModel
En la siguiente sección muestros las clases:
class ReadTable2ListProxyModel(QIdentityProxyModel):
def columnCount(self, parent=QModelIndex()):
return 1
def rowCount(self, parent=QModelIndex()):
return self.sourceModel().rowCount() * self.sourceModel().columnCount()
def mapFromSource(self, sourceIndex):
if sourceIndex.isValid() and sourceIndex.column() == 0\
and sourceIndex.row() < self.rowCount():
r = sourceIndex.row()
c = sourceIndex.column()
row = sourceIndex.model().columnCount() * c + r
return self.index(row, 0)
return QModelIndex()
def mapToSource(self, proxyIndex):
r = proxyIndex.row() / self.sourceModel().columnCount()
c = proxyIndex.row() % self.sourceModel().columnCount()
return self.sourceModel().index(r, c)
def index(self, row, column, parent=QModelIndex()):
return self.createIndex(row, column)
class DuplicateFilterProxyModel(QSortFilterProxyModel):
def setSourceModel(self, model):
model.dataChanged.connect(lambda: self.invalidate())
QSortFilterProxyModel.setSourceModel(self, model)
def filterAcceptsRow(self, row, parent):
value = self.sourceModel().index(row, self.filterKeyColumn())\
.data(self.filterRole())
if value is None:
return False
if row == 0:
return True
for i in reversed(range(0, row)):
val = self.sourceModel().index(i, self.filterKeyColumn())\
.data(self.filterRole())
if val == value:
return False
return True
Then the conversion is established in the delegate:
class TableItemCompleter(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
completer = QCompleter(parent)
proxy1 = ReadTable2ListProxyModel(parent)
proxy2 = DuplicateFilterProxyModel(parent)
proxy1.setSourceModel(index.model())
proxy2.setSourceModel(proxy1)
completer.setModel(proxy2)
editor.setCompleter(completer)
return editor
In the following link you will find an example, and in the following image the operation is illustrated, in the first widget the table is observed, in the second the conversion to list and in the third the list eliminating duplicate elements.
If you want each item to have a list what can be done is to store the list in each item through a role that is not in use as Qt.UserRole through the setData() method, and in the delegate through the method data() of the QModelIndex:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import random
class TableItemCompleter(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
completion_ls = index.data(Qt.UserRole) # get list
completer = QCompleter(completion_ls, parent)
editor.setCompleter(completer)
return editor
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
lay = QHBoxLayout(self)
tv = QTableWidget(3, 4, self)
lay.addWidget(tv)
l = ["AA", "AB", "AC", "AD", "BA", "BB", "BC"]
for i in range(tv.rowCount()):
for j in range(tv.columnCount()):
it = QTableWidgetItem(f"{i},{j}")
tv.setItem(i, j, it)
it.setData(Qt.UserRole, random.sample(l, 3)) # set list
tv.setItemDelegate(TableItemCompleter(tv))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I want to react on a mouseclick on a QLabel.
To achieve this I have redefined the method mouseReleaseEvent of QLabel.
I want to pass two arguments to the slot:
- the QtGui.QMouseEvent
- an ID of the clicked QLabel
But I can only pass one parameter. Either QtGui.QMouseEvent or the ID.
The combination does not work.
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSignal
class ExtendedQLabel(QtGui.QLabel):
#labelClickSignal_1 = pyqtSignal(QtGui.QMouseEvent)
labelClickSignal_1 = pyqtSignal(QtGui.QMouseEvent, int)
labelClickSignal_2 = pyqtSignal()
def __init(self, parent):
QtGui.QLabel.__init__(self, parent)
# redefinition
def mouseReleaseEvent(self, event):
#self.labelClickSignal_1.emit(event)
self.labelClickSignal_1.emit(event, 0)
self.labelClickSignal_2.emit()
class Test(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.names = ['Test1', 'Test2', 'Test3']
self.centralWidget = QtGui.QWidget()
self.setCentralWidget(self.centralWidget)
self.grid = QtGui.QGridLayout(self.centralWidget)
row = 0
for name in self.names:
self.addLabel(name, row)
row = row + 1
def addLabel(self, name, row):
label = ExtendedQLabel(name)
# QtGui.QMouseEvent is automatically passed to the slot
label.labelClickSignal_1.connect(self.onLabelClicked_1)
# The row ID is passed to the slot
label.labelClickSignal_2.connect(lambda id = row:
self.onLabelClicked_2(id))
# *This does not work*
label.labelClickSignal_1.connect(lambda id = row:
self.onLabelClicked_3(QtGui.QMouseEvent, id))
self.grid.addWidget(label, row, 1)
row = row + 1
def onLabelClicked_1(self, event):
if event.button() == QtCore.Qt.RightButton:
print('right')
else:
print('left')
def onLabelClicked_2(self, id):
print('Label {0} clicked'.format(id))
def onLabelClicked_3(self, event, id):
# *This does not work*
if event.button() == QtCore.Qt.RightButton:
print('right {0}'.format(id))
else:
print('left {0}'.format(id))
def main():
app = QtGui.QApplication(sys.argv)
t = Test()
t.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Ok, since your code had several pieces that did not work I rewrote the important Parts to achieve what you want. Please Note that I use PySide and not PyQt. This means you have to change the importe Statements and the Signal back to PyQt Notation.
The rest is explained in the code.
import sys
from PySide import QtGui, QtCore
class ExtendedQLabel(QtGui.QLabel):
#Signal that emits on MouseRelease
labelClickSignal_1 = QtCore.Signal(QtGui.QMouseEvent, int)
# init to -1
labelId = -1
# This is the new Constructor, Please note the double underscore
# before and behind `init`
def __init__(self, parent, labelId):
self.labelId = labelId
QtGui.QLabel.__init__(self, parent)
# emit labelClickSignal
def mouseReleaseEvent(self, event):
self.labelClickSignal_1.emit(event, self.labelId)
class Test(QtGui.QMainWindow):
def __init__(self, parent=None):
# same as yours [...]
def addLabel(self, name, row):
# please note the added argument
label = ExtendedQLabel(name,row)
# connect the signal
label.labelClickSignal_1.connect(self.onLabelClicked_1)
self.grid.addWidget(label, row, 1)
row = row + 1
def onLabelClicked_1(self, event,labelId):
if event.button() == QtCore.Qt.RightButton:
print('right')
print(labelId)
else:
print('left')
print(labelId)
OLD ANSWER
You have to define your Signal to support your two arguments:
labelClickSignal_1 = pyqtSignal(QtGui.QMouseEvent,int)
See here for additional information.
Example from the docs:
from PyQt4.QtCore import QObject, pyqtSignal
class Foo(QObject):
# This defines a signal called 'closed' that takes no arguments.
closed = pyqtSignal()
# This defines a signal called 'rangeChanged' that takes two
# integer arguments.
range_changed = pyqtSignal(int, int, name='rangeChanged')
# This defines a signal called 'valueChanged' that has two overloads,
# one that takes an integer argument and one that takes a QString
# argument. Note that because we use a string to specify the type of
# the QString argument then this code will run under Python v2 and v3.
valueChanged = pyqtSignal([int], ['QString'])
I think I solved it. Your lambda with the comment *and this does not work* took only one argument, though the slot is defined to take two. So I just skipped the first argument.
Next issue: It is difficult to pass Events that have been generated by system. I think they are destroyed after they have been handled. So I copied the data of the event into a namedtuple and passed this instead. (I also tried to copy the event, but this did not work somehow.)
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSignal
from collections import namedtuple
class ExtendedQLabel(QtGui.QLabel):
MouseEventTuple = namedtuple('MouseEventTuple', 'type pos button buttons modifiers')
#labelClickSignal_1 = pyqtSignal(QtGui.QMouseEvent)
labelClickSignal_1 = pyqtSignal(MouseEventTuple, int)
labelClickSignal_2 = pyqtSignal()
def __init(self, parent):
QtGui.QLabel.__init__(self, parent)
# redefinition
def mouseReleaseEvent(self, event):
eventTuple = ExtendedQLabel.MouseEventTuple(type = event.type(), pos = event.pos(), button = QtCore.Qt.RightButton, buttons = event.buttons(), modifiers = event.modifiers())
self.labelClickSignal_1.emit(eventTuple, 0)
self.labelClickSignal_2.emit()
class Test(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.names = ['Test1', 'Test2', 'Test3']
self.centralWidget = QtGui.QWidget()
self.setCentralWidget(self.centralWidget)
self.grid = QtGui.QGridLayout(self.centralWidget)
row = 0
for name in self.names:
self.addLabel(name, row)
row = row + 1
def addLabel(self, name, row):
label = ExtendedQLabel(name)
# QtGui.QMouseEvent is automatically passed to the slot
label.labelClickSignal_1.connect(self.onLabelClicked_1)
# The row ID is passed to the slot
label.labelClickSignal_2.connect(lambda id = row:
self.onLabelClicked_2(id))
# *This works now*
label.labelClickSignal_1.connect(lambda _unused_, id = row:
self.onLabelClicked_3(QtGui.QMouseEvent, id))
self.grid.addWidget(label, row, 1)
row = row + 1
def onLabelClicked_1(self, eventTuple):
if eventTuple.button == QtCore.Qt.RightButton:
print('right')
else:
print('left')
def onLabelClicked_2(self, id):
print('Label {0} clicked'.format(id))
def onLabelClicked_3(self, eventTuple, id):
# *This does not work*
if eventTuple.button == QtCore.Qt.RightButton:
print('right {0}'.format(id))
else:
print('left {0}'.format(id))
def main():
app = QtGui.QApplication(sys.argv)
t = Test()
t.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()