《Textual:权威指南(第一部分)》

原始地址:https://dev.to/wiseai/textual-the-definitive-guide-part-1-1i0p

这是我的系列文章之一,旨在揭秘Textual的每个方面。在本文中,我们将探索基于文本的用户界面的世界,以及如何使用Textual创建一个。我选择写有关Textual的文章,部分原因是因为在deepgram hackathon中使用过它,但主要是因为互联网上除了自述文件之外还没有任何关于Textual的教程。
如果您以前有使用文本用户界面的经验,您可能会遇到其他框架,如urwid、curtsies、asciimatics和prompt-toolkit等。然而,如果您没有,那也没关系,因为您来对地方了解TUIs的一般情况和使用Textual。接下来,我将逐步向您展示如何开发一个wordle克隆版。
PS:嘘,我在未来。我只是想让你知道,当你看到一个以$符号开头的命令时,那意味着你需要在shell中而不是在Python解释器中运行它。

👉 目录(Table Of Content)

  • 什么是TUI?
  • 什么是Textual?
  • 安装Textual
  • 重温安装Textual
  • 基本Textual应用程序
  • 用户界面
  • Textual小部件
  • 可重用组件
  • 使用视图组织
  • 小部件事件处理程序
  • 总结
  • 未来的博客文章

什么是TUI?

可以说,查阅定义的最流行地方是维基百科,并且我引用:
在计算机中,文本用户界面(TUI)(有时控制台用户界面,以反映对计算机终端的属性的依赖,而不仅仅是文本)是一种描述一种用户界面(UI)的重命名中的类型,它作为一种早期的人机交互形式,出现在图形用户界面(GUI)出现之前。
换句话说,文本用户界面(TUI)是一种用户界面类型,它依赖于文本而不是图形来显示信息。TUI最常见的用途是命令行界面。
TUI的命令行界面通常不是图形化的,可能不支持鼠标,而这在Textual中并不适用,并且可能是为键盘输入而设计的。例如,可以在TUI上(也就是您的终端上)使用Linux的cat命令,用命令$ cat调用它。
这将弹出一个基于文本的界面,在此界面上,您可以输入文本,然后按Enter键以显示输入的文本。
现在,对于TUIs有一个高层次的概述,让我们深入了解Textual的世界。

什么是Textual?

Textual是一个相对较新的基于文本的用户界面工具包。它允许程序员在终端内构建交互式但复杂的用户友好的TUIs。像我这样的许多程序员都很喜欢Textual,并且它将改变终端应用程序的世界。Textual具有以下核心特点:

  • Textual帮助您以优雅的方式构建终端应用程序。
  • Textual是在终端内创建高度交互且复杂的应用程序的唯一途径。
  • Textual通过消除用于处理事件等装饰器的可怕样板代码,摆脱了早期TUIs的问题。
    无论您使用Textual的原因是什么,我很高兴你看到了这篇文章。我将逐步介绍Textual的基础知识以及如何在终端内创建完全功能的应用程序。从现在起,我将发表的每一篇文章都将深入讨论Textual的每个元素。

我选择使用textual开发wordle克隆版,主要是因为它具有适当的复杂性。
虽然我希望这篇文章能吸引广泛范围的程序员,但在写作时我有一个特定的观众群体。与任何工作描述一样,您无需了解其中提到的所有内容,但这将帮助您了解我在思考什么以及您可能与我有何不同。我的目标受众是:

  • 初学者至中级编程技能,使用Python。
  • 寻找一些高级的Python概念,希望您渴望学习。
  • 想了解编程工作流程,而不仅仅是Textual。
  • 当然,要有良好的幽默感。
    然而,如果你有兴趣在Textual中创建一个可以工作的应用程序,你来对地方了!我将向您展示如何逐步开发一个wordle克隆版本。您将从使用poetry设置开发环境开始,最后在终端上运行一个应用程序。多么令人兴奋啊!

安装Textual

在编程世界中,有个令人遗憾的真相是,乐趣总是伴随着一些困难。但是,安装和运行Textual并不是一个非常复杂的过程,只需要运行一个命令
pip install textual 或者查看repo并运行
poetry install。由于我在
deepwordle中使用了后者,所以让我简要介绍一下
poetry 以及它如何成为我构建软件包和管理依赖项的首选工具。

用Poetry管理依赖关系

本节将介绍帮助您开始使用poetry的最重要的几个方面。值得一提的是,有多种方法可以安装Python包:使用pip、pypenv、pdm、poetry等等。Poetry不仅仅是“另一个”Python依赖管理工具,它还可以用于交付软件包。我只是喜欢这个漫画:
漫画

