飞道的博客

PyQt5 写个锤子代码,画就完了

541人阅读  评论(0)

写在前面

前些天跟同学交流,谈到了 PyQt5,说是曾经看过这东西,觉得难学麻烦。我也是最近用到才学的,总共耗时三天,前两天时间看官方文档,第三天发现了 Designer 这东西,内心呐喊,这还写个锤子代码!!

本文偏向于应用型,对于一些概念性的东西没有太多的深究。从基本介绍到多窗口的应用,演示了一些常用的功能,基本上学完这些就可以了。想看生成代码部分的可以直接跳到相应模块。


一、QT Designer

1.1 介绍与安装

QT Designer 就是一个软件,用处是创建窗口,拖拉控件,生成 .ui 文件,再由.ui 文件生成 .py 的可调用代码。省去了自己写 ui 的功夫。

在 python 中叫 pyqt5designer,由 pyqt5-tools 包集成,安装简便,安装完之后可以在 python 安装目录的 scripts 里找到它。

pip3 install pyqt5-tools

1.2 基本使用

1. 新建窗口

依次点击左上角 File -> New Project 弹出一个新建窗口的对话框,templates\forms 中有五个模板,对应的是PyQt5中的同名窗口类。Widgets是一些控件,实际上就是左栏中的物件,可以选择在新建窗口时就添加,也可以在窗口创建完后一点点拖拽完善。
Widget 窗口:一个干净的窗口,在右侧可以看到只继承了基类,对应的是 PyQt5 的 QWidget。
MainWindow 窗口:一种软件常用式框体,基于 Widget 增加了不少功能,对于菜单栏,状态栏等需求更便捷地实现,对应的是 PyQt5的 QMainWindow。

Dialog without Buttons、Dialog with Buttons Right、Dialog with Buttons Bottom,这三者都是同一类型,属于 Dialog 对话窗(一个弹窗),区别是带不带两个按钮,按钮的位置是右边还是底部。在实际调用中 Dialog 窗口不包含点击最大化。
2. 拖拽使用

用例窗口常用 QWidget 就可以了,窗口创建后自行拖拽控件添加使用。控件种类繁多,足够有想象力,就可以快捷做出一个非常好看的界面。

右侧为控件的属性,相应的属性来自对应继承的类,字意明了,可以根据属性名判断出用作什么。下表为比较重要的几个属性以及注解。

属性名 属性值 说明 继承自
objectName Designer 根据这个来命名实际调用时使用的控件名 QObject
geometry X, Y, Width, Height 位置与大小设置。x, y 为坐标,默认以窗口左上角为原点0,0 余下两值为宽度与高度 QWidget
sizePolicy 详见本文布局模块 QWidget
minimumSize width, height 最小不能小于 width x height QWidget
maximumSize width, height 最大不能大于 width x height QWidget
windowTitle 窗口名,显示在左上角 QWidget

二、布局与预设

PyQt5 默认无布局,即以坐标为参照的绝对定位,按钮的大小位置固定,在不同大小的设备上显示非常麻烦。使用布局,控件按一定规则自动缩放,省点麻烦。

2.1 四大布局

  1. Vertical Layout:垂直布局,使用了垂直布局部分的整一个框体,所有控件都将在纵向依次排列,横向只能有一行。
  2. Horizontal Layout:水平布局,使用了水平布局部分的整一个框体,所有控件都将在横向依次排列,纵向只能有一列。
  3. Grid Layout :网格布局,使用了网格布局部分的整一个框体,所有控件都将按照网格依次排列,纵向与横向都能有无数列和行,在同一列的所有控件,列宽一致,同理在同一行的所有控件,行高一致。
  4. Form Layout:表格布局,与网格布局类似,区别在于该布局单一控件可以直接横跨几个列或几个行,常用于文章的评论提交等。当表格中控件对称,自动收紧控件,当表格中控件不对称,将自动扩张至整个框体。

引用办法:点击控件后,点击菜单栏 Form 即可看到 布局 Lay Out

2.2 sizePolicy

上一节说到,在控件的属性中有一个sizePolicy,该属性是用于布局时的大小策略,亦或者称之为尺寸策略。该策略是作用在每一个控件内的,也就是说每一个需要策略的控件都应该设置好策略。在该属性中有四个值,H…,V… 分别对应水平方向与垂直方向的设置。

这里首先引入一个 sizeHint 概念,缺省值,即默认大小。在初始化的时候,所有的控件默认都有一个 sizeHint。

Horizontal Policy:水平策略
Vertical Policcy:垂直策略

