1.窗口类型
QMainWindow:可以包含菜单栏、工具栏、标题栏、状态栏,是最常见的窗口形式
QWidgets:不确定窗口的用途,使用QWidgets
QDialog:对话窗口的基类,用于执行短期任务,没有菜单栏、工具栏、状态栏
2.创建窗体应用程序
import sys from PyQt5.QtWidgets import QMainWindow, QApplication class MainWin(QMainWindow): def __init__(self): super(MainWin, self).__init__() self.setWindowTitle("抓取工具") self.resize(400, 300) self.status = self.statusBar() self.status.showMessage('抓取工具') if __name__ == "__main__": app = QApplication(sys.argv) mainWin = MainWin() mainWin.show() sys.exit(app.exec_())
从Main中可知,创建窗体程序,先要创建QApplication对象即应用对象,然后创建窗口对象(继承自三种窗口类型),然后调用窗口的显示函数show(),注意:没有show函数,运行时指挥创建相关进程,即用用程序,窗口不显示,唯有调用show函数,窗口才会显示。
3.在窗体创建控件对象
按钮、输入框、标签等控件基本上都在QtWidgets类中,添加控件代码示例
class MainWin(QMainWindow): path = './app_icon.cio' def __init__(self): super(MainWin, self).__init__() self.init_ui() # 界面初始化 def init_ui(self): # 创建控件对象 self.edit_name = QtWidgets.QLineEdit() self.btn = QtWidgets.QPushButton('退出') # 创建布局 layout = QHBoxLayout() # 往布局中添加控件 layout.addWidget(self.btn) layout.addWidget(self.edit_name) self.btn.clicked.conect(self.btn_click) # 把布局放到窗口上 mainFrame = QtWidgets.QWidget() mainFrame.setLayout(layout) # 居中充满屏幕 self.setCentralWidget(mainFrame) # 设置图标 self.setWindowIcon(QIcon(self.path)) self.setWindowTitle("窗体程序") self.resize(400, 300) self.status = self.statusBar() self.status.showMessage('状态栏')
4.使用QtDesigner进行界面设计
1.配置PyCharm相关开发环境,下载安装相关包,pyqt5以及pyqt5-tools。然后在Pycharm中ExtenalTools中添加
QtDesigner路径:$Python安装路径$\Lib\site-packages\pyqt5_tools\Qt\bin\designer.exe,工作空间:$ProjectFileDir$
PyUIC路径:Python.exe路径,工作空间:$FileDir$,参数变量:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
二者使用时请在Tools -》 ExtenalTools下选择
2.打开QtDesigner设计UI文件,选中UI文件使用PyUIC将其转为Pyton文件。
3.使用UI文件
UI类
from PyQt5 import QtCore, QtGui, QtWidgets # UI执行窗口界面 class Ui_ExecWindow(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(400, 300) self.process_display = QtWidgets.QTextBrowser(Form) self.process_display.setGeometry(QtCore.QRect(50, 50, 300, 200)) self.process_display.setObjectName("process_display") self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Form"))
窗口类
class ExecWindow(QWidget): def __init__(self): QWidget.__init__(self) self.ui = Ui_ExecWindow() self.ui.setupUi(self)
建议二者分开,添加槽和信号的绑定可以在窗口类的初始化函数中书写。例如:
class ConfWindow1(QDialog): def __init__(self, task_type): QDialog.__init__(self) self.ui = Ui_ConfWindow1() self.ui.setupUi(self) self.new_actions = list() self.task_type = task_type # 任务类型 # 信号与槽绑定 self.ui.rbtn_b.toggled.connect(self.input_type) self.ui.btn_save.clicked.connect(self.save_configuration) self.ui.btn_save_single.clicked.connect(self.save_single_action)
ui是UI类的实例对象。
4.重写事件
以关闭窗口事件为例:
def closeEvent(self, event): reply = QMessageBox.question(self, '确认', '确认退出吗', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore()
当窗口关闭时会触发该事件,可以用其他事件绑定窗口对象的close方法进行触发,或其他关闭事件触发
5.界面跳转
事件绑定对应的方法(信号和槽),如单击事件绑定下面函数:(注意如果要传参时请在请在函数(槽)前使用lambda:标记,否则会出错)
请使用self标记窗口类对象为成员变量,否则直接调用show函数会发生闪退现象。
# 信号与槽绑定
self.ui.btn_conf1.clicked.connect(lambda: self.configure_win_open(1))
self.ui.btn_conf2.clicked.connect(lambda: self.configure_win_open(2))
# 打开对应的配置界面 def configure_win_open(self, win_id): if win_id == 1: self.conf_win = ConfWindow1(self.task_type) self.conf_win.show() elif win_id == 2: self.conf_win = ConfWindow2(self.task_type) self.conf_win.show() else: print("没有该窗口")
6.PyQt中多线程处理,解决界面假死问题
当界面要执行一个耗时间较长的任务时,使用单线程会出现界面假死现象。使用多线程可以解决,模板如下
线程子类
# 多线程,执行任务 from PyQt5 import QtCore from PyQt5.QtCore import QThread from selenium import webdriver from application_task.Task import Task from customize_exception.CustomizeException import NoSuchSelectorError from utils import Util class Task_Excutor(QThread): _signal = QtCore.pyqtSignal(str) # 自定义信号 def __init__(self, task_type): super().__init__() self.task_type = task_type def __del__(self): self.wait() def run(self): driver = webdriver.Chrome() actions = [] task_conf = {} # 出现异常传递信号,因为当前线程无法使用GUI try: actions = Util.json_2_element_actions( "./configuration/" + self.task_type + "动作配置.json") task_conf = Util.load_conf("./configuration/" + self.task_type + "杂项配置.json") except FileNotFoundError as e: self._signal.emit(str("e")) except NoSuchSelectorError as e1: self._signal.emit(str("e1")) except Exception as e2: self._signal.emit(str("e2")) else: try: task = Task(driver, actions, task_conf) task.original_task() except Exception as e3: self._signal.emit(str("e3")) else: # 成功信号 self._signal.emit(str("succeed"))
界面调用类中定义的方法
# 任务开始 def start_task(self): self.thread = Task_Excutor(self.ui.task_selector.currentText()) self.thread._signal.connect(self.call_back) self.thread.start() # 信号回调处理 def call_back(self, msg): if msg == "e": QMessageBox(QMessageBox.NoIcon, '错误', '指定路径下配置文件不存在').exec() elif msg == "e1": QMessageBox(QMessageBox.NoIcon, '错误', '配置文件出错,没有指定的选择器').exec() elif msg == "e2": QMessageBox(QMessageBox.NoIcon, '错误', '未知错误,执行过程中出错').exec() elif msg == "e3": QMessageBox(QMessageBox.NoIcon, '错误', '未知错误,初始化动作或配置失败').exec() elif msg == 'succeed': QMessageBox(QMessageBox.NoIcon, '提示', '任务执行成功').exec() else: QMessageBox(QMessageBox.NoIcon, '提示', '未知线程回调消息').exec()
创建一个线程的子类,重写run方法。并定义信号,调用该线程的界面类中添加开始线程的方法start_task(),并与触发线程开始的事件绑定。start_task()方法中为自定义信号绑定方法call_back,用于处理创建的线程的执行任务过程中信号传递处理。
注意:由于pyqt中,非GUI线程是无法调用控件的,如在执行任务中实例化QMessageBox会出现错误导致程序异常退出。所以通过回调函数触发指定的信号并传递到GUI线程中进行控件的处理。