非常好用的图像界面开发框架-PyQt5初体验

3,461 阅读13分钟

pyqt5是pyhon的一个gui界面程序开发,对于测试而言,了解和掌握它,可以有利于理解前后端项目的开发,也可以利用它做一个小工具,qt的designer界面设计工具快速的设计出想要的界面,把重心放到逻辑开发上。对于没有开发经验的测试来说,非常适合,掌握后可以将以前学的零散的python知识运用起来写程序。

废话不多说了,下面我开始介绍pyqt5的

Pyqt5 环境搭建

1 . 安装pytqt 5 库,和pyside 2

pip install pyqt5 -i pypi.douban.com/simple 

pip install PySide2 -i pypi.douban.com/simple/ --trusted-host pypi.douban.com

2 . pycharm关联qtdesigner工具, 和pyqt5 uic转化工具

qtdesigner路径选择site-packages中的pyside2中的designer

image.png Pyqt5uic路径选择script中的pyuic5.exe

image.png

$FileName$ -o $FileNameWithoutExtension$_UI.py   为了规范,加了一个_ui ,表示它是ui文件转化的
$FileDir$

使用时表示在当前路径下,执行了 pyuic5 -o xx_ui.py xx.ui

3. Pycharm中打开designer和ui文件转化

image.png

image.png

二、基本代码结构

目标:界面代码和逻辑分离。

1. 使用desinger界面设计,并保存ui文件

注:新建时选择 Widget

image.png

2. Ui文件转py文件,得到如下

image.png 补充如下代码,可以运行界面程序(如果是Wiget)                               

if __name__ == '__main__':     
    import sys                                            
    app = QtWidgets.QApplication(sys.argv)                   
    Window = QtWidgets.QWidget()        # 创建一个窗口
    ui = Ui_Form()                      # 获取ui对象
    ui.setupUi(Window)                  # 表示ui界面,加载到此窗口上
    Window.show()                       # 展示界面
    sys.exit(app.exec_())

    如果是mainwidow

if __name__ == '__main__':
    import sys
    app =QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui =Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

image.png

3. 编写该界面的逻辑代码

新建一个py文件

image.png 如下:实现,【点击按钮,就打印输入框中的内容】,前提,ui文件新建的是Widget


