student
student

Reputation: 1646

Move row up and down in PyQT4

Consider a QTableWidget and two buttons "move up" and "move down". Clicking on move up, the current row should move up one row, analogously for "move down".

What's the easiest way to implement the corresponding move up and move down functions?

Upvotes: 2

Views: 6327

Answers (2)

student
student

Reputation: 1646

I managed to do it using only QTableWidget, here is a full example:

import sys
from PyQt4 import QtCore
from PyQt4 import QtGui

class mtable(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)


        self.move_up = QtGui.QAction("Move_Up", self)
        self.connect(self.move_up, QtCore.SIGNAL('triggered()'), self.moveUp)

        self.move_down = QtGui.QAction("Move_Down",self)
        self.connect(self.move_down, QtCore.SIGNAL('triggered()'), self.moveDown)

        self.toolbar = self.addToolBar('Toolbar')
        self.toolbar.addAction(self.move_up)
        self.toolbar.addAction(self.move_down)


        ##Init Table
        self.table = QtGui.QTableWidget(4,3)
        for i in range(0,4):
            for j in range(0,4):
                self.table.setItem(i,j,QtGui.QTableWidgetItem("a_"+str(i)+str(j)))

        self.setCentralWidget(self.table)

    def moveDown(self):
        row = self.table.currentRow()
        column = self.table.currentColumn();
        if row < self.table.rowCount()-1:
            self.table.insertRow(row+2)
            for i in range(self.table.columnCount()):
               self.table.setItem(row+2,i,self.table.takeItem(row,i))
               self.table.setCurrentCell(row+2,column)
            self.table.removeRow(row)        


    def moveUp(self):    
        row = self.table.currentRow()
        column = self.table.currentColumn();
        if row > 0:
            self.table.insertRow(row-1)
            for i in range(self.table.columnCount()):
               self.table.setItem(row-1,i,self.table.takeItem(row+1,i))
               self.table.setCurrentCell(row-1,column)
            self.table.removeRow(row+1)        


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    tb = mtable()
    tb.show()
    sys.exit(app.exec_())

Upvotes: 11

jdi
jdi

Reputation: 92647

I have revised my answer because it did not have enough detail previously

The process involves connecting your buttons to a slot (or slots) that will look at the current selection, and move them by taking them from the view, and inserting them into new locations.

The following example is actually using a QTableView + QStandardItemModel. The reason is because QTableWidget is much more limited since you can only use methods from the widget. Its a lot easier to be able to work directly with the model and selection model. Although, it is possible to rework this example for a QTableWidget if you use takeItem() multiple times to build up each row...

Here is a fully working example:

from PyQt4 import QtCore, QtGui
from functools import partial

class Dialog(QtGui.QDialog):

    DOWN    = 1
    UP      = -1

    def __init__(self, parent=None):
        super(Dialog, self).__init__(parent)
        self.resize(800,600)

        self.table = QtGui.QTableView(self)
        self.table.setSelectionBehavior(self.table.SelectRows)

        self.model = QtGui.QStandardItemModel(20, 6, self)
        self.table.setModel(self.model)

        self.upBtn = QtGui.QPushButton('Up', self)
        self.downBtn = QtGui.QPushButton('Down', self)

        self.mainLayout = QtGui.QVBoxLayout(self)
        self.mainLayout.addWidget(self.table)

        self.buttonLayout = QtGui.QHBoxLayout()
        self.buttonLayout.addWidget(self.upBtn)
        self.buttonLayout.addWidget(self.downBtn)
        self.mainLayout.addLayout(self.buttonLayout)

        self.upBtn.clicked.connect(partial(self.moveCurrentRow, self.UP))
        self.downBtn.clicked.connect(partial(self.moveCurrentRow, self.DOWN))

        self._initTable()

    def _initTable(self):
        for row in xrange(self.model.rowCount()):
            for col in xrange(self.model.columnCount()):
                item = QtGui.QStandardItem('%d_%d' % (row+1, col+1))
                self.model.setItem(row, col, item)

    def moveCurrentRow(self, direction=DOWN):
        if direction not in (self.DOWN, self.UP):
            return

        model = self.model
        selModel = self.table.selectionModel()
        selected = selModel.selectedRows()
        if not selected:
            return

        items = []
        indexes = sorted(selected, key=lambda x: x.row(), reverse=(direction==self.DOWN))

        for idx in indexes:
            items.append(model.itemFromIndex(idx))
            rowNum = idx.row()
            newRow = rowNum+direction
            if not (0 <= newRow < model.rowCount()):
                continue

            rowItems = model.takeRow(rowNum)
            model.insertRow(newRow, rowItems)

        selModel.clear()
        for item in items:
            selModel.select(item.index(), selModel.Select|selModel.Rows)


if __name__ == "__main__":
    app = QtGui.QApplication([])
    d = Dialog()
    d.show()
    d.raise_()
    app.exec_()

The init is simple and just sets up the table, model, and buttons. We connect both buttons to the same method using functools.partial, which is really convenient for wrapping up the same function call with different args. Then the table is just filled with 20x6 data.

When a button is clicked, we make sure they have selected rows. For each selected row, we resolve its item (for re-selection later once it has moved), and determine the new row number by either adding or subtracting one. We also make sure its within the valid range to move, otherwise we skip it. Finally, we call takeRow() to remove the entire row as a list of indexes, and then insert that row back into the new row number. After that loop, we use the items we saved to look up the new indexes and reselect them again.

Upvotes: 5

Related Questions