Need help from QT Experts ( Model / View for QDateTimeEdit widget)



  • I have a Tree Model ( Parent A has 6 childs )
    Child 1 : stores date information
    Child 2 : stores time information
    Child 3 : stores day information
    Child 4 : stores hour information
    Child 5 : stores minute information
    Child 6 : stores sec information

    I want to place a widget (QDateTimeEdit) and connect it to the tree model.

    I have tried these things but not succeeded:

    1. Data widget mapper ( works only with one section to widget , i can map only one child and one property)

    2. QComboboxWidget ( SetDelegate and have a set editor and set model data )

    QComboboxWidget* cbx = new QComboboxWidget;

    cbx->setmodel(mymodel); // mymodel has a parent and 6 children and it just adds all children as a list of elements

    i wanted to see 2019:06:10 15:12:25 on my widget and if i change any of this value then it also updates to my model children

    its taking so much time for a special widget where as a lineedit/checkbox/combobox using 1 to 1 data widget mapper is working easy and perfectly. Please help me out


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    You can create a custom widget with 6 properties matching your model content. The you can use the QDataWidgetMapper::addMapping overload that takes a property name.

    Sadly my suggestion is not possible with the current implementation of QDataWidgetMapper.

    [edit: SGaist]



  • @SGaist
    As I understand it, the OP has 6 properties. You will call addMapping() 6 times to map each of these to the same widget? I thought (haven't used it) QDataWidgetMapper gave you one distinct widget per distinct property? Won't something go wrong with multiple properties mapped to a single widget?



  • Hi SGaist,JonB thank you very much , This is my first question happy to see people replying . I am new to QT development as well.

    I tried that as well , sorry didint mention that . I have created a custom widget(extending existing) with six more properties and then used addMapping to map each property. This didnt work , it was working only for the last property . Looks like As JonB mentioned QDataWidgetMapper will only work with one property per one widget.



  • @chakry
    That's kind of what I thought (or it would error). However, be aware that @SGaist is an expert: unless he mis-read your question, he usually knows what he is talking about!


  • Lifetime Qt Champion

    @JonB said in Need help from QT Experts ( Model / View for QDateTimeEdit widget):

    @chakry
    That's kind of what I thought (or it would error). However, be aware that @SGaist is an expert: unless he mis-read your question, he usually knows what he is talking about!

    Thanks for the flowers, but I can be wrong and that's one occurrence. I had the same assumption you have but taking a look at the current QDataWidgetMapper implementation, it doesn't handle mapping of several properties on the same widget. However, I don't know whether it's on purpose in which case the documentation should be updated to reflect that or if it just a case of "use case did not happen before" in which case it could be considered a bug. From the looks of the API, I think the original design was really one different widget per section but that is pure speculation.



  • @SGaist

    Thanks for the flowers

    Since I believe you are Swiss, I send you some "Leontopodium" ("lion's paw", Edelweiss), your national flower ;-)



  • Thank you for your suggestions :). This in general gets me into a new question , suppose i have a database of multiple cells that i want to show them in a single widget then i have the same problem right ? i cannot do that , are there any other ways ?

    isn't it a use case which might be coming in working environments or am i the only one landed there as i don't see much people talking about it :).

    for example i have custom widget with 10 sliders on it where i want to have a database model that updates 10 cells(value) it should be vice versa (if the cells are updated ( background by some one else) and in turn the sliders change automatically ). I know with signals and slots its possible but if i have many of these scenarios like this in a project then it will be a lot of signal/slots and updates right, difficult to track in case of errors ? any other options todo such things .

    @I may be wrong in thinking ( writing whatever on my mind :))


  • Lifetime Qt Champion

    You'll likely have these 10 sliders in one custom widget so you would have the mapper within that widget and then map each slider independently.


  • Lifetime Qt Champion

    As for your single widget with multiple properties, here's a workaround taking advantage of Qt's dynamic properties. Note that I don't find it elegant because it requires one widget per property but at least you can have your single QDateTimeEdit used.

    class QDWMProxy : public QWidget
    {
    public:
        /*!
            Set which property should be set on the target
        */
        void setMapping(QWidget *target, const QByteArray& propertyName)
        {
            _target = target;
            _propertyName = propertyName;
        }
    
        bool event(QEvent *event) override
        {
            if (event->type() == QEvent::DynamicPropertyChange) {
                QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent *>(event);
                if (pe->propertyName() == _propertyName) {
                    _target->setProperty(_propertyName, property(_propertyName));
                    event->accept();
                    return true;
                }
            }
            return QWidget::event(event);
        }
    
    private:
        QWidget *_target;
        QByteArray _propertyName;
    };
    

    You'll create one proxy per properties and use it for the QDataWidgetMapper mapping function. Don't forget to handle their deletion appropriately.

    Dummy main.cpp example:

        MyCustomWidget widget;
        QDWMProxy proxyHours;
        proxy1.setMapping(&widget, "hours");
        QDWMProxy proxyMinutes;
        proxy2.setMapping(&widget, "minutes");
        QDataWidgetMapper mapper;
        mapper.setModel(model);
        mapper.addMapping(&proxyHours, 0, "hours");
        mapper.addMapping(&proxyMinutes, 1, "minutes");
        mapper.toFirst();
        widget.show();
    

    I'll let you replace your model and widget.



  • @SGaist Thank you. you are right , that works. This may be a wrong example.



  • @SGaist Thank you. Good trick, it worked . Mapping works , if the model data is updated then the widget is updated . But when the widget is updated then the model is not updated automatically . I am still looking a reason for this. Probably missing something , will post once it is completely working.


  • Qt Champions 2018

    Just trying to understand. An example always helps me.
    So your model looks like:

    • Parent A
      • 2019-12-25
      • 13:58:15
      • Wednesday
      • 13
      • 58
      • 15
    • Parent B
      • 2019-05-30
      • 20:30:55
      • Thursday
      • 20
      • 30
      • 55

    And you want it displayed as a list that appears

    • 2019-12-25 13:58:15
    • 2019-05-30 20:30:55

    And when one item of that list gets modified then the children of the tree get modified accordingly.
    Is the above an accurate representation? (if so there is a solution that requires a proxy).

    Or do you want a widget completely external to the representation of the data?



  • HI @VRonin ,

    Model is almost correct small difference is ( date is also broken to a separate child as below)

    Parent A

    2019
    12
    25
    13
    58
    15
    Wednesday ( this one is not must)

    And you want it displayed as a list that appears ( not as a list but i want to display and edit using a QDateTimeEdit widget or similar)

    2019-12-25 13:58:15
    2019-05-30 20:30:55
    

    if it is a tree widget or tree view then it is no problem it works directly.( element wise )

    proxy , i have read about this and tried to implement but did not succeed( it is easier in a table widget or Tree i think). do you have a example of this to a seperate widget?



  • @SGaist Dear SGaist, I could not make it 100 % working , so posting here the widget code. I have tried a simple version to check functionality.

    problem: widget mapper mapping to property . updates property when model changed , does not update model when property changed. I didn't understand how widget mapper gets property change info.

    here is code

    class QmDateTimeEdit : public QDateTimeEdit {
    Q_OBJECT

    Q_PROPERTY(uint16_t dateYear READ dateYear WRITE setDateYear NOTIFY dateYearChanged USER true)
    Q_PROPERTY(uint16_t dateMonth READ dateMonth WRITE setDateMonth NOTIFY dateMonthChanged USER true)
    Q_PROPERTY(uint16_t dateDay READ dateDay WRITE setDateDay NOTIFY dateDayChanged USER true)
    

    public:
    explicit QmDateTimeEdit(QWidget* parent = nullptr);

    uint16_t dateYear() const { return mYear; }
    uint16_t dateMonth() const { return mMonth; }
    uint16_t dateDay() const { return mDay; }
    
    QDateTime dateTime() const;
    

    signals:

    void dateYearChanged(const uint16_t& dateYear);
    void dateMonthChanged(const uint16_t& dateMonth);
    void dateDayChanged(const uint16_t& dateDay);
    

    private slots:

    void valueChanged(const QDateTime& dateTime);
    void setDateTime(const QDateTime& dateTime);
    void setDateDay(uint16_t dateDay);
    void setDateYear(uint16_t dateYear);
    void setDateMonth(uint16_t dateMonth);
    

    private:

    uint16_t mYear;
    uint16_t mMonth;
    uint16_t mDay;
    

    };

    // when ever valueChanged() occurs i just set 3 values (mYear , mMonth and mDay) and emit 3 signals . I expected this should update the model data but it doesn't.

    QmDateTimeEdit::QmDateTimeEdit(QWidget* parent)
    : QDateTimeEdit(parent)
    {
    connect(this, &QDateTimeEdit::dateTimeChanged, this, &QmDateTimeEdit::valueChanged);
    }

    void QmDateTimeEdit::setDateYear(uint16_t dateYear)
    {

    mYear = dateYear;
    qDebug() << "Year changed" << mYear;	
    

    }

    void QmDateTimeEdit::setDateMonth(uint16_t dateMonth)
    {

    mMonth = dateMonth;
    qDebug() << "Month changed" << mMonth;	
    

    }

    void QmDateTimeEdit::setDateDay(uint16_t dateDay)
    {

    mDay = dateDay;
    qDebug() << "Day changed" << mDay << this->parent();	
    

    }

    void QmDateTimeEdit::valueChanged(const QDateTime& dateTime)
    {

    setDateYear(dateTime.date().year());
    setDateMonth(dateTime.date().month());
    setDateDay(dateTime.date().day());
    
    emit dateYearChanged(dateTime.date().year());
    emit dateMonthChanged(dateTime.date().month());	
    emit dateDayChanged(dateTime.date().day());
    

    }



  • You can use QDataWidgetMapper with custom delegate.

    void MyDelegate::setEditorData(QWidget* parent, const QModelIndex& index) const
    {
      if (auto dte = qobject_cast<QDateTimeEdit*>(editor); dte) {
        auto* model = index.model();
        const auto& year = model->index(0, 0, index.parent()).data().toString();
        const auto& month = model->index(1, 0, index.parent()).data().toString();
        // ...
        QDateTime dt = QDateTime::fromString(/*combine above to QDateTime*/);
        dte->setDateTime(dt);
      }
    }
    

    Then map your QDateTimeEdit with QDataWidgetMapper, set delegate. and voila


  • Qt Champions 2018

    Something like this minimal example is what you want or did I miss something?

    #include <QApplication>
    #include <QTreeWidget>
    #include <QVBoxLayout>
    #include <QDateTimeEdit>
    #include <QStandardItemModel>
    class ExampleWid : public QWidget{
        //Q_OBJECT
        Q_DISABLE_COPY(ExampleWid)
    public:
        explicit ExampleWid(QWidget *parent = Q_NULLPTR)
            :QWidget(parent)
            ,view(new QTreeView(this))
            ,editor(new QDateTimeEdit(this))
        {
            QAbstractItemModel* model = new QStandardItemModel(this);
            model->insertColumn(0);
            model->insertRows(0,2);
            QModelIndex parIdx= model->index(0,0);
            model->setData(parIdx,QStringLiteral("Parent A"));
            model->insertColumn(0,parIdx);
            model->insertRows(0,7,parIdx);
            model->setData(model->index(0,0,parIdx),2019);
            model->setData(model->index(1,0,parIdx),12);
            model->setData(model->index(2,0,parIdx),25);
            model->setData(model->index(3,0,parIdx),13);
            model->setData(model->index(4,0,parIdx),58);
            model->setData(model->index(5,0,parIdx),15);
            model->setData(model->index(6,0,parIdx),QStringLiteral("Wednesday"));
            parIdx= model->index(1,0);
            model->setData(parIdx,QStringLiteral("Parent B"));
            model->insertColumn(0,parIdx);
            model->insertRows(0,7,parIdx);
            model->setData(model->index(0,0,parIdx),2019);
            model->setData(model->index(1,0,parIdx),5);
            model->setData(model->index(2,0,parIdx),30);
            model->setData(model->index(3,0,parIdx),20);
            model->setData(model->index(4,0,parIdx),30);
            model->setData(model->index(5,0,parIdx),55);
            model->setData(model->index(6,0,parIdx),QStringLiteral("Thursday"));
    
            view->setModel(model);
            QVBoxLayout* minLay = new QVBoxLayout(this);
            minLay->addWidget(editor);
            minLay->addWidget(view);
            QObject::connect(view->selectionModel(),&QItemSelectionModel::currentChanged,this,[this](QModelIndex idx)->void{
                if(!idx.isValid())
                    return;
                while(idx.parent().isValid())
                    idx =idx.parent();
                lastIdx = QPersistentModelIndex();
                editor->setDateTime(QDateTime(
                    QDate(
                        idx.model()->index(0,0,idx).data().toInt()
                        ,idx.model()->index(1,0,idx).data().toInt()
                        ,idx.model()->index(2,0,idx).data().toInt()
                    )
                    , QTime(
                        idx.model()->index(3,0,idx).data().toInt()
                        ,idx.model()->index(4,0,idx).data().toInt()
                        ,idx.model()->index(5,0,idx).data().toInt()
                    )
                ));
                lastIdx = idx;
            });
            QObject::connect(editor,&QDateTimeEdit::dateTimeChanged,this,[this](const QDateTime &datetime)->void {
                if(!lastIdx.isValid())
                    return;
                QAbstractItemModel * model = view->model();
                model->setData(model->index(0,0,lastIdx),datetime.date().year());
                model->setData(model->index(1,0,lastIdx),datetime.date().month());
                model->setData(model->index(2,0,lastIdx),datetime.date().day());
                model->setData(model->index(3,0,lastIdx),datetime.time().hour());
                model->setData(model->index(4,0,lastIdx),datetime.time().minute());
                model->setData(model->index(5,0,lastIdx),datetime.time().second());
                model->setData(model->index(6,0,lastIdx),view->locale().toString(datetime.date(),QStringLiteral("dddd")));
            });
        }
    private:
        QTreeView* view;
        QDateTimeEdit *editor;
        QPersistentModelIndex lastIdx;
    };
    
    int main(int argc, char **argv)
    {
        QApplication app(argc,argv);
        ExampleWid wid;
        wid.show();
        return app.exec();
    }
    


  • @VRonin said in Need help from QT Experts ( Model / View for QDateTimeEdit widget):

    view

    HI VRonin,

    Thank you for your reply. This would work but my model is not a local one , it is set from a different class using a set-model function(example). I need it because the model might change during run time. in this case i need dynamic signal slot right ?. I have a requirement of sessions in my program if a session is changed then the model changes which i have to set it externally.

    Thanks and regards


  • Qt Champions 2018

    Nothing difficult, mine was just an example, just remove everything in the constructor before QVBoxLayout* minLay = new QVBoxLayout(this); and add a public void setModel(QAbstractItemModel* model){view->setModel(model);} that you can use to set a new model from outside this class. Everything else is all the same



  • Yes i tried that but when the model data is changed then the signal is not fired , it never comes to this function . why is that ?

    QObject::connect(lView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](QModelIndex idx) -> void {
    	qDebug() << "modelchanged " << idx;
        }


  • @chakry

    I need it because the model might change during run time

    Do you mean the whole model is changed, or just one item? If it's the former:
    https://doc.qt.io/qt-5/qitemselectionmodel.html#currentChanged

    Note that this signal will not be emitted when the item model is reset.

    and you might mean:
    https://doc.qt.io/qt-5/qitemselectionmodel.html#modelChanged

    This signal is emitted when the model is successfully set with setModel().

    or am I grasping the wrong end of the stick for what you are saying?



  • @JonB Thank you .
    sorry i want to correct few things . Your code is working fine with view and set model i have just verified that. The reason it didnt work was i was not displaying my view.
    I must not show view to user . i just have to map the model to widget(QDateTimeEdit) and show that widget to user. I thought with view it might work if i dont show this to user. just set everything but not showing. This dont work right? please correct me.

    I meant both cases. in one case , one child value will be changed in another case a new model is set.I can connect these signals as you mentioned but they work with TreeView when visible right?


  • Qt Champions 2018

    @chakry said in Need help from QT Experts ( Model / View for QDateTimeEdit widget):

    The reason it didnt work was i was not displaying my view.
    I must not show view to user . i just have to map the model to widget

    So how do you decide what parent item of the model should be edited in the widget?



  • @VRonin parent has a name , which is fixed. I will find it based on the name. The respective Model index is used unless a new model is updated.

    just a bit more explanation to be clear. I have a tree model and all these elements/children in tree model should be shown in view as a widget easily editable( ex: text box, checkbox, combobox, spinbox ..etc) . These standard items are working using widget mapper. I search for a parent and map its model index to one of the these standard widgets. In this model i also have 6 children's having date ,time info. I wanted to map these 6 children's to a single widget datetimeedit. This was my main issue . This is not possible with restriction on datawidgetmapper ( only one property can be mapped ).

    thanks and regards.


  • Qt Champions 2018

    @chakry said in Need help from QT Experts ( Model / View for QDateTimeEdit widget):

    I wanted to map these 6 children's to a single widget datetimeedit. This was my main issue . This is not possible with restriction on datawidgetmapper ( only one property can be mapped ).

    This is what the code in my 2 connect do:
    This puts an index inside the QDatetimeEdit

    if(!idx.isValid())
                    return;
                while(idx.parent().isValid())
                    idx =idx.parent();
                lastIdx = QPersistentModelIndex();
                editor->setDateTime(QDateTime(
                    QDate(
                        idx.model()->index(0,0,idx).data().toInt()
                        ,idx.model()->index(1,0,idx).data().toInt()
                        ,idx.model()->index(2,0,idx).data().toInt()
                    )
                    , QTime(
                        idx.model()->index(3,0,idx).data().toInt()
                        ,idx.model()->index(4,0,idx).data().toInt()
                        ,idx.model()->index(5,0,idx).data().toInt()
                    )
                ));
                lastIdx = idx;
    

    This puts the data from the QDatetimeEdit back into the model

    if(!lastIdx.isValid())
                    return;
                model->setData(model->index(0,0,lastIdx),datetime.date().year());
                model->setData(model->index(1,0,lastIdx),datetime.date().month());
                model->setData(model->index(2,0,lastIdx),datetime.date().day());
                model->setData(model->index(3,0,lastIdx),datetime.time().hour());
                model->setData(model->index(4,0,lastIdx),datetime.time().minute());
                model->setData(model->index(5,0,lastIdx),datetime.time().second());
                model->setData(model->index(6,0,lastIdx),view->locale().toString(datetime.date(),QStringLiteral("dddd")));
    


  • @VRonin said in Need help from QT Experts ( Model / View for QDateTimeEdit widget):

    @chakry said in Need help from QT Experts ( Model / View for QDateTimeEdit widget):

    I wanted to map these 6 children's to a single widget datetimeedit. This was my main issue . This is not possible with restriction on datawidgetmapper ( only one property can be mapped ).

    Thank you VRonin for helping
    This is what the code in my 2 connect do:
    This puts an index inside the QDatetimeEdit ```
    ( Its true but where should i add this code , view and selectionmodel as you mentioned before doesnot work as there is no view in my case , any suggestion where to keep this )

    > ```cpp
    > if(!idx.isValid())
    >                 return;
    >             while(idx.parent().isValid())
    >                 idx =idx.parent();
    >             lastIdx = QPersistentModelIndex();
    >             editor->setDateTime(QDateTime(
    >                 QDate(
    >                     idx.model()->index(0,0,idx).data().toInt()
    >                     ,idx.model()->index(1,0,idx).data().toInt()
    >                     ,idx.model()->index(2,0,idx).data().toInt()
    >                 )
    >                 , QTime(
    >                     idx.model()->index(3,0,idx).data().toInt()
    >                     ,idx.model()->index(4,0,idx).data().toInt()
    >                     ,idx.model()->index(5,0,idx).data().toInt()
    >                 )
    >             ));
    >             lastIdx = idx;
    > ```
    > 
    > This puts the data from the `QDatetimeEdit` back into the model  ```
    **( This works as in this case i use dateTimechanged from QDateTimeEditsignal and set the passed model data)**
    
    if(!lastIdx.isValid())
                    return;
                model->setData(model->index(0,0,lastIdx),datetime.date().year());
                model->setData(model->index(1,0,lastIdx),datetime.date().month());
                model->setData(model->index(2,0,lastIdx),datetime.date().day());
                model->setData(model->index(3,0,lastIdx),datetime.time().hour());
                model->setData(model->index(4,0,lastIdx),datetime.time().minute());
                model->setData(model->index(5,0,lastIdx),datetime.time().second());
                model->setData(model->index(6,0,lastIdx),view->locale().toString(datetime.date(),QStringLiteral("dddd")));
    

  • Qt Champions 2018

    Can you reformat the answer? it's difficult to follow...



Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.