1

In my project, I subclassed QStyledItemDelegate and returned a custom editor from the createEditor function.

QWidget* TagEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    TagEditWidget* tagEditWidget = new TagEditWidget(parent, index.data(Qt::UserRole+4).toInt(), index.data(Qt::UserRole+2).toByteArray(), index.data(Qt::UserRole+3).toByteArray(), index.parent().data(Qt::UserRole+4).toInt() == 9, parent->width());
    return tagEditWidget; //tagEditWidget is my custom QWidget
}

When the editing finishes, I want to write the new data back to the model. So I overrode setModelData.

void TagEditDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const
{
    TagEditWidget * tagEditWidget = qobject_cast<TagEditWidget*>(editor);
    if (!tagEditWidget)
    {
        QStyledItemDelegate::setModelData(editor, model, index);
        return;
    }

    //Edit model here?
}

This works, but the problem is that setModelData gets called no matter HOW the editor was closed. I only want to write the new data if the editor closed using the EndEditHint, QAbstractItemDelegate::SubmitModelCache. So I connected the closeEditor signal to a slot I made called editFinished.

connect(this, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), this, SLOT(editFinished(QWidget*,QAbstractItemDelegate::EndEditHint)));

So now I am able to see HOW the editor closed via the EndEditHint and if I should write the data back to the model. Buuuuut, setModelData get's called BEFORE the closeEditor signal. How does one write the data back to the model when the closeEditor signal gets called last? Am I missing something here?

Vertexwahn
  • 7,709
  • 6
  • 64
  • 90
mrg95
  • 2,371
  • 11
  • 46
  • 89

2 Answers2

1

Basic answer:

Your concept seems good almost till the end. I would focus on the TagEditDelegate::setModelData method.

If you actually don't want to update data in the model just check that it didn't change. Meaning that when oldData == newData just return; and skip model updates.

Additional notes:

Looking at your editor creation I get the impression that it doesn't hold a single value which is presented to the user. To make passing the argument more friendly and comparing the editor data easier consider creating a separate class/struct for it. So you could call:

new TagEditWidget(parent, editorData, parent->width())

where EditorData would be your class/struct which might be acquired by a separate function:

EditorData editorData = readEditorData(index);

The function could be reused in the setModelData method to check the condition:

if (tagEditWidget->getEditorData() == readEditorData(index)) return;

Also avoid using magic numbers like Qt::UserRole+2. Create your own enum to specify the required roles. For example:

enum class MyRole
{
    Data1 = Qt::UserRole,
    Data2,
    Data3,
};

EDIT according to the discussion in the comments

If what you want is to discover whether user actually didn't cancel the edition one way or the other, you could override eventFilter either inside the editor or the delegate. When creating editor call installEventFilter in the constructor. Your eventFilter implementation could look like this:

bool eventFilter(QObject *object, QEvent *event) override
{
    if (event->type() == QEvent::KeyPress)
    {
        const auto key = static_cast<QKeyEvent *>(event)->key();
        if (key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Tab)
            submitted = true;
    }
    else if (event->type() == QEvent::FocusAboutToChange &&
             static_cast<QFocusEvent*>(event)->reason() == Qt::MouseFocusReason)
    {
        submitted = true;
    }
    // extetend the conditions (add else if) to include
    // events which you might want to treat as submitted

    return QLineEdit::eventFilter(object, event);
}

Where submitted is a bool editor member initialized to false in constructor. Then you could create a getter method isSubmitted() and you are ready to check the status inside setModelData method.

