PyQt5桌面应用开发(9):经典布局QMainWindow

news2024/10/6 1:34:53

本文目录

  • PyQt5桌面应用系列
  • 桌面程序基本布局
  • QMainWindow概况与使用
    • 主窗体
    • 菜单栏
    • 工具栏
    • 停靠窗
    • 状态栏
  • 代码编辑器的例子
  • 总结

PyQt5桌面应用系列

  • PyQt5桌面应用开发(1):需求分析
  • PyQt5桌面应用开发(2):事件循环
  • PyQt5桌面应用开发(3):并行设计
  • PyQt5桌面应用开发(4):界面设计
  • PyQt5桌面应用开发(5):对话框
  • PyQt5桌面应用开发(6):文件对话框
  • PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
  • PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
  • PyQt5桌面应用开发(9):经典布局QMainWindow

桌面程序基本布局

传统的桌面程序的布局,已经有很多优秀的软件开发这探索很多年。以IDEA和Pycharm为例,基本形成菜单、工具栏、可停靠工具窗口、主窗口、状态栏的布局。当然,最近JetBrain在逐步推动新的布局方式,进一步减少干扰项,突出编辑代码的主窗体。另外,在桌面工具程序中,还有很多类似于Windows 11版本中推动的更加简洁的布局形式。

在这里插入图片描述

布局方式的演进和发展是一个大的趋势,被新的潮流和审美所推动,也推动不同的软件使用。但这种现在称为传统、以前称为新潮的菜单、工具栏、可停靠工具窗口、主窗口、状态栏的布局依然是严肃桌面应用的一个很好的起点,也是开发过程中可以作为第一选项来进行开发,然后根据软件需求相应改造的基线方案。因此,各个桌面软件开发包都提供了对这一布局的便利性支持。JavaFX有一个BorderLayout;Qt提供了QMainWindow。
在这里插入图片描述

上图取自Qt官网doc.Qt.io。这几个不同的区域,设计目的大概是这样的。

  1. 主窗口:提供与数据交互和展示数据的主要区域;
  2. 菜单栏:提供所有功能的菜单;
  3. 工具栏:提供常用功能的便捷性工具按钮;
  4. 可停靠工具窗口:提供与主窗体关联的工具;
  5. 状态栏:提示状态信息。

从上面的图中可以看到,工具栏、可停靠窗口的位置是可以移动的。在下面会详细给出例子。

QMainWindow概况与使用

QMainWindow直接继承自QWidget,在QWdiget里面增添了菜单、工具栏、工具窗口、主窗口和状态栏的接口。

在这里插入图片描述

  1. 主窗体:QMainWindow.setCentralWidget
  2. 菜单栏:QMainWindow.setMenuBar
  3. 工具栏:QMainWindow.addToolBar
  4. 停靠窗:QMainWindow.addDockWidget
  5. 状态栏:QMainWindow.setStatusBar

从PyQt动词的是用来分类,菜单栏、主窗体和状态栏用的是set,表示只会有一个;工具栏、停靠窗使用的是add,说明可以增加多个。

主窗体

主窗体没什么好说的。展示核心的数据报表,提供核心的数据交互。设置方式调用,setCentralWidget,参数就是一个以QMainWindow为父节点的QWidget。

菜单栏

菜单栏有两个层级的结构,QMenuBar包含多个Menu,每个Menu包含多个Action。从下面的例子可以看到这里所有的动词都是用的add,这也是PyQt5接口一致性的体现。QAction文件对话框里面讲过,不再赘述。

下面的例子中,所有的Action都是关闭主窗体……实际中,把self.close连接到相应的槽函数即可。

def _set_menubar(self: QMainWindow):
    mb = QMenuBar(parent=self)

    file_menu = mb.addMenu('&File')
    for a in ['&New', '&Open', '&Save', '&Save as']:
        file_menu.addAction(QIcon("icon.png"), a, self.close)
    file_menu.addSeparator()
    file_menu.addAction(self.style().standardIcon(QStyle.SP_DialogCloseButton), '&Quit', self.close)

    self.setMenuBar(mb)

工具栏

工具栏是一个可以拖动,放于上下左右的,可以包含工具按钮和其他控件的长条形(左右为纵向,上下为横向)窗体。工具栏是否可以拖动有一个函数setMovable来控制,还可以通过isAreaAllowed来设定允许区域。

