├── .gitignore ├── test ├── qrouter.qrc ├── pages │ ├── customdata.h │ ├── testpage1.h │ ├── testpage2.h │ ├── testpage1.cpp │ ├── testpage2.cpp │ ├── page1.ui │ └── page2.ui ├── main.cpp ├── qroutertest.h ├── CMakeLists.txt ├── qrouter.ui └── qroutertest.cpp ├── src ├── QRouterConfig.cmake.in ├── container │ ├── stackroutercontainer.cpp │ └── layoutroutercontainer.cpp ├── qrouter.cpp ├── CMakeLists.txt ├── abstractrouterwidget.cpp └── routerstackmanager.cpp ├── CMakeLists.txt ├── include ├── qrouterpageevent.h ├── qrouterexception.h ├── container │ ├── stackroutercontainer.h │ └── layoutroutercontainer.h ├── abstractroutercontainer.h ├── abstractrouterwidget.h ├── qrouter.h └── routerstackmanager.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | cmake-build* -------------------------------------------------------------------------------- /test/qrouter.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/QRouterConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | @_find_dependency_calls@ 5 | 6 | include("${CMAKE_CURRENT_LIST_DIR}/QRouterTargets.cmake") -------------------------------------------------------------------------------- /test/pages/customdata.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct CustomData { 6 | qint64 sendTime; 7 | QString message; 8 | int pageStackId; 9 | }; 10 | 11 | Q_DECLARE_METATYPE(CustomData); -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "qroutertest.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | QRouterTest w; 8 | w.show(); 9 | return a.exec(); 10 | } 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(qrouter) 4 | 5 | add_subdirectory(src) 6 | 7 | option(QROUTER_BUILD_TEST "enable the test project of qrouter" OFF) 8 | 9 | if (QROUTER_BUILD_TEST) 10 | add_subdirectory(test) 11 | endif() -------------------------------------------------------------------------------- /include/qrouterpageevent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class QRouterPageEvent : public QEvent { 6 | public: 7 | QRouterPageEvent(QString event, QVariant data) 8 | : QEvent(type) 9 | , event(std::move(event)) 10 | , data(std::move(data)) 11 | {} 12 | 13 | static QEvent::Type type; 14 | 15 | QString event; 16 | QVariant data; 17 | }; -------------------------------------------------------------------------------- /include/qrouterexception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class QRouterRuntimeException : public QException { 6 | public: 7 | explicit QRouterRuntimeException(QString message) 8 | : message(std::move(message)) 9 | {} 10 | 11 | void raise() const override { throw *this; } 12 | [[nodiscard]] QRouterRuntimeException* clone() const override { return new QRouterRuntimeException(*this); } 13 | 14 | QString message; 15 | }; -------------------------------------------------------------------------------- /test/qroutertest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_qrouter.h" 5 | 6 | class QRouterTest : public QWidget 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | QRouterTest(QWidget *parent = Q_NULLPTR); 12 | 13 | private: 14 | Ui::QRouterClass ui; 15 | 16 | private: 17 | void printPageStack(); 18 | 19 | protected: 20 | bool event(QEvent* event) override; 21 | bool eventFilter(QObject *watched, QEvent *event) override; 22 | }; 23 | -------------------------------------------------------------------------------- /test/pages/testpage1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "abstractrouterwidget.h" 4 | 5 | #include 6 | 7 | class TestPage1 : public AbstractRouterWidget { 8 | Q_OBJECT 9 | 10 | public: 11 | Q_INVOKABLE explicit TestPage1(const QVariant& data, QWidget* parent = nullptr); 12 | 13 | void onNavigateResult(const QVariant& data) override; 14 | 15 | private: 16 | Ui::TestPage1 ui; 17 | 18 | int pageStackId; 19 | 20 | static int typeId; 21 | 22 | protected: 23 | void runRouterEvent(const QString& event, const QVariant& data) override; 24 | }; 25 | 26 | Q_DECLARE_METATYPE(TestPage1*); -------------------------------------------------------------------------------- /include/container/stackroutercontainer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../abstractroutercontainer.h" 6 | #include "../abstractrouterwidget.h" 7 | 8 | class StackRouterContainer : public AbstractRouterContainer { 9 | public: 10 | explicit StackRouterContainer(QStackedWidget* stackedWidget); 11 | 12 | QWidget* childPageParent() override; 13 | 14 | QObject * rootPageEventReceiver() override; 15 | 16 | void setCurrentWidget(AbstractRouterWidget *widget) override; 17 | 18 | void removeWidget(AbstractRouterWidget *widget) override; 19 | 20 | private: 21 | QStackedWidget* container; 22 | }; -------------------------------------------------------------------------------- /test/pages/testpage2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "abstractrouterwidget.h" 4 | 5 | #include 6 | 7 | class TestPage2 : public AbstractRouterWidget { 8 | Q_OBJECT 9 | 10 | public: 11 | Q_INVOKABLE explicit TestPage2(const QVariant& data, QWidget* parent = nullptr); 12 | 13 | bool attemptClose() override; 14 | 15 | QVariant readAttemptCloseData() override; 16 | 17 | private: 18 | Ui::TestPage2 ui; 19 | 20 | int pageStackId; 21 | 22 | static int typeId; 23 | 24 | protected: 25 | void runRouterEvent(const QString& event, const QVariant& data) override; 26 | 27 | private: 28 | void popWithData(); 29 | }; 30 | 31 | Q_DECLARE_METATYPE(TestPage2*); -------------------------------------------------------------------------------- /include/container/layoutroutercontainer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../abstractroutercontainer.h" 6 | #include "../abstractrouterwidget.h" 7 | 8 | class LayoutRouterContainer : public AbstractRouterContainer { 9 | public: 10 | explicit LayoutRouterContainer(QBoxLayout* boxLayout); 11 | 12 | QWidget* childPageParent() override; 13 | 14 | QObject * rootPageEventReceiver() override; 15 | 16 | void setCurrentWidget(AbstractRouterWidget *widget) override; 17 | 18 | void removeWidget(AbstractRouterWidget* widget) override; 19 | 20 | virtual void removeAllWidget(); 21 | 22 | private: 23 | QBoxLayout* container; 24 | QList pageInstances; 25 | }; -------------------------------------------------------------------------------- /src/container/stackroutercontainer.cpp: -------------------------------------------------------------------------------- 1 | #include "container/stackroutercontainer.h" 2 | 3 | StackRouterContainer::StackRouterContainer(QStackedWidget *stackedWidget) 4 | : AbstractRouterContainer(stackedWidget) 5 | , container(stackedWidget) 6 | {} 7 | 8 | QWidget *StackRouterContainer::childPageParent() { 9 | return container; 10 | } 11 | 12 | QObject *StackRouterContainer::rootPageEventReceiver() { 13 | return container->parent(); 14 | } 15 | 16 | void StackRouterContainer::setCurrentWidget(AbstractRouterWidget *widget) { 17 | if (container->indexOf(widget) == -1) { 18 | container->addWidget(widget); 19 | } 20 | container->setCurrentWidget(widget); 21 | } 22 | 23 | void StackRouterContainer::removeWidget(AbstractRouterWidget *widget) { 24 | container->removeWidget(widget); 25 | } -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(qroutertest) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | set(CMAKE_AUTOMOC ON) 6 | set(CMAKE_AUTOUIC ON) 7 | set(CMAKE_AUTORCC ON) 8 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 9 | 10 | find_package(QT NAMES Qt6 Qt5 REQUIRED) 11 | find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets REQUIRED) 12 | 13 | add_executable(${PROJECT_NAME} WIN32 14 | main.cpp 15 | qrouter.qrc 16 | qrouter.ui 17 | qroutertest.h 18 | qroutertest.cpp 19 | 20 | pages/customdata.h 21 | pages/page1.ui 22 | pages/testpage1.h 23 | pages/testpage1.cpp 24 | pages/page2.ui 25 | pages/testpage2.h 26 | pages/testpage2.cpp 27 | ) 28 | 29 | target_link_libraries(${PROJECT_NAME} 30 | Qt${QT_VERSION_MAJOR}::Core 31 | Qt${QT_VERSION_MAJOR}::Gui 32 | Qt${QT_VERSION_MAJOR}::Widgets 33 | qrouter 34 | ) -------------------------------------------------------------------------------- /include/abstractroutercontainer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class AbstractRouterWidget; 6 | class AbstractRouterContainer : public QObject { 7 | public: 8 | explicit AbstractRouterContainer(QWidget* container): QObject(container) {} 9 | 10 | /** 11 | * get all child page of parent widget 12 | * @return 13 | */ 14 | virtual QWidget* childPageParent() = 0; 15 | 16 | /** 17 | * set event send target for 'postEventToRoot' 18 | * @return 19 | */ 20 | virtual QObject* rootPageEventReceiver() = 0; 21 | 22 | /** 23 | * set current display page widget 24 | * @param widget 25 | */ 26 | virtual void setCurrentWidget(AbstractRouterWidget* widget) = 0; 27 | 28 | /** 29 | * remove page widget from container 30 | * @param widget 31 | */ 32 | virtual void removeWidget(AbstractRouterWidget* widget) = 0; 33 | }; -------------------------------------------------------------------------------- /include/abstractrouterwidget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class AbstractRouterWidget : public QWidget { 7 | public: 8 | explicit AbstractRouterWidget(const QVariant& data, QWidget* parent); 9 | 10 | virtual void onNavigateResult(const QVariant& data); 11 | 12 | virtual bool attemptClose(); 13 | 14 | virtual QVariant readAttemptCloseData(); 15 | 16 | QVariant onRouterEvent(const QString& event, const QVariant& data); 17 | 18 | virtual bool singletonInstance(); 19 | 20 | private: 21 | QVariant navigateData; 22 | bool firstShow; 23 | 24 | protected: 25 | QVariant getNavigateData(bool clear = true); 26 | 27 | virtual void runRouterEvent(const QString& event, const QVariant& data); 28 | 29 | virtual QVariant executeRouterEvent(const QString& event, const QVariant& data); 30 | 31 | void showEvent(QShowEvent* event) override; 32 | void resizeEvent(QResizeEvent* event) override; 33 | void hideEvent(QHideEvent* event) override; 34 | 35 | bool event(QEvent* event) override; 36 | 37 | virtual void onViewLoad(); 38 | virtual void onReshow(); 39 | virtual void onHidden(); 40 | virtual void onResizeWindow(); 41 | }; -------------------------------------------------------------------------------- /src/qrouter.cpp: -------------------------------------------------------------------------------- 1 | #include "qrouter.h" 2 | 3 | QRouter QRouter::router; 4 | 5 | QRouter::QRouter() 6 | {} 7 | 8 | QRouter &QRouter::install(AbstractRouterContainer *container, int contextId) { 9 | RouterStackManager manager; 10 | manager.container = container; 11 | router.managers.insert(contextId, manager); 12 | return router; 13 | } 14 | 15 | QRouter& QRouter::install(QStackedWidget* stackContainer, int contextId) { 16 | return install(new StackRouterContainer(stackContainer), contextId); 17 | } 18 | 19 | QRouter &QRouter::install(QBoxLayout *boxLayout, int contextId) { 20 | return install(new LayoutRouterContainer(boxLayout), contextId); 21 | } 22 | 23 | RouterStackManager& QRouter::of(int contextId) { 24 | if (!router.managers.contains(contextId)) { 25 | throw QRouterRuntimeException(QString("cannot find container with context id:%1, the container may not be installed.").arg(contextId)); 26 | } 27 | return router.managers[contextId]; 28 | } 29 | 30 | int QRouter::getIdByContainer(QWidget *container) { 31 | for (auto i = router.managers.begin(); i != router.managers.end(); ++i) { 32 | if (i.value().container->parent() == container) { 33 | return i.key(); 34 | } 35 | } 36 | return -1; 37 | } -------------------------------------------------------------------------------- /src/container/layoutroutercontainer.cpp: -------------------------------------------------------------------------------- 1 | #include "container/layoutroutercontainer.h" 2 | 3 | LayoutRouterContainer::LayoutRouterContainer(QBoxLayout *boxLayout) 4 | : AbstractRouterContainer(boxLayout->parentWidget()) 5 | , container(boxLayout) 6 | {} 7 | 8 | QWidget *LayoutRouterContainer::childPageParent() { 9 | return container->parentWidget(); 10 | } 11 | 12 | QObject *LayoutRouterContainer::rootPageEventReceiver() { 13 | return container->parentWidget(); 14 | } 15 | 16 | void LayoutRouterContainer::setCurrentWidget(AbstractRouterWidget *widget) { 17 | if (!pageInstances.contains(widget)) { 18 | pageInstances.append(widget); 19 | } 20 | removeAllWidget(); 21 | widget->setVisible(true); 22 | container->addWidget(widget); 23 | } 24 | 25 | void LayoutRouterContainer::removeWidget(AbstractRouterWidget *widget) { 26 | if (pageInstances.contains(widget)) { 27 | pageInstances.removeOne(widget); 28 | } 29 | if (auto item = container->itemAt(0)) { 30 | if (item->widget() == widget) { 31 | removeAllWidget(); 32 | } 33 | } 34 | } 35 | 36 | void LayoutRouterContainer::removeAllWidget() { 37 | if (auto item = container->takeAt(0)) { 38 | item->widget()->setVisible(false); 39 | delete item; 40 | } 41 | } -------------------------------------------------------------------------------- /test/pages/testpage1.cpp: -------------------------------------------------------------------------------- 1 | #include "testpage1.h" 2 | 3 | #include "qrouter.h" 4 | 5 | #include "customdata.h" 6 | 7 | #include 8 | 9 | int TestPage1::typeId = qRegisterMetaType(); 10 | 11 | TestPage1::TestPage1(const QVariant& data, QWidget* parent) 12 | : AbstractRouterWidget(data, parent) 13 | { 14 | ui.setupUi(this); 15 | 16 | pageStackId = data.toInt(); 17 | 18 | connect(ui.btn_push, &QPushButton::clicked, [&] { 19 | CustomData data; 20 | data.sendTime = QDateTime::currentMSecsSinceEpoch(); 21 | data.message = "send data from page1 to page2"; 22 | data.pageStackId = pageStackId; 23 | QRouter::of(pageStackId).push("TestPage2", QVariant::fromValue(data)); 24 | }); 25 | } 26 | 27 | void TestPage1::onNavigateResult(const QVariant& data) { 28 | 29 | auto value = data.value(); 30 | 31 | ui.data_from_pop->setText( 32 | QString("from %1 to %2, msg: <%3>") 33 | .arg(QDateTime::fromMSecsSinceEpoch(value.sendTime).toString("HH:mm:ss.zzz")) 34 | .arg(QDateTime::currentDateTime().toString("HH:mm:ss.zzz")) 35 | .arg(value.message) 36 | ); 37 | } 38 | 39 | void TestPage1::runRouterEvent(const QString& event, const QVariant& data) { 40 | if (event == "eventtest") { 41 | QRouter::of(pageStackId).postEventToRoot("printevent", "test page1 receive event!"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(qrouter VERSION 0.0.8) 4 | 5 | set(CMAKE_AUTOMOC ON) 6 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 7 | 8 | find_package(QT NAMES Qt6 Qt5 REQUIRED) 9 | find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets REQUIRED) 10 | 11 | set(CMAKE_DEBUG_POSTFIX "d") 12 | 13 | add_library(${PROJECT_NAME} 14 | abstractrouterwidget.cpp 15 | qrouter.cpp 16 | routerstackmanager.cpp 17 | 18 | container/layoutroutercontainer.cpp 19 | container/stackroutercontainer.cpp 20 | ) 21 | 22 | target_link_libraries(${PROJECT_NAME} 23 | Qt${QT_VERSION_MAJOR}::Core 24 | Qt${QT_VERSION_MAJOR}::Gui 25 | Qt${QT_VERSION_MAJOR}::Widgets 26 | ) 27 | 28 | target_include_directories(${PROJECT_NAME} PUBLIC 29 | $ 30 | $ 31 | ) 32 | 33 | include(CMakePackageConfigHelpers) 34 | 35 | install(TARGETS ${PROJECT_NAME} 36 | EXPORT QRouterTargets 37 | LIBRARY DESTINATION lib 38 | ARCHIVE DESTINATION lib 39 | RUNTIME DESTINATION bin 40 | INCLUDES DESTINATION include 41 | ) 42 | 43 | install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/ 44 | DESTINATION include 45 | FILES_MATCHING PATTERN "*.h" 46 | ) 47 | 48 | configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/QRouterConfig.cmake.in 49 | "${CMAKE_CURRENT_BINARY_DIR}/QRouterConfig.cmake" 50 | INSTALL_DESTINATION lib/cmake/QRouter 51 | ) 52 | 53 | install(FILES 54 | "${CMAKE_CURRENT_BINARY_DIR}/QRouterConfig.cmake" 55 | DESTINATION lib/cmake/QRouter 56 | ) 57 | 58 | install(EXPORT QRouterTargets 59 | FILE QRouterTargets.cmake 60 | DESTINATION lib/cmake/QRouter 61 | ) -------------------------------------------------------------------------------- /test/pages/testpage2.cpp: -------------------------------------------------------------------------------- 1 | #include "testpage2.h" 2 | 3 | #include "qrouter.h" 4 | 5 | #include "customdata.h" 6 | 7 | #include 8 | #include 9 | 10 | int TestPage2::typeId = qRegisterMetaType(); 11 | 12 | TestPage2::TestPage2(const QVariant& data, QWidget* parent) 13 | : AbstractRouterWidget(data, parent) { 14 | ui.setupUi(this); 15 | 16 | auto value = data.value(); 17 | pageStackId = value.pageStackId; 18 | 19 | ui.data_from_last_page->setText( 20 | QString("from %1 to %2, msg: <%3>") 21 | .arg(QDateTime::fromMSecsSinceEpoch(value.sendTime).toString("HH:mm:ss.zzz")) 22 | .arg(QDateTime::currentDateTime().toString("HH:mm:ss.zzz")) 23 | .arg(value.message) 24 | ); 25 | 26 | connect(ui.btn_pop, &QPushButton::clicked, this, &TestPage2::popWithData); 27 | } 28 | 29 | bool TestPage2::attemptClose() { 30 | if (ui.block_pop->isChecked()) { 31 | QMessageBox::warning(0, "warning", "page close blocked!"); 32 | return false; 33 | } 34 | return true; 35 | } 36 | 37 | QVariant TestPage2::readAttemptCloseData() { 38 | CustomData data; 39 | data.sendTime = QDateTime::currentMSecsSinceEpoch(); 40 | data.message = "close page2"; 41 | return QVariant::fromValue(data); 42 | } 43 | 44 | void TestPage2::runRouterEvent(const QString& event, const QVariant& data) { 45 | if (event == "eventtest") { 46 | QRouter::of(pageStackId).postEventToRoot("printevent", "test page2 receive event!"); 47 | } 48 | } 49 | 50 | void TestPage2::popWithData() { 51 | if (ui.pop_data_extra->isChecked()) { 52 | auto d = readAttemptCloseData().value(); 53 | d.message += " with extra"; 54 | QRouter::of(pageStackId).pop(QVariant::fromValue(d)); 55 | } else { 56 | QRouter::of(pageStackId).pop(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/pages/page1.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TestPage1 4 | 5 | 6 | 7 | 0 8 | 0 9 | 799 10 | 433 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | this is page1 21 | 22 | 23 | 24 | 25 | 26 | 27 | test send value page to page 28 | 29 | 30 | 31 | 32 | 33 | 34 | push with data 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | receive from pop page: 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Qt::Vertical 60 | 61 | 62 | 63 | 20 64 | 327 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/abstractrouterwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "abstractrouterwidget.h" 2 | 3 | #include "qrouter.h" 4 | 5 | AbstractRouterWidget::AbstractRouterWidget(const QVariant& data, QWidget* parent) 6 | : QWidget(parent) 7 | , navigateData(data) 8 | , firstShow(true) 9 | {} 10 | 11 | void AbstractRouterWidget::onNavigateResult(const QVariant&) { 12 | } 13 | 14 | bool AbstractRouterWidget::attemptClose() { 15 | return true; 16 | } 17 | 18 | QVariant AbstractRouterWidget::readAttemptCloseData() { 19 | return QVariant(); 20 | } 21 | 22 | QVariant AbstractRouterWidget::onRouterEvent(const QString& event, const QVariant& data) { 23 | runRouterEvent(event, data); 24 | 25 | return executeRouterEvent(event, data); 26 | } 27 | 28 | bool AbstractRouterWidget::singletonInstance() { 29 | return false; 30 | } 31 | 32 | QVariant AbstractRouterWidget::getNavigateData(bool clear) { 33 | auto data = navigateData; 34 | if (clear) { 35 | navigateData = QVariant(); 36 | } 37 | return data; 38 | } 39 | 40 | void AbstractRouterWidget::runRouterEvent(const QString&, const QVariant&) { 41 | } 42 | 43 | QVariant AbstractRouterWidget::executeRouterEvent(const QString&, const QVariant&) { 44 | return QVariant(); 45 | } 46 | 47 | void AbstractRouterWidget::showEvent(QShowEvent*) { 48 | if (firstShow) { 49 | onViewLoad(); 50 | firstShow = false; 51 | } else { 52 | onReshow(); 53 | } 54 | } 55 | 56 | void AbstractRouterWidget::resizeEvent(QResizeEvent*) { 57 | onResizeWindow(); 58 | } 59 | 60 | void AbstractRouterWidget::hideEvent(QHideEvent*) { 61 | onHidden(); 62 | } 63 | 64 | bool AbstractRouterWidget::event(QEvent* event) { 65 | if (event->type() == QRouterPageEvent::type) { 66 | auto e = static_cast(event); 67 | runRouterEvent(e->event, e->data); 68 | return true; 69 | } 70 | return QWidget::event(event); 71 | } 72 | 73 | 74 | void AbstractRouterWidget::onViewLoad() { 75 | } 76 | 77 | void AbstractRouterWidget::onReshow() { 78 | } 79 | 80 | void AbstractRouterWidget::onHidden() { 81 | } 82 | 83 | void AbstractRouterWidget::onResizeWindow() { 84 | } 85 | -------------------------------------------------------------------------------- /include/qrouter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "qrouterexception.h" 7 | #include "qrouterpageevent.h" 8 | #include "routerstackmanager.h" 9 | 10 | #include "container/stackroutercontainer.h" 11 | #include "container/layoutroutercontainer.h" 12 | 13 | class QRouter { 14 | public: 15 | /** 16 | * install a page stack container for 'contextId' 17 | * @param container page stack container 18 | * @param contextId page stack id 19 | * @return 20 | */ 21 | static QRouter& install(AbstractRouterContainer* container, int contextId = 0); 22 | 23 | /** 24 | * Use the specified 'stackedWidget' to manage page stack 25 | * @param stackContainer page stack container 26 | * @param contextId page stack id 27 | * @return 28 | */ 29 | static QRouter& install(QStackedWidget* stackContainer, int contextId = 0); 30 | 31 | /** 32 | * Use the specified 'layout' to manage page stack 33 | * @param boxLayout page stack container 34 | * @param contextId page stack id 35 | * @return 36 | */ 37 | static QRouter& install(QBoxLayout* boxLayout, int contextId = 0); 38 | 39 | /** 40 | * Get page stack manager by id 41 | * @param contextId 42 | * @return 43 | */ 44 | static RouterStackManager& of(int contextId = 0); 45 | 46 | /** 47 | * Get current top page instance 48 | * @tparam T instance type 49 | * @param contextId page id 50 | * @return 51 | */ 52 | template 53 | static T* currentInstance(int contextId = 0) { 54 | return dynamic_cast(QRouter::of(contextId).current()); 55 | } 56 | 57 | /** 58 | * Get instance by name in current page stack, return null if not exist 59 | * @tparam T instance type 60 | * @param contextId page id 61 | * @return 62 | */ 63 | template 64 | static T* getInstance(int contextId = 0) { 65 | return dynamic_cast(QRouter::of(contextId).getInstanceFromStack(T::staticMetaObject.className())); 66 | } 67 | 68 | /** 69 | * Get page stack id of the specified container 70 | * @param container container instance 71 | * @return -1 if not register by 'install' 72 | */ 73 | static int getIdByContainer(QWidget* container); 74 | 75 | private: 76 | QRouter(); 77 | 78 | private: 79 | static QRouter router; 80 | 81 | QHash managers; 82 | }; -------------------------------------------------------------------------------- /test/pages/page2.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TestPage2 4 | 5 | 6 | 7 | 0 8 | 0 9 | 728 10 | 417 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | this is page2 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | receive from last page data: 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | pop with data 48 | 49 | 50 | 51 | 52 | 53 | 54 | block pop 55 | 56 | 57 | 58 | 59 | 60 | 61 | pop data extra 62 | 63 | 64 | 65 | 66 | 67 | 68 | Qt::Horizontal 69 | 70 | 71 | 72 | 40 73 | 20 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Qt::Vertical 84 | 85 | 86 | 87 | 20 88 | 40 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /test/qrouter.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | QRouterClass 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1064 10 | 958 11 | 12 | 13 | 14 | QRouter 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | send event to page1 23 | 24 | 25 | 26 | 27 | 28 | 29 | send event to page current 30 | 31 | 32 | 33 | 34 | 35 | 36 | send event to all page 37 | 38 | 39 | 40 | 41 | 42 | 43 | post method 44 | 45 | 46 | 47 | 48 | 49 | 50 | Qt::Horizontal 51 | 52 | 53 | 54 | 40 55 | 20 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | stack widget: 66 | 67 | 68 | 69 | 70 | 71 | 72 | QStackedWidget{border:1px solid red;} 73 | 74 | 75 | 76 | 77 | 78 | 79 | widget: 80 | 81 | 82 | 83 | 84 | 85 | 86 | #widget{ 87 | border:1px solid red; 88 | } 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /test/qroutertest.cpp: -------------------------------------------------------------------------------- 1 | #include "qroutertest.h" 2 | 3 | #include "qrouter.h" 4 | 5 | #include "pages/customdata.h" 6 | 7 | #include "pages/testpage1.h" 8 | 9 | #include 10 | #include 11 | 12 | QRouterTest::QRouterTest(QWidget *parent) 13 | : QWidget(parent) 14 | { 15 | ui.setupUi(this); 16 | ui.widget->installEventFilter(this); 17 | 18 | QRouter::install(ui.stackedWidget, 0); 19 | QRouter::install(ui.container_layout, 1); 20 | 21 | connect(ui.stackedWidget, &QStackedWidget::currentChanged, this, &QRouterTest::printPageStack); 22 | 23 | QRouter::of(0).push("TestPage1", 0); 24 | QRouter::of(1).push("TestPage1", 1); 25 | 26 | //test send event 27 | connect(ui.btn_send1, &QPushButton::clicked, [&] { 28 | if (ui.send_by_post->isChecked()) { 29 | auto ptr = QRouter::getInstance(); 30 | Q_ASSERT(ptr != nullptr); 31 | QRouter::of(0).postEventTo("TestPage1", "eventtest"); 32 | QRouter::of(1).postEventTo("TestPage1", "eventtest"); 33 | } else { 34 | QRouter::of(0).sendEventTo("TestPage1", "eventtest"); 35 | QRouter::of(1).sendEventTo("TestPage1", "eventtest"); 36 | } 37 | }); 38 | 39 | connect(ui.btn_send_cur, &QPushButton::clicked, [&] { 40 | if (ui.send_by_post->isChecked()) { 41 | QRouter::of(0).postEventCur("eventtest"); 42 | QRouter::of(1).postEventCur("eventtest"); 43 | } else { 44 | QRouter::of(0).sendEventCur("eventtest"); 45 | QRouter::of(1).sendEventCur("eventtest"); 46 | } 47 | }); 48 | 49 | connect(ui.btn_send_all, &QPushButton::clicked, [&] { 50 | if (ui.send_by_post->isChecked()) { 51 | QRouter::of(0).postEventAll("eventtest"); 52 | QRouter::of(1).postEventAll("eventtest"); 53 | } else { 54 | QRouter::of(0).sendEventAll("eventtest"); 55 | QRouter::of(1).sendEventAll("eventtest"); 56 | } 57 | }); 58 | } 59 | 60 | void QRouterTest::printPageStack() { 61 | QString stackString; 62 | QDebug(&stackString) << QRouter::of().readStack(); 63 | 64 | ui.log_view->append(QString("[%1] page stack: %2\n") 65 | .arg(QDateTime::currentDateTime().toString("HH:mm:ss.zzz"), stackString)); 66 | } 67 | 68 | bool QRouterTest::event(QEvent* event) { 69 | if (event->type() == QRouterPageEvent::type) { 70 | ui.log_view->append(QString("[%1] receive router event: %2\n") 71 | .arg(QDateTime::currentDateTime().toString("HH:mm:ss.zzz"), 72 | dynamic_cast(event)->data.toString())); 73 | return true; 74 | } 75 | return QWidget::event(event); 76 | } 77 | 78 | bool QRouterTest::eventFilter(QObject *watched, QEvent *event) { 79 | if (watched == ui.widget) { 80 | if (event->type() == QRouterPageEvent::type) { 81 | ui.log_view->append(QString("[%1][layout base] receive router event: %2\n") 82 | .arg(QDateTime::currentDateTime().toString("HH:mm:ss.zzz"), 83 | dynamic_cast(event)->data.toString())); 84 | return true; 85 | } 86 | } 87 | return QObject::eventFilter(watched, event); 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qrouter 2 | 这是一个使用QStackedWidget进行子页面切换的路由框架,基本原理基于qt的反射实现解耦合,使用QList保存页面栈,页面之间通信通过QRouter转发 3 | 4 | ## 如何使用 5 | ### 1. 编写一个page类继承于AbstractRouterWidget,并使这个类可反射 6 | ```cpp 7 | //YourPage.h 8 | class YourPage : public AbstractRouterWidget { 9 | Q_OBJECT 10 | 11 | public: 12 | Q_INVOKABLE explicit YourPage(const QVariant& data, QWidget* parent = nullptr); 13 | 14 | //optional, navigate page closed with data 15 | void onNavigateResult(const QVariant& data) override; 16 | 17 | //optional, be called when attemp close this page 18 | bool attempClose() override; 19 | 20 | protected: 21 | //optional, receive event from other page 22 | void runRouterEvent(const QString& event, const QVariant& data) override; 23 | 24 | //optional, receive event from other page with return data 25 | QVariant executeRouterEvent(const QString& event, const QVariant& data) override; 26 | 27 | //optional, showEvent first be called 28 | void onViewLoad(); 29 | 30 | //optional, showEvent called at other time 31 | void onReshow(); 32 | 33 | //optional, same as hideEvent 34 | void onHidden(); 35 | 36 | //optional, same as resizeEvent 37 | void onResizeWindow(); 38 | 39 | private: 40 | Ui::YourPageUi ui; 41 | 42 | //reflect type id 43 | static int typeId; 44 | } 45 | //register to meta-object system 46 | Q_DECLARE_METATYPE(YourPage*); 47 | 48 | 49 | //YourPage.cpp 50 | 51 | //register meta type 52 | int YourPage::typeId = qRegisterMetaType(); 53 | 54 | YourPage::YourPage(const QVariant& data, QWidget* parent) 55 | : YourPage(data, parent) 56 | { 57 | ui.setupUi(this); 58 | } 59 | 60 | ``` 61 | 可选重载函数说明 62 | - `virtual void onNavigateResult(const QVariant& data)` 63 | 当跳到的目标界面关闭时并携带数据,这个函数将被调用用于接收返回数据`data`,`data`可以传递任何类型数据 64 | 65 | - `virtual bool attempClose()` 66 | qrouter尝试关闭当前页面时,可以重载此函数阻止页面切换,返回false阻止关闭 67 | 68 | - `virtual void runRouterEvent(const QString& event, const QVariant& data)` 69 | 接收其他页面发送的消息,`event`为事件名称,`data`为传递的数据 70 | 71 | - `QVariant executeRouterEvent(const QString& event, const QVariant& data)` 72 | 等同于`runRouterEvent`,可额外返回数据到发送者 73 | 74 | - `virtual void onViewLoad()` 75 | 当页面第一次界面加载完时调用,等同于`showEvent`第一次被调用 76 | 77 | - `virtual void onReshow()` 78 | 除了第一次调用,其他时候`showEvent`被调用 79 | 80 | - `void onHidden()` 81 | 等同于`hideEvent` 82 | 83 | - `void onResizeWindow()` 84 | 等同于`resizeEvent` 85 | 86 | ### 2. 注册页面容器 87 | ```cpp 88 | QRouter::install(ui.stackedWidget, 0); 89 | ``` 90 | - `static QRouter& install(QStackedWidget* stackContainer, int contextId = 0)` 91 | `stackContainer`为用于切换的页面容器,`contextId`为当前页面栈id 92 | 93 | ### 3. 如何使用qrouter切换页面 94 | ```cpp 95 | CustomData data; 96 | data.message = "send data from page1 to page2"; 97 | 98 | //初始化并创建页面栈 99 | QRouter::of().initStack({"Page1", "Page2"}); 100 | 101 | //从页面1跳转到page2,"Page2"为页面2的类名,of函数传递页面栈id,默认id为0的页面栈 102 | QRouter::of().push("Page2", QVariant::fromValue(data)); 103 | 104 | //从页面栈移除当前页面1并跳转到页面2 105 | QRouter::of().pushReplace("Page2", QVariant::fromValue(data)); 106 | 107 | //创建新页面page2并入栈,如果page2已经存在,则移动到栈顶 108 | QRouter::of().pushOrMove2Top("Page2", QVariant::fromValue(data)); 109 | 110 | //尝试从页面栈移除当前页面1(如果可以关闭页面1)并跳转到页面2 111 | QRouter::of().popAndPush("Page2", QVariant::fromValue(data)); 112 | 113 | //关闭指定页面2,页面2不一定在栈顶 114 | QRouter::of().close("Page2"); 115 | 116 | //清除当前页面栈并跳转到页面2 117 | QRouter::of().pushAndClear("Page2", QVariant::fromValue(data)); 118 | 119 | //将页面2移动到栈顶 120 | QRouter::of().move2Top("Page2"); 121 | 122 | //关闭当前页面,并向上个页面传递数据 123 | QRouter::of().pop(QVariant::fromValue(data)); 124 | 125 | //关闭页面栈Page2上面的所有页面 126 | QRouter::of().popUntil("Page2", QVariant::fromValue(data)); 127 | 128 | //页面栈只保留4个页面,并关闭栈之上的所有页面 129 | QRouter::of().popUntil(4); 130 | 131 | //其他功能函数 132 | 133 | //读取当前页面栈所有页面名称 134 | QRouter::of().readStack(); 135 | 136 | //获取当前栈顶页面名 137 | QRouter::of().currentName(); 138 | 139 | //获取当前栈栈顶实例 140 | QRouter::of().current(); 141 | QRouter::currentInstance(); 142 | 143 | //从当前页面栈中获取指定页面实例 144 | QRouter::of().getInstanceFromStack("MyPage"); 145 | QRouter::getInstance("MyPage"); 146 | 147 | //获取页面栈容器的id 148 | QRouter::getIdByContainer(ui.page_container); 149 | ``` 150 | 151 | ### 4. 发送事件 152 | ```cpp 153 | //同步发送事件到当前页面(页面栈顶) 154 | QRouter::of().sendEventCur("eventName"); 155 | 156 | //同步发送事件到Page1 157 | QRouter::of().sendEventTo("Page1", "eventName"); 158 | 159 | //同步发送事件到当前页面栈所有页面 160 | QRouter::of().sendEventAll("eventName"); 161 | 162 | //发送事件到当前页面,使用事件队列 163 | QRouter::of().postEventCur("eventName"); 164 | 165 | //发送事件到Page1,使用事件队列 166 | QRouter::of().postEventTo("Page1", "eventName"); 167 | 168 | //发送事件到当前页面栈所有页面,使用事件队列 169 | QRouter::of().postEventAll("eventName"); 170 | 171 | //发送事件到容器父页面 172 | QRouter::of().postEventToRoot("eventName"); 173 | 174 | //容器所在父页面接收子页面发送的事件 175 | bool event(QEvent* event) override { 176 | if (event->type() == QRouterPageEvent::type) { 177 | //do something... 178 | return true; 179 | } 180 | return QWidget::event(event); 181 | } 182 | 183 | //父页面必须是stackWidget的parent,否则使用eventFilter接收事件 184 | ui.page_container->parent()->installEventFilter(this); 185 | bool eventFilter(QObject *watched, QEvent *event) override { 186 | if (event->type() == QRouterPageEvent::type) { 187 | if (watched == ui.page_container->parent()) { 188 | //do something... 189 | } 190 | return true; 191 | } 192 | return QWidget::eventFilter(watched, event); 193 | } 194 | ``` -------------------------------------------------------------------------------- /include/routerstackmanager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "abstractroutercontainer.h" 7 | 8 | class AbstractRouterWidget; 9 | class RouterStackManager { 10 | public: 11 | /////////////////////////////// base information /////////////////////////////// 12 | /** 13 | * Get all current page stack names 14 | * @return names 15 | */ 16 | QStringList readStack(); 17 | 18 | /** 19 | * Get current top page name 20 | * @return 21 | */ 22 | QString currentName(); 23 | 24 | /** 25 | * Get current top page instance 26 | * @return 27 | */ 28 | AbstractRouterWidget* current(); 29 | 30 | /** 31 | * Get all page instance from current page stack 32 | * @return 33 | */ 34 | QList currentInstances(); 35 | 36 | /** 37 | * Get instance by name in current page stack, return null if not exist 38 | * @param pageClassName 39 | * @return 40 | */ 41 | AbstractRouterWidget* getInstanceFromStack(const QByteArray& pageClassName); 42 | 43 | /** 44 | * Get instance by stack index in current page stack, return null if out of range 45 | * @param stackIndex 46 | * @return 47 | */ 48 | AbstractRouterWidget* getInstanceFromStack(int stackIndex); 49 | 50 | /////////////////////////////// stack operation /////////////////////////////// 51 | 52 | /** 53 | * Initialize the page stack by page names 54 | * @param pages 55 | */ 56 | void initStack(const QList& pages); 57 | 58 | /** 59 | * Create a page instance and push it into the current page stack 60 | * @param pageClassName 61 | * @param data data for new page instance 62 | */ 63 | void push(const QByteArray& pageClassName, const QVariant& data = QVariant()); 64 | 65 | /** 66 | * Create a page instance and replace the top one on the current page stack 67 | * @param pageClassName 68 | * @param data data for new page instance 69 | */ 70 | void pushReplace(const QByteArray& pageClassName, const QVariant& data = QVariant()); 71 | 72 | /** 73 | * If the page does not exist in the current page stack, create a page instance and push it into the current stack, 74 | * otherwise move the page to the top of the stack 75 | * @param pageClassName 76 | * @param data data for the created page instance 77 | * @return true if the page instance exist in stack 78 | */ 79 | bool pushOrMove2Top(const QByteArray& pageClassName, const QVariant& data = QVariant()); 80 | 81 | /** 82 | * Remove the top page from the stack and push the new page into the current stack 83 | * @param pageClassName 84 | * @param data data for new page instance 85 | */ 86 | void popAndPush(const QByteArray& pageClassName, const QVariant& data = QVariant()); 87 | 88 | /** 89 | * Close the specified page (if it exists) 90 | * @param pageClassName the specified page 91 | */ 92 | void close(const QByteArray& pageClassName); 93 | 94 | /** 95 | * Clear current page stack, create a new page and push into the stack 96 | * @param pageClassName 97 | * @param data data for new page instance 98 | */ 99 | void pushAndClear(const QByteArray& pageClassName, const QVariant& data = QVariant()); 100 | 101 | /** 102 | * Move the specified page to top of the page stack 103 | * @param pageClassName the specified page 104 | * @return true if the specified page exist in page stack 105 | */ 106 | bool move2Top(const QByteArray& pageClassName); 107 | 108 | /** 109 | * Pop up the current page and return to the previous page 110 | * @param data data for the previous page (receive data by 'onNavigateResult') 111 | */ 112 | void pop(QVariant data = QVariant()); 113 | 114 | /** 115 | * Pop up the page until the specified page 116 | * @param untilName 117 | */ 118 | void popUntil(const QByteArray& untilName); 119 | 120 | /** 121 | * Pop up the page until 'stackSize' of the current page stack 122 | * @param stackSize 123 | */ 124 | void popUntil(int stackSize); 125 | 126 | /////////////////////////////// event operation /////////////////////////////// 127 | 128 | /** 129 | * Send a event to current page of the top of page stack 130 | * @param event event name 131 | * @param data data for current page, receive data by 'runRouterEvent' (no data to feedback) or 'executeRouterEvent' 132 | * @param ignoreContainerNotInstalled ignore send event when the current context id of container is not installed 133 | * @return processed data of current page 134 | */ 135 | QVariant sendEventCur(const QString& event, const QVariant& data = QVariant(), bool ignoreContainerNotInstalled = true); 136 | 137 | /** 138 | * Send a event to the specified page 139 | * @param pageClassName the specified page name 140 | * @param event event name 141 | * @param data data for current the specified page, receive data by 'runRouterEvent' (no data to feedback) or 'executeRouterEvent' 142 | * @param ignoreContainerNotInstalled ignore send event when the current context id of container is not installed 143 | * @return processed data of the specified page 144 | */ 145 | QVariant sendEventTo(const QByteArray& pageClassName, const QString& event, const QVariant& data = QVariant(), bool ignoreContainerNotInstalled = true); 146 | 147 | /** 148 | * Send a event to all page of the current page stack 149 | * @param event event name 150 | * @param data data for all page, receive data by 'runRouterEvent' 151 | * @param ignoreContainerNotInstalled ignore send event when the current context id of container is not installed 152 | */ 153 | void sendEventAll(const QString& event, const QVariant& data = QVariant(), bool ignoreContainerNotInstalled = true); 154 | 155 | /** 156 | * Post a event to current page of the top of page stack 157 | * @param event event name 158 | * @param data data for current page, receive data by 'runRouterEvent' 159 | * @param ignoreContainerNotInstalled ignore send event when the current context id of container is not installed 160 | */ 161 | void postEventCur(const QString& event, const QVariant& data = QVariant(), bool ignoreContainerNotInstalled = true); 162 | 163 | /** 164 | * Post a event to the specified page 165 | * @param pageClassName the specified page name 166 | * @param event event name 167 | * @param data data for current the specified page, receive data by 'runRouterEvent' 168 | * @param ignoreContainerNotInstalled ignore send event when the current context id of container is not installed 169 | */ 170 | void postEventTo(const QByteArray& pageClassName, const QString& event, const QVariant& data = QVariant(), bool ignoreContainerNotInstalled = true); 171 | 172 | /** 173 | * Post a event to the container parent widget (stackWidget->parent()) 174 | * @param event event name 175 | * @param data data for container parent, receive data by override 'bool event(QEvent* event)' 176 | * @param ignoreContainerNotInstalled ignore send event when the current context id of container is not installed 177 | */ 178 | void postEventToRoot(const QString& event, const QVariant& data = QVariant(), bool ignoreContainerNotInstalled = true); 179 | 180 | /** 181 | * Post a event to all page of the current page stack 182 | * @param event event name 183 | * @param data data for all page, receive data by 'runRouterEvent' 184 | * @param ignoreContainerNotInstalled ignore send event when the current context id of container is not installed 185 | */ 186 | void postEventAll(const QString& event, const QVariant& data = QVariant(), bool ignoreContainerNotInstalled = true); 187 | 188 | /////////////////////////////// transition animation operation /////////////////////////////// 189 | 190 | //RouterStackManager& alpha(int duration = 300, const QEasingCurve& easingCurve = QEasingCurve::OutCubic); 191 | //RouterStackManager& translate(int duration = 300, const QEasingCurve& easingCurve = QEasingCurve::OutCubic); 192 | //RouterStackManager& scale(int duration = 300, const QEasingCurve& easingCurve = QEasingCurve::OutCubic); 193 | //RouterStackManager& rotate(int duration = 300, const QEasingCurve& easingCurve = QEasingCurve::OutCubic); 194 | 195 | private: 196 | AbstractRouterContainer* container = nullptr; 197 | QList stack; 198 | 199 | QHash keepSingletonPageInstance; 200 | 201 | friend class QRouter; 202 | 203 | private: 204 | static AbstractRouterWidget* reflectByName(const QByteArray& className, QWidget* parent, const QVariant& data); 205 | 206 | void removePageInstance(AbstractRouterWidget* widget); 207 | }; -------------------------------------------------------------------------------- /src/routerstackmanager.cpp: -------------------------------------------------------------------------------- 1 | #include "routerstackmanager.h" 2 | 3 | #include "abstractrouterwidget.h" 4 | #include "qrouterexception.h" 5 | #include "qrouterpageevent.h" 6 | 7 | #include 8 | 9 | QEvent::Type QRouterPageEvent::type = static_cast(QEvent::registerEventType()); 10 | 11 | QStringList RouterStackManager::readStack() { 12 | QStringList stackNames; 13 | 14 | for (const auto& i : stack) { 15 | stackNames << i->metaObject()->className(); 16 | } 17 | return stackNames; 18 | } 19 | 20 | AbstractRouterWidget* RouterStackManager::current() { 21 | if (stack.isEmpty()) { 22 | return nullptr; 23 | } 24 | return stack.last(); 25 | } 26 | 27 | QList RouterStackManager::currentInstances() { 28 | return stack; 29 | } 30 | 31 | QString RouterStackManager::currentName() { 32 | auto item = current(); 33 | if (item == nullptr) { 34 | return {}; 35 | } 36 | return item->metaObject()->className(); 37 | } 38 | 39 | AbstractRouterWidget *RouterStackManager::getInstanceFromStack(const QByteArray &pageClassName) { 40 | 41 | AbstractRouterWidget* widgetTag = nullptr; 42 | for (int i=0; imetaObject()->className() == pageClassName) { 44 | widgetTag = stack.value(i); 45 | break; 46 | } 47 | } 48 | 49 | if (widgetTag == nullptr) { 50 | if (keepSingletonPageInstance.contains(pageClassName)) { 51 | widgetTag = keepSingletonPageInstance.value(pageClassName); 52 | } 53 | } 54 | 55 | return widgetTag; 56 | } 57 | 58 | AbstractRouterWidget *RouterStackManager::getInstanceFromStack(int stackIndex) { 59 | if (stackIndex < 0 || stackIndex >= stack.size()) { 60 | return nullptr; 61 | } 62 | return stack[stackIndex]; 63 | } 64 | 65 | void RouterStackManager::initStack(const QList& pages) { 66 | if (pages.isEmpty()) { 67 | return; 68 | } 69 | for (const auto& page: pages) { 70 | try { 71 | stack.append(reflectByName(page, container->childPageParent(), {})); 72 | container->setCurrentWidget(stack.last()); 73 | } catch (QRouterRuntimeException& e) { 74 | qFatal(e.message.toLatin1().data()); 75 | } 76 | } 77 | } 78 | 79 | void RouterStackManager::push(const QByteArray& pageClassName, const QVariant& data) { 80 | AbstractRouterWidget* widget; 81 | if (keepSingletonPageInstance.contains(pageClassName)) { 82 | widget = keepSingletonPageInstance.take(pageClassName); 83 | } else { 84 | for (auto p : stack) { 85 | if (p->metaObject()->className() == pageClassName) { 86 | if (p->singletonInstance()) { 87 | move2Top(pageClassName); 88 | return; 89 | } 90 | } 91 | } 92 | try { 93 | widget = reflectByName(pageClassName, container->childPageParent(), data); 94 | } catch (QRouterRuntimeException& e) { 95 | qFatal(e.message.toLatin1().data()); 96 | } 97 | } 98 | stack.append(widget); 99 | container->setCurrentWidget(widget); 100 | } 101 | 102 | void RouterStackManager::pushReplace(const QByteArray& pageClassName, const QVariant& data) { 103 | if (!stack.isEmpty()) { 104 | auto widget = stack.takeLast(); 105 | container->removeWidget(widget); 106 | removePageInstance(widget); 107 | } 108 | 109 | push(pageClassName, data); 110 | } 111 | 112 | bool RouterStackManager::pushOrMove2Top(const QByteArray &pageClassName, const QVariant &data) { 113 | if (move2Top(pageClassName)) { 114 | return true; 115 | } 116 | push(pageClassName, data); 117 | return false; 118 | } 119 | 120 | void RouterStackManager::popAndPush(const QByteArray &pageClassName, const QVariant& data) { 121 | if (!stack.isEmpty()) { 122 | if (stack.last()->attemptClose()) { 123 | auto widget = stack.takeLast(); 124 | container->removeWidget(widget); 125 | removePageInstance(widget); 126 | } else { 127 | return; 128 | } 129 | } 130 | 131 | push(pageClassName, data); 132 | } 133 | 134 | void RouterStackManager::close(const QByteArray &pageClassName) { 135 | bool isLast = false; 136 | for (int i=0; imetaObject()->className() == pageClassName) { 138 | isLast = i == stack.size() - 1; 139 | auto widget = stack.takeAt(i); 140 | container->removeWidget(widget); 141 | removePageInstance(widget); 142 | break; 143 | } 144 | } 145 | 146 | if (isLast && !stack.isEmpty()) { 147 | container->setCurrentWidget(stack.last()); 148 | } 149 | } 150 | 151 | void RouterStackManager::pushAndClear(const QByteArray& pageClassName, const QVariant& data) { 152 | while (!stack.isEmpty()) { 153 | auto widget = stack.takeLast(); 154 | container->removeWidget(widget); 155 | removePageInstance(widget); 156 | } 157 | 158 | push(pageClassName, data); 159 | } 160 | 161 | bool RouterStackManager::move2Top(const QByteArray& pageClassName) { 162 | AbstractRouterWidget* widgetTag = nullptr; 163 | for (int i=0; imetaObject()->className() == pageClassName) { 165 | widgetTag = stack.takeAt(i); 166 | break; 167 | } 168 | } 169 | 170 | if (widgetTag == nullptr) { 171 | if (keepSingletonPageInstance.contains(pageClassName)) { 172 | widgetTag = keepSingletonPageInstance.take(pageClassName); 173 | } else { 174 | return false; 175 | } 176 | } 177 | 178 | stack.append(widgetTag); 179 | container->setCurrentWidget(widgetTag); 180 | 181 | return true; 182 | } 183 | 184 | void RouterStackManager::pop(QVariant data) { 185 | if (!stack.isEmpty()) { 186 | if (stack.last()->attemptClose()) { 187 | auto widget = stack.takeLast(); 188 | if (data.isNull()) { 189 | data = widget->readAttemptCloseData(); 190 | } 191 | container->removeWidget(widget); 192 | removePageInstance(widget); 193 | 194 | container->setCurrentWidget(stack.last()); 195 | stack.last()->onNavigateResult(data); 196 | } 197 | } 198 | } 199 | 200 | void RouterStackManager::popUntil(const QByteArray& untilName) { 201 | while (!stack.isEmpty() && stack.last()->metaObject()->className() != untilName) { 202 | if (!stack.last()->attemptClose()) { 203 | container->setCurrentWidget(stack.last()); 204 | return; 205 | } 206 | auto widget = stack.takeLast(); 207 | container->removeWidget(widget); 208 | removePageInstance(widget); 209 | } 210 | 211 | if (!stack.isEmpty()) { 212 | container->setCurrentWidget(stack.last()); 213 | } 214 | } 215 | 216 | void RouterStackManager::popUntil(int stackSize) { 217 | while (stack.size() > stackSize) { 218 | auto widget = stack.takeLast(); 219 | container->removeWidget(widget); 220 | removePageInstance(widget); 221 | } 222 | 223 | if (!stack.isEmpty()) { 224 | container->setCurrentWidget(stack.last()); 225 | } 226 | } 227 | 228 | 229 | QVariant RouterStackManager::sendEventCur(const QString& event, const QVariant& data, bool ignoreContainerNotInstalled) { 230 | try { 231 | if (stack.isEmpty()) { 232 | return {}; 233 | } 234 | return stack.last()->onRouterEvent(event, data); 235 | } catch (QRouterRuntimeException& e) { 236 | if (!ignoreContainerNotInstalled) { 237 | #ifdef QT_DEBUG 238 | qFatal(e.message.toLatin1().data()); 239 | #endif 240 | throw e; 241 | } 242 | } 243 | return {}; 244 | } 245 | 246 | QVariant RouterStackManager::sendEventTo(const QByteArray& pageClassName, const QString& event, const QVariant& data, bool ignoreContainerNotInstalled) { 247 | try { 248 | for (const auto &page: stack) { 249 | if (page->metaObject()->className() == pageClassName) { 250 | return page->onRouterEvent(event, data); 251 | } 252 | } 253 | } catch (QRouterRuntimeException& e) { 254 | if (!ignoreContainerNotInstalled) { 255 | #ifdef QT_DEBUG 256 | qFatal(e.message.toLatin1().data()); 257 | #endif 258 | throw e; 259 | } 260 | } 261 | 262 | if (keepSingletonPageInstance.contains(pageClassName)) { 263 | return keepSingletonPageInstance[pageClassName]->onRouterEvent(event, data); 264 | } 265 | 266 | return {}; 267 | } 268 | 269 | void RouterStackManager::sendEventAll(const QString& event, const QVariant& data, bool ignoreContainerNotInstalled) { 270 | try { 271 | for (const auto &widget: stack) { 272 | widget->onRouterEvent(event, data); 273 | } 274 | } catch (QRouterRuntimeException& e) { 275 | if (!ignoreContainerNotInstalled) { 276 | #ifdef QT_DEBUG 277 | qFatal(e.message.toLatin1().data()); 278 | #endif 279 | throw e; 280 | } 281 | } 282 | 283 | for (auto i = keepSingletonPageInstance.begin(); i != keepSingletonPageInstance.end(); ++i) { 284 | i.value()->onRouterEvent(event, data); 285 | } 286 | } 287 | 288 | void RouterStackManager::postEventCur(const QString& event, const QVariant& data, bool ignoreContainerNotInstalled) { 289 | try { 290 | if (stack.isEmpty()) { 291 | return; 292 | } 293 | return qApp->postEvent(stack.last(), new QRouterPageEvent(event, data)); 294 | } catch (QRouterRuntimeException& e) { 295 | if (!ignoreContainerNotInstalled) { 296 | #ifdef QT_DEBUG 297 | qFatal(e.message.toLatin1().data()); 298 | #endif 299 | throw e; 300 | } 301 | } 302 | } 303 | 304 | void RouterStackManager::postEventTo(const QByteArray& pageClassName, const QString& event, const QVariant& data, bool ignoreContainerNotInstalled) { 305 | try { 306 | for (const auto &page: stack) { 307 | if (page->metaObject()->className() == pageClassName) { 308 | qApp->postEvent(page, new QRouterPageEvent(event, data)); 309 | return; 310 | } 311 | } 312 | } catch (QRouterRuntimeException& e) { 313 | if (!ignoreContainerNotInstalled) { 314 | #ifdef QT_DEBUG 315 | qFatal(e.message.toLatin1().data()); 316 | #endif 317 | throw e; 318 | } 319 | } 320 | 321 | if (keepSingletonPageInstance.contains(pageClassName)) { 322 | qApp->postEvent(keepSingletonPageInstance[pageClassName], new QRouterPageEvent(event, data)); 323 | } 324 | } 325 | 326 | void RouterStackManager::postEventToRoot(const QString& event, const QVariant& data, bool ignoreContainerNotInstalled) { 327 | try { 328 | qApp->postEvent(container->rootPageEventReceiver(), new QRouterPageEvent(event, data)); 329 | } catch (QRouterRuntimeException& e) { 330 | if (!ignoreContainerNotInstalled) { 331 | #ifdef QT_DEBUG 332 | qFatal(e.message.toLatin1().data()); 333 | #endif 334 | throw e; 335 | } 336 | } 337 | } 338 | 339 | void RouterStackManager::postEventAll(const QString& event, const QVariant& data, bool ignoreContainerNotInstalled) { 340 | try { 341 | for (const auto &widget: stack) { 342 | qApp->postEvent(widget, new QRouterPageEvent(event, data)); 343 | } 344 | } catch (QRouterRuntimeException& e) { 345 | if (!ignoreContainerNotInstalled) { 346 | #ifdef QT_DEBUG 347 | qFatal(e.message.toLatin1().data()); 348 | #endif 349 | throw e; 350 | } 351 | } 352 | 353 | for (auto i = keepSingletonPageInstance.begin(); i != keepSingletonPageInstance.end(); ++i) { 354 | qApp->postEvent(i.value(), new QRouterPageEvent(event, data)); 355 | } 356 | } 357 | 358 | AbstractRouterWidget* RouterStackManager::reflectByName(const QByteArray& className, QWidget* parent, const QVariant& data) { 359 | #if QT_VERSION_MAJOR >= 6 360 | const auto metaObj = QMetaType::fromName(className + '*').metaObject(); 361 | #else 362 | int type = QMetaType::type(className + '*'); 363 | const auto metaObj = QMetaType::metaObjectForType(type); 364 | #endif 365 | if (metaObj == nullptr) { 366 | throw QRouterRuntimeException(QString("cannot find page:'%1', the page class may not register by call 'qRegisterMetaType<%1*>()'.").arg(QString(className))); 367 | } 368 | 369 | auto obj = metaObj->newInstance(Q_ARG(QVariant, data), Q_ARG(QWidget*, parent)); 370 | auto widget = dynamic_cast(obj); 371 | if (widget == nullptr) { 372 | throw QRouterRuntimeException(QString("cannot create instance of page:'%1', the page class constructor may not be assigned of 'Q_INVOKABLE'.").arg(QString(className))); 373 | } 374 | 375 | return widget; 376 | } 377 | 378 | void RouterStackManager::removePageInstance(AbstractRouterWidget *widget) { 379 | if (widget->singletonInstance()) { 380 | keepSingletonPageInstance.insert(widget->metaObject()->className(), widget); 381 | } else { 382 | widget->deleteLater(); 383 | } 384 | } --------------------------------------------------------------------------------