if (tagEditWidget->isSubmitted())
{
    // process data or update model here
}
Dusteh
  • 1,496
  • 16
  • 21
  • Thanks for those tips, however this does not solve my issue. I'm not trying to quit if the new value == the old value. I'm wanting to cancel the edit depending on how the user closed the editor. Specifically, using the end hint SubmitModelCache. And no, there are several fields in the widget that are shown to the user. And those arguments are not required to construct an instance of the editor. For example, creating a new item. – mrg95 Dec 30 '16 at 10:03
  • What do you mean by "how the user closed the editor"? You want to know whether user used `esc`, `enter`, just focused out or smth? This is can be know at an early stage - reimplement `eventFilter` for the editor to capture what happend. – Dusteh Dec 30 '16 at 11:22
  • The editor does this by default. It returns an EndHint http://doc.qt.io/qt-4.8/qabstractitemdelegate.html#EndEditHint-enum with details as to how it closed. I would like to have different actions taken depending on the hint. Specifically hint 3, 4, and 0. – mrg95 Dec 30 '16 at 11:25
  • Sure it does but you want to interfere with the implemented flow. Take a look at the sources for [QStyledItemDelegate](https://github.com/openwebos/qt/blob/master/src/gui/itemviews/qstyleditemdelegate.cpp) in the `eventFilter` for `enter` it calls `commitData` and then `closeEditor` with the submit hint. In turn the the `setModelData` call is bound to `commitData` - you can check this [here](http://cep.xray.aps.anl.gov/software/qt4-x11-4.8.6-browser/d5/d47/class_q_abstract_item_delegate.html#a4ee042ce9b3d556dd8a1995f5cffabb5). – Dusteh Dec 30 '16 at 11:46
  • Why does the closeEditor signal include the end hint then? I don't see an answer in that source. – mrg95 Dec 30 '16 at 11:51
  • This is why overriding `eventFilter` either in editor or delegate is probably needed for the flow you want to apply. However, consider if this is actually what you need. – Dusteh Dec 30 '16 at 11:51
  • Check where the `_q_commitDataAndCloseEditor` method is invoked. – Dusteh Dec 30 '16 at 11:52
  • Thanks, but I think you're missing my point. Kinda hard to explain. I don't want to interfere with the flow of the `eventFilter`. It is filtering the events perfectly fine in it's base implementation. I don't see anywhere in that source where the model gets edited in the `eventFilter`, yet the model data still changes in the base implementation. How does `commitData` and `closeEditor` with the hints play a role, since I don't see model data being set in the event filter. – mrg95 Dec 30 '16 at 11:57
  • Basically, why reinvent the wheel with the `eventFilter` if it's filtering how I want it to and even in the base implementation it doesn't set any model data. So how it is doing it by default? That's what I'm after. – mrg95 Dec 30 '16 at 11:59
  • From my understanding of your last paragraph in the question you are surprised that `setDataModel` is called before `closeEditor` since you would like to update model data in the `closeEditor` section depending on the hint. Correct? Hence, what I'm saying is that this is how it works. `QStyledItemDelegate` first calls `commitData` which calls `setModelData` (you are overriding) and then a call to `closeEditor` is executed. The links from the previous comments just confirm that. – Dusteh Dec 30 '16 at 12:07
  • Okay so instead of trying to check the `EndEditHint` that was sent to `closeEditor`, I should rewrite the `eventFilter` and choose there when to commit the data? Isn't there a better way? Like calling `commitData` again or something based on the hint? Wait, would that work? – mrg95 Dec 30 '16 at 12:17
  • I get what you're saying now. Thanks :) If you would like to rewrite your answer to explain what you just told me here with the `eventFilter` I will accept it as the answer, even though I went my own, messier route. – mrg95 Dec 30 '16 at 12:41
  • Well it depends on why actually you want to do this on the `closeEditor` which is still not clear to me. – Dusteh Dec 30 '16 at 12:43
  • Because I don't want to have to write my own check and stuff and rewrite the whole `eventFilter`. That's just me though. If you change your answer I will accept it since it's more correct. – mrg95 Dec 30 '16 at 12:45
  • Using `closeEditor` was just my guess for how it might work by default. But now that you've explained it, I see the proper way to do it. I just like my solution because it's like 4 lines and I don't have to rewrite the whole `eventFilter`. – mrg95 Dec 30 '16 at 12:51
  • You wouldn't need to rewrite to whole stuff, especially not the one from delegate I gave you the links to - only a short implementation for the editor. I can update the answer later on if you are still interested. – Dusteh Dec 30 '16 at 13:02
  • Actually, yeah. Update your answer, I think I'm gonna use it. I'll delete mine. – mrg95 Dec 30 '16 at 13:11
  • Still waiting for an updated answer. I'm having difficulties with intercepting the commitData call upon clicking away. – mrg95 Dec 30 '16 at 14:54
  • Updated the answer with the event filter sample implementation – Dusteh Dec 30 '16 at 21:15
  • thanks. would like to point out however that im not able to grab the focus out event when my editor closes. ive resorted back to my previous method since ive been trying all day – mrg95 Dec 31 '16 at 02:17
  • I've checked that on an editor which is a `QLineEdit` subclass and it worked fine. If yours is different you might want to tweak the sequence slightly. Hard to tell exactly sine your implementation of editor is unknown to me. – Dusteh Dec 31 '16 at 07:51
  • I'm not sure what to say really. I qDebugged every event that went through the delegate eventFilter and nothing goes off when I drop focus. – mrg95 Dec 31 '16 at 07:53
0

I just answered my own question in my own way. Dusteh's solution is "more correct" I think though.

I made a bool in the delegate class called shouldCommit and set it to false.

Then in setModelData I check if I should write the data to the model.

void TagEditDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const
{
    TagEditWidget * tagEditWidget = qobject_cast<TagEditWidget*>(editor);
    if (!tagEditWidget)
    {
        QStyledItemDelegate::setModelData(editor, model, index);
        return;
    }

    if(shouldCommit)
    {
        //write data here
    }
}

Then, when the closeEditor signal is emitted, I check the hint and temporarily set shouldCommit to true and call commitData again. Now that shouldCommit is true, the data gets written.

void TagEditDelegate::editFinished(QWidget * editor, QAbstractItemDelegate::EndEditHint hint)
{
    if(hint == QAbstractItemDelegate::SubmitModelCache)
    {
        TagEditWidget * tagEditWidget = qobject_cast<TagEditWidget*>(editor);
        if(tagEditWidget)
        {
            shouldCommit = true;
            commitData(editor);
            shouldCommit = false;
        }
    }
}

While this works for my use case very well, it might not for everyone and Dusteh's comment section might be more useful to some. His method is to override the eventFilter and write your own implementation on when to call commitData. This is probably a more suitable solution for people wanting more control.

But for anyone wanting to use the default eventFilter implementation and simply look at the EndEditHint, here is my solution.

mrg95
  • 2,371
  • 11
  • 46
  • 89
  • Actually I'm not saying that one should reimplement whole `eventFilter` just override it in editor to set an editor member `submitted=true/false`. Or stick to the standard flow described in my answer. As to your answer you are basically calling `setModelData` when `editFinished` is called which looks like an unnecessary workaround. I'd stick to `if (old != newVal){//update}` or `if (tagEditWidget->submitted()){//update}` but if your approach works for you that' fine. – Dusteh Dec 30 '16 at 12:59
  • Again, this has nothing to do with old and new data being different. The whole point of this actually is to retain the old data in case the editor gets closed accidentally, like when clicking off or scrolling without focus. Or the user changes their mind about an edit. It was from my understanding that you were recommending I reimplement `eventFilter` so I can choose there when to call `commitData`, which is the correct way right? – mrg95 Dec 30 '16 at 13:03