什么是Poetry

Poetry是一个非常直观但又优雅的命令行工具,用于安装、管理依赖项、项目和虚拟环境,一个功能齐全的解决方案。发明这个工具的想法是因为包管理的不同惯例(使用requirements.txt、MANIFEST.ini、setup.cfg和许多其他文件)对于Poetry的创建者来说似乎不太方便。只需要一个文件,即
pyproject.toml 文件,旨在清晰、易读,并符合PEP 517和PEP 518标准。

不管您是对依赖管理是新手还是老手,我建议您尝试一下这个工具。它简单易用,可以简化您的Python项目的维护和开发。

Poetry安装

要安装这个工具,您可以按照官方安装指南操作。
如果您是Linux用户,您只需在终端中运行以下命令:
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -

Python版本管理与pyenv

关于Python版本管理,我建议使用pyenv。因此,我将解释如何使用pyenv安装特定版本的Python解释器。

pyenv

pyenv是一种方便的工具,可以轻松地在计算机上安装特定版本的Python解释器。与传统的访问官方Python网站并按照繁琐的步骤安装特定Python解释器的方式不同,pyenv只需运行一个简单的命令即可完成安装。 pyenv的Readme文件中充满了有关安装过程以及如何使用这个工具的信息。然而,如果您是Linux用户,以下命令可以帮助您在您的计算机上安装和运行pyenv。
配置shell config:
$ cat << EOF >> ~/.zshrc

pyenv config

export PATH=“ H O M E / . p y e n v / b i n : {HOME}/.pyenv/bin: HOME/.pyenv/bin:{PATH}”
export PYENV_ROOT=“ H O M E / . p y e n v " e v a l " {HOME}/.pyenv" eval " HOME/.pyenv"eval"(pyenv init -)”
EOF
如果您使用的是默认的bash shell,则运行以下命令:
$ cat << EOF >> ~/.bashrc

pyenv config

export PATH=“ H O M E / . p y e n v / b i n : {HOME}/.pyenv/bin: HOME/.pyenv/bin:{PATH}”
export PYENV_ROOT=“ H O M E / . p y e n v " e v a l " {HOME}/.pyenv" eval " HOME/.pyenv"eval"(pyenv init -)”
EOF
关闭您的终端并打开一个新的shell会话。然后,您可以通过运行以下命令来验证安装过程:
$ pyenv --version

与pyenv一起进行Python版本控制

您可以使用以下命令安装特定版本的Python解释器:
$ pyenv install <python_version>
对于本教程,我将安装Python 3.10.1:
$ pyenv install 3.10.1
要列出可以使用pyenv安装的所有可用python版本,可以运行以下命令:
$ pyenv install --list | grep \ 3.
要列出计算机上已安装的所有Python版本,可以运行以下命令:
$ pyenv versions
system

  • 3.10.1(由PYENV_VERSION环境变量设置)
    3.8.10
    3.9.10
    您可以使用以下命令使其在全局范围内可用:
    $ pyenv global system 3.10.1
    这些是使用pyenv进行Python版本控制的基本命令。现在,让我们返回之前
    poetry。

使用poetry创建一个新项目

在设置了虚拟环境后,可以使用poetry创建一个新项目以及相应的虚拟环境:
$ poetry new deepwordle && cd deepwordle
这将为您提供开始开发所需的最低限度。

激活虚拟环境

如果您在项目目录中,只需运行以下命令即可激活虚拟环境:
$ poetry shell
或者,您可以源化虚拟环境路径:
$ source ~/.cache/pypoetry/virtualenvs/<your_virtual_environment_name>/bin/activate
您可以在
~/.cache/pypoetry/virtualenvs 目录下查看以前安装的虚拟环境:
$ ls ~/.cache/pypoetry/virtualenvs
要退出此虚拟环境,请使用
exit、
Ctrl-d,
deactivate 或您最喜欢的消灭shell方式。

向项目添加包

与使用pip传统方式安装软件包一样,poetry也可以通过运行以下命令将软件包导入项目中:
$ poetry add <name_of_the_package_you_want_to_install>
这将在
tool.poetry.dependencies 部分下添加一个条目:
[tool.poetry.dependencies]
name_of_the_package_you_want_to_install = “^package_version”
您可以随时参考官方文档,了解有关使用poetry设置项目的更多详细信息。
我认为本节是使用poetry构建新项目的好起点。在随后的章节中,我们将使用该工具安装和管理Textual。

