6 changed files with 408 additions and 17 deletions
@ -1,24 +1,97 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<ui version="4.0"> |
|||
<author/> |
|||
<comment/> |
|||
<exportmacro/> |
|||
<class>SampleView</class> |
|||
<widget name="SampleView" class="QMainWindow"> |
|||
<widget class="QMainWindow" name="SampleView"> |
|||
<property name="geometry"> |
|||
<rect> |
|||
<x>0</x> |
|||
<y>0</y> |
|||
<width>800</width> |
|||
<height>600</height> |
|||
<width>577</width> |
|||
<height>336</height> |
|||
</rect> |
|||
</property> |
|||
<property name="windowTitle"> |
|||
<string>MainWindow</string> |
|||
</property> |
|||
<widget name="menubar" class="QMenuBar"/> |
|||
<widget name="centralwidget" class="QWidget"/> |
|||
<widget name="statusbar" class="QStatusBar"/> |
|||
<widget class="QWidget" name="centralwidget"> |
|||
<layout class="QFormLayout" name="formLayout"> |
|||
<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> |
|||
<pixmapfunction/> |
|||
<resources/> |
|||
<connections/> |
|||
</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