16 changed files with 553 additions and 19 deletions
@ -0,0 +1,31 @@ |
|||||
|
#include "containerviewmodel.h" |
||||
|
|
||||
|
ContainerViewModel::ContainerViewModel(QObject *parent) : |
||||
|
ViewModel(parent) |
||||
|
{} |
||||
|
|
||||
|
ContainerViewModel::~ContainerViewModel() |
||||
|
{ |
||||
|
qInfo(Q_FUNC_INFO); |
||||
|
} |
||||
|
|
||||
|
QString ContainerViewModel::vmType() const |
||||
|
{ |
||||
|
return QString::fromUtf8(QMetaType::typeName(qMetaTypeId<ChildViewModel*>())); |
||||
|
} |
||||
|
|
||||
|
void ContainerViewModel::loadChild() |
||||
|
{ |
||||
|
show<ChildViewModel>(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
ChildViewModel::ChildViewModel(QObject *parent) : |
||||
|
ViewModel(parent) |
||||
|
{} |
||||
|
|
||||
|
ChildViewModel::~ChildViewModel() |
||||
|
{ |
||||
|
qInfo(Q_FUNC_INFO); |
||||
|
} |
@ -0,0 +1,34 @@ |
|||||
|
#ifndef CONTAINERVIEWMODEL_H |
||||
|
#define CONTAINERVIEWMODEL_H |
||||
|
|
||||
|
#include <QtMvvmCore/ViewModel> |
||||
|
|
||||
|
class ChildViewModel : public QtMvvm::ViewModel |
||||
|
{ |
||||
|
Q_OBJECT |
||||
|
|
||||
|
public: |
||||
|
Q_INVOKABLE explicit ChildViewModel(QObject *parent = nullptr); |
||||
|
~ChildViewModel(); |
||||
|
}; |
||||
|
|
||||
|
class ContainerViewModel : public QtMvvm::ViewModel |
||||
|
{ |
||||
|
Q_OBJECT |
||||
|
|
||||
|
Q_PROPERTY(QString vmType READ vmType CONSTANT) |
||||
|
|
||||
|
public: |
||||
|
Q_INVOKABLE explicit ContainerViewModel(QObject *parent = nullptr); |
||||
|
~ContainerViewModel(); |
||||
|
|
||||
|
QString vmType() const; |
||||
|
|
||||
|
public Q_SLOTS: |
||||
|
void loadChild(); |
||||
|
}; |
||||
|
|
||||
|
Q_DECLARE_METATYPE(ChildViewModel*) |
||||
|
Q_DECLARE_METATYPE(ContainerViewModel*) |
||||
|
|
||||
|
#endif // CONTAINERVIEWMODEL_H
|
@ -0,0 +1,29 @@ |
|||||
|
import QtQuick 2.10 |
||||
|
import QtQuick.Controls 2.3 |
||||
|
import de.skycoder42.QtMvvm.Core 1.1 |
||||
|
import de.skycoder42.QtMvvm.Quick 1.1 |
||||
|
import de.skycoder42.QtMvvm.Sample 1.1 |
||||
|
|
||||
|
Rectangle { |
||||
|
property ChildViewModel viewModel: null |
||||
|
|
||||
|
color: "red" |
||||
|
|
||||
|
property bool dummyClosed: false |
||||
|
|
||||
|
function closeAction() { |
||||
|
if(dummyClosed) |
||||
|
return false; |
||||
|
else { |
||||
|
dummyClosed = true; |
||||
|
label.text = qsTr("I was closed!") |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Label { |
||||
|
id: label |
||||
|
anchors.centerIn: parent |
||||
|
text: qsTr("I am the child!") |
||||
|
} |
||||
|
} |
@ -0,0 +1,71 @@ |
|||||
|
import QtQuick 2.10 |
||||
|
import QtQuick.Controls 2.3 |
||||
|
import QtQuick.Layouts 1.3 |
||||
|
import de.skycoder42.QtMvvm.Core 1.1 |
||||
|
import de.skycoder42.QtMvvm.Quick 1.1 |
||||
|
import de.skycoder42.QtMvvm.Sample 1.1 |
||||
|
|
||||
|
Page { |
||||
|
id: containerView |
||||
|
property ContainerViewModel viewModel: null |
||||
|
|
||||
|
function presentItem(item) { |
||||
|
return viewPlaceholder.presentItem(item); |
||||
|
} |
||||
|
|
||||
|
function closeAction() { |
||||
|
return viewPlaceholder.closeAction(); |
||||
|
} |
||||
|
|
||||
|
header: ContrastToolBar { |
||||
|
ToolBarLabel { |
||||
|
text: qsTr("View Container") |
||||
|
anchors.fill: parent |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
PresenterProgress {} |
||||
|
|
||||
|
Pane { |
||||
|
anchors.fill: parent |
||||
|
|
||||
|
ColumnLayout { |
||||
|
anchors.fill: parent |
||||
|
|
||||
|
ViewPlaceholder { |
||||
|
id: viewPlaceholder |
||||
|
|
||||
|
viewModelType: containerView.viewModel.vmType |
||||
|
parentViewModel: containerView.viewModel |
||||
|
autoPresent: false |
||||
|
|
||||
|
Layout.fillWidth: true |
||||
|
Layout.fillHeight: true |
||||
|
|
||||
|
BusyIndicator { |
||||
|
anchors.centerIn: parent |
||||
|
anchors.verticalCenterOffset: -(hookButton.height/2) |
||||
|
running: !viewPlaceholder.loadedView |
||||
|
} |
||||
|
|
||||
|
Button { |
||||
|
id: hookButton |
||||
|
Layout.fillWidth: true |
||||
|
text: qsTr("Load from QML") |
||||
|
onClicked: viewPlaceholder.presentView(); |
||||
|
enabled: !viewPlaceholder.loadedView |
||||
|
|
||||
|
anchors.left: parent.left |
||||
|
anchors.right: parent.right |
||||
|
anchors.bottom: parent.bottom |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Button { |
||||
|
Layout.fillWidth: true |
||||
|
text: viewPlaceholder.loadedView ? qsTr("Discard child") : qsTr("Show Child") |
||||
|
onClicked: viewPlaceholder.loadedView ? viewPlaceholder.discardView() : viewModel.loadChild(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,201 @@ |
|||||
|
#include "qqmlviewplaceholder.h" |
||||
|
#include <QtCore/QMetaMethod> |
||||
|
#include <QtQml/QQmlInfo> |
||||
|
#include <QtQml/QQmlProperty> |
||||
|
#include <QtMvvmCore/CoreApp> |
||||
|
using namespace QtMvvm; |
||||
|
|
||||
|
QQmlViewPlaceholder::QQmlViewPlaceholder(QQuickItem *parent) : |
||||
|
QQuickItem{parent} |
||||
|
{ |
||||
|
// auto presenting
|
||||
|
connect(this, &QQmlViewPlaceholder::viewModelTypeChanged, |
||||
|
this, &QQmlViewPlaceholder::presentIfReady); |
||||
|
connect(this, &QQmlViewPlaceholder::autoPresentChanged, |
||||
|
this, &QQmlViewPlaceholder::presentIfReady); |
||||
|
|
||||
|
// size changes
|
||||
|
connect(this, &QQmlViewPlaceholder::widthChanged, |
||||
|
this, &QQmlViewPlaceholder::resizeView); |
||||
|
connect(this, &QQmlViewPlaceholder::heightChanged, |
||||
|
this, &QQmlViewPlaceholder::resizeView); |
||||
|
connect(this, &QQmlViewPlaceholder::autoResizeViewChanged, |
||||
|
this, &QQmlViewPlaceholder::resizeView); |
||||
|
} |
||||
|
|
||||
|
bool QQmlViewPlaceholder::presentItem(const QVariant &item) |
||||
|
{ |
||||
|
// check if the parameter is valid
|
||||
|
auto quickItem = item.value<QQuickItem*>(); |
||||
|
if(!quickItem) { |
||||
|
qmlWarning(this) << "presentItem called with invalid item of type " << item.typeName(); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// handle already existing view case
|
||||
|
if(_loadedView) { |
||||
|
if(_replaceViews) { // quick discard without reenableing all children
|
||||
|
_loadedView->setVisible(false); |
||||
|
_loadedView->deleteLater(); |
||||
|
_loadedView = nullptr; |
||||
|
} else { |
||||
|
qmlWarning(this) << R"(A view has already been presented. Discard it first via "discardView" or set "replaceViews" to true)"; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// add
|
||||
|
_loadedView = quickItem; |
||||
|
quickItem->setParent(this); |
||||
|
quickItem->setParentItem(this); |
||||
|
resizeView(); |
||||
|
quickItem->setVisible(true); |
||||
|
|
||||
|
// hide all children
|
||||
|
for(auto child : childItems()) { |
||||
|
if(child != _loadedView) |
||||
|
child->setVisible(false); |
||||
|
} |
||||
|
|
||||
|
emit loadedViewChanged(_loadedView); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool QQmlViewPlaceholder::closeAction() |
||||
|
{ |
||||
|
if(!_loadedView) |
||||
|
return false; |
||||
|
|
||||
|
// call close on the view
|
||||
|
auto vMetaObject = _loadedView->metaObject(); |
||||
|
auto cMethodIndex = vMetaObject->indexOfMethod("closeAction()"); |
||||
|
if(cMethodIndex != -1) { |
||||
|
auto cMethod = vMetaObject->method(cMethodIndex); |
||||
|
QVariant ok = false; |
||||
|
cMethod.invoke(_loadedView, Q_RETURN_ARG(QVariant, ok)); |
||||
|
if(ok.toBool()) |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// close the view itself (if wished)
|
||||
|
if(_closeViewOnAction) { |
||||
|
discardView(); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// otherwise: nothing closed
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
void QQmlViewPlaceholder::setParentViewModel(ViewModel *parentViewModel) |
||||
|
{ |
||||
|
// first: clear the auto-connected viewmodel, if required
|
||||
|
if(_clearParentVmCon && _parentVmCon) |
||||
|
disconnect(_parentVmCon); |
||||
|
|
||||
|
// then: set property as usual
|
||||
|
if(_parentViewModel == parentViewModel) |
||||
|
return; |
||||
|
|
||||
|
_parentViewModel = parentViewModel; |
||||
|
emit parentViewModelChanged(_parentViewModel); |
||||
|
|
||||
|
// check the vm parent for a presenter method
|
||||
|
auto view = qobject_cast<QQuickItem*>(_parentViewModel->parent()); |
||||
|
if(view) { |
||||
|
if(view->metaObject()->indexOfMethod("presentItem(QVariant)") == -1) |
||||
|
qmlWarning(this) << R"(Parent item of "parentViewModel" does not have a "presentItem" method. Check the ViewPlaceholder documentation!)"; |
||||
|
} else |
||||
|
qmlWarning(this) << R"(Parent item of "parentViewModel" is not an Item)"; |
||||
|
|
||||
|
presentIfReady(); |
||||
|
} |
||||
|
|
||||
|
QQuickItem *QQmlViewPlaceholder::loadedView() const |
||||
|
{ |
||||
|
return _loadedView; |
||||
|
} |
||||
|
|
||||
|
void QQmlViewPlaceholder::componentComplete() |
||||
|
{ |
||||
|
// auto-set the vm if not already set
|
||||
|
if(!_parentViewModel) |
||||
|
getParentViewModel(); |
||||
|
|
||||
|
// last step: call base implementation, then present
|
||||
|
QQuickItem::componentComplete(); |
||||
|
_isReady = true; |
||||
|
presentIfReady(); |
||||
|
} |
||||
|
|
||||
|
void QQmlViewPlaceholder::presentView() |
||||
|
{ |
||||
|
CoreApp::show(qUtf8Printable(_viewModelType), _showParams, _parentViewModel); |
||||
|
} |
||||
|
|
||||
|
void QQmlViewPlaceholder::discardView() |
||||
|
{ |
||||
|
// hide view
|
||||
|
_loadedView->setVisible(false); |
||||
|
|
||||
|
// show all children again
|
||||
|
for(auto child : childItems()) { |
||||
|
if(child != _loadedView) |
||||
|
child->setVisible(true); |
||||
|
} |
||||
|
|
||||
|
// now delete it
|
||||
|
_loadedView->deleteLater(); |
||||
|
_loadedView = nullptr; |
||||
|
emit loadedViewChanged(nullptr); |
||||
|
} |
||||
|
|
||||
|
void QQmlViewPlaceholder::parentItemVmChanged(ViewModel *viewModel) |
||||
|
{ |
||||
|
// set vm without clearing the connection
|
||||
|
_clearParentVmCon = false; |
||||
|
setParentViewModel(viewModel); |
||||
|
_clearParentVmCon = true; |
||||
|
} |
||||
|
|
||||
|
void QQmlViewPlaceholder::presentIfReady() |
||||
|
{ |
||||
|
if(_isReady && |
||||
|
_autoPresent && |
||||
|
!_loadedView && |
||||
|
_parentViewModel && |
||||
|
!_viewModelType.isEmpty()) |
||||
|
presentView(); |
||||
|
} |
||||
|
|
||||
|
void QQmlViewPlaceholder::resizeView() |
||||
|
{ |
||||
|
if(_loadedView && _autoResizeView) { |
||||
|
_loadedView->setWidth(width()); |
||||
|
_loadedView->setHeight(height()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void QQmlViewPlaceholder::getParentViewModel() |
||||
|
{ |
||||
|
auto pItem = parentItem(); |
||||
|
if(!pItem) |
||||
|
return; |
||||
|
|
||||
|
QQmlProperty vmProp{pItem, QStringLiteral("viewModel")}; |
||||
|
if(!vmProp.isValid()) |
||||
|
return; |
||||
|
|
||||
|
// set the vm from the property
|
||||
|
auto vm = vmProp.read().value<ViewModel*>(); |
||||
|
if(vm) |
||||
|
setParentViewModel(vm); // no warning here - might be ok for lazy presented vms
|
||||
|
|
||||
|
// connect to further changes, via helper slot
|
||||
|
auto cSlotIndex = metaObject()->indexOfSlot("parentItemVmChanged(QtMvvm::ViewModel*)"); |
||||
|
Q_ASSERT(cSlotIndex != -1); |
||||
|
if(_parentVmCon) |
||||
|
disconnect(_parentVmCon); |
||||
|
_parentVmCon = connect(pItem, vmProp.property().notifySignal(), |
||||
|
this, metaObject()->method(cSlotIndex)); |
||||
|
} |
@ -0,0 +1,77 @@ |
|||||
|
#ifndef QTMVVM_QQMLVIEWPLACEHOLDER_H |
||||
|
#define QTMVVM_QQMLVIEWPLACEHOLDER_H |
||||
|
|
||||
|
#include <QtQuick/QQuickItem> |
||||
|
|
||||
|
#include <QtMvvmCore/ViewModel> |
||||
|
|
||||
|
namespace QtMvvm { |
||||
|
|
||||
|
class QQmlViewPlaceholder : public QQuickItem |
||||
|
{ |
||||
|
Q_OBJECT |
||||
|
|
||||
|
Q_PROPERTY(QString viewModelType MEMBER _viewModelType NOTIFY viewModelTypeChanged) |
||||
|
Q_PROPERTY(QVariantHash showParams MEMBER _showParams NOTIFY showParamsChanged) |
||||
|
Q_PROPERTY(QtMvvm::ViewModel* parentViewModel MEMBER _parentViewModel WRITE setParentViewModel NOTIFY parentViewModelChanged) |
||||
|
Q_PROPERTY(bool autoPresent MEMBER _autoPresent NOTIFY autoPresentChanged) |
||||
|
Q_PROPERTY(bool autoResizeView MEMBER _autoResizeView NOTIFY autoResizeViewChanged) |
||||
|
Q_PROPERTY(bool replaceViews MEMBER _replaceViews NOTIFY replaceViewsChanged) |
||||
|
Q_PROPERTY(bool closeViewOnAction MEMBER _closeViewOnAction NOTIFY closeViewOnActionChanged) |
||||
|
|
||||
|
Q_PROPERTY(QQuickItem* loadedView READ loadedView NOTIFY loadedViewChanged) |
||||
|
|
||||
|
public: |
||||
|
explicit QQmlViewPlaceholder(QQuickItem *parent = nullptr); |
||||
|
|
||||
|
Q_INVOKABLE bool presentItem(const QVariant &item); |
||||
|
Q_INVOKABLE bool closeAction(); |
||||
|
|
||||
|
void setParentViewModel(ViewModel *parentViewModel); |
||||
|
QQuickItem* loadedView() const; |
||||
|
|
||||
|
void componentComplete() override; |
||||
|
|
||||
|
public Q_SLOTS: |
||||
|
void presentView(); |
||||
|
void discardView(); |
||||
|
|
||||
|
Q_SIGNALS: |
||||
|
void viewModelTypeChanged(const QString &viewModelType); |
||||
|
void showParamsChanged(const QVariantHash &showParams); |
||||
|
void parentViewModelChanged(QtMvvm::ViewModel* parentViewModel); |
||||
|
void autoPresentChanged(bool autoPresent); |
||||
|
void autoResizeViewChanged(bool autoResizeView); |
||||
|
void replaceViewsChanged(bool replaceViews); |
||||
|
void closeViewOnActionChanged(bool closeViewOnAction); |
||||
|
void loadedViewChanged(QQuickItem* loadedView); |
||||
|
|
||||
|
private Q_SLOTS: |
||||
|
void parentItemVmChanged(QtMvvm::ViewModel *viewModel); |
||||
|
void presentIfReady(); |
||||
|
|
||||
|
void resizeView(); |
||||
|
|
||||
|
private: |
||||
|
QString _viewModelType; |
||||
|
QVariantHash _showParams; |
||||
|
ViewModel *_parentViewModel = nullptr; |
||||
|
bool _autoPresent = true; |
||||
|
bool _autoResizeView = true; |
||||
|
bool _replaceViews = false; |
||||
|
bool _closeViewOnAction = false; |
||||
|
|
||||
|
QPointer<QQuickItem> _loadedView = nullptr; |
||||
|
|
||||
|
bool _isReady = false; |
||||
|
bool _clearParentVmCon = true; |
||||
|
QMetaObject::Connection _parentVmCon; |
||||
|
|
||||
|
void getParentViewModel(); |
||||
|
void connectSizeChanges(); |
||||
|
void disconnectSizeChanges(); |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
#endif // QTMVVM_QQMLVIEWPLACEHOLDER_H
|
Loading…
Reference in new issue