1

I have a customized qtablemodel and a qtableview. I would like to add a feature that the user can select multiple rows and by changing one of the values within this rows. He would actually change this value in all rows. e.g. the user could change the name of all persons in the table to alice when he selected the whole table.

Can you help me to achieve this?

I do not understand how i can trigger the setData of the model a multiple times for different rows. Or can you tell me which signal the qtableview sends to the model before the setData function within the model is called?

Thanks a lot in advance donny

Donny
  • 549
  • 2
  • 10
  • 24
  • There's no such thing as `QTableModel`. Are you using QStandardItemModel? – Phlucious Jan 31 '13 at 20:00
  • Well, no public interface anyway. – Phlucious Jan 31 '13 at 20:15
  • Sorry, I'm using a Class MyModel : public QAbstractTableModel and overwriting flags(), data(), setData(),headerData(), setHeaderData(), columnCount() and rowCount(). And on the other hand I'm using a MyTableView: public QTableView... – Donny Feb 01 '13 at 11:19

3 Answers3

3

I might have a slightly more straight-forward solution the problem of editing all selected values in the same column at the same time. Instead of overriding QTableView::edit(), it's easier to override QTableView::commitData(editor), which is called after the user has submitted their edit.

In short: When the user submits their edits, iterate through all other selected rows and apply the exact same value change to cells with the same column as the edited cell.

Here's a Python example, translation to C++ should be easy (add semicolons everywhere, replace self with this):

class ImageTableView(QtGui.QTableView):
    def commitData(self, editor):
        # call parent commitData first
        super(ImageTableView, self).commitData(editor)

        # self.currentIndex() is the QModelIndex of the cell just edited
        theModel = self.currentIndex().model()
        # get the value that the user just submitted
        value = theModel.data(self.currentIndex(), QtCore.Qt.EditRole)

        curRow, curCol = self.currentIndex().row(), self.currentIndex().column()

        # selection is a list of QItemSelectionRange instances
        for isr in self.selectionModel().selection():
            rows = range(isr.top(), isr.bottom()+1)
            for row in rows:
                if row != curRow:
                    # row,curCol is also in the selection. make an index:
                    idx = theModel.index(row, curCol)
                    # so we can apply the same value change
                    theModel.setData(idx, value, QtCore.Qt.EditRole)
Charl Botha
  • 4,373
  • 34
  • 53
0

Depending on you have implemented your model/view, you might be able to connect the QAbstractItemModel::dataChanged signal to a slot that cycles through every selected item. Not every version of setData emits this signal, but you can opt to do so if you override it.

Take a look at the source code for QTableWidgetItem::setData for an example. It's in the qtablewidget.cpp file.


Edit: Alternatively, you could key in on either the delegate's closeEditor or commitData signals to intercept the editor's value and apply it to every selected item. You would have to subclass QTableView to accomplish this, so here's a little sample code to get you started inspired from here:

class MyTableView : public QTableView {
    Q_OBJECT
public:
    explicit MyTableView(QWidget* parent = 0) : QTableView(parent) {
        connect(this->itemDelegate(), SIGNAL(closeEditor(QWidget*)),
                this, SLOT(editMultipleItems(QWidget*)));
    }

public slots:
    void editMultipleItems(QWidget* editor) {
        QLineEdit* myeditor = qobject_cast<QLineEdit*>(editor);     //recast to whatever widget was actually used
        if(myeditor != 0) {
            foreach(const QModelIndex& index, this->selectionModel()->selectedIndexes()) {
                QVariant v(myeditor->text());
                model()->setData(index, v, Qt::EditRole);
            }
        }
    }
};

As a third option, you can override QStyledItemDelegate with a special case for multiple selections, then customize setModelData() to edit every selected item instead of just the one that received the edit trigger.

You could also combine the second and third options by having the trivially subclassed QStyleItemDelegate::setModelData() emit a signal connected to your MyTableView's multiItemEdit slot with the new value.

