赞
踩
在 Qt 开发中,构建复杂的 GUI 应用常常面临一个挑战:如何优雅地管理多个页面,并在它们之间进行切换?今天,我将介绍一种基于 QStackedWidget 和工厂模式的解决方案,帮助你轻松实现多页面应用。
效果如下:
QStackedWidget:堆叠式容器
QStackedWidget 是 Qt 框架中一个强大的容器小部件,它允许你将多个子窗口堆叠在一起,但只显示最上面的一个。这种特性类似于实体世界中的卡片堆叠,每次只能看到最顶部的卡片。
当页面数量众多且复杂时,手动创建和管理每个窗口会变得繁琐且容易出错。这时,我们可以引入工厂模式,将窗口的创建过程封装起来,实现解耦和复用。
首先,我们需要定义一个基类 BaseWindow,作为所有子窗口的父类。基类中包含一个抽象方法 initLoadData,用于加载每个窗口所需的数据。
#ifndef BASEWINDOW_H #define BASEWINDOW_H #include <QWidget> // 这个基类里面有个抽象方法 initLoadData,用来加载子窗口所需数据 class BaseWindow : public QWidget { Q_OBJECT public: explicit BaseWindow(QWidget *parent = nullptr); /** * @brief 初始化加载数据 * @param reset */ virtual void initLoadData() = 0; }; #endif // BASEWINDOW_H
WindowFactory 类充当创建窗口的“工厂”。它使用一个 QMap 存储窗口名称和对应的创建函数。通过注册机制,我们可以将每个窗口类与一个创建函数关联起来。
#ifndef WINDOWFACTORY_H #define WINDOWFACTORY_H #include <QMap> #include "basewindow.h" class WindowFactory { public: using Creator = BaseWindow * (*)(QWidget *); static WindowFactory &instance() { static WindowFactory instance; return instance; } void registerWindow(const QString &name, Creator creator) { creators[name] = creator; } BaseWindow *createWindow(const QString &name, QWidget *parent = nullptr) { if (creators.contains(name)) { return creators[name](parent); } return nullptr; } private: QMap<QString, Creator> creators; WindowFactory() = default; ~WindowFactory() = default; WindowFactory(const WindowFactory &) = delete; WindowFactory &operator=(const WindowFactory &) = delete; }; #endif // WINDOWFACTORY_H
为了简化窗口注册过程,我们定义了一个宏 REGISTER_WINDOW_CLASS。使用该宏,只需传入窗口名称和类名,即可自动注册窗口到工厂中。
#ifndef WINDOWREGISTRATION_H #define WINDOWREGISTRATION_H #include "windowfactory.h" #define REGISTER_WINDOW_CLASS(NAME, CLASS) \ class CLASS##Registrator { \ public: \ CLASS##Registrator() { \ WindowFactory::instance().registerWindow(NAME, CLASS##Registrator::createInstance); \ } \ static BaseWindow* createInstance(QWidget* parent) { \ return new CLASS(parent); \ } \ }; \ static CLASS##Registrator global_##CLASS##Registrator; #endif // WINDOWREGISTRATION_H
在主窗口中,我们定义一个 QMap 来存储已经创建的窗口实例,确保每个窗口只创建一次。当需要切换到某个窗口时,只需通过窗口名称从工厂中获取实例,并将其设置为 QStackedWidget 的当前页面即可。
// 示例代码,展示如何使用工厂模式和 QStackedWidget QMap<QString, BaseWindow*> windowInstances; QStackedWidget *stackedWidget = new QStackedWidget; // windowName 窗口名称 void switchToWindow(const QString &windowName) { BaseWindow *window = nullptr; if (!windowInstances.contains(windowName)) { BaseWindow *window = WindowFactory::instance().createWindow(windowName); if (window) { windowInstances[windowName] = window; stackedWidget->addWidget(window); } } window = windows[windowName]; if (window) { //加载数据 window->initLoadData(); ui->stackedWidget->setCurrentWidget(window); } stackedWidget->setCurrentWidget(windowInstances[windowName]); }
为了提供更好的用户体验,我们还可以实现窗口历史记录功能。通过 BrowserHistory 模板类,我们可以轻松地管理历史记录和前进后退操作。
#ifndef BROWSERHISTORY_H #define BROWSERHISTORY_H #include <QStack> #include <QWidget> template <typename T> class BrowserHistory { public: BrowserHistory() {} /** * @brief 访问新页面,并清空栈 * @param page 页面 */ void visit(const T &page) { if (backStack.contains(page)) { QStack<T> tempStack; while (backStack.top() != page) { tempStack.push(backStack.pop()); } backStack.pop(); while (!tempStack.isEmpty()) { backStack.push(tempStack.pop()); } } backStack.push(page); forwardStack.clear(); setNavigationButtonState(backStack.size() > 1, false); } /** * @brief 返回上一页,将当前页移到前进栈,并返回新的当前页 * @return */ T back() { if (backStack.size() > 1) { T page = backStack.pop(); forwardStack.push(page); setNavigationButtonState(backStack.size() >= 2, forwardStack.size() >= 1); return backStack.top(); } setNavigationButtonState(false, forwardStack.size() >= 1); return T(); } /** * @brief 前进到下一页,将前进栈的栈顶页移到历史记录栈,并返回新的当前页 * @return */ T forward() { if (!forwardStack.isEmpty()) { T page = forwardStack.pop(); backStack.push(page); setNavigationButtonState(backStack.size() >= 2, forwardStack.size() >= 1); return page; } setNavigationButtonState(backStack.size() >= 2, false); return T(); } /** * @brief 获取当前页 * @return */ T current() const { if (!backStack.isEmpty()) { return backStack.top(); } return T(); } /** * @brief 设置导航按钮 * @param forward 前进按钮 * @param back 后退按钮 */ void setNavigationButton(QWidget *forward, QWidget *back) { this->m_forward = forward; this->m_back = back; //初始全部禁用 setNavigationButtonState(false, false); } private: void setNavigationButtonState(bool forwardEnabled, bool backEnabled) { if (m_forward&&m_back) { m_forward->setEnabled(forwardEnabled); m_back->setEnabled(backEnabled); } } private: QStack<T> backStack; // 存储历史记录 QStack<T> forwardStack; // 存储前进记录 QWidget *m_forward; //前进组件 QWidget *m_back; //后退组件 }; #endif // BROWSERHISTORY_H
// 示例代码,展示如何使用浏览记录 BrowserHistory<QString> history; //记录 bool isRecord = true;//用于防止浏览记录的时候记录 // windowName 窗口名称 void switchToWindow(const QString &windowName) { BaseWindow *window = nullptr; if (!windowInstances.contains(windowName)) { BaseWindow *window = WindowFactory::instance().createWindow(windowName); if (window) { windowInstances[windowName] = window; stackedWidget->addWidget(window); } } window = windows[windowName]; if (window) { //加载数据 window->initLoadData(); ui->stackedWidget->setCurrentWidget(window); if (isRecord) { history.visit(windowName); } else { isRecord = true; } } stackedWidget->setCurrentWidget(windowInstances[windowName]); } //点击事件 void Widget::forwardButtonClick() { QString windowName = history.back(); if (!windowName.isEmpty()) { isRecord = false; switchToWindow(windowName); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。