Need help from QT Experts ( Model / View for QDateTimeEdit widget)
-
@SGaist
As I understand it, the OP has 6 properties. You will calladdMapping()
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.
-
@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.
-
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 :))
-
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.
-
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. 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.
-
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?
- Parent A
-
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_OBJECTQ_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
withQDataWidgetMapper
, set delegate. and voila -
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
-
Nothing difficult, mine was just an example, just remove everything in the constructor before
QVBoxLayout* minLay = new QVBoxLayout(this);
and add a publicvoid 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; }
-
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#currentChangedNote 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#modelChangedThis 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?