这个允许区域是一个枚举类型(enum)来描述,包括左右上下全否。

  • Qt.LeftToolBarArea 左边允许
  • Qt.RightToolBarArea 右边允许
  • Qt.TopToolBarArea 上方允许
  • Qt.BottomToolBarArea 下方允许
  • Qt.AllToolBarAreas 所有区域都允许
  • Qt.NoToolBarArea 所有区域都不允许

此外,还可以通过setToolButtonStyle来设定显示样式,具体来说就是文字和图标的显示。具体的设置同样是Qt的枚举类型。

  • Qt::ToolButtonIconOnly 仅显示图标
  • Qt::ToolButtonTextOnly 仅显示文字
  • Qt::ToolButtonTextBesideIcon 文字在图标侧方
  • Qt::ToolButtonTextUnderIcon 文字在图标下方
  • Qt::ToolButtonFollowStyle 按照StyleHint来设置(这里不深入)
def _set_toolbar(self: QMainWindow):
    tb = QToolBar(self)

    for a in ['New', 'Open', 'Save', 'Save as']:
        tb.addAction(QIcon("icon.png"), a, self.close)
    tb.addSeparator()
    tb.addAction(self.style().standardIcon(QStyle.SP_DialogCloseButton), "Close", self.close)
    tb.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
    tb.setMovable(False)

    self.addToolBar(tb)

停靠窗

停靠窗的位置也可以上上下左右。其设置方式是包括三步:

  1. 创建一个QDockWidget对象;
  2. 调用QDockWidget对象的setWidget方法;
  3. 调用QMainWindow的addDockWidget方法。

停靠窗的允许位置同样采用Qt的枚举来设置,设置方法是setAllowedAreas。停靠窗是否可以移动要用QDockWidget的枚举量来设置,设置方法是setFeatures,当然这里不仅仅是是否可以移动,还包括是否可以关闭、是否可以浮动等等。

停靠窗的位置包括一下几个。

  • Qt::LeftDockWidgetArea 左边
  • Qt::RightDockWidgetArea 右边
  • Qt::TopDockWidgetArea 上方
  • Qt::BottomDockWidgetArea 下方
  • Qt::AllDockWidgetAreas 所有都允许
  • Qt::NoDockWidgetArea 所有都不允许

枚举DockWidgetFeature包括以下几个:

  • QDockWidget::DockWidgetClosable 是否可以关闭
  • QDockWidget::DockWidgetMovable 是否可以移动
  • QDockWidget::DockWidgetFloatable 是否可以漂浮
  • QDockWidget::DockWidgetVerticalTitleBar 标题栏纵向放置
  • QDockWidget::NoDockWidgetFeatures 没有特性
def _set_left_dock(self: QMainWindow):
    dock = QDockWidget('Project structure', self)
    tv = QTreeWidget(dock)
    tv.setColumnCount(4)
    # tv.setHeaderHidden(True)
    tv.setHeaderLabels(['', '', 'name', 'path'])

    root = self._get_file_tree(".", tv)

    root.setExpanded(True)

    tv.addTopLevelItem(root)

    dock.setWidget(tv)

    dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
    dock.setFeatures(QDockWidget.DockWidgetMovable)
    self.addDockWidget(Qt.LeftDockWidgetArea, dock)

    self.tv = tv
    self.tv.clicked.connect(self.load_item)

状态栏

状态栏最为简单,创建一个QStatusBar对象,调用QMainWindow.setStatusBar就可以,后面调用QMainWindow.statusBar就能随时得到状态栏句柄,然后调用showMessage即可。

def _set_statusbar(self: QMainWindow):
    sb = QStatusBar(self)
    sb.showMessage("Hello!")

    self.setStatusBar(sb)

代码编辑器的例子

基本的信息介绍完毕,下面可以实现一个代码编辑器的例子。

数据报表

  • 显示python源程序的内容,包括行号、高亮
  • 统计源文件的行数、字符数

数据来源,源文件从文件树中读取。这就给交互提供了依据:

  • 查看文件树
  • 打开文件
  • 编辑文件
  • 保存文件

在这里插入图片描述

最终实现的代码如下。

from PyQt5.QtCore import Qt, QDir, QModelIndex
from PyQt5.QtGui import QIcon, QTextDocument
from PyQt5.QtWidgets import QApplication, QTreeWidgetItem, QToolBar, QStatusBar, QStyle, QFormLayout, QLineEdit
from PyQt5.QtWidgets import QDockWidget
from PyQt5.QtWidgets import QMainWindow, QWidget, QMenuBar, QTreeWidget

