How to intercept View-based widgets item selection with ability for user to cancel it



  • Hello everyone!

    Recently I faced a problem in implementing some common UI feature. For example I have a form where user can edit some model through a standart view widget. But right before he selects another row (or cell) there should be some validation checks and in some conditions show a confirmation dialog for the user. If he pushes "Ok"-button selection should happen, othewise selection should be canceled.

    The most obvious solution seemed to me was interception of mouse click events (using eventFilter() method), but the problem is that there is no mouse click events neither in eventFilter(), not in event() methods. I looked in a Qt sources but still doesn`t fugure who receives this mouse events.

    Then I tried to reimplement QListView::selectionCommand() method. I succeed to cancel selection, but here was a problem that the method is constant, so I'm able to show user confirmation dialog but cant do any actions there (in my case submiting changes to server).

    The third way I tried to solve the problem was to set some custom selection model (derived from QItemSelectionModel). It seemed to be the most proper way, cause user can select items (and rows) in many ways (mouse click, keyboard..). But there I faced a problem with infinite loop of showing dialog window.

    For now I found the only workaround to reimplement mousePressEvent() and keyPressEvent() in a class derived from QListView. I find this code messy. And more now I faced the same problem with QCalendarWidget, and here view-object is incapsulated so I cant substitute it with a custom one.

    What I`m doing wrong? It looks to me like a common UI task, so I believe it should be some simple elegant way to implement such features.


  • Qt Champions 2018

    You are overcomplicating it. you just need to connect(view->itemSelectionModel(),&QItemSelectionModel::selectionChanged to a slot that does the check and decides what to do with the selections



  • You mean select previous item back instead of prevent the selection? For now it seems to me as this solution can have some unwanted side effects, because selection signal is often connected with some slots with visual changes (like loading row values of the model to widgets by QDataWidgetMapper etc.) so frequent selection can result in interface flickering, or am I wrong?

    Anyway thank you for the answer, I will try and check your way.


  • Qt Champions 2018

    @levolex said in How to intercept View-based widgets item selection with ability for user to cancel it:

    so frequent selection can result in interface flickering

    If the slot is run on the GUI thread and connected with Qt::DirectConnection then the visual updates of selection gets aggregated into 1 by Qt automatically and no flickering should occur



  • @VRonin I tried to connect my slot to QItemSelectionModel::selectionChanged. It works but only with some workarounds. If I call QItemSelectionModel::select() in the connected slot it results in infinite loop of selection (and stack overflow error), so I used boolean variable to avoid it. But anyway it doesn't work if I have QMessageBox::question in the slot to control selection process.

    So this slot code is working (preventing selection):

    void MainWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
    {
        if (_isCanceling || deselected.isEmpty())
            return;
    
        QItemSelectionRange prevRange = deselected.first();
        QItemSelection selection(prevRange.topLeft(), prevRange.bottomRight());
        _isCanceling = true;
        ui->listView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
        _isCanceling = false;
    }
    

    This doesn't:

    void MainWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
    {
        if (_isCanceling || deselected.isEmpty())
            return;
    
        bool allowed = QMessageBox::question(this, "Confirmation", "Allow selection?");
        if (!allowed) {
            QItemSelectionRange prevRange = deselected.first();
            QItemSelection selection(prevRange.topLeft(), prevRange.bottomRight());
            _isCanceling = true;
            ui->listView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
            _isCanceling = false;
        }
    }
    

    Thank you in advance.


  • Qt Champions 2018

    What part doesn't work?



  • @VRonin This:

    ui->listView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
    

    It doen't select previous item.


  • Qt Champions 2018

    QMessageBox::question does not return a boolean.

    replace

    bool allowed = QMessageBox::question(this, "Confirmation", "Allow selection?");
    if (!allowed)
    

    With

    const QMessageBox::StandardButton allowed = QMessageBox::question(this, "Confirmation", "Allow selection?");
    if (allowed == QMessageBox::No)
    

Log in to reply