重新安装Textual

据我所知,从代码库来看,Textual当前仅适用于
Linux MacOS / Linux / Windows。要将Textual添加到您的项目中,请运行以下命令:
$ poetry add textual
正如你所看到的,对于这种依赖和环境设置写作也让我感到沮丧。我不知道您使用的操作系统是什么;我希望使用的是Linux系统。否则,我无法预测问题的出现。

基本Textual应用程序

现在一切都准备好了,让我们创建最基本的Textual应用程序。为此,请使用您喜欢的代码编辑器创建一个名为
app.py 的文件,并添加以下代码:

from textual.app import App
App.run()

这是您可以编写的最基本的Textual应用程序;只需导入App类,然后调用run方法类务即可。
如果您仍然在虚拟环境中,可以使用Python运行此代码,运行以下命令:
$ python app.py
或使用
poetry,但这并不是必需的,因为我们已经进行了
$ poetry shell ,这意味着您在包含
pyproject.toml 文件的目录中,并且
虚拟环境已经激活,仍然可以使用以下命令:
$ poetry run python app.py
这将在您的终端上创建一个黑色背景的空白窗口。
如果一个黑色背景的空白窗口恰好是您想要编写的应用程序,那么您已经完成了!恭喜你。只是开玩笑,当然不是!对吧?
现在让我们构建稍微基本的Textual应用程序,使用以下代码:

from textual.app import App

class MainApp(App):
    ...

MainApp.run()

通过继承创建了一个名为MainApp的App子类。这是您将在本系列文章中开发的应用程序。您实际上并没有对新类做任何操作,因此它仍然像以前的基本版本一样运行。但是,在接下来的文章中,我们将做很多事情。

在接下来的几节中,我们将讨论Textual的不同组件以及如何使用它们构建我们的TUI应用程序。但是,首先,我们需要设计我们的应用程序,也就是模拟。

用户界面

作为前端开发人员或工程师,善于设计用户界面是一项至关重要的技能。它可以在编码阶段之前快速了解如何布局可视应用程序组件。让我们将这个实践应用到我们的deepwordle应用程序中。以下是我想在项目中涵盖的一些功能:

  • 在屏幕上呈现每个猜测的单词,并在单词之间留出空格。
  • 从基本wordle应用程序的角度来看,单词只有五个字母长。
  • 用户只能有六次尝试来猜测单词。
  • 显示一个格式漂亮的消息,告诉用户发生了什么。
  • 告诉用户此游戏的可用键。
    给定这些特点,可以很容易地想象该应用程序所需的视图和小部件集合:
  • 一个 6x5 的网格视图。
  • 每个字母一个按钮。
  • 消息面板。
  • 一个标题和页脚。

Textual小部件

在Textual中,小部件是TUI界面的基本可视组件。它带有一个Canvas,可以用于终端上的绘图。它接收事件并对其作出反应。我认为将小部件视为带有反应属性和行为的容器并容纳其他容器是很方便的。Widget类是最基本的容器。

小部件有十二个反应属性,您可以操作它的可视属性,如小部件的样式、边框、填充、大小等。在内部,反应属性实现为Python描述符。

每个反应属性都有一个单独的观察程序,可以使用
watch关键字后面跟着_和要观察的属性名称来定义:

foo = Reactive("")

def watch_foo(self, val):
    if val == "bar":
        do_something()
    #
    # custom logic
    #

除了本文之外,深入了解有关小部件的更多信息,唯一地方就是阅读自述文件和代码库。

占位符

用于原型设计目的,可以在实施阶段之前使用占位符查看应用程序的外观。例如,我们的应用程序如下所示,以占位符的形式。

from textual.app import App
from textual.widgets import Placeholder

class MainApp(App):
    async def on_mount(self) -> None:
        await self.view.dock(Placeholder(name="header"), edge="top", size=3)
        await self.view.dock(Placeholder(name="footer"), edge="bottom", size=3)
        await self.view.dock(Placeholder(name="stats"), edge="left", size=40)
        await self.view.dock(Placeholder(name="message"), edge="right", size=40)
        await self.view.dock(Placeholder(name="grid"), edge="top")

MainApp.run()

按钮

按钮是带有关联事件的标签,当按钮被点击时触发事件。按钮具有三个属性:

  • label:按钮上显示的文本。
  • name:小部件的名称。
  • style:标签的样式。它的定义使用“前景色在背景色”的表示法。例如:style = “white on dark_blue”
