2

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?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
student
  • 1,636
  • 3
  • 29
  • 53

2 Answers2

11

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_())
student
  • 1,636
  • 3
  • 29
  • 53
  • Yea this is what I was referring to about having to take each item individually. My answer has a bit more code because it also preserves the selection. Good job figuring this out. – jdi Aug 13 '12 at 15:29
  • 1
    @student : where u used self.table.setItem(row-1,i,self.table.takeItem(row+1,i)) what should be used when QListWidget, because QListWidget doesnt have setItem ? –  Dec 11 '12 at 21:18
  • @san I don't know. I guess it would be the best to ask a new question. You can just copy and paste my example, refer with a link to my answer and ask what you want in a new question. I am sure you will get some usefull answers. – student Dec 12 '12 at 09:46
  • Or even better, modify my example to a non working one using QListWidget show that in your question and ask how to fix it... – student Dec 12 '12 at 09:48
  • @san Great! If you want: just ask your question and answer is by yourself to share your knowledge with the community. – student Dec 12 '12 at 11:55
  • This was really helpful, and should be the accepted answer. There is no point in doing a code rewrite with QTableViews and Models when you just want a simple "Move up" and "Move down"-functionality. – Niko Föhr Aug 01 '18 at 12:44
  • can you make it pyqt5 ? –  Aug 12 '20 at 00:33
5

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.

jdi
  • 90,542
  • 19
  • 167
  • 203