Phlucious
  • 3,704
  • 28
  • 61
  • I'm not quite sure but i guess this might lead to a problem if i would have multiple views with different selections. Plus if i have 3 cells selected, and i change any of the content of one cell. This would call model::setData() which would then call the view again with the signal "datachanged". So this would loop over the selection and call setData for every selected row() leading to the same call over and over again, wouldnt it? – Donny Feb 01 '13 at 08:14
  • That's probably true, although you could include an escape control inside setData that returns if the current value is the same as the one you're attempting to set. – Phlucious Feb 01 '13 at 17:03
  • See my edits for a second and third option. I'm shooting in the dark a little, but I find myself needing to do something similar to you. – Phlucious Feb 01 '13 at 17:11
  • thanks a lot for all your ideas. The QStyledItemDelegate might be a reasonable idea, but you will need to inform the delegate about the current selection in the view. And by doing so, you could also just inform the model about any selection. – Donny Feb 06 '13 at 13:56
  • True, which is why I'm again leaning toward the second option. I found [this link](http://www.qtcentre.org/threads/45348-QTableWidget-problem-when-editing-multiple-items), which had a couple suggestions to get the second idea working. I'll add a little sample code to my answer to help with that. – Phlucious Feb 06 '13 at 16:54
  • again you idea by catching the closeEditor is good. but doesnt work for me, as i would like to implement this feature only for specific columns in the table. and the closeEditor is not having any information about which cell (or index for that matter) was changed. – Donny Feb 13 '13 at 10:45
  • I understand what you're saying. Fortunately, [you can assign delegates to entire columns, rows, and individual cells](http://qt-project.org/doc/qt-4.8/qabstractitemview.html#setItemDelegateForColumn). Thus you can leave the default delegate for columns where you don't want that feature and assign a `new` delegate to all of the columns where you do want that feature. Then `connect` your new delegate's `closeEditor` signal to your `editMultipleItems` slot. – Phlucious Feb 13 '13 at 16:28
  • (continued) If your slot is a member of the `MyTableView` class then it doesn't matter which item was originally edited (unless it does in your application?) since you already have the captured value. All you have to do in the slot is iterate through all of the selected items. The original might get edited twice, but that's a small price to pay. – Phlucious Feb 13 '13 at 16:29
  • I see, this works unless i'm using custom delegates for the whole table anyway. (And yes for my application it matters which column i was changing!) – Donny Feb 13 '13 at 16:38
  • 1
    but i found a solution now: i overwrote QTableView::edit( const QModelIndex & index, EditTrigger trigger, QEvent * event ) such that if the base function (QTableView::edit(..) returns true, i check if we had an edittrigger, and if yes i read the newly set data on the "edited" index, and iterate over all other selected indexes and call setData for all of these indexes... – Donny Feb 13 '13 at 16:43
  • I suppose it matters whether you want to implement this at the view level versus the model level versus the delegate level. I can see why you wouldn't want to have to implement this feature into every one of your custom delegate classes. – Phlucious Mar 14 '13 at 17:44
0

Can you not get a QModelList from QTableView::selectedIndexes(), and iterate through that?

kiss-o-matic
  • 1,111
  • 16
  • 32
  • The thing is that the every single edittrigger calls QAbstractItemView::edit(QModelIndex &index). So i would need to overwrite all edit triggers, to loop over the QTableView::selectedIndexes(), right? So I'm pretty sure I will miss some triggers. I was hoping to find an easier and more general way to do that... – Donny Feb 01 '13 at 08:22
  • This is essentially the second option I proposed. If you iterate through the [selectedIndexes()](http://qt-project.org/doc/qt-4.8/qtableview.html#selectedIndexes) from a slot triggered by [closeEditor()](http://doc-snapshot.qt-project.org/4.8/qabstractitemdelegate.html#closeEditor), you simply have to set the item's data from there, so no new editor will be opened. – Phlucious Feb 06 '13 at 17:12