from textual.app import App
from textual.widgets import Button

class MainApp(App):
    
    async def on_mount(self) -> None:
        button1 = Button(label='Hello', name='button1')
        button2 = Button(label='world', name='button2', style='black on white')
        await self.view.dock(button1, button2, edge="left")

MainApp.run()

头部

此小部件为终端应用程序定义一个标题。它可以用于显示应用程序的标题、时间和图标(目前不可自定义,但在issues/PR中已有人要求…)。

from textual.app import App
from textual.widgets import Header

class MainApp(App):
    
    async def on_mount(self) -> None:
        header = Header(tall=False)
        await self.view.dock(header)

MainApp.run(title="DeepWordle")

页脚

此小部件为终端应用程序定义一个页脚,并可用于显示用户可用的键。

from textual.app import App
from textual.widgets import Footer

class MainApp(App):
	
	async def on_load(self) -> None:
	    """Bind keys here."""
	    await self.bind("q", "quit", "Quit")
	    await self.bind("t", "tweet", "Tweet")
	    await self.bind("r", "None", "Record")
	
	async def on_mount(self) -> None:
	    footer = Footer()
	    await self.view.dock(footer, edge="bottom")

MainApp.run(title="DeepWordle")

滚动视图

from textual.app import App
from textual.widgets import ScrollView, Button

class MainApp(App):
	
	async def on_mount(self) -> None:
	    scroll_view = ScrollView(contents= Button(label='button'), auto_width=True)
	    await self.view.dock(scroll_view)

MainApp.run()

静态小部件

from textual.app import App
from textual.widgets import Static, Button

class MainApp(App):

	async def on_mount(self) -> None:
	    static = Static(renderable= Button(label='button'), name='')
	    await self.view.dock(static)

MainApp.run()

您可以尝试一下其他小部件,如
TreeClick、
TreeControl、
TreeNode、
NodeID、
ButtonPressed、
DirectoryTree、
FileClick 等。

自定义小部件

通过扩展通用的widget类,您可以创建任何类型的小部件。

from textual.app import App
from textual.widget import Widget
from textual.reactive import Reactive
from rich.console import RenderableType
from rich.padding import Padding
from rich.align import Align
from rich.text import Text

class Letter(Widget):
    label = Reactive("")

    def render(self) -> RenderableType:
        return Padding(
            Align.center(Text(text=self.label), vertical="middle"),
            (0, 1),
            style="white on rgb(51,51,51)",
        )

class MainApp(App):

	async def on_mount(self) -> None:
	    letter = Letter()
	    letter.label = "A"
	    await self.view.dock(letter)

MainApp.run(title="DeepWordle")

这只是一个简单的小部件类,声明了您自定义的Letter组件,并进行自定义渲染。
有关小部件的更多信息,请参阅自述文件以及代码库。

可重用组件

在开发Web应用程序或任何一般应用程序时,您倾向于在项目中重复使用现有代码。为了使您的代码可重用,最佳实践是将应用程序的每个组件创建为单独的文件。这样,您的代码库将看起来更有组织和结构。

├── deepwordle
│ ├── __init__.py
│ ├── app.py
│ ├── components
│ │ ├── __init__.py
│ │ ├── constants.py
│ │ ├── letter.py
│ │ ├── letters_grid.py
│ │ ├── message.py
│ │ ├── rich_text.py
│ │ └── utils.py

使用视图组织

Textual中的小部件在视图中组织,使用停靠技术在终端上对其进行排列。停靠类似于css网格布局,可以自定义。

默认情况下,小部件将在终端的中心渲染。在Textual中,可以通过将
edge参数更改为
left、
right、
top、
bottom,来将小部件固定或排列到终端的左侧、右侧、顶部和底部。

在Textual中,有五种类型的视图:

停靠视图(DockView)

这是Textual应用程序使用的默认视图。它以纵向(默认)或横向分组小部件以填满所有终端空间。可以使用
edge参数来控制如何分组小部件。

默认为
edge = top。

from textual.app import App
from textual.widgets import Placeholder
from textual.views import DockView

class SimpleApp(App):

	async def on_mount(self) -> None:
	    view: DockView = await self.push_view(DockView())
	    await view.dock(Placeholder(), Placeholder(), Placeholder())

SimpleApp.run()

也可以控制每个小部件的字符大小。

from textual.app import App
from textual.widgets import Placeholder
from textual.views import DockView

class SimpleApp(App):

	async def on_mount(self) -> None:
	    view: DockView = await self.push_view(DockView())
	    await view.dock(Placeholder(), Placeholder(), Placeholder(), edge='left')

