未显示 Tkinter ttk 背景样式的自定义
Customization of Tkinter ttk background style is not being shown
在下面的代码中,show_widget_validity()
函数要么应用自定义样式,该自定义样式只是对小部件现有样式的背景颜色进行了更改,要么恢复原始样式。这是一个库例程,因此不能完全控制样式。背景颜色似乎已正确重新配置,如每次更改后条目小部件中报告的背景样式描述所示。但是,小部件的实际背景颜色并没有改变。
此行为在 Python 2.7 和 3.6 的 Linux 以及 Python 2.7 的 Windows 上出现;我没有测试过其他环境。
任何有关此行为原因的线索,或解决此问题所需的代码更改,我们将不胜感激。
编辑: 使用 "fieldbackground" 而不是 "background" 对 Linux 有效,但对 Windows 无效,并且不允许修改禁用状态下的背景颜色。
try:
import Tkinter as tk
except:
import tkinter as tk
try:
import ttk
except:
from tkinter import ttk
def show_widget_validity(widget, is_valid):
invalid_color = "#fff5ff"
invalid_disabled_color = "#f6f0f6"
sname = widget["style"] or widget.winfo_class()
if sname.startswith("Invalid."):
if is_valid:
widget['style'] = sname[8:]
else:
if not is_valid:
invname = "Invalid." + sname
ttk.Style().configure(invname, background=[('disabled', invalid_disabled_color), ('active', invalid_color)])
# Simpler, but also ineffective:
#ttk.Style().configure(invname, background=invalid_color)
widget['style'] = invname
def show_invalid():
show_widget_validity(entry, False)
entry_var.set(ttk.Style().lookup(entry["style"] or entry.winfo_class(), "background"))
def show_valid():
show_widget_validity(entry, True)
entry_var.set(ttk.Style().lookup(entry["style"] or entry.winfo_class(), "background"))
root = tk.Tk()
root.title("Testing of Style Customization")
appframe = tk.Frame(root, padx=12, pady=12)
appframe.pack(expand=True, fill=tk.BOTH)
entry_var = tk.StringVar()
entry = ttk.Entry(appframe, textvariable=entry_var, width=40, exportselection=False)
entry.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
btnframe = ttk.Frame(appframe)
btnframe.grid(row=1, column=0)
invalid_btn = ttk.Button(btnframe, text="Make invalid", command=show_invalid)
valid_btn = ttk.Button(btnframe, text="Make valid", command=show_valid)
invalid_btn.grid(row=0, column=0, padx=3, pady=3)
valid_btn.grid(row=0, column=1, padx=3, pady=3)
root.mainloop()
您似乎正试图通过更改背景使输入字段显示内容的有效性。但是,您每次都试图重新配置样式,这不是正确的方法。相反,您应该根据小部件状态配置动态属性。这就是 disabled/!disabled 和 pressed/!pressed 外观变化的处理方式。 ttk 小部件有一个状态方法,您可以在其中更改许多标志。 disabled 是最常见的,pressed 是 button-type 小部件。另一个是 active,当指针悬停在小部件上时小部件改变其外观,使其看起来 'hot'。为了您的目的,定义了一个方便的 invalid 状态。我们只需要将它添加到小部件样式的样式映射中即可。由于我们不想影响所有 Entry 小部件,我们可以将当前样式复制到新的 Custom.Entry
样式:
style = ttk.Style()
style.layout("Custom.Entry", style.layout('TEntry'))
style.configure("Custom.Entry", **style.configure('TEntry'))
style.map("Custom.Entry", **style.map('TEntry'))
style.map("Custom.Entry",
fieldbackground=[(['invalid','!disabled'], '#ff4040'),
(['invalid','disabled'], '#ffc0c0')])
entry = ttk.Entry(root, style='Custom.Entry')
在 Tk-based 主题上,这足以让小部件的背景根据小部件 invalid 状态更改颜色。即:entry.state(['invalid'])
将使其使用红色背景。对于使用 Windows 等原生绘图元素的主题和 MacOS 主题,这需要做更多的工作。我们不一定要更改本机主题引擎绘制元素的外观,除非本机引擎已经支持 invalid 状态。如果没有,那么我们可以通过从 Tk-based 主题之一克隆新元素来覆盖构成小部件表示的元素。为了说明这一点,请参阅下面的 createCustomEntry
函数,该函数从 'default' 主题复制字段背景,以便我们可以更改 Windows.
的外观
在 Linux 现在看起来像这样:
在 Windows 7:
修改后的代码
try:
import Tkinter as tk
except:
import tkinter as tk
try:
import ttk
except:
from tkinter import ttk
def createCustomEntry(style):
if 'Custom.Entry.field' not in style.element_names():
style.element_create('Custom.Entry.field', 'from', 'default')
if style.theme_use() in ['alt', 'clam', 'default', 'classic']:
style.layout('Custom.Entry', style.layout('TEntry'))
else:
style.layout("Custom.Entry", [
("Custom.Entry.field", {'sticky': 'nswe', 'border': '1', 'children': [
("Custom.Entry.padding", {'sticky':'nswe', 'children': [
("Custom.Entry.textarea", {'sticky':'nswe'})
]})
]})
])
style.configure('Custom.Entry', **style.configure('TEntry'))
style.map('Custom.Entry', **style.map('TEntry'))
style.map('Custom.Entry',
fieldbackground=[(['invalid','!disabled'], '#ff4040'),
(['invalid','disabled'], '#ffc0c0')])
def show_invalid():
[w.state(['invalid']) for w in (entry, entry2)]
def show_valid():
[w.state(['!invalid']) for w in (entry,entry2)]
root = tk.Tk()
# Simple version:
style = ttk.Style()
style.layout("Custom.Entry", style.layout('TEntry'))
style.configure("Custom.Entry", **style.configure('TEntry'))
style.map("Custom.Entry", **style.map('TEntry'))
style.map("Custom.Entry",
fieldbackground=[(['invalid','!disabled'], '#ff4040'),
(['invalid','disabled'], '#ffc0c0')])
#createCustomEntry(style)
root.title("Testing of Style Customization")
appframe = tk.Frame(root, padx=12, pady=12)
appframe.pack(expand=True, fill=tk.BOTH)
entry_var = tk.StringVar()
entry = ttk.Entry(appframe, textvariable=entry_var, width=40,
exportselection=False, style="Custom.Entry")
entry.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
entry2 = ttk.Entry(appframe, textvariable=entry_var, width=40,
exportselection=False, style="Custom.Entry")
entry2.grid(row=1, column=0, padx=3, pady=3, sticky=tk.EW)
entry2.state(['disabled'])
btnframe = ttk.Frame(appframe)
btnframe.grid(row=2, column=0)
invalid_btn = ttk.Button(btnframe, text="Make invalid", command=show_invalid)
valid_btn = ttk.Button(btnframe, text="Make valid", command=show_valid)
invalid_btn.grid(row=0, column=0, padx=3, pady=3)
valid_btn.grid(row=0, column=1, padx=3, pady=3)
root.mainloop()
在下面的代码中,show_widget_validity()
函数要么应用自定义样式,该自定义样式只是对小部件现有样式的背景颜色进行了更改,要么恢复原始样式。这是一个库例程,因此不能完全控制样式。背景颜色似乎已正确重新配置,如每次更改后条目小部件中报告的背景样式描述所示。但是,小部件的实际背景颜色并没有改变。
此行为在 Python 2.7 和 3.6 的 Linux 以及 Python 2.7 的 Windows 上出现;我没有测试过其他环境。
任何有关此行为原因的线索,或解决此问题所需的代码更改,我们将不胜感激。
编辑: 使用 "fieldbackground" 而不是 "background" 对 Linux 有效,但对 Windows 无效,并且不允许修改禁用状态下的背景颜色。
try:
import Tkinter as tk
except:
import tkinter as tk
try:
import ttk
except:
from tkinter import ttk
def show_widget_validity(widget, is_valid):
invalid_color = "#fff5ff"
invalid_disabled_color = "#f6f0f6"
sname = widget["style"] or widget.winfo_class()
if sname.startswith("Invalid."):
if is_valid:
widget['style'] = sname[8:]
else:
if not is_valid:
invname = "Invalid." + sname
ttk.Style().configure(invname, background=[('disabled', invalid_disabled_color), ('active', invalid_color)])
# Simpler, but also ineffective:
#ttk.Style().configure(invname, background=invalid_color)
widget['style'] = invname
def show_invalid():
show_widget_validity(entry, False)
entry_var.set(ttk.Style().lookup(entry["style"] or entry.winfo_class(), "background"))
def show_valid():
show_widget_validity(entry, True)
entry_var.set(ttk.Style().lookup(entry["style"] or entry.winfo_class(), "background"))
root = tk.Tk()
root.title("Testing of Style Customization")
appframe = tk.Frame(root, padx=12, pady=12)
appframe.pack(expand=True, fill=tk.BOTH)
entry_var = tk.StringVar()
entry = ttk.Entry(appframe, textvariable=entry_var, width=40, exportselection=False)
entry.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
btnframe = ttk.Frame(appframe)
btnframe.grid(row=1, column=0)
invalid_btn = ttk.Button(btnframe, text="Make invalid", command=show_invalid)
valid_btn = ttk.Button(btnframe, text="Make valid", command=show_valid)
invalid_btn.grid(row=0, column=0, padx=3, pady=3)
valid_btn.grid(row=0, column=1, padx=3, pady=3)
root.mainloop()
您似乎正试图通过更改背景使输入字段显示内容的有效性。但是,您每次都试图重新配置样式,这不是正确的方法。相反,您应该根据小部件状态配置动态属性。这就是 disabled/!disabled 和 pressed/!pressed 外观变化的处理方式。 ttk 小部件有一个状态方法,您可以在其中更改许多标志。 disabled 是最常见的,pressed 是 button-type 小部件。另一个是 active,当指针悬停在小部件上时小部件改变其外观,使其看起来 'hot'。为了您的目的,定义了一个方便的 invalid 状态。我们只需要将它添加到小部件样式的样式映射中即可。由于我们不想影响所有 Entry 小部件,我们可以将当前样式复制到新的 Custom.Entry
样式:
style = ttk.Style()
style.layout("Custom.Entry", style.layout('TEntry'))
style.configure("Custom.Entry", **style.configure('TEntry'))
style.map("Custom.Entry", **style.map('TEntry'))
style.map("Custom.Entry",
fieldbackground=[(['invalid','!disabled'], '#ff4040'),
(['invalid','disabled'], '#ffc0c0')])
entry = ttk.Entry(root, style='Custom.Entry')
在 Tk-based 主题上,这足以让小部件的背景根据小部件 invalid 状态更改颜色。即:entry.state(['invalid'])
将使其使用红色背景。对于使用 Windows 等原生绘图元素的主题和 MacOS 主题,这需要做更多的工作。我们不一定要更改本机主题引擎绘制元素的外观,除非本机引擎已经支持 invalid 状态。如果没有,那么我们可以通过从 Tk-based 主题之一克隆新元素来覆盖构成小部件表示的元素。为了说明这一点,请参阅下面的 createCustomEntry
函数,该函数从 'default' 主题复制字段背景,以便我们可以更改 Windows.
在 Linux 现在看起来像这样:
在 Windows 7:
修改后的代码
try:
import Tkinter as tk
except:
import tkinter as tk
try:
import ttk
except:
from tkinter import ttk
def createCustomEntry(style):
if 'Custom.Entry.field' not in style.element_names():
style.element_create('Custom.Entry.field', 'from', 'default')
if style.theme_use() in ['alt', 'clam', 'default', 'classic']:
style.layout('Custom.Entry', style.layout('TEntry'))
else:
style.layout("Custom.Entry", [
("Custom.Entry.field", {'sticky': 'nswe', 'border': '1', 'children': [
("Custom.Entry.padding", {'sticky':'nswe', 'children': [
("Custom.Entry.textarea", {'sticky':'nswe'})
]})
]})
])
style.configure('Custom.Entry', **style.configure('TEntry'))
style.map('Custom.Entry', **style.map('TEntry'))
style.map('Custom.Entry',
fieldbackground=[(['invalid','!disabled'], '#ff4040'),
(['invalid','disabled'], '#ffc0c0')])
def show_invalid():
[w.state(['invalid']) for w in (entry, entry2)]
def show_valid():
[w.state(['!invalid']) for w in (entry,entry2)]
root = tk.Tk()
# Simple version:
style = ttk.Style()
style.layout("Custom.Entry", style.layout('TEntry'))
style.configure("Custom.Entry", **style.configure('TEntry'))
style.map("Custom.Entry", **style.map('TEntry'))
style.map("Custom.Entry",
fieldbackground=[(['invalid','!disabled'], '#ff4040'),
(['invalid','disabled'], '#ffc0c0')])
#createCustomEntry(style)
root.title("Testing of Style Customization")
appframe = tk.Frame(root, padx=12, pady=12)
appframe.pack(expand=True, fill=tk.BOTH)
entry_var = tk.StringVar()
entry = ttk.Entry(appframe, textvariable=entry_var, width=40,
exportselection=False, style="Custom.Entry")
entry.grid(row=0, column=0, padx=3, pady=3, sticky=tk.EW)
entry2 = ttk.Entry(appframe, textvariable=entry_var, width=40,
exportselection=False, style="Custom.Entry")
entry2.grid(row=1, column=0, padx=3, pady=3, sticky=tk.EW)
entry2.state(['disabled'])
btnframe = ttk.Frame(appframe)
btnframe.grid(row=2, column=0)
invalid_btn = ttk.Button(btnframe, text="Make invalid", command=show_invalid)
valid_btn = ttk.Button(btnframe, text="Make valid", command=show_valid)
invalid_btn.grid(row=0, column=0, padx=3, pady=3)
valid_btn.grid(row=0, column=1, padx=3, pady=3)
root.mainloop()