6 changed files with 408 additions and 17 deletions
@ -1,24 +1,97 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
<ui version="4.0"> |
||||
<author/> |
|
||||
<comment/> |
|
||||
<exportmacro/> |
|
||||
<class>SampleView</class> |
<class>SampleView</class> |
||||
<widget name="SampleView" class="QMainWindow"> |
<widget class="QMainWindow" name="SampleView"> |
||||
<property name="geometry"> |
<property name="geometry"> |
||||
<rect> |
<rect> |
||||
<x>0</x> |
<x>0</x> |
||||
<y>0</y> |
<y>0</y> |
||||
<width>800</width> |
<width>577</width> |
||||
<height>600</height> |
<height>336</height> |
||||
</rect> |
</rect> |
||||
</property> |
</property> |
||||
<property name="windowTitle"> |
<property name="windowTitle"> |
||||
<string>MainWindow</string> |
<string>MainWindow</string> |
||||
</property> |
</property> |
||||
<widget name="menubar" class="QMenuBar"/> |
<widget class="QWidget" name="centralwidget"> |
||||
<widget name="centralwidget" class="QWidget"/> |
<layout class="QFormLayout" name="formLayout"> |
||||
<widget name="statusbar" class="QStatusBar"/> |
<item row="0" column="0"> |
||||
|
<widget class="QLabel" name="nameLabel"> |
||||
|
<property name="text"> |
||||
|
<string>&Name:</string> |
||||
|
</property> |
||||
|
<property name="buddy"> |
||||
|
<cstring>nameLineEdit</cstring> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="0" column="1"> |
||||
|
<widget class="QLineEdit" name="nameLineEdit"/> |
||||
|
</item> |
||||
|
<item row="1" column="0"> |
||||
|
<widget class="QLabel" name="activeLabel"> |
||||
|
<property name="text"> |
||||
|
<string>&Active:</string> |
||||
|
</property> |
||||
|
<property name="buddy"> |
||||
|
<cstring>activeCheckBox</cstring> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="1" column="1"> |
||||
|
<widget class="QCheckBox" name="activeCheckBox"/> |
||||
|
</item> |
||||
|
<item row="3" column="0" colspan="2"> |
||||
|
<widget class="QListView" name="eventsListView"/> |
||||
|
</item> |
||||
|
<item row="2" column="0"> |
||||
|
<widget class="QLabel" name="eventsLabel"> |
||||
|
<property name="text"> |
||||
|
<string>&Events:</string> |
||||
|
</property> |
||||
|
<property name="buddy"> |
||||
|
<cstring>eventsListView</cstring> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="2" column="1"> |
||||
|
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
|
<item> |
||||
|
<widget class="QPushButton" name="clearButton"> |
||||
|
<property name="text"> |
||||
|
<string>&Clear</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<spacer name="horizontalSpacer"> |
||||
|
<property name="orientation"> |
||||
|
<enum>Qt::Horizontal</enum> |
||||
|
</property> |
||||
|
<property name="sizeHint" stdset="0"> |
||||
|
<size> |
||||
|
<width>40</width> |
||||
|
<height>20</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
</spacer> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
<widget class="QMenuBar" name="menubar"> |
||||
|
<property name="geometry"> |
||||
|
<rect> |
||||
|
<x>0</x> |
||||
|
<y>0</y> |
||||
|
<width>577</width> |
||||
|
<height>23</height> |
||||
|
</rect> |
||||
|
</property> |
||||
|
</widget> |
||||
|
<widget class="QStatusBar" name="statusbar"/> |
||||
</widget> |
</widget> |
||||
<pixmapfunction/> |
<resources/> |
||||
<connections/> |
<connections/> |
||||
</ui> |
</ui> |
||||
|
@ -0,0 +1,195 @@ |
|||||
|
#include "binding.h" |
||||
|
#include "binding_p.h" |
||||
|
#include "qtmvvm_logging_p.h" |
||||
|
using namespace QtMvvm; |
||||
|
|
||||
|
Binding QtMvvm::bind(QObject *viewModel, const char *viewModelProperty, QObject *view, const char *viewProperty, Binding::BindingDirection type, const char *viewModelChangeSignal, const char *viewChangeSignal) |
||||
|
{ |
||||
|
if(!viewModel) { |
||||
|
logWarning() << "ViewModel must not be null"; |
||||
|
return {}; |
||||
|
} |
||||
|
auto moModel = viewModel->metaObject(); |
||||
|
auto piModel = moModel->indexOfProperty(viewModelProperty); |
||||
|
if(piModel == -1) { |
||||
|
logWarning() << "ViewModel of type" << moModel->className() |
||||
|
<< "has no property named" << viewModelProperty; |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
if(!view) { |
||||
|
logWarning() << "View must not be null"; |
||||
|
return {}; |
||||
|
} |
||||
|
auto moView = view->metaObject(); |
||||
|
auto piView = moView->indexOfProperty(viewProperty); |
||||
|
if(piView == -1) { |
||||
|
logWarning() << "View of type" << moView->className() |
||||
|
<< "has no property named" << viewProperty; |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return bind(viewModel, moModel->property(piModel), view, moView->property(piView), type, viewModelChangeSignal, viewChangeSignal); |
||||
|
} |
||||
|
|
||||
|
Binding QtMvvm::bind(QObject *viewModel, const QMetaProperty &viewModelProperty, QObject *view, const QMetaProperty &viewProperty, Binding::BindingDirection type, const char *viewModelChangeSignal, const char *viewChangeSignal) |
||||
|
{ |
||||
|
if(!viewModel) { |
||||
|
qWarning() << "ViewModel must not be null"; |
||||
|
return {}; |
||||
|
} |
||||
|
QMetaMethod smModel; |
||||
|
if(viewModelChangeSignal) { |
||||
|
auto moModel = viewModel->metaObject(); |
||||
|
auto siModel = moModel->indexOfSignal(viewModelChangeSignal); |
||||
|
if(siModel == -1) { |
||||
|
logWarning() << "ViewModel of type" << moModel->className() |
||||
|
<< "has no signal named" << viewModelChangeSignal; |
||||
|
return {}; |
||||
|
} |
||||
|
smModel = moModel->method(siModel); |
||||
|
} |
||||
|
|
||||
|
if(!view) { |
||||
|
qWarning() << "View must not be null"; |
||||
|
return {}; |
||||
|
} |
||||
|
QMetaMethod smView; |
||||
|
if(viewChangeSignal) { |
||||
|
auto moView = view->metaObject(); |
||||
|
auto siView = moView->indexOfSignal(viewChangeSignal); |
||||
|
if(siView == -1) { |
||||
|
logWarning() << "View of type" << moView->className() |
||||
|
<< "has no signal named" << viewChangeSignal; |
||||
|
return {}; |
||||
|
} |
||||
|
smView = moView->method(siView); |
||||
|
} |
||||
|
|
||||
|
return BindingPrivate::bind(viewModel, viewModelProperty, view, viewProperty, type, smModel, smView); |
||||
|
} |
||||
|
|
||||
|
Binding QtMvvm::bind(QObject *viewModel, const QMetaProperty &viewModelProperty, QObject *view, const QMetaProperty &viewProperty, Binding::BindingDirection type, const QMetaMethod &viewModelChangeSignal, const QMetaMethod &viewChangeSignal) |
||||
|
{ |
||||
|
//check not null
|
||||
|
if(!viewModel) { |
||||
|
qWarning() << "ViewModel must not be null"; |
||||
|
return {}; |
||||
|
} |
||||
|
if(!view) { |
||||
|
qWarning() << "View must not be null"; |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return BindingPrivate::bind(viewModel, viewModelProperty, view, viewProperty, type, viewModelChangeSignal, viewChangeSignal); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
Binding::Binding(QPointer<BindingPrivate> d_ptr) : |
||||
|
d(d_ptr) |
||||
|
{} |
||||
|
|
||||
|
Binding::~Binding() {} |
||||
|
|
||||
|
bool Binding::isValid() const |
||||
|
{ |
||||
|
return d; |
||||
|
} |
||||
|
|
||||
|
// ------------- Private Implementation -------------
|
||||
|
|
||||
|
Binding BindingPrivate::bind(QObject *viewModel, const QMetaProperty &viewModelProperty, QObject *view, const QMetaProperty &viewProperty, Binding::BindingDirection type, const QMetaMethod &viewModelChangeSignal, const QMetaMethod &viewChangeSignal) |
||||
|
{ |
||||
|
QPointer<BindingPrivate> binderPrivate = new BindingPrivate(viewModel, viewModelProperty, view, viewProperty); |
||||
|
|
||||
|
if(type.testFlag(Binding::SingleInit)) |
||||
|
binderPrivate->init(); |
||||
|
if(type.testFlag(Binding::OneWayToView)) |
||||
|
binderPrivate->bindFrom(viewModelChangeSignal); |
||||
|
if(type.testFlag(Binding::OneWayToViewModel)) |
||||
|
binderPrivate->bindTo(viewChangeSignal); |
||||
|
|
||||
|
return binderPrivate; |
||||
|
} |
||||
|
|
||||
|
BindingPrivate::BindingPrivate(QObject *viewModel, const QMetaProperty &viewModelProperty, QObject *view, const QMetaProperty &viewProperty) : |
||||
|
QObject(view), |
||||
|
viewModel(viewModel), |
||||
|
view(view), |
||||
|
viewModelProperty(viewModelProperty), |
||||
|
viewProperty(viewProperty) |
||||
|
{ |
||||
|
connect(viewModel, &QObject::destroyed, |
||||
|
this, &BindingPrivate::deleteLater); |
||||
|
} |
||||
|
|
||||
|
void BindingPrivate::viewModelTrigger() |
||||
|
{ |
||||
|
viewProperty.write(view, viewModelProperty.read(viewModel)); |
||||
|
} |
||||
|
|
||||
|
void BindingPrivate::viewTrigger() |
||||
|
{ |
||||
|
viewModelProperty.write(viewModel, viewProperty.read(view)); |
||||
|
} |
||||
|
|
||||
|
void BindingPrivate::init() |
||||
|
{ |
||||
|
testReadable(viewModelProperty, false); |
||||
|
testWritable(viewProperty, true); |
||||
|
viewProperty.write(view, viewModelProperty.read(viewModel)); |
||||
|
} |
||||
|
|
||||
|
void BindingPrivate::bindFrom(QMetaMethod changeSignal) |
||||
|
{ |
||||
|
//testReadable and testWritable are alredy tested by the init() method
|
||||
|
if(!changeSignal.isValid()) { |
||||
|
testNotifier(viewModelProperty, false); |
||||
|
changeSignal = viewModelProperty.notifySignal(); |
||||
|
} |
||||
|
auto trigger = metaObject()->method(metaObject()->indexOfSlot("viewModelTrigger()")); |
||||
|
connect(viewModel, changeSignal, this, trigger); |
||||
|
} |
||||
|
|
||||
|
void BindingPrivate::bindTo(QMetaMethod changeSignal) |
||||
|
{ |
||||
|
testReadable(viewProperty, true); |
||||
|
testWritable(viewModelProperty, false); |
||||
|
if(!changeSignal.isValid()) { |
||||
|
testNotifier(viewProperty, true); |
||||
|
changeSignal = viewProperty.notifySignal(); |
||||
|
} |
||||
|
auto signal = viewProperty.notifySignal(); |
||||
|
auto trigger = metaObject()->method(metaObject()->indexOfSlot("viewTrigger()")); |
||||
|
connect(view, changeSignal, this, trigger); |
||||
|
} |
||||
|
|
||||
|
void BindingPrivate::testReadable(const QMetaProperty &property, bool asView) const |
||||
|
{ |
||||
|
if(!property.isReadable()) { |
||||
|
logWarning() << (asView ? "View" : "ViewModel") |
||||
|
<< "property" << property.name() |
||||
|
<< "of" << property.enclosingMetaObject()->className() |
||||
|
<< "is not readable"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void BindingPrivate::testWritable(const QMetaProperty &property, bool asView) const |
||||
|
{ |
||||
|
if(!property.isWritable()) { |
||||
|
logWarning() << (asView ? "View" : "ViewModel") |
||||
|
<< "property" << property.name() |
||||
|
<< "of" << property.enclosingMetaObject()->className() |
||||
|
<< "is not writable"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void BindingPrivate::testNotifier(const QMetaProperty &property, bool asView) const |
||||
|
{ |
||||
|
if(!property.hasNotifySignal()) { |
||||
|
logWarning() << (asView ? "View" : "ViewModel") |
||||
|
<< "property" << property.name() |
||||
|
<< "of" << property.enclosingMetaObject()->className() |
||||
|
<< "has no notify singal"; |
||||
|
} |
||||
|
} |
@ -0,0 +1,58 @@ |
|||||
|
#ifndef QTMVVM_BINDING_H |
||||
|
#define QTMVVM_BINDING_H |
||||
|
|
||||
|
#include <QtCore/qobject.h> |
||||
|
#include <QtCore/qpointer.h> |
||||
|
#include <QtCore/qmetaobject.h> |
||||
|
|
||||
|
#include "QtMvvmCore/qtmvvmcore_global.h" |
||||
|
|
||||
|
namespace QtMvvm { |
||||
|
|
||||
|
class BindingPrivate; |
||||
|
class Q_MVVMCORE_EXPORT Binding |
||||
|
{ |
||||
|
Q_GADGET |
||||
|
|
||||
|
Q_PROPERTY(bool valid READ isValid CONSTANT FINAL) |
||||
|
|
||||
|
public: |
||||
|
enum BindingDirectionFlag { |
||||
|
SingleInit = 0x01, |
||||
|
OneWayToView = (0x02 | SingleInit), |
||||
|
OneWayToViewModel = 0x04, |
||||
|
TwoWay = (OneWayToView | OneWayToViewModel) |
||||
|
}; |
||||
|
Q_DECLARE_FLAGS(BindingDirection, BindingDirectionFlag) |
||||
|
Q_FLAG(BindingDirection) |
||||
|
|
||||
|
Binding(QPointer<BindingPrivate> d_ptr = nullptr); |
||||
|
~Binding(); |
||||
|
|
||||
|
bool isValid() const; |
||||
|
|
||||
|
private: |
||||
|
QPointer<BindingPrivate> d; |
||||
|
}; |
||||
|
|
||||
|
Q_MVVMCORE_EXPORT Binding bind(QObject *viewModel, const char *viewModelProperty, |
||||
|
QObject *view, const char *viewProperty, |
||||
|
Binding::BindingDirection type = Binding::TwoWay, |
||||
|
const char *viewModelChangeSignal = nullptr, |
||||
|
const char *viewChangeSignal = nullptr); |
||||
|
Q_MVVMCORE_EXPORT Binding bind(QObject *viewModel, const QMetaProperty &viewModelProperty, |
||||
|
QObject *view, const QMetaProperty &viewProperty, |
||||
|
Binding::BindingDirection type = Binding::TwoWay, |
||||
|
const char *viewModelChangeSignal = nullptr, |
||||
|
const char *viewChangeSignal = nullptr); |
||||
|
Q_MVVMCORE_EXPORT Binding bind(QObject *viewModel, const QMetaProperty &viewModelProperty, |
||||
|
QObject *view, const QMetaProperty &viewProperty, |
||||
|
Binding::BindingDirection type = Binding::TwoWay, |
||||
|
const QMetaMethod &viewModelChangeSignal = {}, |
||||
|
const QMetaMethod &viewChangeSignal = {}); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(QtMvvm::Binding::BindingDirection) |
||||
|
|
||||
|
#endif // QTMVVM_BINDING_H
|
@ -0,0 +1,48 @@ |
|||||
|
#ifndef QTMVVM_BINDING_P_H |
||||
|
#define QTMVVM_BINDING_P_H |
||||
|
|
||||
|
#include <QtCore/QObject> |
||||
|
#include <QtCore/QPointer> |
||||
|
|
||||
|
#include "qtmvvmcore_global.h" |
||||
|
#include "binding.h" |
||||
|
|
||||
|
namespace QtMvvm { |
||||
|
|
||||
|
class BindingPrivate : public QObject |
||||
|
{ |
||||
|
friend class QtMvvm::Binding; |
||||
|
Q_OBJECT |
||||
|
|
||||
|
public: |
||||
|
static Binding bind(QObject *viewModel, const QMetaProperty &viewModelProperty, |
||||
|
QObject *view, const QMetaProperty &viewProperty, |
||||
|
Binding::BindingDirection type, |
||||
|
const QMetaMethod &viewModelChangeSignal, |
||||
|
const QMetaMethod &viewChangeSignal); |
||||
|
|
||||
|
private Q_SLOTS: |
||||
|
void viewModelTrigger(); |
||||
|
void viewTrigger(); |
||||
|
|
||||
|
private: |
||||
|
BindingPrivate(QObject *viewModel, const QMetaProperty &viewModelProperty, |
||||
|
QObject *view, const QMetaProperty &viewProperty); |
||||
|
|
||||
|
void init(); |
||||
|
void bindFrom(QMetaMethod changeSignal); |
||||
|
void bindTo(QMetaMethod changeSignal); |
||||
|
|
||||
|
QPointer<QObject> viewModel; |
||||
|
QPointer<QObject> view; |
||||
|
QMetaProperty viewModelProperty; |
||||
|
QMetaProperty viewProperty; |
||||
|
|
||||
|
void testReadable(const QMetaProperty &property, bool asView) const; |
||||
|
void testWritable(const QMetaProperty &property, bool asView) const; |
||||
|
void testNotifier(const QMetaProperty &property, bool asView) const; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
#endif // QTMVVM_BINDING_P_H
|
Loading…
Reference in new issue