Tutorial¶
This tutorial introduces usage of QTrio to enable integration of Qt into a Trio async application. For help with relevant async concepts and usage of Trio itself see the Trio tutorial.
I know, I know… we are supposed to do one thing well. But QTrio presently targets three distinct development tools. In time perhaps pieces will be spun off but for now they provide increasing layers you can use or not as they interest you.
The first layer allows you to run Trio tasks in the same thread as the Qt event loop.
This is valuable as it let’s the tasks safely interact directly with the Qt GUI objects.
It is a small wrapper around
Trio’s guest mode.
This layer is exposed directly under the qtrio
package.
Now that Qt and Trio are friends we can focus on making the relationship smoother. This
second layer of QTrio is also available directly in the qtrio
package and allows for
awaiting signals and iterating over the emissions of signals. This avoids the normal
callback design of GUI systems in favor of Trio’s structured concurrency allowing GUI
responses to be handled where you want within the task tree.
Not everything Qt provides will be easily integrated into this structure. The rest of QTrio will grow to contain helpers and wrappers to address these cases.
In addition to the above three layers there is also adjacent support for testing.
Layer 1 - Crossing Paths¶
With one extra character you can turn trio.run()
into qtrio.run()
. This
gets you the Trio guest mode hosted by a Qt QApplication
. Note how
there is only one function and you are able to asynchronously sleep in it to avoid
blocking the GUI. By default, when you leave your main function the Qt application will
be exited.
import typing
import qtrio
from qtpy import QtWidgets
import trio
async def main(
label: typing.Optional[QtWidgets.QWidget] = None,
message: str = "Hello world.",
change_delay=0.5,
close_delay=3,
):
if label is None: # pragma: no cover
label = QtWidgets.QLabel()
# start big enough to fit the whole message
label.setText(message)
label.show()
label.setText("")
for i in range(len(message)):
await trio.sleep(change_delay)
label.setText(message[: i + 1])
await trio.sleep(close_delay)
if __name__ == "__main__": # pragma: no cover
qtrio.run(main)
Layer 2 - Building Respect¶
A good relationship goes both ways. Above, Trio did all the talking and Qt just
listened. Now let’s have Trio listen to Qt. Emissions from Qt signals can be made
available in a trio.MemoryReceiveChannel
. You can either
trio.MemoryReceiveChannel.receive()
them one at a time or asynchronously iterate
over them for longer lasting activities. The received object is a
qtrio.Emission
and contains both the originating signal and the arguments.
import typing
import qtrio
from qtpy import QtWidgets
async def main(button: typing.Optional[QtWidgets.QPushButton] = None):
if button is None: # pragma: no cover
button = QtWidgets.QPushButton()
button.setText("Exit")
async with qtrio.enter_emissions_channel(signals=[button.clicked]) as emissions:
button.show()
await emissions.channel.receive()
if __name__ == "__main__": # pragma: no cover
qtrio.run(main)