教程 2 - 使之更有趣#

Tutorial 1 中,我们生成了一个可以运行的基础项目,但我们自己并没有编写任何代码。让我们看看为我们生成了什么。

生成的内容#

src/helloworld 目录中,你应该看到 3 个文件:__init__.py``、__main__.py`` 和 app.py`

__init__.py`helloworld 目录标记为可导入的 Python 模块。这是一个空文件;它的存在告诉 Python 解释器 helloworld 目录定义了一个模块。

__main__.pyhelloworld 模块标记为一种特殊的模块 - 可执行模块。如果你尝试使用 python -m helloworld 试图运行 helloworld 模块,Python 将从``__main__.py`` 文件开始执行。__main__.py 的内容相对简单:

from helloworld.app import main

if __name__ == '__main__':
    main().main_loop()

也就是说,它从 helloworld 应用程序中导入 main 方法;如果它作为入口点执行,则调用 main() 方法,并启动应用程序的主循环。主循环是 GUI 应用程序监听用户输入(如鼠标点击和键盘按下)的方式。

更有趣的文件是 app.py - 它包含创建我们应用程序窗口的逻辑:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW

class HelloWorld(toga.App):
    def startup(self):
        main_box = toga.Box()

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

def main():
    return HelloWorld()

让我们逐行查看:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW

首先,我们导入 toga 小部件工具包,以及一些与样式相关的实用类和常量。目前我们的代码还没有使用这些——但我们很快就会使用它们。

然后,我们定义了一个类:

class HelloWorld(toga.App):

每个 Toga 应用程序都有一个 toga.App 实例,代表应用程序的运行实体。应用程序最终可能会管理多个窗口;但是对于简单的应用程序来说,可能只有一个主窗口。

接下来,我们定义一个 startup() 方法 (startup意为启动):

def startup(self):
    main_box = toga.Box()

startup 方法的第一件事是定义一个主盒子 (main box)。Toga 的布局方案类似于 HTML。你通过构造一系列盒子 (box) 来构建应用程序,每个盒子包含其他盒子或实际的小部件 (widgets)。然后,你对这些盒子应用样式 (styles),以定义它们将如何消耗可用的窗口空间 (window space)。

在这个应用程序中,我们定义了一个单独的空盒子 (我们没有放任何东西进去)。

接下来,我们定义一个窗口,并将这个空盒子放入其中:

self.main_window = toga.MainWindow(title=self.formal_name)

这将创建一个 toga.MainWindow 的实例,它的标题 (title) 将与应用程序的名称 (self.formal_name) 匹配。主窗口是 Toga 中的一种特殊窗口——它是与应用程序的生命周期 (life cycle) 密切绑定的窗口。当主窗口关闭时,应用程序退出。主窗口也是具有应用程序菜单的窗口(如果你在像 Windows 这样的平台上,菜单栏是窗口的一部分;如果你没有进行任何菜单栏的增删操作,你将看到默认的 file (文件) 和 help (帮助) 这两个菜单栏选项)

然后,我们将空盒子作为主窗口的内容,并指示应用程序显示我们的窗口:

self.main_window.content = main_box
self.main_window.show()

最后,我们定义一个 main() 方法。它将创建应用程序的实例::

def main():
    return HelloWorld()

这个 main() 方法由 __main__.py 导入并调用。它创建并返回我们的 HelloWorld 应用程序的实例。

这是最简单的可能的 Toga 应用程序。接下来让我们在应用程序中加入一些自己的内容,使应用程序做一些有趣的事情。

添加一些我们自己的内容#

修改 src/helloworld/app.py 中的 HelloWorld 类,使其看起来像这样::

class HelloWorld(toga.App):
    def startup(self):
        main_box = toga.Box(style=Pack(direction=COLUMN))

        name_label = toga.Label(
            "Your name: ",
            style=Pack(padding=(0, 5))
        )
        self.name_input = toga.TextInput(style=Pack(flex=1))

        name_box = toga.Box(style=Pack(direction=ROW, padding=5))
        name_box.add(name_label)
        name_box.add(self.name_input)

        button = toga.Button(
            "Say Hello!",
            on_press=self.say_hello,
            style=Pack(padding=5)
        )

        main_box.add(name_box)
        main_box.add(button)

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

    def say_hello(self, widget):
        print(f"Hello, {self.name_input.value}")

