1.2 PySide6窗口的运行机理

窗口是图形用户界面(GUI)程序开发的基础,我们平常所见的各种图形界面都是在窗口中放置不同的控件、菜单和工具条,实现不同的动作和目的。图形界面程序开发就是在窗口上放置不同类型的控件、菜单和工具条按钮,并为各个控件、菜单和工具条按钮编写代码使其“活跃”起来。因此要进行图形界面开发,必须首先理解PySide6中窗口产生的机理和运行方法,之后再在窗口中添加各种控件。

1.2.1 关于QWidget窗口

PySide6的QtWidgets模块集中了可视化编程的各种窗口和控件,这些窗口和控件一般都是直接或间接从QWidget类继承来的。继承自QWidget类的控件按照功能可以分成如图1-9所示的分类,进行可视化编程就要熟悉这些控件的方法、属性、信号及槽函数,以及控件的事件和事件的处理函数。QWidget是从QObject和QPaintDevice类继承而来的,QObject类主要实现信号和槽的功能,QPaintDevice类主要实现控件绘制的功能。

QWidget类通常用作独立显示的窗口,这时窗口上部有标题栏,QWidget类也可以当作普通的容器控件使用,在一个窗口或其他容器中添加QWidget,再在QWidget中添加其他控件。当一个控件有父窗口时,不显示该控件的标题栏;当控件没有父窗口时,会显示标题栏。常用于独立窗口的类还有QMainWindow和QDialog,它们都是从QWidget类继承而来的,关于QWidget、QMainWindow和QDialog窗口的详细内容参见第3章。

图1-9 继承自QWidget的类

1.2.2 QWidget窗口的初始化类

在创建QWidget窗口对象之前,需要先介绍一个QApplication类。QApplication类的继承关系如图1-10所示,QApplication类管理可视化QWidget窗口,对QWidget窗口的运行进行初始化参数设置,并负责QWidget窗口的退出收尾工作,因此在创建QWidget窗口对象之前,必须先创建一个QApplication类的实例,为后续的窗口运行做好准备。如果不是基于QWidget窗口的程序,可以使用QGuiApplication类进行初始化,有些程序通过命令行参数执行任务而不是通过GUI,这时可以使用QCoreApplication类进行初始化,以避免初始化占用不必要的资源。

图1-10 QApplication类的继承关系

QApplication类是从QGuiApplication类继承来的,QGuiApplication类为QWidget窗口提供会话管理功能,用户退出时可以友好地终止程序,如果终止不了还可以取消对应的进程,可以保存程序的所有状态用于将来的会话。QGuiApplication类继承自QCoreApplication类,QCoreApplication类的一个核心功能是提供事件循环(event loop)。这些事件可以来自操作系统,如鼠标、定时器(timer)、网络,以及其他原因产生的事件都可以被收发。通过调用exec()函数进入事件循环,遇到quit()函数退出事件循环,退出时发送aboutToQuit()信号,类似于Python的sys模块的exit()方法。当某个控件发出信号时,sendEvent()函数立即处理事件,postEvent()函数把事件放入事件队列以等待后续处理,处于队列中的事件可以通过removePostedEvent()方法删除,也可通过sendPostedEvent()方法立即处理事件。

由于QApplication类进行可视化界面的初始化工作,因此在任何可视化对象创建之前必须先创建QApplication对象,而且还可以通过命令行参数设置一些内部状态。QApplication类的主要功能有处理命令行参数,设置程序的内部初始状态;处理事件,从窗口接收事件,并通过sendEvent()和postEvent()发送给需要的窗口;获取指定位置处的窗口(widgetAt())、顶层窗口列表(topLevelWidgets()),处理窗口关闭(closeAllWindows())等事件;使用桌面对象信息进行初始化,这些设置如调色板(palette)、字体(font)、双击间隔(doubleClickInterval),并跟踪这些对象的变化;定义整个可视化程序界面的外观,外观由QStyle对象包装,运行时通过setStyle()函数进行设置;提供一些非常方便的类,例如屏幕信息类(desktop)和剪贴板类(clipboard);管理鼠标(setOverrideCursor())。

1.2.3 QWidget窗口的创建

