Search code examples
pythonpyqtpyqt5qtablewidgetqcombobox

Selection not working in QTableWidget with ComboBox - copy event not triggering


An embedded comboBox in a QTableWidget (PyQt5) seems to be preventing selection and events such as Copy from triggering. Selection and copy event work fine without the comboBox.

I am using eventFilters to capture the copy event (CTRL+C). Without the comboBox everything works as expected. You can selected any range of cells and it copies/pastes to Excel perfectly.

Once I added the comboBox, selections which include the comboBox no longer seem to trigger events. In fact the selection turns from blue to gray when it contains the comboBox.

Interestingly when you select the whole table or the whole row it stays blue and I am able to copy and paste everything including the comboBox. Selections that don't include the comboBox also work fine.

Below is minimum working example of a QTableWidget with a copy event filter.

import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(528, 436)
        self.tableWidget = QtWidgets.QTableWidget(Dialog)
        self.tableWidget.setGeometry(QtCore.QRect(0, 0, 523, 399))

        self.tableWidget.setColumnCount(4)
        self.tableWidget.setRowCount(3)

class dialogForm(QDialog):
    def __init__(self, ui_layout):
        QDialog.__init__(self)
        self.ui = ui_layout
        ui_layout.setupUi(self)

        self.cbItems = ["A", "B", "C", "D"]
        self.setupComboBoxAll()

        self.ui.tableWidget.installEventFilter(self)

    def setupComboBox(self, row):
        comboBoxAircraftName = QComboBox()
        comboBoxAircraftName.setProperty("row", row)
        comboBoxAircraftName.setProperty("col", 1)        
        for t in self.cbItems:
            comboBoxAircraftName.addItem(t)
        self.ui.tableWidget.setCellWidget(row,1,comboBoxAircraftName)        

    def setupComboBoxAll(self):
        for i in range(0, self.ui.tableWidget.rowCount()):
            self.setupComboBox(i)

###########################
### NOT BEING TRIGGERED
###########################
   # Used for copying and pasting
    def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.KeyPress and
            event.matches(QtGui.QKeySequence.Copy)):
            self.copySelection()
            return True

        return super(dialogForm, self).eventFilter(source, event)

    # Allow copying from QTableWidget and Pasting into Excel
    def copySelection(self):
        selection = self.ui.tableWidget.selectedIndexes()

        if selection:
            rows = sorted(index.row() for index in selection)
            columns = sorted(index.column() for index in selection)
            rowcount = rows[-1] - rows[0] + 1
            colcount = columns[-1] - columns[0] + 1
            table = [[''] * colcount for _ in range(rowcount)]
            for index in selection:
                row = index.row() - rows[0]
                column = index.column() - columns[0]
                if column == 1:
                    table[row][column] = self.ui.tableWidget.cellWidget(row, column).currentText()
                else:
                    table[row][column] = index.data()
            stream = io.StringIO()
            csv.writer(stream, delimiter='\t').writerows(table)
            QtWidgets.qApp.clipboard().setText(stream.getvalue())


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = dialogForm(Ui_Dialog())
    window.show()
    sys.exit(app.exec_())

main()

I am working on Windows 10. Anaconda Python 3.6.3


Solution

  • The solution is simple, install the filters also in the QComboBox as I show below:

    def setupComboBox(self, row):
        comboBoxAircraftName = QComboBox()
        comboBoxAircraftName.setProperty("row", row)
        comboBoxAircraftName.setProperty("col", 1)
    
        comboBoxAircraftName.installEventFilter(self)
    
        comboBoxAircraftName.addItems(self.cbItems)
        self.ui.tableWidget.setCellWidget(row, 1 ,comboBoxAircraftName)  
    

    Another simpler solution is to use QShortcut:

    class dialogForm(QDialog):
        def __init__(self, ui_layout):
            QDialog.__init__(self)
            self.ui = ui_layout
            ui_layout.setupUi(self)
    
            self.cbItems = ["A", "B", "C", "D"]
            self.setupComboBoxAll()
    
            shorcut = QtWidgets.QShortcut(QtGui.QKeySequence.Copy, self.ui.tableWidget)
            shorcut.activated.connect(self.copySelection)
    
        def setupComboBox(self, row):
            comboBoxAircraftName = QComboBox()
            comboBoxAircraftName.setProperty("row", row)
            comboBoxAircraftName.setProperty("col", 1)
            comboBoxAircraftName.addItems(self.cbItems)
            self.ui.tableWidget.setCellWidget(row, 1 ,comboBoxAircraftName)  
    
        def setupComboBoxAll(self):
            for i in range(self.ui.tableWidget.rowCount()):
                self.setupComboBox(i)
    
        # Allow copying from QTableWidget and Pasting into Excel
        def copySelection(self):
            selection = self.ui.tableWidget.selectedIndexes()
    
            if selection:
                rows = sorted(index.row() for index in selection)
                columns = sorted(index.column() for index in selection)
                rowcount = rows[-1] - rows[0] + 1
                colcount = columns[-1] - columns[0] + 1
                table = [[''] * colcount for _ in range(rowcount)]
                for index in selection:
                    row = index.row() - rows[0]
                    column = index.column() - columns[0]
                    if column == 1:
                        table[row][column] = self.ui.tableWidget.cellWidget(row, column).currentText()
                    else:
                        table[row][column] = index.data()
                stream = io.StringIO()
                csv.writer(stream, delimiter='\t').writerows(table)
                QtWidgets.qApp.clipboard().setText(stream.getvalue())