QApplication和主窗口连接

时间:2019-11-02 13:19:30

标签: python pyqt5

考虑一个非常基本的 HelloWorld PyQt5应用程序,例如:

app = QApplication(sys.argv)

window = QWidget()
window.setWindowTitle('PyQt5 app')
window.setGeometry(100, 100, 280, 80)
window.move(60, 15)

helloMsg = QLabel('<h1>Hello World!</h1>', parent=window)
helloMsg.move(60, 15)

window.show()

sys.exit(app.exec_())

它构造了一个QApplication,一个成为 less-parent 的QWidget成为主窗口,添加了QLabel并显示了它。

我的问题是:QApplication如何知道主窗口? 此代码中没有任何内容可以将两者联系起来。

也许这是一个幼稚的问题,但仅看一看,就好像魔术一样。

如何将主窗口的paint事件添加到应用程序的事件队列中而不在源代码中说明呢? QApplication实例如何知道要在源代码下面添加什么?

1 个答案:

答案 0 :(得分:1)

tl; dr

不涉及“魔术”:子模块可以访问其“主”模块,并且Qt的每个模块都可以知道QApplication实例是否正在运行。

长版

我认为这是一个有趣的问题,特别是对于那些不喜欢低级编程的人。例如,我一直将QApplication视为某种“笛卡尔”假设:“它存在”。

作为前提,我不会向您提供非常技术性和低级的解释:我没有足够的技能来做到这一点(并且我真的很欢迎其他任何答案或对此做修改),但是我我以为那不是您要的东西。

[几乎]从技术上讲,您必须记住Qt和PyQt以及它是一个环境(确切的术语是框架)。这样,每个子元素(类,最终是它们的实例)都“了解”该环境。
QApplication(及其基类QGuiApplicationQCoreApplication)是可从任何“子” Qt模块内部访问的类。

就像内置类型(strintbool等)一样,任何模块都可以访问。例如,os.path是一个Python模块,您可以作为独立模块导入,但是它知道主要的os模块是什么,以及os.path的每个功能实际上使用了主模块的一部分。

与大多数框架一样,Qt具有所谓的event loop,通常在您调用Q[*]Application.exec()后就运行。事件循环通常会阻止自身等待事件(事件)的发生并最终对其做出反应。

只要Qt类需要它,它就会在内部调用Q[*]Application.instance()方法以确保该应用程序的实例正在运行,这意味着事件循环处于活动状态并且正在运行。例如,Qt小部件需要能够显示界面并与其交互:告诉操作系统已创建了一个新窗口,因此必须在屏幕上绘制该窗口,因此操作系统将说“确定,让我们来展示通过向Qt发送一个请求绘制的事件来发送它,然后Qt将该事件“发送”到该窗口,该窗口将通过告诉Qt如何绘制来最终绘制自身。最终Qt将“告诉”操作系统将要显示的内容。同时,该窗口可能需要知道是否已向其发送了一些键盘或鼠标事件并以某种方式做出反应。

您可以在Qt源中看到这一点:每当创建新的QWidget时,它都会通过调用QApplication exists来确保QCoreApplication.instance()

对于其他需要运行应用程序事件循环的Qt对象,也会发生同样的情况。这是QTimer(不需要图形界面,但必须与系统进行接口以确保正确的计时)和QPixmap(需要了解图形环境以正确显示其图像)的情况。它也取决于平台(例如,在MacOS上创建QIcon需要运行事件循环,而在Linux和Windows上则不需要)。

因此,最后,这就是运行代码时(大致)发生的情况:

# create an application instance; at this point the loop is not "running"
# (but that might be enough to let know most classes about the current system
# environment, such as available desktop geometries or cursor position)
app = QApplication(sys.argv)

# create a widget; an application exists and the widget can "begin" to create its
# interface using the information provided by it, like the system default font 
# (it's actually a bit more complicated due to cross-platform issues, but let's
# ignore those things now)
window = QWidget()
window.setWindowTitle('PyQt5 app')
window.setGeometry(100, 100, 280, 80)
window.move(60, 15)

helloMsg = QLabel('<h1>Hello World!</h1>', parent=window)
helloMsg.move(60, 15)

# "ask Qt to prepare" the window that is going to be shown; at this point the
# widget's window is not shown yet even if it's "flagged as shown" to Qt, meaning
# that "window.isVisible()" will return True even if it's not actually visible yet
window.show()

# start the event loop by running app.exec(); sys.exit will just "wait" for the
# application to return its value as soon as it actually exits, while in the
# meantime the "exec" function will run its loop almost as a "while True" cycle
# would do; at this point the loop will start telling the OS that a new window
# has to be mapped and wait from the system to tell what to do: it will probably
# "answer" that it's ok to show that window, then Qt will tell back the widget
# that it can go on by "polishing" (use the current style and app info to finally
# "fix" its size) and begin drawing itself, then Qt will give back those drawing
# information allowing the OS to actually "paint" it on the screen; then it will
# be probably waiting for some user (keyboard/mouse) interaction, but the event
# loop might also tell the OS that the window is willing to close itself (as a
# consequence of a QTimer calling "widget.close", for instance) which could
# possibly end with ending the whole event loop, which is the case of
# https://doc.qt.io/qt-5/qguiapplication.html#quitOnLastWindowClosed-prop
# which would also cause the application to, finally, return "0" to sys.exit()
sys.exit(app.exec_())