使用 Tkinter Python 库创建现代用户界面

TKVue 在开发图形界面时节省了时间,并使之后的代码维护和修改更加容易。
5 位读者喜欢这篇文章。
Digital images of a computer desktop

Opensource.com

Python 的 Tkinter 库并不以其美观而闻名。我开发了一个库来帮助为 Python 创建现代图形用户界面。

在开发名为 TKVue 的新库之前,我花了很多时间搜索简单但现代的 GUI 工具包,该库为桌面应用程序创建图形界面。通过我的研究,我意识到有几个不同的库可以创建图形界面。然而,大多数都涉及到添加新的依赖项以与图形库绑定。例如,有一个用于 Qt 的库,另一个用于 wxWidgets,第三个用于 GTK。它们都不是 Python 原生的,也不是完全用 Python 编写的。这是一个问题。如果您想使用 Qt 编写 GUI,则需要在您要支持的每个平台上编译 Qt 源代码。我想以三个领先的平台为目标:Linux、Windows 和 Mac。

Tkinter 的最大优点是它嵌入在 Python 中。无需新的依赖项或编译新的库。一切都已经为您完成。

简而言之,最好使用 Tkinter 创建一些可移植的东西。

带有现代主题的 Tkinter

使用 Tkinter 创建 GUI 很容易,但不可否认的是,默认情况下,它看起来像 1980 年代的 GUI。除了创建外观不太令人愉悦的图形界面外,编程方法也来自 1980 年代:Tkinter 图形界面的编程不是声明式的。

出于对 Tkinter 可移植性的考虑,我决心使用它来创建专业且现代的图形界面。我的研究引导我发现了一个完整的系统,用于使用主题修改 Tkinter 的外观。Tkinter 主题类似于网页的 CSS 文件。它们允许您通过声明新样式来配置构成图形界面的组件的外观。这些样式的创建需要一些工作,但该系统足够灵活,可以创建外观现代的图形界面。这项工作类似于在 Web 开发中自定义 CSS 主题。如果您创建没有 CSS 的网页,则外观不现代,并且需要大量工作来改进它。这就是为什么使用 bootstrap 等 CSS 库来加速图形界面的创建。

在 Tkinter 世界中,没有 CSS 库。存在一些预先存在的主题,但对于任何项目来说,最好自定义调色板以匹配您的产品品牌并赋予其 Web 外观和感觉。

为了实现这一点,使用 Tkinter 创建现代界面的最重要元素是更改背景颜色和按钮的外观。

一旦根据您的喜好正确个性化,结果将是一个干净且具有视觉吸引力的图形界面。

这是“默认”主题

Default theme

(Patrik Dufresne,CC BY-SA 4.0)

“clam”主题看起来像这样

Clam theme

(Patrik Dufresne,CC BY-SA 4.0)

然后是我的个性化

Personalized theme

(Patrik Dufresne,CC BY-SA 4.0)

TKVue

import tkvue
import tkinter.ttk as ttk

tkvue.configure_tk(theme="clam")


class RootDialog(tkvue.Component):
    template = """
<TopLevel title="TKVue Test" geometry="450x200">
    <Frame style="default.TFrame" pack-fill="both" pack-expand="1" padding="10">
        <Label text="Hello World!" style="H1.TLabel" pack-padx="25" pack-pady="25"/>
        <Frame style="default.TFrame" pack-fill="both" pack-expand="1" pack-padx="10" pack-pady="10">
            <Button style="default.TButton" text="Continue" pack-side="right" pack-padx="5"/>
            <Button style="default.TButton" text="Cancel" pack-side="right"/>
        </Frame>
    </Frame>
</TopLevel>
    """

    def __init__(self, master=None):
        super().__init__(master)
        s = ttk.Style(master=self.root)
        s.configure('H1.TLabel', font=['Lato', '-60'], background='#ffffff')
        s.configure('default.TFrame', background='#ffffff')
        s.configure(
            default.TButton',
            foreground='#0E2933',
            background='#B6DDE2',
            bordercolor='#ACD1D6',
            darkcolor='#B6DDE2',
            lightcolor='#B6DDE2',
            focuscolor='#0E2933',
        )
        s.map(
            default.TButton',
            background=[('disabled', '#E9F4F6'), ('hover !disabled', '#9ABBC0'), ('pressed !disabled', '#88A5A9')],
        )


