QStackedWidget 的高级使用方法
在 Qt 开发中,构建复杂的 GUI 应用常常面临一个挑战:如何优雅地管理多个页面,并在它们之间进行切换?今天,我将介绍一种基于 QStackedWidget 和工厂模式的解决方案,帮助你轻松实现多页面应用。
效果如下:
QStackedWidget:堆叠式容器
QStackedWidget 是 Qt 框架中一个强大的容器小部件,它允许你将多个子窗口堆叠在一起,但只显示最上面的一个。这种特性类似于实体世界中的卡片堆叠,每次只能看到最顶部的卡片。
工厂模式:构建窗口的“工厂”
当页面数量众多且复杂时,手动创建和管理每个窗口会变得繁琐且容易出错。这时,我们可以引入工厂模式,将窗口的创建过程封装起来,实现解耦和复用。
1. 基类
首先,我们需要定义一个基类 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
2. 工厂类
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
3. 注册宏
为了简化窗口注册过程,我们定义了一个宏 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
4. 使用方法
在主窗口中,我们定义一个 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);
}
}