两个策略都有 7 个值:
Fixed:固定大小,为 sizeHint
Minimum:最小不能小于 sizeHint,如果设置了 minimumSize则 minimumSize 为 sizeHint
Maximum:最大不能超过 sizeHint,如果设置了 maximumSize则 maximumSize 为 sizeHint。
Preferred:优先策略,相对于其他策略优先扩张
Expanding:扩展策略,在允许的范围内如两个控件中间的间距充足,则会利用该额外空间进行扩张
MinimumExpanding:最小可扩展策略,在满足最小 sizeHint 的情况下使用 Expanding
Ignored:忽略 sizeHint,控件尽可能地利用所有空间

这一块没有深究,所言不保证精准到一字不差,也不打算深扒,在使用的时候按实际设置为准,不合适的时候换一个策略即可。

接着是两个 Stretch,分别是水平和垂直拉伸占比,默认值为0。当设置了Stretch 时,在满足策略的情况下,同一横向或同一纵向的所有控件按照拉伸占比来对可用空间进行划分。如下图所示,三个按钮都是 Preferred 策略,都遵循了自身设置的拉伸占比对可用空间进行了划分。PS:Stretch 大部分人叫伸缩因子,但这实在不够具体,本文称之为占比。

如果拉伸占比为0,视为不参与到按比例的分配,一旦同方向的任意控件拉伸占比不为0,则该控件被压缩至最小,情况如下:

2.3 布局间距(margin)

(8.22补)
在上一小节中可以明显看到,画篮圈的位置间距十分大,实际使用体验并不好。这里补充一个margin,外边距,如你所见,与css中的那个margin一个意思。使用了布局后在属性栏底部会出现一个 layout 属性栏,其中包含了上下左右四个方位的外边距、以及水平和垂直的空间大小,默认是 9 9 9 9 6 6 ,根据实际需要来调整大小。需要注意,在PyQt5 中只有 margin,而不存在 padding。

三、布局与Group box

Group box 是一个将控件划分为组的框体,该框体所占的空间与主窗口无关,为独立空间。无论主窗口多大,group box 都独立拥有无限大的空间(如不设置布局,超出目前group box 范围的控件将无法显示)。

在Group box 中可以使用与主窗口不一样的布局。如下图所示,主窗口中使用了网格布局,而两个 Group box 分别使用了垂直布局与水平布局。

四、生成代码:UI与逻辑分离式示例

在这一节开始之前必须要提一句,UI 和 逻辑或是控制函数必须分离,这跟前后端分离一个理,想想稍微改改 UI 还要改逻辑代码,累不累呐。在这一节中要做的是一个不管怎么改 UI,后期直接覆盖整个代码都不影响调用的效果。

为了展示实例,这里我用这样的 UI 来生成:

4.1 UI 生成代码

  1. 使用 Designer 画好你的 UI,保存后得到一个 .ui 文件
  2. 使用命令 pyuic5 -o name.py name.ui 生成 .py 文件(name.py 为即将生成的.py文件,name.ui 就是你的 UI 文件),pyuic5是一个exe,也在开头说的 scripts 下。
  3. 得到一个 name.py 文件,里面就是UI的所有代码

