pyqtpyqt4qtableviewqstandarditemmodelqabstractitemview

How to Copy - Paste Multiple Items form QTableView created by QStandardItemModel to a text/excel file?


How can I copy and paste multiple items/values of a QTableView to a text/ excel file?

My Code:

tab_table_view = QtGui.QWidget()
self.Tab.insertTab(0, tab_table_view, self.File_Name)
self.tableView = QtGui.QTableView(tab_table_view)
self.tableView.setGeometry(QtCore.QRect(0, 0, 721, 571))
self.model = QtGui.QStandardItemModel(self)
self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection

This line self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection helps with selecting multiple items in QTableView but when I do CTRL+C and paste it only pastes the last item or value of the selection?

I have seen the line self.tableView.setEditTriggers(QAbstractItemView.AllEditTriggers) but this one only applies to a single item in the table and ignores the extended selection.

Please help me this is the second time I am posting this question. Now, I am wondering if it is even possible? Please let me know Anyone!


Solution

  • The difficulty here is that the selected cells in the table may be non-contiguous and not in any particular order. So the task is to calculate the smallest rectangle that will include all the selected cells, and then create a data structure from that which is suitable for passing to a csv writer.

    Below is a demo script that does this (based on this previous answer - the additions have been commented):

    PyQt4:

    # add imports
    import sys, csv, io
    import pandas as pd, numpy as np
    from PyQt4 import QtCore, QtGui
    
    class Window(QtGui.QWidget):
        def __init__(self):
            super(Window, self).__init__()
            self.model = QtGui.QStandardItemModel(self)
            self.model.setSortRole(QtCore.Qt.UserRole)
            self.tableView = QtGui.QTableView()
            self.tableView.setSortingEnabled(True)
            self.tableView.setModel(self.model)
            # install event filter
            self.tableView.installEventFilter(self)
            self.button = QtGui.QPushButton('Open CSV', self)
            self.button.clicked.connect(self.handleButton)
            layout = QtGui.QVBoxLayout(self)
            layout.addWidget(self.tableView)
            layout.addWidget(self.button)
    
        # add event filter
        def eventFilter(self, source, event):
            if (event.type() == QtCore.QEvent.KeyPress and
                event.matches(QtGui.QKeySequence.Copy)):
                self.copySelection()
                return True
            return super(Window, self).eventFilter(source, event)
    
        # add copy method
        def copySelection(self):
            selection = self.tableView.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]
                    table[row][column] = index.data()
                stream = io.StringIO()
                csv.writer(stream).writerows(table)
                QtGui.qApp.clipboard().setText(stream.getvalue())
    
        def handleButton(self):
            filters = (
                'CSV files (*.csv *.txt)',
                'Excel Files (*.xls *.xml *.xlsx *.xlsm)',
                )
            path, filter = QtGui.QFileDialog.getOpenFileNameAndFilter(
                self, 'Open File', '', ';;'.join(filters))
            if path:
                csv = filter.startswith('CSV')
                if csv:
                    dataframe = pd.read_csv(path)
                else:
                    dataframe = pd.read_excel(path)
                self.model.setRowCount(0)
                dateformat = '%m/%d/%Y'
                rows, columns = dataframe.shape
                for row in range(rows):
                    items = []
                    for column in range(columns):
                        field = dataframe.iat[row, column]
                        if csv and isinstance(field, str):
                            try:
                                field = pd.to_datetime(field, format=dateformat)
                            except ValueError:
                                pass
                        if isinstance(field, pd.tslib.Timestamp):
                            text = field.strftime(dateformat)
                            data = field.timestamp()
                        else:
                            text = str(field)
                            if isinstance(field, np.number):
                                data = field.item()
                            else:
                                data = text
                        item = QtGui.QStandardItem(text)
                        item.setData(data, QtCore.Qt.UserRole)
                        items.append(item)
                    self.model.appendRow(items)
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication(sys.argv)
        window = Window()
        window.setGeometry(500, 150, 600, 400)
        window.show()
        sys.exit(app.exec_())
    

    PyQt5:

    # add imports
    import sys, csv, io
    import pandas as pd, numpy as np
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class Window(QtWidgets.QWidget):
        def __init__(self):
            super(Window, self).__init__()
            self.model = QtGui.QStandardItemModel(self)
            self.model.setSortRole(QtCore.Qt.UserRole)
            self.tableView = QtWidgets.QTableView()
            self.tableView.setSortingEnabled(True)
            self.tableView.setModel(self.model)
            # install event filter
            self.tableView.installEventFilter(self)
            self.button = QtWidgets.QPushButton('Open CSV', self)
            self.button.clicked.connect(self.handleButton)
            layout = QtWidgets.QVBoxLayout(self)
            layout.addWidget(self.tableView)
            layout.addWidget(self.button)
    
        # add event filter
        def eventFilter(self, source, event):
            if (event.type() == QtCore.QEvent.KeyPress and
                event.matches(QtGui.QKeySequence.Copy)):
                self.copySelection()
                return True
            return super(Window, self).eventFilter(source, event)
    
        # add copy method
        def copySelection(self):
            selection = self.tableView.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]
                    table[row][column] = index.data()
                stream = io.StringIO()
                csv.writer(stream).writerows(table)
                QtWidgets.qApp.clipboard().setText(stream.getvalue())
    
        def handleButton(self):
            filters = (
                'CSV files (*.csv *.txt)',
                'Excel Files (*.xls *.xml *.xlsx *.xlsm)',
                )
            path, filter = QtWidgets.QFileDialog.getOpenFileName(
                self, 'Open File', '', ';;'.join(filters))
            if path:
                csv = filter.startswith('CSV')
                if csv:
                    dataframe = pd.read_csv(path)
                else:
                    dataframe = pd.read_excel(path)
                self.model.setRowCount(0)
                dateformat = '%m/%d/%Y'
                rows, columns = dataframe.shape
                for row in range(rows):
                    items = []
                    for column in range(columns):
                        field = dataframe.iat[row, column]
                        if csv and isinstance(field, str):
                            try:
                                field = pd.to_datetime(field, format=dateformat)
                            except ValueError:
                                pass
                        if isinstance(field, pd.Timestamp):
                            text = field.strftime(dateformat)
                            data = field.timestamp()
                        else:
                            text = str(field)
                            if isinstance(field, np.number):
                                data = field.item()
                            else:
                                data = text
                        item = QtGui.QStandardItem(text)
                        item.setData(data, QtCore.Qt.UserRole)
                        items.append(item)
                    self.model.appendRow(items)
    
    if __name__ == '__main__':
    
        app = QtWidgets.QApplication(sys.argv)
        window = Window()
        window.setGeometry(500, 150, 600, 400)
        window.show()
        sys.exit(app.exec_())