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. Two-way binding with QDataWidgetMapper
Forum Updated to NodeBB v4.3 + New Features

Two-way binding with QDataWidgetMapper

Scheduled Pinned Locked Moved Solved General and Desktop
5 Posts 2 Posters 1.1k Views
  • 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.
  • N Offline
    N Offline
    naavis
    wrote on last edited by naavis
    #1

    I am building an application with Qt Widgets where I adjust some properties of a model in real-time with slider controls. I also want to update the controls if the model updates on its own.

    I am using a QStandardItemModel as the model, and QDataWidgetMapper to map widgets to the model. The problem is that QDataWidgetMapper only submits changes done by touching the controls when a widget loses focus or I press enter. Changes to the model reflect to the controls correctly.

    If I connect valueChanged signals from the controls to the submit slot of the QDataWidgetMapper, touching the controls changes the model immediately like expected. However, if I change the model in the background, the controls do not update anymore. It is as if manually connecting valueChanged to submit breaks the connection between the model and the controls.

    Is it possible to have both directions work at the same time? Effectively I want two-way binding between the controls and the model.

    Here is a very minimal code example, where I have a QStandardItemModel with one row and one column. I connect a QSlider, a QSpinBox and a QTableView to the model. There is also a QPushButton that sets the model data to a certain value. Ideally pressing it would also reset all the other widgets to reflect the model.

    #include "MainWindow.h"
    #include "ui_MainWindow.h"
    #include <QStandardItemModel>
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
        , m_model(new QStandardItemModel(1, 1, this))
        , m_mapper(new QDataWidgetMapper(this))
    {
        populateModel();
    
        ui->setupUi(this);
        ui->tableView->setModel(m_model);
        initializeMapper();
    
        /* Reset model when button is pushed */
        connect(ui->pushButton, &QPushButton::clicked, [this]() {
            m_model->setData(m_model->index(0, 0), 10);
        });
    }
    
    void MainWindow::populateModel()
    {
        m_model->setData(m_model->index(0, 0), 10);
        m_model->setHeaderData(0, Qt::Orientation::Horizontal, "Column header");
    }
    
    void MainWindow::initializeMapper()
    {
        m_mapper->setModel(m_model);
        m_mapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);
        m_mapper->addMapping(ui->spinBox, 0);
        m_mapper->addMapping(ui->horizontalSlider, 0);
        m_mapper->toFirst();
    
        /* The two following lines cause updates in the model to not show up in the control widgets */
        connect(ui->horizontalSlider, &QSlider::valueChanged, m_mapper, &QDataWidgetMapper::submit);
        connect(ui->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), m_mapper, &QDataWidgetMapper::submit);
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    

    If I comment out the two connect lines, updates in the model show up correctly in the controls.

    JonBJ 1 Reply Last reply
    0
    • N naavis

      I am building an application with Qt Widgets where I adjust some properties of a model in real-time with slider controls. I also want to update the controls if the model updates on its own.

      I am using a QStandardItemModel as the model, and QDataWidgetMapper to map widgets to the model. The problem is that QDataWidgetMapper only submits changes done by touching the controls when a widget loses focus or I press enter. Changes to the model reflect to the controls correctly.

      If I connect valueChanged signals from the controls to the submit slot of the QDataWidgetMapper, touching the controls changes the model immediately like expected. However, if I change the model in the background, the controls do not update anymore. It is as if manually connecting valueChanged to submit breaks the connection between the model and the controls.

      Is it possible to have both directions work at the same time? Effectively I want two-way binding between the controls and the model.

      Here is a very minimal code example, where I have a QStandardItemModel with one row and one column. I connect a QSlider, a QSpinBox and a QTableView to the model. There is also a QPushButton that sets the model data to a certain value. Ideally pressing it would also reset all the other widgets to reflect the model.

      #include "MainWindow.h"
      #include "ui_MainWindow.h"
      #include <QStandardItemModel>
      
      MainWindow::MainWindow(QWidget *parent)
          : QMainWindow(parent)
          , ui(new Ui::MainWindow)
          , m_model(new QStandardItemModel(1, 1, this))
          , m_mapper(new QDataWidgetMapper(this))
      {
          populateModel();
      
          ui->setupUi(this);
          ui->tableView->setModel(m_model);
          initializeMapper();
      
          /* Reset model when button is pushed */
          connect(ui->pushButton, &QPushButton::clicked, [this]() {
              m_model->setData(m_model->index(0, 0), 10);
          });
      }
      
      void MainWindow::populateModel()
      {
          m_model->setData(m_model->index(0, 0), 10);
          m_model->setHeaderData(0, Qt::Orientation::Horizontal, "Column header");
      }
      
      void MainWindow::initializeMapper()
      {
          m_mapper->setModel(m_model);
          m_mapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);
          m_mapper->addMapping(ui->spinBox, 0);
          m_mapper->addMapping(ui->horizontalSlider, 0);
          m_mapper->toFirst();
      
          /* The two following lines cause updates in the model to not show up in the control widgets */
          connect(ui->horizontalSlider, &QSlider::valueChanged, m_mapper, &QDataWidgetMapper::submit);
          connect(ui->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), m_mapper, &QDataWidgetMapper::submit);
      }
      
      MainWindow::~MainWindow()
      {
          delete ui;
      }
      
      

      If I comment out the two connect lines, updates in the model show up correctly in the controls.

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #2

      @naavis
      I don't know whether my situation is the same/will help you, but....

      I want my model to update immediately on QSpinBox::valueChanged. I too connect valueChanged to submit. It works, and I have no problem that the box value updates if I change the back-end model. I leave the submitPolicy on default (which I think is AutoSubmit).

      Which sounds like your situation, which does not work for you? If it makes a difference, note that for QSpinBoxes I always connect valueChanged with Qt::QueuedConnection, else my like is miserable:

      template <typename Context, typename Method>
          QMetaObject::Connection connectSpinBoxValueChanged(QSpinBox *spin, Context slotObject, Method method)
          {
              // connect `spin->valueChanged(int i)` signal to slot
              // see https://forum.qt.io/topic/113606/qspinbox-valuechanged-with-debugger-breakpoint-brain-damaged and https://bugreports.qt.io/browse/QTBUG-14259
              // for why `Qt::QueuedConnection` is specified here
              return QObject::connect(spin, QOverload<int>::of(&QSpinBox::valueChanged), slotObject, method, Qt::QueuedConnection);
          }
      

      Now, I might have the dimmest recollection that you might need to do this. But for the reason of: I'm thinking that updating inside the slot gets mashed when within the QDataWidgetMapper code, which is doing its own model/view updating.... Anyway, does Qt::QueuedConnection change your behaviour?

      1 Reply Last reply
      1
      • N Offline
        N Offline
        naavis
        wrote on last edited by
        #3

        @JonB said in Two-way binding with QDataWidgetMapper:

        Anyway, does Qt::QueuedConnection change your behaviour?

        Wow, it indeed fixed the problem!

        It works just as expected after adding the Qt::QueuedConnection parameter to the connection calls, like this:

        connect(ui->horizontalSlider, &QSlider::valueChanged, m_mapper, &QDataWidgetMapper::submit, Qt::QueuedConnection);
        connect(ui->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), m_mapper, &QDataWidgetMapper::submit, Qt::QueuedConnection);
        

        Thanks a ton! I have been struggling with this a long time. If someone knows more details about why this happens, it would be interesting to hear.

        1 Reply Last reply
        0
        • N Offline
          N Offline
          naavis
          wrote on last edited by
          #4

          Now that I found some good keywords to Google with, I found another thread where you had mentioned the same solution: https://forum.qt.io/topic/119814/auto-update-model-in-qwidgetmapper/3

          Thanks again!

          JonBJ 1 Reply Last reply
          0
          • N naavis

            Now that I found some good keywords to Google with, I found another thread where you had mentioned the same solution: https://forum.qt.io/topic/119814/auto-update-model-in-qwidgetmapper/3

            Thanks again!

            JonBJ Offline
            JonBJ Offline
            JonB
            wrote on last edited by
            #5

            @naavis
            Yes, thanks for digging it out. I recall it was tough figuring at the time, because using a debugger with breakpoints in the valueChanged (or anything it calls) would alter behaviour from when no breakpoint. To do with Qt internal timer for spinboxes.

            Anyway, the issue with valueChanged: isn't it something like if called DirectConnection you must not cause the value to change yourself? But the code does work if it's run QueuedConnection.

            1 Reply Last reply
            1

            • Login

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