PySide6的窗口类主要有三种,分别为QWidget、QMainWindow和QDialog,其中QMainWindow和QDialog从QWidget类继承而来。要创建和显示窗口,需要用这3个类中的任意一个类实例化对象,并让窗口对象显示并运行起来。窗口类在PySide6的QtWidgets模块中,使用窗口类之前,需要用“from PySide6.QtWidgets import QWidget,QMainWindow,QDialog”语句把它们导入进来。

下面的代码创建一个空白的QWidget窗口,读者需要理解这段代码,这是整个PySide6可视化编程最基础的知识。

· 第1行导入系统模块sys,这个系统模块是指Python系统,而不是操作系统。

· 第2行导入QApplication类和QWidget类,PySide6的类都是以大写字母“Q”开始。

· 第4行创建QApplication类的实例对象app,为窗口的创建进行初始化,其中sys.argv是字符串列表,记录启动程序时的程序文件名和运行参数,可以通过print(sys.argv)函数输出sys.argv的值,sys.argv的第1个元素的值是程序文件名及路径,也可以不输入参数sys.argv创建QApplication实例对象app。QApplication可以接受的两个参数是-nograb和-dograb,-nograb告诉Python禁止获取鼠标和键盘事件,-dograb则忽略-nograb选项功能,而不管-nograb参数是否存在于命令行参数中。一个程序中只能创建一个QApplication实例,并且要在创建窗口前创建。

· 第5行用不带参数的QWidget类创建QWidget窗口实例对象myWindow,该窗口是独立窗口,有标题栏。

· 第6行用show()方法显示窗口,这时窗口是可见的。

· 第7行执行QApplication实例对象的exec()方法,开始窗口的事件循环,从而保证窗口一直处于显示状态。如果窗口上有其他控件,并为控件的消息编写了处理程序,则可以完成相应的动作。如果用户单击窗口右上角的关闭窗口按钮正常退出界面,或者因程序崩溃而非正常终止窗口的运行,都将引发关闭窗口(closeAllWindows())事件,这时app的方法exec()会返回一个整数,如果这个整数是0表示正常退出,如果非0表示非正常退出。请注意,当执行到app的exec()方法时,会停止后续语句的执行,直到所有可视化窗体都关闭(退出)后才执行后续的语句。需要注意的是,还有一个与exec()方法功能相同的方法exec_(),但exec_()方法已过时。

· 第8行调用系统模块的exit()方法,通知Python解释器程序已经结束,如果是sys.exit(0)状态,则Python认为是正常退出;如果不是sys.exit(0)状态,则Python认为是非正常退出。无论什么情况,sys.exit()都会抛出一个异常SystemExit,这时可以使用try…except语句捕获这个异常,并执行except中的语句,例如清除程序运行过程中的临时文件;如果没有try…except语句,则Python解释器终止sys.exit()后续语句的执行。第7行和第8行可以合并成一行sys.exit(app.exec())来执行。

运行上面的程序,会得到一个窗口,这还只是一个空白窗口,在窗口上没有放置任何控件。

下面的程序是在上面程序的基础上,在窗口上添加1个标签和1个按钮,同时将按钮的单击事件和窗口的关闭事件相关联,从而起到单击按钮关闭窗口的作用。程序在第2行中除了导入QApplication和QWidget类外,还导入标签类QLabel和按钮类QPushButton。第7行中用窗口的setWindowTitle()方法设置窗口的标题。第8行用窗口的resize()方法设置窗口的长和宽。第10行用QLabel类在窗口上创建一个标签。第12行设置标签显示的文字。第13行设置标签的位置和长宽。第15行用QPushButton类在窗口上创建一个按钮。第16行设置按钮上显示的文字。第17行设置按钮的位置和长宽。第18行将按钮的单击信号和窗口的关闭槽函数连接,从而实现单击按钮关闭窗口的功能,有关按钮信号和窗口事件及关联将在后续内容中进行详细介绍。第21行用QApplication的实例方法exec()进入事件循环,从而保证窗口一直处于显示状态,当单击按钮时,关闭窗口事件发生,这时会得到返回值n。第22行输出n的值。第23行使用try语句捕获Python解释器停止工作的事件,转到except语句执行一些需要额外完成的工作。当然,在这个例子中没有需要额外完成的工作,这里只是用一个print()语句代替。

运行上面的程序,将会得到一个窗口,窗口上只有一个标签和按钮,单击按钮会关闭窗口,并在输出窗口中得到“n=0”和“请在此做一些其他工作。”的返回信息。