Bakefish
Bakefish

Reputation: 317

Update QHeaderView's model after Drag & Drop

I have a QTableWidget, where Drag & Drop is enabled using the table widget's QHeaderView instance:

m_tableWidget->verticalHeader()->setSectionsMovable(true);

Also, I have implemented an undo mechanic. Now, I want to implement that a Drag & Drop action can be reversed using said undo mechanic.
For this undo mechanic, I need to save the data before and after the Drag & Drop. Let's say I have a method to store old data, which is not important right now.
To store the new data and push on the undo stack, I connect to the sectionMoved signal of the QHeaderView:

connect(m_tableWidget->verticalHeader(), &QHeaderView::sectionMoved, this, &MyTableWidget::dragAndDrop);

The function which is then called basically looks like this:

void
MyTableWidget::dragAndDrop(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
    // Get the table data which is stored in a vector
    auto& tableData = oldVector;
    // Synchronize to reflect the drag & drop change
    auto synchronizedTableData = synchronize(oldVisualIndex, newVisualIndex);
    // Push on the undo stack
    m_undoStack->push(new Undo(synchronizedTableData, oldTableData));
}

And, finally, the function in the undo stack basically looks like this:

void
Undo::redo()
{
    tableWidget->setRowCount(synchronizedTableData.size());
    // Apply the synchronized vector data to the table
    for (int i = 0; i < synchronizedTableData.size(); ++i) {
        for (int j = 0; j < synchronizedTableData.at(i).size(); ++j) {
                m_tableWidget->setItem(i, j, new QTableWidgetItem(synchronizedTableData.at(i).at(j).toString()));
            }
        }
    }
}

However, the whole approach has one major flaw: Afaik, the drag & drop using the vertical header only changes the view, while the model itself is not updated. So, basically, if I iterated over the table and would display its contents, I would still have the items arranged the way they were before the drag & drop. Now, if the table is set as shown in the undo stack function, the view is broken as shown in the next few images:
Normal view

If I want to drag the second row before the last one, it should look like this:

Correct

But instead, it looks like this:

Broken

This looks like the drag & drop has actually been performed twice: 123456 -> 134526 -> 145236. Interesting, however, is that if I iterate over the table once more, the data has been changed correctly. So now, the model is intact, yet the view is not updated properly.
Now, my guess is that I mixed something up between the view and model for the vertical header. So, my idea is that I could fix this problem by updating the model behind the header after I dragged & dropped a row. Or, alternatively, that I have to reupdate the view after the model has been successfully updated in the undo stack.
But I don't know how to achieve that. I still do not really know what exactly in the header is called twice so that the view is changed twice. Maybe the whole approach itself is flawed? Any idea would be appreciated.
Thanks in advance!

Upvotes: 0

Views: 75

Answers (1)

Bakefish
Bakefish

Reputation: 317

So, I found a first solution. However, it only works as some sort of a workaround.
The new idea is, that I can save the header state and restore it.

QByteArray m_headerState;

connect(m_tableWidget->verticalHeader(), &QHeaderView::sectionPressed, this, [](int logicalIndex) {
    m_headerState = m_tableWidget->verticalHeader->saveState();
});

So basically, every time I want to rearrange header items via drag and drop, I have to press such an item. I can use this to store the old header state. Then, after the drag & drop, I can do the following:

void
MyTableWidget::dragAndDrop(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
    // Restore the old header state
    m_tableWidget->verticalHeader()->restoreState(m_headerState);

    // Get the table data which is stored in a vector
    auto& tableData = oldVector;
    // Synchronize to reflect the drag & drop change
    auto synchronizedTableData = synchronize(oldVisualIndex, newVisualIndex);
    // Push on the undo stack
    m_undoStack->push(new Undo(synchronizedTableData, oldTableData));
}

Now, I restore this state after the drag & drop and do it manually. This is of course error-prone, but at least prevents the drag & drop being performed twice.
However, it does not answer the question itself. Therefore, I won't mark this as an answer. But in case someone else might encounter this problem, this might help you.

Upvotes: 0

Related Questions