SimpleApp.run()

可以通过字符来控制每个小部件的大小。

from textual.app import App
from textual.widgets import Placeholder
from textual.views import DockView

class SimpleApp(App):

	async def on_mount(self) -> None:
	    view: DockView = await self.push_view(DockView())
	    await view.dock(Placeholder(), Placeholder(), Placeholder(), size=10)

SimpleApp.run()

正如你所看到的,每个小部件(Placeholder)的高度是10个字符。

网格视图(GridView)

GridView在终端上以矩形/表格方式布置TUI小部件,通过指定行数、列数和要布局在终端上的小部件列表来完成。

from textual.app import App
from textual.widgets import Placeholder
from textual.views import GridView

class SimpleApp(App):

	async def on_mount(self) -> None:
	    await self.view.dock(GridView(), size=10)
	    await self.view.dock(Placeholder(name='sad'), size=10)
	    await self.view.dock(GridView(), size=10)

SimpleApp.run(log="textual.log")

一个空的网格示例:

from textual.app import App
from textual.widgets import Placeholder
from textual.views import GridView

class SimpleApp(App):

	async def on_mount(self) -> None:
	    await self.view.dock(GridView(), size=10)
	    await self.view.dock(Placeholder(name='sad'), size=10)
	    await self.view.dock(GridView(), size=10)

SimpleApp.run(log="textual.log")

一个6x6占位符网格示例:

from textual.app import App
from textual import events
from textual.widgets import Placeholder

class GridView(App):

	async def on_mount(self, event: events.Mount) -> None:
	    """Create a grid with auto-arranging cells."""
	    grid = await self.view.dock_grid()

	    grid.add_column("col", repeat=6, size=7)
	    grid.add_row("row", repeat=6, size=7)
	    grid.set_align("stretch", "center")

	    placeholders = [Placeholder() for _ in range(36)]
	    grid.place(*placeholders)

GridView.run(title="Grid View", log="textual.log")

窗口视图(WindowView)

用于小部件的占位符。

from textual.app import App
from textual.widgets import Placeholder
from textual.views import WindowView

class SimpleApp(App):

	async def on_mount(self) -> None:
	    await self.view.dock(WindowView(widget=Placeholder(name='sad')), size=10)
	    await self.view.dock(WindowView(widget=Placeholder(name='sad')), size=10)
	    await self.view.dock(Placeholder(name='sad'), size=10)

SimpleApp.run(log="textual.log")

小部件事件处理程序

在Textual中,您可以使用下划线命名约定为小部件分配处理程序方法,而不是传统的使用装饰器的方式。例如,可以简单地编写一个键事件处理程序:

def on_key(self, event):
    ...

而不是通常捕获事件的方式如下:

@on(event)
def key(self):

使用下划线约定,您的代码更具可读性,并且包含的样板代码较少。然而,作为Python开发人员,您可能会认为编写样板代码比所谓的“最佳实践”更具Python特色,对此我表示同意。例如,起初,我对这种下划线表示法以及底层发生了什么以及如何触发和处理事件感到困惑,直到我进入源代码并且一切对我来说都变得清晰明了。我实际上很喜欢那种表示法,它更易读。

总结

构建自己的基于TUI的应用程序可以帮助您将UI技能提升到更高的水平。使用Textual,您可以构建任何类型的终端应用程序。它可以为您节省大量时间,并为您的观众提供更好的体验。

Textual软件包隐藏了许多底层细节,使您可以专注于应用程序的逻辑。

在本文中,您学会了:

  • 安装和使用Poetry。
  • 安装和使用pyenv。
  • 安装和使用Textual构建自定义界面。

您可以将本文中的代码作为各种需求的起点。不要忘记查看读我文件并发挥想象力,以创建对您的用例有意义的更复杂的应用程序。

下一篇关于开发的博客

我目前计划在这个面向开发人员的平台上分享我的经验。几周前,我加入了Dev.to,从我的Medium资料中可以看出(为什么我离开Medium的原因我将在另一篇文章中写出来),我主要在数据科学和Python计算机视觉方面发布技术博客。我想这只是我个人定期发布的开始,所以让我们一起成长吧!

那么,有什么事情吗?好吧,其实没有。我只是在送走这篇文章。这是送给你的礼物,你可以与任何你喜欢的人分享,也可以以任何对您个人和职业发展有益的方式使用它。提前感谢您的支持!

愉快的编码,朋友们;下次见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值