if __name__ == "__main__":
    dlg = RootDialog()
    dlg.mainloop()

使用声明式语言

在发现 Tkinter 样式系统之后,要解决的另一个问题是使用声明式语言。对于每天开发 Web 界面的人来说,使用 Python 代码创建 GUI 没有意义。它需要太多的代码和太多的变量,以及结构不良的代码。创建一个简单的按钮需要许多行代码,这些代码与其他许多组件的创建交织在一起。

修改 GUI 很繁琐,因为它需要许多缠结在一起的变量。

相比之下,使用 HTML 标签创建图形界面更具声明性,并且允许在保持结构化的同时创建复杂的图形界面。这就是为什么我决定使用标记语言从 Tkinter 中可用的组件轻松创建图形界面。

最初的想法非常简单;一个标签对应一个组件。如果我想创建一个文本为Hello的按钮组件,我只需要创建一个名为 button 的标签,并使用值为Hello的属性。

TKVue

<Button text="Hello" />

Python

b = Button(parent, text ="Hello")

b.pack()

我从这个最初的想法将概念外推到 Tkinter 库中的所有组件和功能。因此,要创建具有不同元素的更复杂的图形界面,只需通过创建父子结构并将每个组件的属性嵌套在彼此内部即可。

TKVue

<TopLevel title="TKVue Test">
   <Frame pack-fill="both" pack-expand="1" pack-padx="10" pack-pady="10">
       <Label text="Available values: " width="20" pack-side="left"/>
       <ComboBox id="label" pack-side="left" pack-expand="1" values="['zero', 'one', 'two', 'three']" />
   </Frame>
</TopLevel>

Python

import tkinter
from tkinter.ttk import Frame, Label, Combobox

top = tkinter.Tk()
top.title = "TKVue Test

frame = tkinter.Frame(top)
frame.pack(fill='both', expand=1, padx=10, pady=10)

l = Label(frame, text="Available values: ", width=20)
l.pack(side='left')

c = Combobox(frame, values=['zero', 'one', 'two', 'three'])
c.pack(side='left', expand=1)

top.mainloop()

结果

Initial result

(Patrik Dufresne,CC BY-SA 4.0)

使用类似于 HTML 的分层设计简化了图形界面的创建,并显着简化了每个组件之间创建的父子结构的呈现。

创建动态界面

我使用 Tkinter 遇到的另一个问题是界面的非动态方面。在使用 Jinja2 模板编码 Web GUI 后,Jinja2 模板允许您轻松地重用变量并创建循环,我希望在创建桌面 GUI 时具有类似的功能。

我必须承认,TKVue 的动态方面给我带来了一些问题。我的最初计划是在代码更改变量时动态更新 GUI。因此,应该可以在标记语言中使用变量。假设我从下拉列表中选择一个值。我希望能够将指定的值与一个变量关联起来,并允许该变量在另一个组件中重用以进行显示。请参阅下面的代码示例

TKVue

import tkvue

class RootDialog(tkvue.Component):
   template = """
<TopLevel title="TKVue Test">
   <Frame pack-fill="both" pack-expand="1" pack-padx="10" pack-pady="10">
       <Label text="Available values: " width="20" pack-side="left"/>
       <ComboBox pack-side="left" pack-expand="1" values="{{myvalues}}" textvariable="{{var1}}" />
   </Frame>
   <Frame pack-fill="both" pack-expand="1" pack-padx="10" pack-pady="10">
       <Label text="{{'Available values:' + var1 }}" />
   </Frame>
</TopLevel>
   """
   data = tkvue.Context({"myvalues": ["zero", "one", "two", "three"], "var1": ""})

dlg = RootDialog()
dlg.mainloop()

Python

import tkinter
from tkinter.ttk import Frame, Label, Combobox

top = tkinter.Tk()
top.title = "TKVue Test

frame = tkinter.Frame(top)
frame.pack(fill='both', expand=1, padx=10, pady=10)

l = Label(frame, text="Available values: ", width=20)
l.pack(side='left')

var1 = tkinter.StringVar()

c = Combobox(frame, values=['zero', 'one', 'two', 'three'], textvariable=var1)
c.pack(side='left', expand=1)

frame2 = tkinter.Frame(top)
frame2.pack(fill='both', expand=1, padx=10, pady=10)
s = Label(frame2, text='Value selected:')
s.pack(side='bottom')

