{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 样式与主题\n", "现代Tk小部件的主题特性是这套较新小部件集中最强大且令人兴奋的方面之一。然而,它也是最容易让人困惑的方面之一。\n", "\n", "本章将解释样式(控制按钮等小部件外观的方式)和主题(一组样式的集合,定义了应用程序中所有小部件的外观)。更换主题可以使你的应用程序呈现出完全不同的外观。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "请注意,变化的不仅仅是颜色,还包括各个小部件的实际形状。样式和主题极具灵活性。\n", "\n", "```{admonition} 为什么呢?\n", "然而,在你过于兴奋之前,很少有应用程序会从这样的主题切换中受益。一些游戏或教育程序可能是例外。使用特定平台的标准Tk主题将会以人们期望的方式显示小部件,特别是如果他们正在运行macOS和Windows。\n", "\n", "在Linux系统上,外观和感觉的标准化程度要低得多。用户期待(并且更习惯)一定程度的变化性和“酷”。因为不同的小部件集(通常是GTK和QT)被窗口管理器、控制面板和其他系统工具所使用,所以Tk无法与任何特定系统的当前设置完美融合。本教程中的大多数Linux截图使用了Tk的备用主题。尽管用户习惯于变化性,但大多数用户能接受的变化还是有限度的。一个典型的例子是Tk经典小部件集中核心小部件的样式,它匹配大约1992年的OSF/Motif。\n", "\n", "以更加针对性和显著克制的方式使用样式和主题,可以在现代应用程序中发挥作用。本章将解释为什么以及何时你可能想要使用它们,以及如何去做。我们将首先通过将Tk的样式和主题与软件开发的另一个领域进行类比来开始。\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **理解样式和主题**\n", "如果你熟悉网页开发,那么你一定知道层叠样式表(CSS)。它有两种方法可以用来定制HTML页面中元素的外观。一种方法是通过style属性直接在HTML代码中的元素上添加一堆样式属性(字体、颜色、边框等)。例如:\n", "\n", "```html\n", "\n", "```\n", "另一种使用CSS的方式是通过class属性为每个小部件附加一个类。该类的元素如何显示的详细信息通常提供在其他地方,通常是在一个单独的CSS文件中。你可以将同一个类附加到许多元素上,它们都将具有相同的外观。你不需要为每个元素重复完整的细节。更重要的是,你将站点的逻辑内容(HTML)与其外观(CSS)分离。\n", "\n", "```html\n", "\n", "...\n", "\n", "```\n", "回到Tk。\n", "\n", "- 在经典的Tk小部件中,所有的外观定制都需要在单个小部件上指定每个细节,类似于总是使用style HTML属性。\n", "- 在有主题的Tk小部件中,所有的外观定制都是通过将样式附加到小部件上来完成的,类似于使用class HTML属性。另外,你会定义带有该样式的小部件将如何显示,类似于编写CSS。\n", "- 与HTML不同,你不能自由混合和匹配。你不能通过直接更改外观选项来定制一些有主题的条目或按钮的样式,而其他则不行。\n", "\n", "```{note}\n", "是的,有一些例外,比如标签,你可以通过样式和配置选项来定制字体和颜色。\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 好处\n", "\n", "那么,为什么要在Tk中使用样式和主题呢?它们将外观决策的细微细节从各个小部件的实例中抽离出来。\n", "\n", "这样做可以使代码更干净且减少重复。如果你的应用中有20个输入小部件,你不需要每次创建时都重复相同的外观细节(或编写一个包装函数)。相反,你可以为它们分配一个样式。\n", "\n", "样式还将所有外观决策集中在一个地方。由于按钮的样式和其他小部件的样式可以共享共同的元素,这促进了一致性并提高了重用性。\n", "\n", "```{note}\n", "对于小部件作者来说,样式还有许多好处。小部件可以将大多数外观决策委托给样式。小部件作者不再需要硬编码逻辑来处理“当状态是禁用时,参考'disabledforeground'配置选项并使用该颜色作为前景色”的效果。这不仅使编写小部件的代码变得更长(并且更加重复),而且还限制了根据其状态更改小部件的方式。如果小部件作者忽略了在状态改变时更改字体的逻辑,那么作为应用程序开发者的你就无法更改字体了。\n", "\n", "使用样式,小部件作者不需要为每个可能的外观选项提供代码。这不仅简化了小部件,而且悖论地确保了可以设置更广泛的外观,包括那些小部件作者可能未曾预料到的外观。\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 使用现有主题\n", "\n", "在我们深入探讨如何有品味和选择性地修改并应用样式以提升你的应用程序的可用性和代码的清晰性之前,让我们先处理一些有趣的部分:使用现有主题来彻底改变你的应用程序的外观。\n", "\n", "主题是通过名称来标识的。你可以通过以下方式获取所有可用主题的名称:\n", "\n", "```python\n", ">>> s = ttk.Style()\n", ">>> s.theme_names()\n", "('aqua', 'step', 'clam', 'alt', 'default', 'classic')\n", "```\n", "\n", "```{note}\n", "Tkinter将所有的样式操作都封装在ttk.Style类中。因此,我们需要这个类的实例来进行这项和其他操作。\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "除了内置的主题(alt、default、clam 和 classic),macOS 还包含一个名为 aqua 的主题,以匹配系统整体风格;而 Windows 则提供了 vista、winxpnative 和 winnative 主题。\n", "\n", "一次只能激活一个主题。要获取当前使用中的主题名称,可以使用以下命令:\n", "\n", "```python\n", ">>> s.theme_use()\n", "'aqua'\n", "```\n", "\n", "这个 API 最初是为 Tk 8.6 设计的,后来被移植到 Tk 8.5.9。如果你使用的是更早版本的 Tk,获取这一信息会稍微复杂一些。\n", "\n", "切换到新主题可以通过以下方式完成:\n", "```python\n", "s.theme_use('themename')\n", "```\n", "这实际上做了什么?显然,它将当前主题设置为指定的主题。这样做之后,所有当前可用的样式将被替换为该主题定义的一组样式。最后,它会刷新所有小部件,使它们呈现出新主题所描述的外观。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "第三方主题\n", "通过一番寻找,你可以发现一些现有的、可供下载的附加主题。一个不错的起点是https://wiki.tcl-lang.org/page/List+of+ttk+Themes。\n", "\n", "尽管主题可以用Tk支持的任何语言定义,但你找到的大多数都是用Tcl编写的。你怎样才能安装它们,以便在你的应用程序中使用呢?\n", "\n", "举个例子,让我们使用“awdark”主题,可以从https://sourceforge.net/projects/tcl-awthemes/下载。下载并解压awthemes-*.zip文件到某个地方。你会注意到它包含一堆.tcl文件,一个名为i的子目录,其中包含主题使用的图片等更多目录。\n", "\n", "其中一个文件名为pkgIndex.tcl。这表明它是一个Tcl包,类似于其他语言中的模块。如果我们查看内部,你会看到许多类似package ifneeded awdark 7.7的行。这里,awdark是包的名称,7.7是它的版本号。在这种情况下,一个单独的pkgIndex.tcl文件提供几个包并不罕见。\n", "\n", "要使用它,我们需要告诉Tcl在哪里找到这个包(通过将其目录添加到Tcl的auto_path中)以及要使用的包的名称。\n", "```python\n", "root.tk.call('lappend', 'auto_path', '/full/path/to/awthemes-9.3.1')\n", "root.tk.call('package', 'require', 'awdark')\n", "```\n", "如果主题被实现为一个单一的Tcl源文件,而没有pkgIndex.tcl,你可以这样使其可用:\n", "```python\n", "root.tk.call('source', '/full/path/to/themefile.tcl')\n", "```\n", "你现在应该能够像使用内置主题一样在自己的应用程序中使用这个主题了。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 使用样式与主题\n", "\n", "现在,我们将着手处理一个更复杂的问题:充分利用应用程序内的样式和主题,而不仅仅是使用现有主题进行简单的换肤。\n", "\n", "首先,我们需要介绍几个基本概念。\n", "\n", "`小部件类(Widget Class)`\n", ": 小部件类标识特定小部件的类型,无论是按钮、标签、画布等。所有有主题的小部件都有一个默认类。按钮具有TButton类,标签具有TLabel类,依此类推。\n", "\n", "`小部件状态(Widget State)`\n", ": 小部件状态允许单个小部件根据鼠标位置、应用程序设置的不同状态选项等因素具有多种外观或行为。\n", "\n", " 正如您所记得的,所有有主题的小部件都会维护一组二进制状态标志,通过state和instate方法访问这些标志。这些标志包括:active, disabled, focus, pressed, selected, background, readonly, alternate, 和 invalid。所有小部件都有相同的状态标志集,尽管它们可能会忽略其中一些(例如,标签小部件可能会忽略invalid状态标志)。有关每个状态标志的确切含义,请参阅参考手册中的有[主题小部件](https://tcl.tk/man/tcl8.6/TkCmd/ttk_widget.htm)页面。\n", "\n", "`样式(Style)`\n", ": 样式描述了一个小部件类的外观(或多种外观)。所有具有相同小部件类的有主题小部件将具有相同的外观。\n", "\n", " 样式通过它们描述的小部件类的名称来引用。例如,TButton样式定义了所有具有TButton类的小部件的外观。\n", "\n", " 样式了解不同的状态,并且一种样式可以根据小部件的状态定义不同的外观。例如,样式可以指定如果设置了pressed状态标志,小部件的外观应该如何变化。\n", "\n", "`主题(Theme)`\n", ": 主题是样式的集合。虽然每种样式都是针对特定小部件的(如按钮、输入框等),但一个主题会将许多样式收集在一起。同一主题中的所有样式将被设计为在视觉上相互“匹配”。(不幸的是,Tk并不技术上限制糟糕的设计或判断!)\n", "\n", " 在应用程序中使用特定主题实际上意味着,默认情况下,每个小部件的外观将由该主题中负责该小部件类的样式控制。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "每种样式都有一个名称。如果你打算修改一种样式,创建一个新的样式,或者为一个控件使用某种样式,你需要知道它的名字。\n", "\n", "你如何知道这些样式的名称是什么呢?如果你有特定的控件,并且想知道它当前使用的是哪种样式,你可以首先检查其样式配置选项的值。如果该值为空,这意味着控件正在使用该控件的默认样式。你可以通过控件的类来检索这个信息。例如:\n", "\n", "```python\n", ">>> b = ttk.Button()\n", ">>> b['style']\n", "''\n", ">>> b.winfo_class()\n", "'TButton'\n", "```\n", "在这种情况下,正在使用的样式是 TButton。其他主题化控件的默认样式名称类似,例如 TEntry, TLabel 等。\n", "\n", "始终明智的做法是检查具体情况。例如,treeview 控件的类是 Treeview,而不是 TTreeview。\n", "\n", "除了默认样式之外,样式的名称几乎可以是任何内容。你可能创建一个自己的样式(或使用一个带有样式的主题),命名为 FunButton、NuclearReactorButton,甚至是 GuessWhatIAm(这不是一个明智的选择)。\n", "\n", "更常见的是,你会发现像 Fun.TButton 或 NuclearReactor.TButton 这样的名字。这些建议了基础样式的变化;正如你将看到的,这是 Tk 支持创建和修改样式的一种方式。\n", "\n", "目前还不支持检索所有当前可用样式列表的能力。这可能会在 Tk 8.7 中以新命令的形式出现,ttk::style theme styles,返回由主题实现的样式列表。它还提议为所有控件添加一个样式方法,这样你就不需要同时检查控件的样式配置选项和它的类。参见 [`TIP #584`](https://core.tcl-lang.org/tips/doc/trunk/tip/584.md)。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 应用样式\n", "要使用一种样式,意味着将该样式应用于单个小部件。你所需要的只是样式的名称和要应用它的小部件。可以在创建时设置样式:\n", "```python\n", "b = ttk.Button(parent, text='Hello', style='Fun.TButton')\n", "```\n", "也可以稍后通过样式配置选项更改小部件的样式:\n", "```python\n", "b['style'] = 'Emergency.TButton'\n", "```\n", "### 创建简单样式\n", "那么,我们如何创建一个新的样式,如Emergency.TButton?\n", "\n", "在这种情况下,你是在创建一个与现有样式略有不同的新样式。这是创建新样式最常见的原因。\n", "\n", "例如,你希望你的应用程序中的大多数按钮保持它们通常的外观,但有一些“紧急”按钮以不同的方式突出显示。基于基础样式(TButton)创建新样式(例如Emergency.TButton)是合适的。\n", "\n", "在一个现有样式前加上另一个名称(Emergency),然后是一个点,可以创建一个从现有样式派生的新样式。新样式将具有与现有样式完全相同的选项,除了指示的差异:\n", "```python\n", "s.configure('Emergency.TButton', font='helvetica 24', foreground='red', padding=10)\n", "```\n", "如前面所示,你可以通过其样式配置选项将该样式应用于单个按钮小部件。所有其他按钮小部件将保持其正常外观。\n", "\n", "你怎么知道对于给定的样式有哪些可更改的选项?这需要更深入地了解样式内部。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{note}\n", "您可能拥有使用经典小部件的现有代码,并希望迁移到主题化的小部件。通过配置选项对经典小部件所做的大多数外观更改可能都会被放弃。对于那些不能放弃的更改,您可能需要创建一个新的样式,如上所示。\n", "\n", "特定状态的外观更改可以类似地处理。在经典的Tk中,几个小部件通过配置选项支持了一些状态变化。例如,将按钮的状态选项设置为disabled会使其显示为灰色的标签。有些还允许一个额外的active状态,这代表了不同的外观。你可以通过一组配置选项改变小部件在多种状态下的外观,例如前景色、禁用时的前景色和激活时的前景色。\n", "\n", "通过配置选项进行的状态更改应改为使用主题小部件上的状态方法。修改特定状态下小部件外观的配置选项应在样式中处理。\n", "\n", "经典Tk小部件还支持一种非常原始的样式形式,你可能会遇到。这使用了选项数据库,这是一个现在已不太为人所知的X11风格配置文件前端。\n", "\n", "在经典Tk中,所有按钮都属于同一个类(Button),所有标签都属于同一个类(Label),依此类推。你可以使用这个小部件类进行内省,并通过选项数据库全局更改选项。例如,你可以说所有按钮都应该有红色背景。\n", "\n", "包括框架和小窗口在内的一些经典Tk小部件,允许你在首次创建特定小部件时通过提供类配置选项来更改小部件的类。例如,你可以指定一个特定的框架小部件有一个名为SpecialFrame的类,而其他则使用默认的Frame类。你可以使用选项数据库仅改变SpecialFrame框架的外观。\n", "\n", "样式和主题将这一简单概念加以扩展,为其增加了强大的功能。\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 风格内含何物?\n", "如果你只是想使用一种风格或对现有风格进行一些微调来创建新的风格,现在你已经知道你需要的一切了。然而,如果你想做出更实质性的改变,事情就开始变得“有趣”起来。\n", "\n", "### 元素\n", "虽然每种风格控制着单一类型的小部件,但每个小部件通常由更小的部分组成,这些部分被称为元素。风格的作者的任务是使用这些较小的元素构建整个小部件。这些元素是什么取决于小部件本身。\n", "\n", "以按钮为例。它最外面可能有一个边框。这是一个元素。紧接着里面,可能有一个焦点环。通常情况下,只是背景颜色,但当用户通过Tab键进入按钮时可能会被高亮显示。所以这是第二个元素。然后,在那个焦点环和按钮的标签之间可能有一段间距。那段间距是第三个元素。最后,按钮本身的文本标签是第四个元素。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![按钮可能包含的元素](images/button_elements.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "为什么作者会以这种方式划分?如果你发现一个部件的一部分可能位于不同的位置或具有不同的颜色,那么它可能是一个好的元素候选。请注意,这只是按钮如何由元素构成的一个例子。不同的风格和主题可以(而且确实)以不同的方式实现这一目标。\n", "\n", "这里有一个垂直滚动条的例子。它包括一个“轨道”元素,该元素包含其他元素。这些包括位于两端的向上和向下箭头元素,以及中间的“滑块”元素(它可能还有额外的元素,如边框)。\n", "\n", "![滚动条的元素](images/scrollbar_elements.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 布局\n", "\n", "除了指定哪些元素构成一个控件外,样式还定义了这些元素在控件内的排列方式。这被称为它们的布局。我们的按钮包含一个标签元素在一个间距元素内,该间距元素又位于一个焦点环元素内部,最后这个焦点环元素被一个边框元素所包围。其逻辑布局如下:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "border {\n", " focus {\n", " spacing {\n", " label\n", " }\n", " }\n", "}\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "我们可以向Tk查询TButton样式的布局:\n", "\n", "```python\n", ">>> s.layout('TButton')\n", "[(\"Button.border\", {\"children\": [(\"Button.focus\", {\"children\": [(\"Button.spacing\",\n", "{\"children\": [(\"Button.label\", {\"sticky\": \"nswe\"})], \"sticky\": \"nswe\"})], \n", "\"sticky\": \"nswe\"})], \"sticky\": \"nswe\", \"border\": \"1\"})]\n", "```\n", "如果我们整理并格式化这段代码,我们可以得到如下结构:\n", "\n", "```\n", "Button.border -sticky nswe -border 1 -children {\n", " Button.focus -sticky nswe -children {\n", " Button.spacing -sticky nswe -children {\n", " Button.label -sticky nswe\n", " }\n", " }\n", "}\n", "```\n", "这开始变得有意义了;我们有四个元素,分别是Button.border、Button.focus、Button.spacing和Button.label。每个元素都有不同的选项,如children(子元素)、sticky(粘性)和border(边框),这些选项指定了布局或大小。在这个阶段不过多深入细节,我们可以清楚地看到基于children(子元素)和sticky(粘性)属性的嵌套布局。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{note}\n", "Styles采用了Tk的pack几何管理器的简化版本来指定元素布局。这一内容在[样式参考](https://tcl.tk/man/tcl8.6/TkCmd/ttk_style.htm)手册中有详细说明。\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 元素选项\n", "每个元素都有几种不同的选项。例如,标签元素具有字体和前景色。代表滚动条滑块的元素可能有一个选项用于设置其背景颜色,另一个选项用于提供边框的宽度。这些都可以自定义,以调整整体小部件内元素的外观。\n", "\n", "您如何确定每个元素可用的选项?这里有一个示例,检查按钮内部的标签(根据布局方法我们知道被标识为Button.label)有哪些可用选项:\n", "```python\n", ">>> s.element_options('Button.label')\n", "('compound', 'space', 'text', 'font', 'foreground', 'underline', 'width', 'anchor', 'justify',\n", "'wraplength', 'embossed', 'image', 'stipple', 'background')\n", "```\n", "\n", "在接下来的部分中,我们将研究与元素选项一起工作的并不完全直接的方法。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 修改样式\n", "在本节中,我们将看到如何通过修改样式选项来改变样式的外观。你可以通过修改现有样式来完成这一点,或者更常见的是,创建一个新的样式。我们之前已经看到了如何创建一个从另一个样式派生出来的简单样式:\n", "```python\n", "s.configure('Emergency.TButton', font='helvetica 24', foreground='red', padding=10)\n", "```\n", "### 修改样式选项\n", "修改现有样式的选项与修改任何其他配置选项类似,通过指定样式、选项的名称和新值来完成:\n", "```python\n", "s.configure('TButton', font='helvetica 24')\n", "```\n", "你将很快了解到有效的选项有哪些。\n", "```{note}\n", "如果你像我们这里对TButton所做的那样修改了一个现有样式,那么这个修改将应用于所有使用该样式的小部件(默认情况下,所有的按钮)。这可能正是你想要做的。\n", "```\n", "要检索一个选项的当前值,使用lookup方法:\n", "```python\n", ">>> s.lookup('TButton', 'font')\n", "'helvetica 24'\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 特定状态下的样式选项\n", "除了常规的样式配置选项外,小部件作者可能已经指定了当小部件处于特定状态时使用的不同选项。例如,当按钮被禁用时,它可能会将按钮的标签颜色改为灰色。\n", "```{note}\n", "请记住,状态是由一个或多个状态标志(或它们的否定)组成的,这些标志通过小部件的状态方法设置或通过instate方法查询。\n", "```\n", "您可以使用映射来为样式的一个或多个配置选项指定特定于状态的变化。对于每个配置选项,您可以指定一个小部件状态列表以及在小部件处于该状态时应分配给该选项的值。\n", "\n", "以下示例提供了从按钮正常外观变化的情况:\n", "\n", "- 当小部件处于禁用状态时,背景色应设置为#d9d9d9\n", "- 当小部件处于活动状态(鼠标悬停其上)时,背景色应设置为#ececec\n", "- 当小部件处于禁用状态时,前景色应设置为#a3a3a3(这是除了我们已经注意到的背景色变化之外的额外变化)\n", "- 当小部件处于按钮被按下且小部件未被禁用的状态时,浮雕效果应设置为凹陷\n", "```python\n", "s.map('TButton', \n", " background=[('disabled','#d9d9d9'), ('active','#ececec')],\n", " foreground=[('disabled','#a3a3a3')],\n", " relief=[('pressed', '!disabled', 'sunken')])\n", "```\n", "\n", "```{note}\n", "由于小部件状态可以包含多个标志,因此可能不止一种状态与某个选项匹配(例如,如果小部件的按下状态标志被设置,则“pressed”和“pressed !disabled”都将匹配)。状态列表按照您在映射命令中提供的顺序进行评估。列表中第一个匹配的状态将被使用。\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 听起来困难吗?\n", "\n", "现在您已经知道,样式由各种元素组成,每个元素都有不同的选项,这些元素按照布局组合在一起。您可以更改样式的选项,使使用该样式的所有小部件看起来不同。任何使用该样式的小部件都会呈现出样式定义的外观。主题收集了一组相关的样式,使得更改整个用户界面的外观变得容易。\n", "\n", "那么,在实践中,是什么让样式和主题如此困难呢?有三个原因。首先:\n", "\n", "您只能修改样式的选项,而不能修改元素的选项(有时除外)。\n", "我们之前讨论过,通过检查样式的布局并确定每个元素的可用选项来识别样式中使用的元素。但是当我们试图改变一个样式时,我们似乎在配置一个没有指定单个元素的样式选项。这是怎么回事?\n", "\n", "再次以我们的按钮为例,我们有一个元素Button.label,它除了其他事情外,还有一个字体配置选项。当Button.label元素被绘制时,它会查看样式上设置的字体配置选项,以确定自己应该用哪种字体绘制。\n", "```{note}\n", "要理解这一点,您需要知道,当样式包含一个元素作为其一部分时,该元素不会维护任何(元素特定的)存储。特别是,它不会存储任何配置选项本身。当它需要检索选项时,它会通过传递到元素的包含样式来做到这一点。因此,各个元素在GoF模式术语中是“轻量级”对象。\n", "```\n", "同样,任何其他元素也会从样式上设置的选项中查找它们的配置选项。如果两个元素使用相同的配置选项(例如背景颜色),会发生什么?因为只有一个背景配置选项(存储在样式中),所以两个元素将使用相同的背景颜色。你不能让一个元素使用一种背景颜色,而另一个元素使用另一种背景颜色。\n", "```{note}\n", "除非您可以这样做。在当前实现中有一些称为子布局的讨厌的小部件特定的东西,它们允许您有时只修改单个元素,通过配置像TButton.Label这样的选项(而不是TButton,即样式的名称)。\n", "\n", "一些样式还提供了额外的配置选项,让您指定哪个元素受选项影响。例如,TCheckbutton样式为小部件的主要部分提供了一个背景选项,并为显示是否选中的框提供了一个指示器背景选项。\n", "\n", "可以这样做的情况是否有文档记录?有没有某种方法可以进行内省,以确定何时可以这样做?这两个问题的答案都是“有时”(信不信由你,这是一个改进;过去对这两个问题的回答都是明确的“不”)。有时您可以通过调用样式的configure方法而不提供任何新的配置选项来找到一些样式的选项。现在每个主题化小部件的参考手册页面通常都包括一个列出可能更改的选项的样式选项部分。\n", "```\n", "这是主题化小部件API的一个持续进化的区域。\n", "\n", "第二个困难也与修改样式选项有关:\n", "\n", "可用选项不一定有效果,修改一个不存在的选项并不是错误的。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "有时,您会尝试修改某个根据元素选项理应存在的选项,但这样做并不会有任何效果。例如,在macOS使用的aqua主题中,您无法更改按钮的背景颜色。虽然这些情况有其合理的原因,但发现它们并不容易,这可能会使实验过程时而令人沮丧。\n", "\n", "更令人沮丧的是,当您进行试验时,指定错误的样式名或选项名并不会引发错误。在进行配置或查找时,您可以为样式或选项提供一个完全任意的名称。因此,如果您对背景和字体选项感到厌倦,不妨尝试配置一个名为“dowhatimean”的选项。它可能不会做任何事情,但这不构成错误。同样,这也可能导致难以知道应该修改什么以及不应该修改什么。\n", "```{note}\n", "这是拥有一个非常轻量级和动态系统的不利之处之一。您可以通过在配置样式选项时提供其名称来创建新样式,而无需显式创建样式对象。同时,这也容易出错。此外,也不可能知道当前存在或使用哪些样式。请记住,样式选项实际上只是元素选项的前端,而样式中的元素随时都可能发生变化。显然,选项应仅限于当前元素所引用的那些,而这些元素本身可能并非全部可内省。\n", "```\n", "最后,让样式和主题如此困难的是:\n", "\n", "每个主题中可用的元素、这些元素的名称、影响每个元素的可用或已用选项,以及用于特定小部件的选项可能各不相同。\n", "那么?请记住,每个平台(Windows、macOS和Linux)的默认主题是不同的(这是一件好事)。这意味着:\n", "\n", "- 如果您想为您的应用程序定义一种新的小部件类型(或现有小部件的变体),您需要分别为您的应用程序使用的主题(即,跨平台应用程序至少三种)单独且不同地进行操作。\n", "- 由于每个主题/平台可用的元素和选项可能不同,您可能需要为每个主题/平台采用完全不同的定制方法。\n", "- 每个主题中可用的元素、名称和元素选项通常未被记录(除了阅读主题定义文件本身外),但通常通过主题内省来确定。因为所有主题并不在所有平台上都可用(例如,aqua仅在macOS上可用),您将需要准备好访问您需要在每个平台上运行的所有平台和主题。\n", "- 考虑尝试自定义一个按钮。您知道它使用了TButton样式。但该样式在每个平台上使用不同的主题实现。如果您检查每个主题中的该样式布局,您会发现每个主题使用不同的元素以不同的方式排列。如果您试图找到每个元素宣传的可用选项,您会发现这些也有所不同。当然,即使一个选项名义上可用,它也可能不会产生任何效果。\n", "\n", "总之,在经典的Tk中,您可以修改单个小部件的大量属性,您在一个平台上能够做到的事情,在其他地方可能也能行得通(但可能需要调整)。在有主题的Tk中,轻松的选择并不存在,如果您希望您的应用程序能在多个主题/平台上工作,您几乎被迫必须以正确的方式进行。这需要更多的前期工作。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 高级:更多关于元素的内容\n", "虽然在这个教程中,我们将不再深入探讨样式和主题的风格与主题,但对于那些好奇的用户以及那些想进一步探索创建新主题的人来说,我们可以提供更多有关元素的有趣信息。\n", "\n", "由于元素是样式和主题的构建块,这就引出了一个问题:“元素从哪里来?”实际上,我们可以说元素通常是用C代码创建的,并符合主题引擎所理解的特定API。\n", "\n", "在最底层,元素来源于所谓的元素工厂。目前,有一个默认的元素工厂,大多数主题都在使用它,它使用Tk绘图程序来创建元素。第二种方法允许你从图像中创建元素,并且可以通过脚本级别的ttk::style element create方法(来自Tcl)访问。支持所有Tk支持的图像格式,包括可缩放的图像格式如SVG,如果你有正确的扩展名的话。最后,还有一个特定的Windows引擎,使用底层的“视觉样式”平台API。\n", "\n", "如果一个主题使用了通过平台的本地小部件创建的元素,那么调用这些本地小部件的代码通常会出现在该主题的元素规范代码中。当然,依赖本地小部件或API调用的主题只能在支持它们的平台上运行。\n", "\n", "然后,主题会取一组元素,并使用它们来组装实际由小部件使用的样式。考虑到整个主题的概念是几种样式可以共享相同的外观,不同样式共享相同元素也就不足为奇了。\n", "\n", "因此,尽管TButton样式包含一个Button.padding元素,而TEntry样式包含一个Entry.padding元素,但在底层,这些填充元素很可能是同一个。它们可能看起来不同,但那是因为不同的配置选项,正如我们记得的那样,这些配置选项存储在使用元素的样式中。\n", "\n", "发现一个主题可以提供一套通用选项作为每个样式的默认值,如果样式没有特别指定的话,这也许并不奇怪。这意味着,如果整个主题中的几乎所有东西都有一个绿色背景,那么这个主题不需要为每个样式显式地说明这一点。这使用的是名为\".\"的根样式。如果Fun.TButton可以从TButton继承,为什么TButton不能从\".\"继承呢?\n", "\n", "最后,值得一看现有主题是如何定义的,无论是在Tk的C库中的C代码级别上,还是通过Tk的\"library/ttk\"目录或第三方主题中找到的Tk脚本。在Tk的C库中搜索Ttk_RegisterElementSpec以查看如何指定元素。" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 }