from code_editor import QCodeEditor
from py_syntax_highlighter import PythonHighlighter


class MyMainWindow(QMainWindow):
    def __init__(self):
        super(MyMainWindow, self).__init__()
        self.setWindowIcon(QIcon('icon.png'))
        self.setWindowTitle('PyQt5 Editor')
        self.setMinimumSize(1440, 768)
        self.editor = QCodeEditor(display_line_numbers=True,
                                  highlight_current_line=True,
                                  syntax_high_lighter=PythonHighlighter)
        self.setCentralWidget(self.editor)

        self.editor.setPlainText(open("mainwindow.py", encoding="utf-8").read())
        self._set_menubar()
        self._set_toolbar()
        self._set_statusbar()
        self._set_left_dock()
        self._set_right_dock()

    def _set_menubar(self: QMainWindow):
        mb = QMenuBar(parent=self)

        file_menu = mb.addMenu('&File')
        for a in ['&New', '&Open', '&Save', '&Save as']:
            file_menu.addAction(QIcon("icon.png"), a, self.close)
        file_menu.addSeparator()
        file_menu.addAction(self.style().standardIcon(QStyle.SP_DialogCloseButton), '&Quit', self.close)

        self.setMenuBar(mb)

    def _set_toolbar(self: QMainWindow):
        tb = QToolBar(self)

        for a in ['New', 'Open', 'Save', 'Save as']:
            tb.addAction(QIcon("icon.png"), a, self.close)
        tb.addSeparator()
        tb.addAction(self.style().standardIcon(QStyle.SP_DialogCloseButton), "Close", self.close)
        tb.setToolButtonStyle(Qt.ToolButtonFollowStyle)
        # tb.setMovable(False)

        self.addToolBar(tb)

    def _set_statusbar(self: QMainWindow):
        sb = QStatusBar(self)
        sb.showMessage("Hello!")

        self.setStatusBar(sb)

    def _set_left_dock(self: QMainWindow):
        dock = QDockWidget('Project structure', self)
        tv = QTreeWidget(dock)
        tv.setColumnCount(4)
        # tv.setHeaderHidden(True)
        tv.setHeaderLabels(['', '', 'name', 'path'])

        root = self._get_file_tree(".", tv)

        root.setExpanded(True)

        tv.addTopLevelItem(root)

        dock.setWidget(tv)

        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        dock.setFeatures(QDockWidget.DockWidgetMovable)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)

        self.tv = tv
        self.tv.clicked.connect(self.load_item)

    def load_item(self, index: QModelIndex):
        item = self.tv.itemFromIndex(index)
        fn = item.text(3)
        if QDir(fn).exists():
            return

        if fn.endswith(".py"):
            try:
                self.editor.setPlainText(open(fn, encoding="utf-8").read())
                doc: QTextDocument = self.editor.document()
                self.statusBar().showMessage(f"Open file {fn}, length: {doc.characterCount()}.")
                doc.blockCountChanged.connect(lambda count: self.line_edit.setText(f"{count}"))
                doc.contentsChanged.connect(lambda: self.character_edit.setText(f"{doc.characterCount()}"))

                doc.blockCountChanged.emit(doc.blockCount())
                doc.contentsChanged.emit()
            except Exception as e:
                print(e)

    def _set_right_dock(self: QMainWindow):

        dock = QDockWidget('File information', self)
        info = QWidget(dock)
        layout = QFormLayout(info)

        self.line_edit = QLineEdit(f"{self.editor.document().blockCount()}")
        self.line_edit.setEnabled(False)
        self.character_edit = QLineEdit(f"{self.editor.document().characterCount()}")
        self.character_edit.setEnabled(False)
        layout.addRow("Block count", self.line_edit)
        layout.addRow("Character count", self.character_edit)

        info.setLayout(layout)

        dock.setWidget(info)
        dock.setFeatures(QDockWidget.DockWidgetMovable)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)

        doc: QTextDocument = self.editor.document()
        doc.blockCountChanged.connect(lambda count: self.line_edit.setText(f"{count}"))
        doc.contentsChanged.connect(lambda: self.character_edit.setText(f"{doc.characterCount()}"))

    def _get_file_tree(self, root_dir, parent) -> QTreeWidgetItem:

        d = QDir(QDir(root_dir).absolutePath())
        item = QTreeWidgetItem(parent)
        item.setIcon(1, self.style().standardIcon(QStyle.SP_DirIcon))
        item.setText(2, d.dirName())
        item.setText(3, d.canonicalPath())

        for f in d.entryInfoList(QDir.NoDot | QDir.NoDotDot | QDir.Dirs):
            if f.isDir():
                self._get_file_tree(f.canonicalFilePath(), item)

        for f in d.entryInfoList(["*.py"], QDir.NoDot | QDir.NoDotDot | QDir.Files):
            fi = QTreeWidgetItem(item)
            fi.setText(2, f.fileName())
            fi.setText(3, f.canonicalFilePath())
            if f.fileName().endswith("py"):
                fi.setIcon(1, QIcon("icon.png"))

        return item


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    main_window = MyMainWindow()

    main_window.show()
    sys.exit(app.exec_())