var1.trace_add("write", lambda *args: s.configure(text='Value selected: ' + var1.get()))

top.mainloop()

结果

Result

(Patrik Dufresne,CC BY-SA 4.0)

有了这个,就可以以简单且声明式的方式创建一个对用户操作做出反应的图形界面。

使用循环和条件

为了使模型尽可能动态,有必要支持使用循环和条件来根据变量显示或隐藏组件。TKVue 引入了两个特殊属性来满足此需求,用于创建循环的 for 属性和 visible 属性。

以下示例允许用户从下拉列表中选择要在循环中显示的项目数。以下示例演示了循环对用户操作做出反应的动态方面

TKVue

import tkvue


class RootDialog(tkvue.Component):
   template = """
<TopLevel geometry="970x500" title="TKVue Test">
   <Frame pack-fill="both" pack-expand="true" padding="10">
       <Label text="Selection number of row to display:" />
       <Combobox values="{{ list(range(1, 100)) }}" textvariable="{{ count }}"/>
       <Frame pack-fill="both" pack-expand="1" pack-side="left">
           <Label pack-fill="x" pack-expand="1" for="i in range(1, count)" text="{{ 'row %s' % i }}" />
       </Frame>
   </Frame>
</TopLevel>
   """
   data = tkvue.Context({'count': 5})


if __name__ == "__main__":
   dlg = RootDialog()
   dlg.mainloop()

结果

Result with rows

(Patrik Dufresne,CC BY-SA 4.0)

条件声明甚至更简单。您必须在 visible 属性中定义一个表达式,如下所示

TkVue

import tkvue


class RootDialog(tkvue.Component):
   template = """
<TopLevel geometry="970x500" title="TKVue Test">
   <Frame pack-fill="both" pack-expand="true" padding="10">
       <Checkbutton text="Show Text" variable="{{show}}" />
       <Label text="Text is Visible" visible="{{show}}" />
   </Frame>
</TopLevel>
   """
   data = tkvue.Context({'show': True})


if __name__ == "__main__":
   dlg = RootDialog()
   dlg.mainloop()

结果

Result with text

(Patrik Dufresne,CC BY-SA 4.0)

使用本地化

我的应用程序开发的另一个重要方面是允许通过使用 Python 中已经已知和广泛使用的技术快速翻译图形界面:gettext

为此,该库为 Babel 提供了一个适配器,该适配器允许提取要翻译的字符串。然后,您可以在翻译目录(.po 文件)中找到这些字符串。

这是一个 Babel 配置文件示例,该文件允许您提取字符串以进行翻译

将以下内容放在 Babel.cfg

[python: **.py]

[tkvue: **/templates/**.html]

我正在为我的数据备份软件 Minarca 使用 Tkinter 和我的主题库。

最终结果既优雅又简洁。样式与 Minarca 项目的调色板相匹配。

Result welcome

(Patrik Dufresne,CC BY-SA 4.0)

立即试用 TKVue

所有这项工作的目的是将 Web 开发中熟悉的优势带到传统开发中。TKVue 在开发图形界面时节省了时间,并使之后的代码维护和修改更加容易。

到目前为止,我在该库中所做的工作还相当基础,在更广泛地用于项目中之前需要进一步修改。我希望本文的发表能够让其他人接手类似库的开发,或者继续在 TKVue 中直接开发以改进其功能。TKVue 的代码在 GitLab 上可用。

User profile image.
Patrik Dufresne 热衷于 IT,并相信在这个前沿领域合作的重要性,他已参与开源社区超过 10 年。

2 条评论

您好,我是 witkets 库 (https://www.leandromattioli.com.br/witkets/) 的作者,我必须承认我没有花太多精力让人们意识到它的存在。

witkets 库支持从 XML 动态构建 tkinter GUI,使用类似 INI 的文件或字符串(没有外部依赖项,感谢 configparser)对其进行样式设置,并且它还添加了许多新的小部件。下一步将是 GUI 设计器,类似于 Glade。

似乎我们可以一起工作,分享一些想法,并改进两个项目(或者最终甚至合并它们)。感谢您分享您的工作,这意味着我不是唯一一个坚持使用 tkinter 的人。:)

非常酷的阅读体验!在我的 Python 项目中,自从转向 Linux 以来,我一直致力于 Gtk,因此从不需要互操作性。但是,如果我需要类似的东西,这看起来很棒。3

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.