备注

不要删除 app.py 文件顶部的导入 (import),也不要删除底部的 main()。您只需更新 HelloWorld 类。

让我们详细看看有哪些变化。

我们仍然在创建一个主盒子;然而,现在我们正在应用一个样式:

main_box = toga.Box(style=Pack(direction=COLUMN))

Toga 的内置布局系统称为 “Pack” (包)。它的行为很像 CSS (Cascading Style Sheets 层叠样式表)。你可以在一个层次结构中定义对象–在 HTML 中,对象是 <div> (division 块级容器)、<span> (inline span 内联容器) 和其他 DOM 元素 (Document Object Model 文档对象模型);在 Toga 中,对象是部件 (widgets ) 和盒子 (boxes)。然后,您可以为各个元素指定样式。在本例中,我们表示这是一个 COLUMN (垂直) 框,也就是说,它是一个将占用所有可用宽度 (width) 的框,并会随着内容的添加而扩大高度 (height),但会尽量使高度更短。

接下来,我们定义了一些小部件:

name_label = toga.Label(
    "Your name: ",
    style=Pack(padding=(0, 5))
)
self.name_input = toga.TextInput(style=Pack(flex=1))

在这里,我们定义了一个标签 (toga.Label) 和一个文本输入框 (toga.TextInput)。这两个小部件都有相关的样式;标签左右各有 5px 的填充,上下没有填充 (padding=(0, 5)。文本输入框被标记为灵活的 (flex=1)——也就是说,它将吸收其布局横向方向上所有可用的空间。

文本输入框被分配为类的实例变量 (self.name_input)。这使我们能够轻松访问小部件 (widget) 实例 - 这是我们马上就会使用的东西。

接下来,我们定义了一个盒子来容纳这两个小部件:

name_box = toga.Box(style=Pack(direction=ROW, padding=5))
name_box.add(name_label)
name_box.add(self.name_input)

name_box 就像主盒子一样;然而,这次它是一个 ROW (水平) 盒子。这意味着内容将水平添加,并且它会尽量使其宽度尽可能窄 (以确保屏幕横向方向能容纳下整个水平盒子)。盒子也有一些空白填充 (以提高可读性)——四周各为 5px。

现在我们定义了一个按钮:

button = toga.Button(
    "Say Hello!",
    on_press=self.say_hello,
    style=Pack(padding=5)
)

按钮的四周也有 5px 的填充。我们还定义了一个*处理程序* (handler)——当按钮被按下时要调用的方法 (on_press=self.say_hello)。

然后,我们将名称盒子和按钮添加到主盒子中:

main_box.add(name_box)
main_box.add(button)

这完成了我们的布局;其余的 startup 方法与以前一样 - 定义一个 MainWindow,并将主盒子指定为窗口的内容:

self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.main_window.show()

我们需要做的最后一件事是定义按钮的处理器。处理器可以是任何方法、生成器或异步协程;它接受生成事件的小部件 (widget ) 作为参数,并且每当按下按钮时就会被调用:

def say_hello(self, widget):
    print(f"Hello, {self.name_input.value}")

方法的主体是一个简单的打印语句——然而,它会使用名称输入的当前值 (self.name_input.value),并使用该内容作为打印的文本。

现在我们已经做了这些更改,我们可以通过再次启动应用程序来看看它们的样子。和以前一样,我们将使用开发者模式:

(beeware-venv) $ briefcase dev

[helloworld] Starting in dev mode...
===========================================================================

你会注意到,这次它*没有*安装依赖项。Briefcase可以检测到应用程序已经运行过,为了节省时间,它只会运行应用程序。如果你在应用程序中添加了新的依赖项,你可以在运行 briefcase dev 时通过 -r 选项来确保它们被安装。

这将打开一个图形用户界面 (GUI) 窗口:

Hello World 教程 2 窗,在 MacOS 上

如果在文本框中输入名称并按下 GUI 按钮,就会在启动程序的控制台中看到输出结果。

下一步#

我们现在有了一个做一些更有趣的事情的应用程序。但它只能在我们自己的电脑上运行。让我们将这个应用程序打包以供分发。在 Tutorial 3 中,我们将把应用程序打包成一个独立的安装程序,我们可以发送给朋友、客户,或上传到应用商店。