第4章 主窗口框架设计:QMainWindow布局、菜单栏与工具栏、状态栏、中心控件(QStackedWidget)多页面切换
做上位机开发,第一步就是搭架子。你想想看,一个监控软件,总得有个像样的主窗口吧?菜单栏、工具栏、状态栏,这些是标配。我刚开始做光伏逆变器监控时,就吃过亏——界面搭得太随意,后面加功能时改得想哭。
今天咱们就聊聊QMainWindow这个核心框架。说白了,它就是Qt给咱们准备好的主窗口模板。你不需要从零画一个窗口,直接用这个,省心省力。
4.1 QMainWindow的结构拆解
QMainWindow自带五个关键区域:菜单栏、工具栏、状态栏、中心控件、还有dock窗口。我个人习惯,先把中心控件定下来,再往四周加东西。
核心结构:
- 菜单栏(Menu Bar)—— 放功能菜单,比如文件、设置、帮助
- 工具栏(Tool Bar)—— 常用操作的快捷按钮
- 中心控件(Central Widget)—— 核心内容区域,我们用QStackedWidget
- 状态栏(Status Bar)—— 显示当前状态、提示信息
- Dock窗口(可选)—— 侧边面板,比如日志、参数列表
嗯,这里要注意:QMainWindow有自己的布局管理器,你不能直接往上面放控件。必须用setCentralWidget()来设置中心区域。
4.2 菜单栏与工具栏的设计
菜单栏我建议按功能分组。光伏逆变器监控软件,常见的菜单有:
| 菜单 | 功能 | 快捷键 |
|---|---|---|
| 文件 | 打开配置文件、导出数据、退出 | Ctrl+O, Ctrl+E |
| 监控 | 启动/停止监控、实时数据、历史数据 | F5, F6 |
| 设置 | 通信参数、报警阈值、界面主题 | Ctrl+S |
| 帮助 | 关于、版本信息、使用手册 | F1 |
工具栏呢,我建议只放最常用的操作。比如「启动监控」「停止监控」「导出数据」。别把工具栏塞得满满当当,用户看着眼晕。
我曾经在一个项目里,把二十多个按钮全塞进工具栏,结果用户反馈说「找按钮比找故障还难」。后来我学乖了,只放5个核心按钮,其他的放菜单里。
# 创建菜单栏和工具栏的示例代码
menu_bar = self.menuBar()
# 文件菜单
file_menu = menu_bar.addMenu('文件')
file_menu.addAction('打开配置', self.open_config)
file_menu.addAction('导出数据', self.export_data)
file_menu.addSeparator()
file_menu.addAction('退出', self.close)
# 监控菜单
monitor_menu = menu_bar.addMenu('监控')
monitor_menu.addAction('启动监控', self.start_monitor, 'F5')
monitor_menu.addAction('停止监控', self.stop_monitor, 'F6')
# 工具栏
tool_bar = self.addToolBar('监控工具')
tool_bar.addAction('启动', self.start_monitor)
tool_bar.addAction('停止', self.stop_monitor)
tool_bar.addSeparator()
tool_bar.addAction('导出', self.export_data)
小技巧:菜单项的图标用QIcon加载,建议用SVG格式。我习惯把图标文件放在项目根目录的resources/icons/文件夹里,方便管理。
4.3 状态栏的妙用
状态栏很多人觉得无所谓,其实它很有用。你想想看,用户操作时,总得有个地方显示当前状态吧?比如「正在连接逆变器...」「数据接收正常」「通信超时」。
我一般把状态栏分成三块:
- 左侧:显示当前操作提示
- 中间:显示通信状态(连接/断开)
- 右侧:显示时间或版本号
# 状态栏设置
self.status_bar = self.statusBar()
# 添加永久控件
self.status_label = QLabel('就绪')
self.status_bar.addWidget(self.status_label, 1)
self.comm_status = QLabel('未连接')
self.comm_status.setStyleSheet('color: red;')
self.status_bar.addPermanentWidget(self.comm_status)
# 更新状态
def update_status(self, message):
self.status_label.setText(message)
避坑指南:我曾经在状态栏里频繁更新文本,导致界面卡顿。后来发现是更新频率太高,建议用QTimer控制,每秒更新一次就够了。
4.4 中心控件:QStackedWidget多页面切换
中心控件是整个窗口的灵魂。光伏逆变器监控软件,通常有多个页面:实时数据、历史曲线、参数设置、故障报警等。用QStackedWidget实现页面切换,再合适不过了。
QStackedWidget就像一个卡片容器,每张卡片是一个独立的页面。你只需要调用setCurrentIndex()或setCurrentWidget(),就能切换页面。
# 创建QStackedWidget
self.stacked_widget = QStackedWidget()
self.setCentralWidget(self.stacked_widget)
# 创建各个页面
self.page_realtime = RealtimePage() # 实时数据页
self.page_history = HistoryPage() # 历史数据页
self.page_settings = SettingsPage() # 设置页
self.page_alarm = AlarmPage() # 报警页
# 添加页面到QStackedWidget
self.stacked_widget.addWidget(self.page_realtime) # index 0
self.stacked_widget.addWidget(self.page_history) # index 1
self.stacked_widget.addWidget(self.page_settings) # index 2
self.stacked_widget.addWidget(self.page_alarm) # index 3
# 切换到实时数据页
self.stacked_widget.setCurrentIndex(0)
页面切换的触发方式,我建议用工具栏按钮或者菜单项。比如点击「实时数据」按钮,就切换到index 0的页面。
页面切换的核心逻辑:
- 创建各个页面类,继承QWidget
- 用
addWidget()添加到QStackedWidget - 通过信号槽或按钮点击,调用
setCurrentIndex() - 每个页面独立管理自己的控件和逻辑
4.5 整体框架的SVG结构图
为了让你更直观地理解整个框架,我画了一张结构图。嗯,这张图我花了不少心思,把各个组件的关系都理清了。
从这张图你能看到,菜单栏和工具栏在上方,状态栏在底部,中间是QStackedWidget。每个页面独立管理自己的控件,切换时互不干扰。这种结构,说白了就是「高内聚、低耦合」——每个页面只管自己的事,主窗口只负责切换。
4.6 完整的初始化代码
最后,我把整个主窗口的初始化代码贴出来。你直接复制就能用,稍微改改页面类名就行。
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('光伏逆变器监控系统')
self.resize(1200, 800)
# 1. 创建中心控件
self.stacked_widget = QStackedWidget()
self.setCentralWidget(self.stacked_widget)
# 2. 创建页面
self.pages = {
'realtime': RealtimePage(),
'history': HistoryPage(),
'settings': SettingsPage(),
'alarm': AlarmPage()
}
# 3. 添加页面
for page in self.pages.values():
self.stacked_widget.addWidget(page)
# 4. 创建菜单栏
self._create_menu_bar()
# 5. 创建工具栏
self._create_tool_bar()
# 6. 创建状态栏
self._create_status_bar()
# 7. 默认显示实时数据页
self.stacked_widget.setCurrentIndex(0)
def _create_menu_bar(self):
menu_bar = self.menuBar()
# ... 菜单代码同上 ...
def _create_tool_bar(self):
tool_bar = self.addToolBar('主工具栏')
# ... 工具栏代码同上 ...
def _create_status_bar(self):
self.status_bar = self.statusBar()
# ... 状态栏代码同上 ...
def switch_page(self, index):
self.stacked_widget.setCurrentIndex(index)
个人建议:页面类最好单独放在pages/文件夹里,每个页面一个文件。比如pages/realtime_page.py、pages/history_page.py。这样代码结构清晰,后期维护也方便。
好了,主窗口的框架就搭完了。你想想看,有了这个架子,后面加页面、加功能是不是就简单多了?嗯,下一节咱们就开始填充第一个页面——实时数据监控页面。
公众号:蓝海资料掘金营,微信deep3321