Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Row provided by list/table currentChanged signal is incorrect after removeRow on model
Forum Updated to NodeBB v4.3 + New Features

Row provided by list/table currentChanged signal is incorrect after removeRow on model

Scheduled Pinned Locked Moved Solved General and Desktop
7 Posts 4 Posters 519 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • B Offline
    B Offline
    boohbah
    wrote on last edited by
    #1

    Hi, I'm writing a program that uses List & TableViews as a means of navigation.

    When a specific row is selected in the list, other widgets should receive that new row index and reset its current state to reflect the data that the new row index corresponds to (the row index directly corresponds to an element in a container, and a custom model is written as a wrapper around that).

    So far I've got that to work if you click a specific row and that was simple, however I'm having issues with row removal.

    When I select a row and click my button to call removeRow on it, the currentChanged signal of the list/table view's selection model is emitted, which is good & expected, however, the row index it provides in current is 1 ahead of what is actually currently selected, causing the related widgets to now show data for the wrong row.

    Here is an example program that exhibits the issue:

    #include <QApplication>
    #include <QMainWindow>
    #include <QListView>
    #include <QAbstractListModel>
    #include <QHBoxLayout>
    #include <QVBoxLayout>
    #include <QPushButton>
    #include <QScreen>
    #include <QLabel>
    
    class DummyListModel : public QAbstractListModel {
    public:
    	explicit DummyListModel(QObject* parent = nullptr) :
    		QAbstractListModel(parent),
    		numDone(0)
    	{
    		// Just append some dummy data
    		for (uint i = 0; i < 8; i++) {
    			list.append(++numDone);
    		}
    	}
    
    	int rowCount(const QModelIndex& parent = {}) const override {
    		return list.size();
    	}
    
    	QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override {
    		if (!index.isValid())
    			return {};
    
    		int row = index.row();
    		if (role == Qt::DisplayRole) {
    			return tr("test%1 (row %2, index %3)").arg(list[row]).arg(row + 1).arg(row);
    		}
    
    		return {};
    	}
    
    	bool insertRows(int row, int count, const QModelIndex& parent = {}) override {
    		Q_UNUSED(parent);
    		beginInsertRows({}, row, row + count - 1);
    		list.insert(list.begin() + row, count, ++numDone);
    		endInsertRows();
    		return true;
    	}
    
    	bool removeRows(int row, int count, const QModelIndex& parent = {}) override {
    		Q_UNUSED(parent);
    		auto begin = list.begin() + row;
    		beginRemoveRows({}, row, row + count - 1);
    		list.erase(begin, begin + count);
    		endRemoveRows();
    		return true;
    	}
    
    private:
    	uint numDone;
    	QList<uint> list;
    };
    
    class MainWindow : public QMainWindow {
    public:
    	explicit MainWindow(QWidget* parent = nullptr) :
    		QMainWindow(parent)
    	{
    		resize(250, 300);
    		move(QApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center());
    
    		list = new QListView();
    		btnAddItem = new QPushButton(QIcon::fromTheme("list-add"), "");
    		btnDelItem = new QPushButton(QIcon::fromTheme("list-remove"), "");
    		lblCur = new QLabel();
    
    		list->setModel(new DummyListModel());
    		list->setEditTriggers(QAbstractItemView::NoEditTriggers);
    
    		connect(list->selectionModel(), &QItemSelectionModel::currentChanged, this,
    		        [&](const QModelIndex& current, const QModelIndex& previous) {
    			Q_UNUSED(previous);
    			QString text = "Current index: " % QString::number(current.row());
    			qDebug() << text;
    			lblCur->setText(text);
    		});
    
    		connect(btnAddItem, &QPushButton::clicked, this, [&]() {
    			int row, col;
    			auto* model = list->model();
    			QModelIndex cur = list->currentIndex();
    
    			// In this case, I'd prefer inserting after the cursor
    			if (cur.isValid()) {
    				row = cur.row() + 1;
    				col = cur.column();
    			} else {
    				row = col = 0;
    			}
    
    			if (model->insertRow(row)) {
    				// Need to change current index after this
    				list->setCurrentIndex(model->index(row, col));
    			}
    		});
    
    		connect(btnDelItem, &QPushButton::clicked, this, [&]() {
    			QModelIndex cur = list->currentIndex();
    			auto* model = list->model();
    
    			// Oops, after this the current row index the signal gets is off by 1
    			if (cur.isValid() && model->removeRow(cur.row())) {
    				return true;
    			}
    
    			return false;
    		});	
    
    		QHBoxLayout* btnLayout = new QHBoxLayout();
    		btnLayout->addWidget(btnAddItem);
    		btnLayout->addWidget(btnDelItem);
    		btnLayout->addWidget(lblCur);
    		btnLayout->addStretch(1);
    
    		QVBoxLayout* mainLayout = new QVBoxLayout();
    		mainLayout->addWidget(list);
    		mainLayout->addLayout(btnLayout);
    
    		QWidget* mainLayoutWidget = new QWidget();
    		mainLayoutWidget->setLayout(mainLayout);
    		setCentralWidget(mainLayoutWidget);
    	}
    
    private:
    	QListView* list;
    	QPushButton* btnAddItem;
    	QPushButton* btnDelItem;
    	QLabel* lblCur;
    };
    
    int main(int argc, char** argv) {
    	QApplication app(argc, argv);
    
    	MainWindow mainWindow;
    	mainWindow.show();
    	
    	return app.exec();
    }
    

    Say you select row index 0 and click the remove button, the current reported index in the label at the bottom now incorrectly says index 1, despite the current selection still being at index 0. However, if you select row index 7 (the very last row) and remove it, it gives back index 6 after removal, which is correct and expected.

    What shall I do? I am using Qt 6.7.2 for the record.

    Thanks in advance.

    1 Reply Last reply
    0
    • artwawA Offline
      artwawA Offline
      artwaw
      wrote on last edited by
      #2

      Have you tried QWidgetMapper? This is the use case.

      For more information please re-read.

      Kind Regards,
      Artur

      1 Reply Last reply
      1
      • B Offline
        B Offline
        boohbah
        wrote on last edited by
        #3

        Hmm, I have not, but from how I understand this, I'm not sure that fits my use case. The content of a cell (which this seems to be for) is of no use, it is the index of it that is. However it does have its own currentIndexChanged signal, and perhaps that might not have the same inaccuracy problem as the selection model signals have. I'll give it a try when I can and report back.

        1 Reply Last reply
        0
        • B Offline
          B Offline
          boohbah
          wrote on last edited by
          #4

          Okay, I've read over this again and unfortunately it doesn't seem a WidgetMapper is of any use in this case. Its currentIndexChanged signal is only emitted when you use its toFirst/toNext/etc methods, whereas I need to get the correct current row that's selected in the table. The example I provided does do this, however the issue is that after removing a row, the row provided by the list's selectionChanged model's currentChanged signal is off by one, thus providing the wrong index that I can use to access the container from.

          1 Reply Last reply
          0
          • C Offline
            C Offline
            ChrisW67
            wrote on last edited by
            #5

            The current index changed signal is emitted during the removeRows() call. The current row is moved to the next row and its current index signalled. The target row is removed which has the effect of changing all the indexes of subsequent rows. I expect that because the same piece of data is still the new current item the currentChanged() is not emitted a second time. (Analogous to the selectionChanged behaviour).

            Your label is effectively storing part of a QModelIndex. QModelIndexes are transient, hence the note in the docs, that you should not store them.
            You may be able to do something with a QPersistentModelIndex or schedule the label update at the end of the remove button slot: a zero-length timer and slot that should fire after the index has changed..

            1 Reply Last reply
            3
            • B Offline
              B Offline
              boohbah
              wrote on last edited by
              #6

              Aha, armed with what you told me, I changed my currentChanged slot lambda to be like the following:

              connect(list->selectionModel(), &QItemSelectionModel::currentChanged, this, [&](const QModelIndex& current, const QModelIndex& previous) {
              	Q_UNUSED(previous);
              	QPersistentModelIndex idx(current);
              
              	QTimer::singleShot(0, this, [this, idx]() {
              		QString text = "Current index: " % QString::number(idx.row());
              		qDebug() << text;
              		lblCur->setText(text);
              	});
              });
              

              And now this does seem to work. The whole QTimer thing seems a little hacky but as you've put it, the signal is emitted during the removeRows call, so there doesn't seem to be any other "proper" way.

              Thank you!

              1 Reply Last reply
              0
              • B boohbah has marked this topic as solved on
              • GrecKoG Online
                GrecKoG Online
                GrecKo
                Qt Champions 2018
                wrote on last edited by
                #7

                Use a queued QMetaMethod::invoke instead of a Qtimer

                1 Reply Last reply
                0

                • Login

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved