本文目录

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

PyQt5桌面应用系列

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

桌面程序基本布局

传统的桌面程序的布局,已经有很多优秀的软件开发这探索很多年。以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 = tvself.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, QTreeWidgetfrom code_editor import QCodeEditor
from py_syntax_highlighter import PythonHighlighterclass 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 = tvself.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():returnif 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 itemif __name__ == '__main__':import sysapp = QApplication(sys.argv)main_window = MyMainWindow()main_window.show()sys.exit(app.exec_())

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

总结

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

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

  1. PyQt5桌面应用开发(10):界面布局基本支持

    本文目录 PyQt5桌面应用系列 布局 利器 游戏 总结 PyQt5桌面应用系列 PyQt5桌面应用开发(1):需求分析 PyQt5桌面应用开发(2):事件循环 PyQt5桌面应用开发(3):并行设计 ...

  2. PyQt5桌面应用开发(17):中文书评+类结构+QWebEngineView

    本文目录 PyQt5桌面应用系列 PyQt5学习 PyQt5类结构和帮助速查 实现与解释 最终界面和完整源代码 界面 完整的代码 总结 PyQt5桌面应用系列 PyQt5桌面应用开发(1):需求分析 ...

  3. PyQt5桌面应用开发(4):界面设计

    本文目录 PyQt5桌面应用系列 前言 为什么又是需求分析? PyQt5的界面设计元素 界面设计元素分类 编译为Python代码使用 转换命令行 组合使用 继承使用方式 直接使用ui文件的方法 总结 ...

  4. PyQt5桌面应用开发(11):摸鱼也要讲基本法之桌面精灵

    本文目录 PyQt5桌面应用系列 鼠标不要钱,手腕还不要钱吗? PyQt5源程序 python文件 资源定义 界面定义文件 技术要素 资源文件 StyleSheets QMainWindow设置 窗体 ...

  5. PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递

    本文目录 PyQt5桌面应用系列 How old are you, Dialog? QInputDialog minimalist why not lambda and how partial wor ...

  6. PyQt5桌面应用开发(2):事件循环

    本文目录 PyQt5桌面应用系列 前言 当君怀GUI日,是妾断肠时 几时得GUI去,依旧作山夫 需求分析 代码 事件循环的基本结构 PyQt5的事件机制 启动事件循环 唯将往来信,遥将连信槽 借问Si ...

  7. python桌面程序开发_程序员之路:python3+PyQt5+pycharm桌面GUI开发

    先看效果: 图 1 没错,学过C#的同学应该很熟悉这个界面,按钮风格和界面风格很相似,万万没想到,python也可以做出这样的界面,简直了!(图 1) 正文开始 一.安装python 为啥要说这个,我 ...

  8. python桌面开发吐血_想用java写个桌面小demo,就布局都差点写吐血了,学艺不精...

    demo简略需求 项目背景 很多文件重复存放,除了管理混乱,还会对患有强迫症用户的身心造成10000点的伤害...其实就是360云盘当时上传了有上传,造成很多重复的图片+视频,前阵子360个人云盘&q ...

  9. 基于Qt Designer和PyQt5的桌面软件开发--环境搭建和入门例子

      本文介绍了如何使用技术栈PyCharm+Qt Designer+PyQt5来开发桌面软件,从环境搭建.例子演示到对容易混淆概念的解释.文中用到的全部软件+代码下载链接为:https://url39 ...

最新文章

  1. This和Super关键字的对比
  2. Linux基础笔记_01
  3. python进行两个大数相加
  4. java制作安卓客户端_制作网页的Android客户端(一)
  5. linux kvm虚拟机配置及常见问题处理
  6. Redis 实现用户积分排行榜
  7. Spring的注入方式详解
  8. linux按键检测程序,Tiny4412 Linux驱动之按键(使用查询方式) | 技术部落
  9. parafac 分解_基于PARAFAC分解的大规模MU-MIMO稀疏信道估计
  10. 基于openstack的自动化测试平台设计头脑风暴
  11. 【生信进阶练习1000days】day8-OrganismDb.dplyr包
  12. deepin驱动精灵_深度Linux Deepin系统安装教程使用体验
  13. 每日一记—获取Bing每日一图实现Android欢迎页(一)
  14. lottie-动画转代码神器
  15. BZOJ4771七彩树——可持久化线段树+set+树链的并+LCA
  16. (vue) 前端实现下载本地Excel模板
  17. 红米手机5 Plus启用root超级权限的步骤
  18. Android 网络属性详解
  19. 红米k30s至尊纪念版参数配置
  20. Himall商城ExpressDaDaHelper订单预发布 查询运费后发单接口

热门文章

  1. mysql查询特定时间数据视频_MySQL如何查询指定时间数据
  2. 从微信头像链接下载图片到服务器
  3. GB28181之国标编码
  4. 基于单片机的红外遥控LED电子钟
  5. springBoot 2.x过滤器--监听器--拦截器
  6. Java类继承(extends)题目练习,求周长,求面积
  7. WordCount编程及执行流程
  8. 通过设置路由器屏避小米电视盒子广告
  9. asp.net1053-走班制排课系统#毕业设计
  10. WordPress阿里百秀XIU v7.5博客主题