实例代码如下:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(285, 112)
        self.pushButton = QtWidgets.QPushButton(Form)
        self.pushButton.setGeometry(QtCore.QRect(30, 50, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.lineEdit = QtWidgets.QLineEdit(Form)
        self.lineEdit.setGeometry(QtCore.QRect(120, 50, 131, 20))
        self.lineEdit.setObjectName("lineEdit")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.pushButton.setText(_translate("Form", "按钮一"))

4.2 分离式调用UI

从上一节生成的代码中可以看到,该 UI 类由两部分组成,setupUiretranslateUi,前者是整一个窗口的结构代码,后者主要对窗口里控件的 text 值、字体、颜色等进行了变更。更多的还有多语言的标记,当然这是题外话了,如有需要可以在我的博客中找到。

调用与显示窗口,在另一个类中引用之前生成的 UI 代码,这样做的好处便是前面提到的分离,好处已经说过了。在这段代码中继承的是 QWidget 类,继承的窗口类才是决定你窗口种类的真正因素。

import sys

from PyQt5.QtWidgets import QApplication, QWidget

from name import Ui_Form


class NameController(QWidget, Ui_Form):

    def __init__(self):
        super().__init__()
        self.setupUi(self)			# 初始化时构建窗口


if __name__ == '__main__':
    app = QApplication(sys.argv)    # 实例化一个 app
    window = NameController()       # 实例化一个窗口
    window.show()                   # 以默认大小显示窗口
    sys.exit(app.exec_())           # 运行 app 并在 app 退出时做一些清理工作

增加一个小功能,使得点击按钮将输入框的值设置为 “hello world”,修改代码如下,这个时候可以很明显觉察到分离的好处,完全不用看着又长又臭的 UI 代码,赏心悦目才是一个 Pythonist 该做的。

class NameController(QWidget, Ui_Form):

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.controller()

    def controller(self):
        # 将点击信号绑定到槽函数
        self.pushButton.clicked.connect(self.hello_world)

    def hello_world(self):
        self.lineEdit.setText('hello world!')

五、表格与设取值

5.1 表格的使用

使用一个表格,你应该选取的控件是 Table Widget。表格默认是 0 行 0 列,所以在最开始表格将显示为空白,双击表格来添加默认的列和行,当然在实际使用中,更多的是动态增加,这并没有什么大用处,除了好看点。

5.2 表格的取值与设值

在上表的基础上,我将增加两个小按键来做演示,都是 Designer 生成的代码,从这开始到文末,不再贴出 UI 部分代码。

这一块写的比较简单,主要用的是坐标定位,去设值或取值。无论是获取和设置,都需要传入一个坐标。

import sys

from PyQt5.QtWidgets import QApplication, QWidget, QTableWidgetItem

from formTest import Ui_Form


class FormController(QWidget, Ui_Form):

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.controller()
        # 存放表格行数,此处默认有2行
        self.rowCount = 2

    def controller(self):
        self.pushButton_add.clicked.connect(self.add_row)                  # 好的命名让你省掉不少功夫
        self.pushButton_read.clicked.connect(lambda: self.read_row(1))     # 使用匿名函数来传递参数
        self.pushButton_set.clicked.connect(lambda: self.set_row(1))

    def add_row(self):
        # 增加一行,并更新 rowCount
        self.tableWidget.setRowCount(self.rowCount + 1)
        self.rowCount += 1

    def read_row(self, row):
        # 读取指定行
        value1 = self.tableWidget.item(row, 0).text()
        value2 = self.tableWidget.item(row, 1).text()
        print(value1, value2)

    def set_row(self, row):
        item = QTableWidgetItem('我在这')
        item2 = QTableWidgetItem('我在这2')
        # setItem 里前面为行与列的坐标,左上角第一格为 0,0
        self.tableWidget.setItem(row, 0, item)
        self.tableWidget.setItem(row, 1, item2)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = FormController()
    window.show()
    sys.exit(app.exec_())

如果你想着让代码更加灵动,根据当前已有的列来调整,可以适当加入一点循环,此处用读取一行中的值来做演示,将代码修改如下,其中 columnCount 是 tableWidget 中获取当前所有列的方法,除此之外都是些 python 基础了,不多做讲解。

def read_row(self, row):
    for col in range(self.tableWidget.columnCount()):
        print(self.tableWidget.item(row, col).text())

六、堆叠布局

堆叠布局,也叫切换页面,通俗来讲,就是点击某个按钮,切换到对应的页面。这些页面实际上是一个已经存在的独立页面,显示在一个约束好的区域里。在这个过程中你需要一个 frame 来承载页面;两个页面约定显示在frame 里面;如下图所示:

代码的流程大致是这样的:

  1. 绑定堆叠布局到 frame,也就是前面所说的约定好的区域
  2. 实例化两个独立窗口
  3. 将两个窗口加入到布局中
  4. 绑定槽函数到主页面的按钮中
  5. 点击切换

这里面需要注意的点是两个窗口的引用,使用 Designer 画出来的 UI 在实际引用前都不是一个窗口类的,必须在继承了一个窗口类如 QWidget 后才能被布局接收。

import sys

from PyQt5.QtWidgets import QApplication, QWidget, QStackedLayout

from mainPage import Ui_main
from page1 import Ui_page1
from page2 import Ui_page2


class FramePage1(QWidget, Ui_page1):

    def __init__(self):
        super().__init__()
        self.setupUi(self)


class FramePage2(QWidget, Ui_page2):

    def __init__(self):
        super().__init__()
        self.setupUi(self)


class MainController(QWidget, Ui_main):

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        # 设置堆叠布局到 frame
        self.stackedLayout = QStackedLayout(self.frame)

        # 实例化两个页面,但不显示
        self.page1 = FramePage1()
        self.page2 = FramePage2()

        # 添加到布局中
        self.stackedLayout.addWidget(self.page1)
        self.stackedLayout.addWidget(self.page2)

        self.controller()

    def controller(self):
        # 页面一
        self.pushButton_page1.clicked.connect(self.switch1)
        # 页面二
        self.pushButton_page2.clicked.connect(self.switch2)

    def switch1(self):
        self.stackedLayout.setCurrentIndex(0)    # 索引按加入布局的顺序

    def switch2(self):
        self.stackedLayout.setCurrentIndex(1)

整体效果:


写到这里就告一段落了,一篇文章谈不尽,还需多动手实践。有问题的在下方评论留言讨论。designer 可以实现绝大部分常规,有需求使用高级用法的,推荐 csdn博主 la_vie_est_belle 的专栏 《快速掌握PyQt5》

那么,恭喜你又学完了一课,我是庸了个白,你的点赞将是我更新的最大动力。


转载:https://blog.csdn.net/qq_39177678/article/details/107423644
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场