From 8faa6a8cad743d9f671c6a16bb9f7181292552a9 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 26 Feb 2024 10:52:52 +0100 Subject: [PATCH] bugs fixed, QskModelObjectBinder improved --- playground/models/Window.cpp | 117 +++++++++++++++----------- playground/models/Window.h | 2 - src/controls/QskModelObjectBinder.cpp | 115 ++++++++++++++++++++----- src/controls/QskModelObjectBinder.h | 21 ++++- 4 files changed, 178 insertions(+), 77 deletions(-) diff --git a/playground/models/Window.cpp b/playground/models/Window.cpp index 76c2f27b..352dc538 100644 --- a/playground/models/Window.cpp +++ b/playground/models/Window.cpp @@ -17,60 +17,77 @@ #include #include -Window::Window() +namespace { - auto db = QSqlDatabase::addDatabase( "QSQLITE" ); - db.setDatabaseName( ":memory:" ); - db.open(); + class Model : public QSqlTableModel + { + public: + Model( QObject *parent = nullptr ) + : QSqlTableModel( parent, QSqlDatabase::addDatabase( "QSQLITE" ) ) + { + auto db = database(); - QSqlQuery q(db); - q.exec( "create table test(id integer,value text);" ); - q.exec( "insert into test (id,value) values (1,'HELLO');" ); - q.exec( "insert into test (id,value) values (2,'WORLD');" ); + db.setDatabaseName( ":memory:" ); + db.open(); - auto table = new QSqlTableModel( nullptr, db ); - table->setTable( "test" ); - table->select(); + QSqlQuery query( db ); + query.exec( "create table test(id integer,value text);" ); + query.exec( "insert into test (id,value) values (1,'HELLO');" ); + query.exec( "insert into test (id,value) values (2,'WORLD');" ); - auto txt = new QskTextInput(); - auto spin = new QskSpinBox(); + setTable( "test" ); + select(); + } - auto mapper = new QskModelObjectBinder(); - mapper->bindModel( table ); - - mapper->bindObject( spin, 0 ); - mapper->bindObject( txt, 1 ); - - // this loads the record from the first row and updates the controls data. - mapper->setCurrentRow(0); - - auto v = new QskLinearBox(Qt::Vertical); - v->addSpacer( 0,100 ); - v->addItem( spin ); - v->addItem( txt ); - addItem(v); - - auto h = new QskLinearBox(Qt::Horizontal); - auto prev = new QskPushButton("<",h); - auto next = new QskPushButton(">",h); - connect(prev,&QskPushButton::clicked,[ = ]() { mapper->setCurrentRow( 0 ); }); - connect(next,&QskPushButton::clicked,[ = ]() { mapper->setCurrentRow( 1 ); }); - v->addItem(h); - - auto save = new QskPushButton("Save Data to Model",v); - connect(save,&QskPushButton::clicked,[ = ]() { - // this will update the current record with the data from the SpinBox and TextInput - mapper->updateModel(); - // just for illustration we print out the record - auto r = table->record(mapper->currentRow() ); - qDebug() << r; - }); - auto set0 = new QskPushButton("Set Model field to 0",v); - connect(set0,&QskPushButton::clicked,[ = ]() { - // this should trigger the binder and update the spinbox - table->setData( table->index(mapper->currentRow(),0),0 ); - }); - v->addSpacer( 0,100 ); + void setValue( int row, const QVariant &value ) + { + setData( index( row, 0 ), value ); + } + }; } -#include "moc_Window.cpp" +Window::Window() +{ + auto model = new Model( this ); + + auto textInput = new QskTextInput(); + auto spinBox = new QskSpinBox(); + + auto mapper = new QskModelObjectBinder( model, this ); + + mapper->bindObject( spinBox, 0 ); + mapper->bindObject( textInput, 1 ); + + auto vBox = new QskLinearBox( Qt::Vertical ); + vBox->addSpacer( 0, 100 ); + vBox->addItem( spinBox ); + vBox->addItem( textInput ); + + addItem( vBox ); + + auto hBox = new QskLinearBox( Qt::Horizontal ); + + { + auto prev = new QskPushButton( "<", hBox ); + auto next = new QskPushButton( ">", hBox); + + connect(prev, &QskPushButton::clicked, + [ mapper ]() { mapper->setCurrentRow( 0 ); }); + + connect( next, &QskPushButton::clicked, + [ mapper ]() { mapper->setCurrentRow( 1 ); }); + + vBox->addItem( hBox ); + } + + // update the current record with the data from the SpinBox and TextInput + auto save = new QskPushButton( "Save Data to Model", vBox ); + connect( save, &QskPushButton::clicked, + [=]() { mapper->submit(); qDebug() << model->record( mapper->currentRow() ); }); + + // trigger the binder and update the spinbox + auto set0 = new QskPushButton( "Set Model field to 0", vBox ); + connect( set0, &QskPushButton::clicked, + [=]() { model->setValue( mapper->currentRow(), 0 ); } ); + vBox->addSpacer( 0,100 ); +} diff --git a/playground/models/Window.h b/playground/models/Window.h index 816d6f4f..112f59c2 100644 --- a/playground/models/Window.h +++ b/playground/models/Window.h @@ -9,8 +9,6 @@ class Window : public QskWindow { - Q_OBJECT - public: Window(); }; diff --git a/src/controls/QskModelObjectBinder.cpp b/src/controls/QskModelObjectBinder.cpp index b33d51a5..b344eef5 100644 --- a/src/controls/QskModelObjectBinder.cpp +++ b/src/controls/QskModelObjectBinder.cpp @@ -19,6 +19,21 @@ static inline QMetaProperty qskMetaProperty( return ( idx >= 0 ) ? metaObject->property( idx ) : QMetaProperty(); } +static void qskEnableConnections( QObject* object, + QskModelObjectBinder* binder, bool on ) +{ + if ( on ) + { + QObject::connect( object, &QObject::destroyed, + binder, &QskModelObjectBinder::unbindObject ); + } + else + { + QObject::disconnect( object, &QObject::destroyed, + binder, &QskModelObjectBinder::unbindObject ); + } +} + namespace { struct Binding @@ -37,17 +52,29 @@ namespace class QskModelObjectBinder::PrivateData { public: - void updateProperties(const QModelIndex& topLeft, + void updateProperties() + { + if ( model && currentRowIndex.isValid() ) + { + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) + updateObjectProperty( it.key(), it.value() ); + } + } + + void updateProperties( const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector< int >& roles ) { - if ( !roles.contains( Qt::EditRole ) ) + if ( !( model && currentRowIndex.isValid() ) ) + return; + + if ( !roles.isEmpty() && roles.contains( Qt::EditRole ) ) return; const int row = currentRowIndex.row(); if ( topLeft.row() <= row && row <= bottomRight.row() ) { - for ( auto it = bindings.begin(); it != bindings.end(); ++it ) + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) { const int col = it.value().column; @@ -78,7 +105,7 @@ QskModelObjectBinder::QskModelObjectBinder( QObject* parent ) QskModelObjectBinder::QskModelObjectBinder( QAbstractItemModel* model, QObject* parent ) : QskModelObjectBinder( parent ) { - bindModel( model ); + setModel( model ); } void QskModelObjectBinder::bindObject( @@ -94,9 +121,13 @@ void QskModelObjectBinder::bindObject( if ( metaProperty.isValid() ) { - m_data->bindings.insert( object, { column, metaProperty } ); - connect( object, &QObject::destroyed, - this, &QskModelObjectBinder::unbindObject ); + const Binding binding = { column, metaProperty }; + + m_data->bindings.insert( object, binding ); + qskEnableConnections( object, this, true ); + + if ( m_data->model && m_data->currentRowIndex.isValid() ) + m_data->updateObjectProperty( object, binding ); } } @@ -105,20 +136,37 @@ void QskModelObjectBinder::unbindObject( QObject* object ) auto it = m_data->bindings.find( object ); if ( it != m_data->bindings.end() ) { - disconnect( object, &QObject::destroyed, - this, &QskModelObjectBinder::unbindObject ); - + qskEnableConnections( object, this, false ); m_data->bindings.erase( it ); } } -void QskModelObjectBinder::bindModel( QAbstractItemModel* model ) +void QskModelObjectBinder::clearBindings() { + auto& bindings = m_data->bindings; + + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) + qskEnableConnections( it.key(), this, false ); + + bindings.clear(); +} + +void QskModelObjectBinder::setModel( QAbstractItemModel* model ) +{ + if ( model == m_data->model ) + return; + if ( m_data->model ) - disconnect( m_data->model, nullptr, this, nullptr ); + { + disconnect( m_data->model, &QAbstractItemModel::dataChanged, this, nullptr ); + + m_data->model = nullptr; + m_data->currentRowIndex = QModelIndex(); + + clearBindings(); + } m_data->model = model; - m_data->currentRowIndex = QModelIndex(); if( model ) { @@ -126,25 +174,45 @@ void QskModelObjectBinder::bindModel( QAbstractItemModel* model ) const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector< int >& roles ) { - m_data->updateProperties( topLeft, bottomRight, roles ); + m_data->updateProperties( topLeft, bottomRight, roles ); }; connect( m_data->model, &QAbstractItemModel::dataChanged, this, updateProperties ); + + connect( m_data->model, &QObject::destroyed, + this, &QskModelObjectBinder::clearBindings ); + + setCurrentRow( 0 ); } } +const QAbstractItemModel* QskModelObjectBinder::model() const +{ + return m_data->model; +} + +QAbstractItemModel* QskModelObjectBinder::model() +{ + return m_data->model; +} + void QskModelObjectBinder::setCurrentRow( int row ) { - Q_ASSERT( m_data->model != nullptr ); + auto model = m_data->model.data(); + auto& bindings = m_data->bindings; - if ( m_data->model == nullptr ) - return; + Q_ASSERT( model != nullptr ); - m_data->currentRowIndex = m_data->model->index( row, 0 ); + if ( model && row >= 0 && row < model->rowCount() ) + { + m_data->currentRowIndex = model->index( row, 0 ); - for ( auto it = m_data->bindings.begin(); it != m_data->bindings.end(); ++it ) - m_data->updateObjectProperty( it.key(), it.value() ); + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) + m_data->updateObjectProperty( it.key(), it.value() ); + + Q_EMIT currentRowChanged( row ); + } } int QskModelObjectBinder::currentRow() const @@ -152,7 +220,7 @@ int QskModelObjectBinder::currentRow() const return m_data->currentRowIndex.row(); } -void QskModelObjectBinder::updateModel() +void QskModelObjectBinder::submit() { if ( auto model = m_data->model ) { @@ -170,4 +238,9 @@ void QskModelObjectBinder::updateModel() } } +void QskModelObjectBinder::revert() +{ + m_data->updateProperties(); +} + #include "moc_QskModelObjectBinder.cpp" diff --git a/src/controls/QskModelObjectBinder.h b/src/controls/QskModelObjectBinder.h index 7cd3dc80..a7338cfe 100644 --- a/src/controls/QskModelObjectBinder.h +++ b/src/controls/QskModelObjectBinder.h @@ -19,20 +19,33 @@ class QSK_EXPORT QskModelObjectBinder : public QObject { Q_OBJECT + Q_PROPERTY( int currentRow READ currentRow + WRITE setCurrentRow NOTIFY currentRowChanged ) + public: QskModelObjectBinder( QObject* parent = nullptr ); QskModelObjectBinder( QAbstractItemModel*, QObject* parent = nullptr ); + void setModel( QAbstractItemModel* ); + + const QAbstractItemModel* model() const; + QAbstractItemModel* model(); + + void setCurrentRow( int row ); + int currentRow() const; + void bindObject( QObject*, int column, const QByteArray& propertyName = QByteArray() ); void unbindObject( QObject* ); - void bindModel( QAbstractItemModel* ); - void setCurrentRow( int row ); - [[nodiscard]] int currentRow() const; + Q_SIGNALS: + void currentRowChanged( int ); - void updateModel(); + public Q_SLOTS: + void submit(); + void revert(); + void clearBindings(); private: class PrivateData;