上面的程序跟前面数篇最大的区别在于,我这种面向过程的顽固分子,也不得不向面向对象屈服,因为实在太香。

总结

  1. 经典的不一定是最好,但绝对是能成为一个好基础的;
  2. QMainWindow可以作为桌面应用开发的第一个版本,作为迭代的基础;
  3. QMainWindow实现了几个典型的用户界面区域,菜单、工具栏、停靠窗、状态栏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/496611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

113-Linux_安装c/c++开发库及连接mysql数据库

文章目录 一.安装c/c开发库二.连接mysql数据库三.用户的管理与授权 mysql数据库的安装 一.安装c/c开发库 安装开发c/c的库&#xff0c;命令&#xff1a;apt install libmysqlclient-dev 二.连接mysql数据库 #include<stdio.h> #include<mysql/mysql.h>void fun…

JAVA13新特性

JAVA13新特性 概述 2019年9月17日&#xff0c;国际知名的OpenJDK开源社区发布了Java编程语言环境的最新版本OpenJDK13。 Features&#xff1a;总共有5个新的JEP(JDK Enhancement Proposals): http://openjdk.java.net/projects/jdk/13/Features 350:Dynamic CDS Archives 动…

C++ STL之vector基础

文章目录 前言STL之vector基础1. What&#xff1a;什么是 vector&#xff1f;2. Why&#xff1a;为什么使用 vector&#xff1f;3. How&#xff1a;怎么使用vector?3.1 vector的定义演示&#xff1a;输出&#xff1a; 3.2 vector iterator 的使用演示&#xff1a;输出&#xf…

网络基础学习:什么是tcp/ip协议

什么是tcp/ip协议 TCP/ip协议是什么东西&#xff1f;tcp/ip四层模型一、应用层二、传输层三、网络层四、网络接口层 TCP/ip协议是什么东西&#xff1f; TCP/IP是一种网络协议套件&#xff0c;它由传输控制协议&#xff08;TCP&#xff09;和互联网协议&#xff08;IP&#xff…

最大网络流算法之dinic算法详解

1、题目描述 On the Internet, machines (nodes) are richly interconnected, and many paths may exist between a given pair of nodes. The total message-carrying capacity (bandwidth) between two given nodes is the maximal amount of data per unit time that can b…

2016年上半年软件设计师下午试题

试题四 【说明】 模式匹配是指给定主串t和子串s&#xff0c;在主串 t 中寻找子串s的过程&#xff0c;其中s称为模式。如果匹配成功&#xff0c;返回s在t中的位置&#xff0c;否则返回-1 。 KMP算法用next数组对匹配过程进行了优化。KMP算法的伪代码描述如下&#xff1a; 在串…

【Python入门】Python的判断语句(if语句的基本格式)

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Python零基础入门系列&#xff0c;本专栏主要内容为Python基础语法、判断、循环语句、函…

如何使用SpringMVC之常用注解

❣️关注专栏&#xff1a;JavaEE Spring MVC ⌛️ 1. Spring MVC 创建和连接⌛️ 1.1 RequestMapping⌛️ 1.2 GetMapping⌛️ 1.3 PostMapping ⌛️ 2. 获取参数⌛️ 2.1 传递/获取单个参数⌛️ 2.2 传递/获取多个参数⌛️ 2.3 传递/获取对象⌛️ 2.4 参数重命名⌛️ 2.4.1 …

【链表OJ题 5】牛客 CM11 链表分割

目录 题目来源&#xff1a; 代码实现&#xff1a; 1.带哨兵位的头结点 2.不带哨兵位的头结点 思路分析&#xff1a; 1.带哨兵位的头结点 实现过程&#xff1a; 易错点&#xff1a; 2.不带哨兵位的头结点 实现过程&#xff1a; 易错点&#xff1a; 题目来源&#xff…

