第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的页面。

页面切换的核心逻辑:

  1. 创建各个页面类,继承QWidget
  2. addWidget()添加到QStackedWidget
  3. 通过信号槽或按钮点击,调用setCurrentIndex()
  4. 每个页面独立管理自己的控件和逻辑

4.5 整体框架的SVG结构图

为了让你更直观地理解整个框架,我画了一张结构图。嗯,这张图我花了不少心思,把各个组件的关系都理清了。

QMainWindow 主窗口框架结构 菜单栏:文件 | 监控 | 设置 | 帮助 工具栏:启动 | 停止 | 导出 | 设置 QStackedWidget (中心控件) 实时数据页 (Index 0) 历史数据页 (Index 1) 参数设置页 (Index 2) 故障报警页 (Index 3) ← setCurrentIndex(n) 切换页面 → 状态栏:就绪 | 通信状态:已连接 | 2025-01-15 14:30:00 每个页面独立管理自己的控件和逻辑,互不干扰

从这张图你能看到,菜单栏和工具栏在上方,状态栏在底部,中间是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.pypages/history_page.py。这样代码结构清晰,后期维护也方便。

好了,主窗口的框架就搭完了。你想想看,有了这个架子,后面加页面、加功能是不是就简单多了?嗯,下一节咱们就开始填充第一个页面——实时数据监控页面。


公众号:蓝海资料掘金营,微信deep3321