```js
from PyQt5.Qt import *
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from test_case.test import Ui_Form
class Login(QWidget,Ui_Form):
    def __init__(self,parent=None,*args,**kwargs):
        super().__init__(parent,*args,**kwargs)
        self.setupUi(self)
        self.func_list()
    def func_list(self):
        self.func()
    def func(self):
        pass
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Login()
    window.show()
    sys.exit(app.exec_())

如果ui文件是新建的main window 类要继承QtWidgets.QMainWindow, 代码如下, 下面的写法也兼容上面的Widget的ui文件

from PyQt5.Qt import *
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from sq_autotest_tool_ui import Ui_MainWindow
class Login(QtWidgets.QMainWindow,Ui_MainWindow):
    def __init__(self,parent=None,*args,**kwargs):
        super().__init__(parent,*args,**kwargs)
        self.setupUi(self)
        self.func_list()
    def func_list(self):
        self.func()
    def func(self):
        pass
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Login()
    window.show()
    sys.exit(app.exec_())

小技巧,可以给以上代码设置一个实时代码:

image.png

image.png

4. 编写主程序代码

一个程序不光一个界面,主程序代码中就控制该展示哪个界面

image.png

三、QObject

1 . 所有东西都继承O bject 对象类****

每一个东西都有一个唯一的objectName, 在desiner中可以发现。

也可以用  实例.inherits("A类")   # 实例是否继承于A类

按钮、标签、窗口等都要设置objectName

image.png

image.png 如果是designer设置的ui文件,转化成py文件后, self。ObjectName就代表 那个控件,如下:

image.png

相关代码:

对象. objectName() # 货物 objectname
对象.setProperty('level1', '第一')
对象.setProperty('level2', '第二')
对象.property('level1') # 获取property设置的某个属性的值
对象.dynamicPropertyNames() # 获取所有 setProperty设置的属性的对象

这里涉及 QSS样式中的 id选择器, 和  属性选择器。

2. 所有东西(控件等)公用方法-删除

代码中,如果想删除某个控件,用  对象.deleteLater(),  控件被删除时,会触发被摧毁信号, 如:对象.destroyed.connect(lambda: print('对象被释放'))

3. 所有东西(控件等)公用方法-定时器

使用 time_id=对象.startTimer(1000)  设置定时间隔1秒  的 定时器对象 在类下面重写事件 timerEvent() ,可以设置定时做的事情。    停止计时器:对象.killTimer(time_id)         # 参数为 定时器对象 基本示例

from PyQt5.Qt import *
import sys
class Obj2(QObject):
    def timerEvent(self, QTimerEvent):      # 重写事件  (专门的参数用来接东西的)
        print(QTimerEvent)
        print("我是另一个Qbject继承类的定时器事件")
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QWidget()
    obj2=Obj2()
    obj2.startTimer(2000)
    # obj2.killTimer(timer_id)           # 终止定时器(如:可以在某次判断后给它终止)
    window.show()
    sys.exit(app.exec_())

如果,要给标签或者按钮设置定时器,就自己写个子控件类,标签或者按钮

标签设置定时器案例

由于,timerEvent在子标签类中,重写了,只要是 字标签类的对象 都能使用这个定时器

image.png

from PyQt5.Qt import *
import sys
class Window(QWidget):
    def __init__(self):
        super().__init__()  # 调用父类QWidget中的init方法
        self.setWindowTitle("软件名称")
        self.resize(600, 500)
class Label(QLabel):
    def __init__(self, *args, **kwargs):        # 这样能接收各种参数
        super().__init__(*args, **kwargs)       # 保留父类的方法
        self.resize(80, 40)
        self.move(100, 100)
        # self.setText('10')
        self.setStyleSheet("background-color:#02CDB9;font-size:25px")
        # self.timer_id=self.startTimer(1000)
    # 定义初始时间
    def setSec(self,sec):
        self.setText(str(sec))
    # 定义到计时时间间隔
    def ms(self,ms):
        self.timer_id = self.startTimer(ms)
    def timerEvent(self, *args, **kwargs):
        print('事件案例演示')
        # 获取text中的数据
        sec = int(self.text())
        # 倒计时
        sec -= 1
        self.setText(str(sec))
        # 停止计时
        if sec == 0:
            self.killTimer( self.timer_id)        #timer_id 是全局的变量,所以这里可以用
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Window()
    label = Label(window)
    label.setSec(20)        # 设置初始时间
    label.ms(1000)          # 设置跳转时间
    # timer_id = label.startTimer(1000)
    # # label.killTimer(timer_id)
    window.show()
    sys.exit(app.exec_())

下面是一个无用的例子,可以实现按钮定时的去点击

image.png

四、事件、信号和槽(动作)

要完成对程序的操作,需要用鼠标和键盘对程序某些事件,

触发事件后,就会释放默认的一些信号,或者也可以自定义信号,

通过信号,去触发接下来的动作,也就是pyqt5的核心,信号和槽。

 

1.事件

举例:按钮类QPushButton 的一些事件。

有点击,双击,按下,释放等。

查看事件代码中点击QPushButton 查看事件

image.png

image.png qtdesiner 也可以查看信号猜测有哪些事件

查看下面效果

1.可以发现程序会先分发到event方法,再往下分发

  1. 可以去修改按下事件和双击事件
from PyQt5.Qt import *
import sys

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('事件机制')
        self.resize(600, 450)
        self.move(300, 300)
class Btn(QPushButton):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.move(60, 60)
        self.resize(50, 35)
        self.setText('按钮控件')
        self.setStyleSheet('background-color:green')
    # # 重写event方法
    def event(self, evt):           # 之前的功能是会分发 到不同的信号
        # print(evt,"任何一个信号都要经过event方法", 'WWWWWWWWWW')
        return super().event(evt)       #有这句才可以向下分发(鼠标按下事件)
    def mousePressEvent(self, evt):
        # print("鼠标按下事件")
        return super().mousePressEvent(evt) #      把父类的  同一个方法向下 分发
    def mouseDoubleClickEvent(self,evt):
        print("双击")
        return super().mouseDoubleClickEvent(evt)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Window()
    btn = Btn(window)
    window.show()
    sys.exit(app.exec_())

窗口QWidget当然,也有一些事件,如,鼠标右击事件,简单看看效果如下:

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('事件机制')
        self.resize(600, 450)
        self.move(300, 300)
    def contextMenuEvent(self, evt):        # 重写方法,名字要写对
        print("在窗口中点击右键")
        return super().contextMenuEvent(evt)

利用这点,再结合QMenu可以实现右击后出现菜单的功能。 前面Qobject提到的定时器,也是一个事件。

2.信号,以及传递参数

信号在释放时,可以不带参数,也可以带参数。比如:

a. 单选按钮进行点击事件时,可以传递点击时是否选中的Bool值

b. 文本输入框编辑完关闭离开事件时,会传递 编辑文本(str格式)

c. 多选框,切换选项事件时,当前文本改变信号时,会传递所选的文本(str)

d. 数字调节器, 调节数值时,值改变信号,传递值(传的可以是整形,也可以是文本) 在qtdesiner 中,可以去查看不同控件的信号,以及信号要传递的参数

image.png 如 数值步长调节器,文本改变信号时,可以传递整形,也可以传递字符串,就可以在信号后面加中括号【】。如下写法:

self.qsb.valueChanged[int].connect(lambda val: print(type(val)) # 信号传出来的时 int类型\
self.qsb.valueChanged[str].connect(lambda val: print(type(val))) # 信号传出来的时 str类型

自定义信号

通过改变事件方法,在事件中用pyqtSignal().emit()发出信号。

如下例子,在按钮子类中, 改变按钮按钮事件 (变成鼠标右键按下)

class Btn(QPushButton):
    # 右键信号
    rightSignal = pyqtSignal()  # 先定义好一个信号,然后寻找这个信号所属事件
    def mousePressEvent(self, evt):
        super().mousePressEvent(evt)
        # print(evt.button()) # 可以看出,左键点击是1 ,右键点击是2
        # print(evt)     #<PyQt5.QtGui.QMouseEvent object at 0x0000022ED32583A8>  可以看出他是 QMouseEvent 里面的
        #evt.button() 为2 表示右键
        # if evt.button() == 2:
        # Qt.RightButton 也表示2
        if evt.button() == Qt.RightButton:      # 点进去看,也可以知道是2
            # print('鼠标右键被按下')  # 这是用来测试右键是否生效,不是槽函数功能
            self.rightSignal.emit()  # 通过emit函数来把之定义的信号发射出去

以上例子,不用多在意,接着看信号传递参数, 理解就好,方便对控件的信心进行理解

a. 定义信号        (表示,信号可以释放的数据类型)

i. rightSignal = pyqtSignal([str],[str,int])  

b. 释放信号   (要传递哪种数据类型的参数,就在信号对象后写 【】)如下

i. self.rightSignal[str].emit("一个参数")

ii. self.rightSignal[str,int].emit(“一个参数”,555)

c. 信号连接槽函数 (使用时,同样是写哪个类型,就能释放哪个信号),如下

i. btn.rightSignal[str,int].connect(槽函数)    

ii. btn.rightSignal[str].connect(槽函数)

装饰器实现信号和槽连接,对于其他信号能否也生效,未做研究 :如下简单的例子:

image.png

3.槽函数

如果信号传递出了参数,定义槽函数时,对应的槽函数,需要能接收到,

即:定义的槽函数需要传递参数,如下

def get_msg(a,b)
    pass
def get_msg2(a)
    pass
对象.sendmsged[str,int].connect(get_msg)  
对象.sendmsged[str].connect(get_msg2)

四、QWidget

常用事件

1.窗口事件

  1. QWidget有窗口事件,可以在定义QWidget子类时,重新这些事件,查看效果

 窗口打开事件showEvent、

 窗口关闭closeEvent事件、

 窗口缩放resizeEvent事件,

2.鼠标事件

2. QWidget的一些鼠标的事件,例子如下

鼠标按下mousePressEvent,鼠标松开mouseReleaseEvent,

鼠标双击 mouseDoubleClickEvent 鼠标移动mouseMoveEvent

鼠标进入enterEvent,

鼠标离开 leaveEvent

鼠标右击 contextMenuEvent

from PyQt5.Qt import *
import sys
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("获取和设置控件尺寸、大小以及控件大小尺寸限定")
        self.resize(600,500)
        self.func_list()
    def enterEvent(self, QEvent):
        print('鼠标进入事件')
        self.setStyleSheet("background-color:red;")
    def leaveEvent(self, QEvent):
        print('鼠标离开事件')
        self.setStyleSheet("background-color:green;")

    def contextMenuEvent(self, evt):
        super().contextMenuEvent(evt)
        # 重写方法,名字要写对
        print("在窗口中点击右键")
        print(evt)

    def func_list(self):
        self.func()
    def func(self):
        pass

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Window()
    """针对mouseMoveEvent的设置成---- 不按下鼠标。也能鼠标跟踪移动"""
    # window.setMouseTracking(True)
    window.show()
    sys.exit(app.exec_())

鼠标按下事件,可以从evtent中获取 点击的位置

image.png

3.键盘事件

在Qwidget子类中 ,需重写事件,注意的是, 拼接 虚键用  | ,  拼接实键用  and

# 键盘按下
    def keyPressEvent(self, QKeyEvent):
        if QKeyEvent.key() == Qt.Key_5:     #
            print(' 按5键')
        if QKeyEvent.modifiers() == Qt.ControlModifier and QKeyEvent.key() == Qt.Key_C:
            print('按 ctrl+c')
        # ctrl+ shift+ c  (两个虚键用 或者  |     拼接 实键用 and  )
        if QKeyEvent.modifiers() == Qt.ControlModifier | Qt.ShiftModifier and QKeyEvent.key() == Qt.Key_C:
            print('ctrl+ shift+ c')
        print("随便按")
    # 键盘松开
    def keyReleaseEvent(self, QKeyEvent):
        print('释放')

常用方法

对象.setMinimumSize(200, 200)     #  允许最小尺寸
对象.setMaximumSize(500, 600)     #  允许最大尺寸
对象.setGeometry(50, 200, 60, 30)   # 设置位置和大小

对象..setContentsMargins(50, 50, 10, 0)  # 设置控件边界(如下图)
print(label.contentsRect())             # 获取内容尺寸对象 (相对位置,大小)


label2.lower()  # 置于底层        见图(层级关系)
label1.raise_()  # 置于最上层
label2.stackUnder(label1)  # 对象2置于 对象1之下
 
对象.childAt(2, 2)       #查看 window 的 (2,2)处的 子控件
对象.parentWidget()       # 查看他的父控件
对象.childrenRect()       # 获取所有子控件,相对于本身的边界

image.png

image.png

image.png

image.png 常用方法系列2

# btn.setEnabled(False)         # 是否可用
# btn.setVisible(False)        # 是否显示
# btn.setHidden(False)          # 是否隐藏
# window.show()        # 展示,  父window展示,子控件按钮也展示了
# window.hide()          # 隐藏主窗口
btn.hide()                # 隐藏按钮

"""允许在title中写字符串 [*] 展示出来是  * """
window.setWindowModified(True)        # 表示为编辑状态没啥用

"""是否活跃"""
window1.show()
window.show()       # 代码在后面的,层级在上面, 为活跃状态
print(window.isActiveWindow())      # 层级在上,为活跃状态
print(window1.isActiveWindow())

self.setWindowFlags(Qt.FramelessWindowHint)  #无边框

鼠标样式设置

image.png 自定义图标:使用QPixmap和QCursor

pixmap= QPixmap('aaa.png')
new_pixmap = pixmap.scaled(20,20)   # 设置鼠标样式图片的大小
cursor = QCursor(new_pixmap,1,1)    # 表示鼠标变样式时的一个 过度范围
window.setCursor(cursor)

设置最大化最小化按钮

假如要设置无边框的东西,即没有上面的部分,它就不能拖动,也不饿能最大化,最小化了,这个时候自己来定义定义

self.setWindowFlags(Qt.FramelessWindowHint)     # 设置成无边框

image.png 要使用的代码如下

按钮相关

close()  关闭
showMinimized()  最小化
self.showMaximized()   最大化
self.isMaximized()   是否最大化
self.showMaximized()  是否最小化
self.showNormal()    # 恢复


拖动相关
要修改按下事件,修改移动事件,修改松开事件
逻辑:鼠标按钮下记录 相对坐标和全局坐标
鼠标移动,找到移动后的全局坐标,计算移动量,让相对坐标移动
释放事件, 让移动都在一个标记下进行,鼠标释放,让它

image.png 再设置个透明度

from PyQt5.Qt import *
import sys
class Window(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowFlags(Qt.FramelessWindowHint) # 无边框
        self.setWindowOpacity(0.7)  # 半透明,必须是浮点型
        self.setWindowTitle('窗口案例')
        self.resize(600, 450)

        self.mouse_press = False        # 初始的标记,鼠标按下之后改变标记
        # self.move(300, 300)
        self.btn_w = 50
        self.btn_x = 20
        self.func_list()
        
    def func_list(self):
        self.btn()
        
    def btn(self):
        # 添加最大化、最小化和关闭按钮
        self.close_btn = QPushButton(self)
        self.close_btn.setText('关闭')
        self.close_btn.resize(self.btn_w, self.btn_x)

        self.max_btn = QPushButton(self)
        self.max_btn.setText('最大化')
        self.max_btn.resize(self.btn_w, self.btn_x)
        self.min_btn = QPushButton(self)
        self.min_btn.setText('最小化')
        self.min_btn.resize(self.btn_w, self.btn_x)

        self.close_btn.pressed.connect(self.close)          # 关闭按钮 的 按下信号   触发  关闭槽函数

        def max_signal():
            if self.isMaximized():                    # 如果 是最大化时:
                self.showNormal()
                self.max_btn.setText('最大化')
            else:                                       # 如果是正常时:
                self.showMaximized()
                self.max_btn.setText('恢复')
        self.max_btn.pressed.connect(max_signal)

        self.min_btn.pressed.connect(self.showMinimized)        # 最小化按钮 的 按下信号   触发  展示最小化

 
    """窗口大小变化,也可以跟着自由变化"""
    def resizeEvent(self, QResizeEvent):
        self.close_btn.move(self.width() - 50, 2)
        self.max_btn.move(self.width() - 100, 2)
        self.min_btn.move(self.width() - 150, 2)

    """鼠标按下记录起始位置"""
    def mousePressEvent(self, QMouseEvent):
        if QMouseEvent.button() == Qt.LeftButton:       # 左键才能移动
            self.mouse_press = True  # 需要在属性中先定义为False
            self.win_x = self.x()                       # 鼠标按扭下的 坐标(窗口相对位置)
            self.win_y = self.y()
            self.m_x = QMouseEvent.globalX()            # 鼠标按扭下的 坐标(屏幕全局)
            self.m_y = QMouseEvent.globalY()
            
            # 1.创建一个标记,用来判定鼠标只有在按下之后才能移动
            # 2.窗口的原始坐标
            # 3.鼠标按下的坐标


    """鼠标移动根据全局坐标移动量,来让按下的起始位置移动"""
    def mouseMoveEvent(self, QMouseEvent):
        if self.mouse_press:
            move_x = QMouseEvent.globalX()         # 鼠标移动后的 全局坐标(屏幕全局)
            move_y = QMouseEvent.globalY()

            # 移动量
            xx = move_x - self.m_x
            yy = move_y - self.m_y
            self.move(self.win_x + xx, self.win_y + yy)

        # if 窗口标记 == True:
        # 2.根据鼠标按下的点计算移动量
        # 3.根据移动量和窗口的原始坐标得到新坐标
        # 4.移动窗口位置

    """ 松开后,把标记改为False ,这样就保证了按下的时候才会移动"""
    def mouseReleaseEvent(self, QMouseEvent):
        self.mouse_press = False
        # 1.把mousePressEvent中创建的标记重置为False

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

焦点控制

默认可以用鼠标和tab切换 焦点

# TabFocus  只能使用Tab键才能获取焦点
# ClickFocus  只能使用鼠标点击才能获取焦点
# StrongFocus 上面两种都行
# NoFocus  上面两种都不行
led2.setFocusPolicy(Qt.ClickFocus)
 led2.setFocus()      # 设置一个焦点

对于qt5的各种控件总结,我会用界面工具设计出一个学习和查看的工具出来,已经完成了一部分,如下

image.png

qt5.GIF

后续回接着更新,感谢支持!

-----本人是一个测试菜鸟,理解不到位还请包涵。