Browse Source

added bindings with test code

pull/2/head
Skycoder42 7 years ago
parent
commit
964f021051
  1. 12
      examples/mvvmwidgets/SampleWidgets/sampleview.cpp
  2. 93
      examples/mvvmwidgets/SampleWidgets/sampleview.ui
  3. 195
      src/mvvmcore/binding.cpp
  4. 58
      src/mvvmcore/binding.h
  5. 48
      src/mvvmcore/binding_p.h
  6. 19
      src/mvvmcore/mvvmcore.pro

12
examples/mvvmwidgets/SampleWidgets/sampleview.cpp

@ -1,5 +1,6 @@
#include "sampleview.h"
#include "ui_sampleview.h"
#include <QtMvvmCore/Binding>
SampleView::SampleView(QtMvvm::ViewModel *viewModel, QWidget *parent) :
QMainWindow(parent),
@ -8,6 +9,17 @@ SampleView::SampleView(QtMvvm::ViewModel *viewModel, QWidget *parent) :
{
Q_ASSERT(_viewModel);
ui->setupUi(this);
QtMvvm::bind(_viewModel, "active",
ui->activeCheckBox, "checked");
QtMvvm::bind(_viewModel, "name",
ui->nameLineEdit, "text",
QtMvvm::Binding::TwoWay,
nullptr,
"editingFinished()");
ui->eventsListView->setModel(_viewModel->eventsModel());
connect(ui->clearButton, &QPushButton::clicked,
_viewModel, &SampleViewModel::clearEvents);
}
SampleView::~SampleView()

93
examples/mvvmwidgets/SampleWidgets/sampleview.ui

@ -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>&amp;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>&amp;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>&amp;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>&amp;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>

195
src/mvvmcore/binding.cpp

@ -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";
}
}

58
src/mvvmcore/binding.h

@ -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

48
src/mvvmcore/binding_p.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

19
src/mvvmcore/mvvmcore.pro

@ -8,17 +8,20 @@ HEADERS += \
viewmodel_p.h \
coreapp.h \
coreapp_p.h \
serviceregistry.h \
serviceregistry_p.h \
qtmvvmcore_helpertypes.h \
ipresenter.h \
qtmvvm_logging_p.h
serviceregistry.h \
serviceregistry_p.h \
qtmvvmcore_helpertypes.h \
ipresenter.h \
qtmvvm_logging_p.h \
binding.h \
binding_p.h
SOURCES += \
viewmodel.cpp \
coreapp.cpp \
serviceregistry.cpp \
qtmvvmcore_global.cpp
serviceregistry.cpp \
qtmvvmcore_global.cpp \
binding.cpp
TRANSLATIONS += \
translations/qtmvvmcore_de.ts \
@ -45,3 +48,5 @@ else: include($$OUT_PWD/qpmx_generated.pri)
qpmx_ts_target.files -= $$OUT_PWD/$$QPMX_WORKINGDIR/qtmvvmcore_template.qm
qpmx_ts_target.files += translations/qtmvvmcore_template.ts
message(CONFIG = $$CONFIG) # TODO debug remove

Loading…
Cancel
Save