【图神经网络】GNNExplainer代码解读及其PyG实现

GNNExplainer代码解读及其PyG实现 使用GNNExplainerGNNExplainer源码速读前向传播损失函数 基于GNNExplainer图分类解释的PyG代码示例参考资料 接上一篇博客图神经网络的可解释性方法及GNNexplainer代码示例&#xff0c;我们这里简单分析GNNExplainer源码&#xff0c;并用PyTor…

2023年中职组“网络空间安全”赛项XX市竞赛任务书

2023年中职组“网络空间安全”赛项 XX市竞赛任务书 一、竞赛时间 共计&#xff1a;180分钟 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段单兵模式系统渗透测试 任务一 SSH弱口令渗透测试 100分钟 100 任务二 Linux操作系统渗透测试 100 任…

deep learning system 笔记 自动微分 reverse mode AD

计算图 Computational Graph 图上的每个节点代表一个中间值边事输入输出的关系 forward 求导 forward mode AD 上图中从前向后&#xff0c;一步一步计算每个中间值对 x1的偏导&#xff0c;那么计算到 v7&#xff0c;就得到了整个函数对于 x1的偏导。 有limitation 对一个参数…

单机版部署Redis详细教程

概述 大多数企业都是基于Linux服务器来部署项目&#xff0c;而且Redis官方也没有提供Windows版本的安装包。因此课程中我们会基于Linux系统来安装Redis. 此处选择的Linux版本为CentOS 7. Redis的官方网站地址&#xff1a;https://redis.io/ 单机安装Redis 1.1.安装Redis依…

【IP地址与子网掩码】如何计算网络地址、广播地址、地址范围、主机个数、子网数(附详解与习题)

【写在前面】其实很多时候通过IP地址和子网掩码计算其网络地址、广播地址、可用IP&#xff0c;地址范围&#xff0c;主机数啥的&#xff0c;有些人不太清楚规则就只能瞎猜了&#xff0c;但是作为一个网络管理员还是一个基础常识的&#xff0c;这不因为最近备考网络管理员&#…

【数据结构】八大排序(二)

&#x1f61b;作者&#xff1a;日出等日落 &#x1f4d8; 专栏&#xff1a;数据结构 在最黑暗的那段人生&#xff0c;是我自己把自己拉出深渊。没有那个人&#xff0c;我就做那个人。 …

API接口的对接流程和注意事项

一、对接API数据接口的步骤通常包括以下几个部分&#xff1a; 了解API&#xff1a;首先需要详细了解API的基本信息、请求格式、返回数据格式、错误码等相关信息。可以查看API的官方文档或者使用API探索工具。同时&#xff0c;还需要明确数据请求的频率和使用权限等限制。 ​​测…

恐怖,又要有多少人下岗!AI零成本设计主图,渗入10万亿电商市场

在电商平台上&#xff0c;主图是吸引消费者点击进入商品详情页的重要因素之一。 一张高点击的电商主图&#xff0c;不仅要能够吸引消费者的眼球&#xff0c;还要能够清晰地展示产品的特点和卖点。下面是一些制作高点击电商主图的建议。 1. 突出产品特点&#xff1a;在制作主图…

【Spring】Spring的事务管理

目录 1.Spring事务管理概述1.1 事务管理的核心接口1. PlatformTransactionManager2. TransactionDefinition3. TransactionStatus 1.2 事务管理的方式 2.声明式事务管理2.1 基于XML方式的声明式事务2.2 基于Annotation方式的声明式事务 1.Spring事务管理概述 Spring的事务…

惠普暗影精灵5 super 873-068rcn如何重装系统

惠普暗影精灵5 super 873-068rcn是一款家用游戏台式电脑&#xff0c;有时候你可能用久会遇到系统出现故障、中毒、卡顿等问题&#xff0c;或者你想要更换一个新的操作系统&#xff0c;这时候你就需要重装系统。重装系统可以让你的电脑恢复到出厂状态&#xff0c;清除所有的个人…

【vite+vue3.2 项目性能优化实战】打包体积分析插件rollup-plugin-visualizer视图分析

rollup-plugin-visualizer是一个用于Rollup构建工具的插件&#xff0c;它可以生成可视化的构建报告&#xff0c;帮助开发者更好地了解构建过程中的文件大小、依赖关系等信息。 使用rollup-plugin-visualizer插件&#xff0c;可以在构建完成后生成一个交互式的HTML报告&#xf…