TCL/TK GUI in Python 浅试笔记

TK GUI 学习

Tk 使用单线程、事件驱动的编程模型.
所有 GUI 代码、事件循环和 应用程序在同一线程中运行。因此,强烈建议不要进行任何阻止事件处理程序的调用或计算。

耗时操作的处理:

  1. 如果需要从另一个线程与运行 Tkinter 的线程进行通信,请尽可能保持简单。
    root.event_generate("<<MyOwnEvent>>") 将虚拟事件发布到 Tkinter 事件队列,然后在代码中处理该事件。
  2. 也可以在长时间的callback中,自己维护一个事件循环。

https://tkdocs.com/
https://oooutlk.github.io/tk/

个人感受:

TK来自于TCL,用来做小工具UI和脚本语言的UI是很不错的。
如果想要好的UI,大规模程序,现代化的IDE支持,建议选择其他GUI框架。

Python

Calculate

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("温度换算工具")

# main frame
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))

# 设置权重,拉伸
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)

# row 1
feet = StringVar(value='30')
feet_entry = ttk.Entry(mainframe, width=7, textvariable=feet)
feet_entry.grid(column=2, row=1, sticky=(W,E))
ttk.Label(mainframe, text=C').grid(column=3, row=1, sticky=(W,E))

# row 2
ttk.Label(mainframe, text='is equivalent to').grid(column=1, row=2, sticky=E)
meters = StringVar()
ttk.Label(mainframe, textvariable=meters).grid(column=2, row=2, sticky=(W,E))
ttk.Label(mainframe, text=F').grid(column=3, row=2, sticky=W)

# row 3
def btn_calculate(*args):
    val = float(feet.get())
    meters.set(val*1.8+32)

ttk.Button(mainframe, text='Calculate', command=btn_calculate).grid(column=3, row=3, sticky=W)

# layout 
for child in mainframe.winfo_children(): 
    child.grid_configure(padx=5, pady=5)

# focus
feet_entry.focus()

# key binding
root.bind("<Return>", btn_calculate)

root.mainloop()

########### class version #############

from tkinter import *
from tkinter import ttk

class CalculateGUI:
    def __init__(self, root: Tk) -> None:
        root.title("温度换算工具")

        # main frame
        mainframe = ttk.Frame(root, padding="3 3 12 12")
        mainframe.grid(column=0, row=0, sticky=(N, W, E, S))

        root.columnconfigure(0, weight=1)
        root.rowconfigure(0, weight=1)

        # row 1
        self.feet = StringVar(value='26.3')
        feet_entry = ttk.Entry(mainframe, width=7, textvariable=self.feet)
        feet_entry.grid(column=2, row=1, sticky=(W,E))
        ttk.Label(mainframe, text=C').grid(column=3, row=1, sticky=(W,E))

        # row 2
        ttk.Label(mainframe, text='is equivalent to').grid(column=1, row=2, sticky=E)
        self.meters = StringVar()
        ttk.Label(mainframe, textvariable=self.meters).grid(column=2, row=2, sticky=(W,E))
        ttk.Label(mainframe, text=F').grid(column=3, row=2, sticky=W)

        # row 3
        ttk.Button(mainframe, text='Calculate', command=self.btn_calculate).grid(column=3, row=3, sticky=W)

        # layout 
        for child in mainframe.winfo_children(): 
            child.grid_configure(padx=5, pady=5)

        # focus
        feet_entry.focus()

        # key binding
        root.bind("<Return>", self.btn_calculate)

    def btn_calculate(self, *args):
        val = float(self.feet.get())
        self.meters.set(val*1.8+32)


if __name__ == '__main__':
    root = Tk()
    CalculateGUI(root)
    root.mainloop()

Widget

  1. https://tcl.tk/man/tcl8.6/TkCmd/contents.htm
  2. https://tcl.tk/man/tcl8.6/TkCmd/ttk_widget.htm
# 三种方式修改属性
button = ttk.Button(root, text="Hello", command="buttonpressed")
button['text'] = 'goodbye'
button.configure('text')

# list all
button.configure()

widget的几个方法:

  • winfo_class: 标识小部件类型的类,例如,主题按钮TButton
  • winfo_children: 作为层次结构中小组件的直接子级的小组件列表
  • winfo_parent: 层次结构中小组件的父级
  • winfo_toplevel: 包含此小组件的顶级窗口
  • winfo_width, winfo_height: 小部件的当前宽度和高度;在屏幕上出现之前不准确
  • winfo_reqwidth, winfo_reqheight: “几何管理器”(Geometry Manager) 的微件请求的宽度和高度
  • winfo_x, winfo_y: 小组件左上角相对于其父项的位置
  • winfo_rootx, winfo_rooty: 小组件左上角相对于整个屏幕的位置
  • winfo_vieweable: 小组件是显示还是隐藏(其在层次结构中的所有祖先必须可查看才能查看)
事件循环:
from tkinter import *
from tkinter import ttk
root = Tk()
l =ttk.Label(root, text="Starting...")
l.grid()
l.bind('<Enter>', lambda e: l.configure(text='Moved mouse inside'))
l.bind('<Leave>', lambda e: l.configure(text='Moved mouse outside'))
l.bind('<ButtonPress-1>', lambda e: l.configure(text='Clicked left mouse button'))
l.bind('<3>', lambda e: l.configure(text='Clicked right mouse button'))
l.bind('<Double-1>', lambda e: l.configure(text='Double clicked'))
l.bind('<B3-Motion>', lambda e: l.configure(text='right button drag to %d,%d' % (e.x, e.y)))
root.mainloop()

事件List:

  • : 窗口已变为活动状态。
  • : 窗口已停用。
  • : 鼠标上的滚轮已移动。
  • : 键盘上的键已被按下。
  • : 密钥已发布。
  • : 已按下鼠标按钮。
  • : 鼠标按钮已松开。
  • : 鼠标已移动。
  • : 小组件已更改大小或位置。
  • : 小部件正在被销毁。
  • : 小部件已获得键盘焦点。
  • : Widget 已失去键盘焦点。
  • : 鼠标指针进入小部件。
  • : 鼠标指针离开小部件。
  • keyboard bind: https://tcl.tk/man/tcl8.6/TkCmd/keysyms.htm

也可以bind和产生自己的虚拟event: root.event_generate("<<MyOwnEvent>>")

mutable value
s = StringVar(value="abc")   # default value is ''
b = BooleanVar(value=True)   # default is False
i = IntVar(value=10)         # default is 0
d = DoubleVar(value=10.5)    # default is 0.0
width/height
f['width'] = 350   # pixels
f['width'] = 350c   # 厘米
f['width'] = 350m   # 毫米
f['width'] = 350i   # 英寸
f['width'] = 350p   # point
Padding
f['padding'] = 5           # 5 pixels on all sides
f['padding'] = (5,10)      # 5 on left and right, 10 on top and bottom
f['padding'] = (5,7,10,12) # left: 5, top: 7, right: 10, bottom: 12
border
frame['borderwidth'] = 2
frame['relief'] = 'sunken'
style
s = ttk.Style()
s.configure('Danger.TFrame', background='red', borderwidth=5, relief='raised')
ttk.Frame(root, width=200, height=200, style='Danger.TFrame').grid()
widget - label
# 静态文本
label = ttk.Label(parent, text='Full name:')

# 动态
resultsContents = StringVar()
label['textvariable'] = resultsContents
resultsContents.set('New value to display')

# 图像
image = PhotoImage(file='myimage.gif')
label['image'] = image

# font
# 一般是通过style改变一类,也可以单独设置
label['font'] = "TkDefaultFont"
label['foregroundbackgroundred'] = "#ff340a"

# layout
grid, sticky

# multi-line
label['text'] = 'abc\n123'  # 强制换行
label['wraplength'] = 5     # 自动换行
widget - button
action = ttk.Button(root, text="Action", default="active", command=myaction)
root.bind('<Return>', lambda e: action.invoke())

# state
b.state(['disabled'])          # set the disabled flag
b.state(['!disabled'])         # clear the disabled flag
b.instate(['disabled'])        # true if disabled, else false
b.instate(['!disabled'])       # true if not disabled, else false
b.instate(['!disabled'], cmd)  # execute 'cmd' if not disabled

widget - checkbutton
measureSystem = StringVar()
check = ttk.Checkbutton(parent, text='Use Metric', 
	    command=metricChanged, variable=measureSystem,
	    onvalue='metric', offvalue='imperial')

# state
check.instate(['alternate'])   # 三态state

widget - radiobutton
# 一组 radio button
phone = StringVar()
home = ttk.Radiobutton(parent, text='Home', variable=phone, value='home')
office = ttk.Radiobutton(parent, text='Office', variable=phone, value='office')
cell = ttk.Radiobutton(parent, text='Mobile', variable=phone, value='cell')

widget - entry/input
username = StringVar()
name = ttk.Entry(parent, textvariable=username)

# change it
print('current value is %s' % name.get())
name.delete(0,'end')          # delete between two indices, 0-based
name.insert(0, 'your name')   # insert new text at a given index

# watch value change
def it_has_been_written(*args):
    pass
username.trace_add("write", it_has_been_written)  # trace_remove, trace_info

# password
passwd = ttk.Entry(parent, textvariable=password, show="*")

# validate TODO

widget - Text

多行编辑框

t = Text(root, width=40, height=10)

txt['state'] = 'disabled'

# pos format: linenum.charnum
t.index('2.1') # cur move
t.see('2.1')   # view 移动到第二行,第一列

t.insert/delete # 插入,删除
thetext = text.get('1.0', 'end')  

# scroll 
t = Text(root, width = 40, height = 5, wrap = "none")
ys = ttk.Scrollbar(root, orient = 'vertical', command = t.yview)
xs = ttk.Scrollbar(root, orient = 'horizontal', command = t.xview)
t['yscrollcommand'] = ys.set
t['xscrollcommand'] = xs.set

# image
flowers = PhotoImage(file='flowers.gif')
text.image_create('sel.first', image=flowers)

# widget 
b = ttk.Button(text, text='Push Me')
text.window_create('1.0', window=b)
widget - combox
countryvar = StringVar()
country = ttk.Combobox(parent, textvariable=countryvar)

# 可选内容
country['values'] = ('USA', 'Canada', 'Australia')
country.state(["readonly"])  # 不能随意填写,只能选择

# selected
country.bind('<<ComboboxSelected>>', function)

# index current
country.current   # 0 based index

widget - listBox
choices = ["apple", "orange", "banana"]
choicesvar = StringVar(value=choices)
l = Listbox(parent, height=10, listvariable=choicesvar)

# 动态改变可选内容
choices.append("peach")
choicesvar.set(choices)

# 单选,多选
l.configure('selectmode', 'browse')  # extended 多选

if lbox.selection_includes(2): ...  # 2 是否选中

lbox.selection_set(idx)  # 选中 index
lbox.see(idx)  # index move 到可见范围

# 事件绑定
lbox.bind("<<ListboxSelect>>", lambda e: updateDetails(lbox.curselection()))  # When a user changes the selection
lbox.bind("<Double-1>", lambda e: invokeAction(lbox.curselection())) # mouse double-click

widget - Scrollbar

tk的scrollbar 都是单独的,不是widget的一部分。需要手动代码绑定。

s = ttk.Scrollbar( parent, orient=VERTICAL, command=listbox.yview) # Every widget that can be scrolled vertically includes a method named yview

# 关联listbox item数量与scrollbar的滚动范围
listbox.configure(yscrollcommand=s.set)
l['yscrollcommand'] = s.set

# 主动scroll
s.set(0.25, 0.26)  # 0~1 之间
widget - Scale

缩放,或者进度表示

# label tied to the same variable as the scale, so auto-updates
num = StringVar()
ttk.Label(root, textvariable=num).grid(column=0, row=0, sticky='we')

# label that we'll manually update via the scale's command callback
manual = ttk.Label(root)
manual.grid(column=0, row=1, sticky='we')

def update_lbl(val):
   manual['text'] = "Scale at " + val

scale = ttk.Scale(root, orient='horizontal', length=200, from_=1.0, to=100.0, variable=num, command=update_lbl)
scale.grid(column=0, row=2, sticky='we')
scale.set(20)
widget - Progressbar

进度条

p = ttk.Progressbar(parent, orient=HORIZONTAL, length=200, mode='determinate')  # indeterminate

# determinate 确定进度
p.maximum
p.value

# indeterminate 不确定的进度
p.start
p.stop
widget - Spinbox

带有编辑功能的选择框

spinval = StringVar()
s = ttk.Spinbox(parent, from_=1.0, to=100.0, textvariable=spinval, command=xxx)

# 也可以参考combox的用法,在一个list中选择
values, from

# event
Increment/Decrement
Layout
  • pack 最早期的方式,调整改动麻烦
  • grid 可以完全替代pack, 动态布局
  • place

一般的application都推荐使用grid布局。

  • columnspan/rowspan 跨多个单元格的占用。
  • sticky 控制cell内部widget的填充方式,默认是居中。
  • weight 控制多余空间grow权重。默认0 不改变。
  • columnconfigure/rowconfigure 可以配置weight.
  • padding/padx/pady 控制间距
from tkinter import *
from tkinter import ttk

root = Tk()

# 主frame
content = ttk.Frame(root, padding=(3,3,12,12))
content.grid(column=0, row=0, sticky=(N, S, E, W))

# 占用 0,0 的2行3列
frame = ttk.Frame(content, borderwidth=5, relief="ridge", width=200, height=100)
frame.grid(column=0, row=0, columnspan=3, rowspan=2, sticky=(N, S, E, W))

# 从第三列开始,占用2列
namelbl = ttk.Label(content, text="Name")
namelbl.grid(column=3, row=0, columnspan=2, sticky=(N, W), padx=5)
name = ttk.Entry(content)
name.grid(column=3, row=1, columnspan=2, sticky=(N, W), padx=5, pady=5)

# 第4行,每一列一个
onevar = BooleanVar(value=True)
twovar = BooleanVar(value=False)
threevar = BooleanVar(value=True)

one = ttk.Checkbutton(content, text="One", variable=onevar, onvalue=True)
two = ttk.Checkbutton(content, text="Two", variable=twovar, onvalue=True)
three = ttk.Checkbutton(content, text="Three", variable=threevar, onvalue=True)

one.grid(column=0, row=3)
two.grid(column=1, row=3)
three.grid(column=2, row=3)

# 第4行,c3
ok = ttk.Button(content, text="Okay")
ok.grid(column=3, row=3)

# 第4行,c4
cancel = ttk.Button(content, text="Cancel")
cancel.grid(column=4, row=3)

# 注意,没有row=2, 所以row=2占用高度为0

# resize时,自动grow的设定
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)

content.columnconfigure(0, weight=3)
content.columnconfigure(1, weight=3)
content.columnconfigure(2, weight=3)
content.columnconfigure(3, weight=1)
content.columnconfigure(4, weight=1)
content.rowconfigure(1, weight=1)

root.mainloop()

如果想要运行过程中,query/modify grid的属性:

# list the widgets
content.grid_slaves()

for w in content.grid_slaves(row=3): print(w)

# get info for grid
namelbl.grid_info()

# modify it
namelbl.grid_configure(sticky=(E,W))

# delete 
forget  # lost grid options
remove  # keep options when add it again
Widget - Menus
from tkinter import *
from tkinter import ttk, messagebox

root = Tk()
# 关闭默认的虚线,它会弹出菜单
root.option_add('*tearOff', FALSE)

# Menu
m = Menu(root)   # root Menubar
m_edit = Menu(m) # 最外面一层的UI的Menu
m.add_cascade(menu=m_edit, label="Edit")  # 添加到menubar menu中
m_edit.add_command(label="Paste", command=lambda: root.focus_get().event_generate("<<Paste>>")) # Edit的弹出选择子command
m_edit.add_command(label="Find Some thing", command=lambda: root.event_generate("<<OpenFindDialog>>"), underline=6)

root['menu'] = m  # 设置GUI上的 menu 

def launchFindDialog(*args):
    messagebox.showinfo(message="I hope you find what you're looking for!")
    
root.bind("<<OpenFindDialog>>", launchFindDialog)

# 多级menu
m_multi = Menu(m);
m.add_cascade(label='Top2', menu=m_multi);  # membar 上新增 Top2
m_multi.add_command(label='Top2-1')  # Top2 的子菜单 Top2-1
m_multi.add_command(label='Top2-2')
m_multi.add_separator()

# check menu
check = StringVar(value='1')
m_multi.add_checkbutton(label='Check', variable=check, onvalue=1, offvalue=0)

m_multi.add_separator()
# radio menu
radio = StringVar(value='2')
m_multi.add_radiobutton(label='One', variable=radio, value=1)
m_multi.add_radiobutton(label='Two', variable=radio, value=2)

m_multi.add_separator()

# submenu
m_multi2 = Menu(m_multi);
for i in range(10): 
    m_multi2.add_command(label='Top2-3_'+str(i))

m_multi.add_cascade(label='Top2-3', menu=m_multi2);  # 添加 menu 到 Top2-3子菜单下


# 用来测试的 Entry
ent = ttk.Entry(root)
ent.grid()
ent.focus()

# state
print( m_multi.entrycget(0, 'label')) # get label of top entry in menu
m_multi.entryconfigure('Top2-2', state=DISABLED)
m_multi.entryconfigure(3, label="change label")
m_edit.entryconfigure('Paste', accelerator='Command+V')


# conetextual menus  右键菜单
rclickm = Menu(root)
rclickm.add_command(label="Copy")
rclickm.add_command(label="Past")

if root.tk.call('tk', 'windowingsystem') == 'aqua':
    pass
else:
    # win32
    # 只绑定 Entry 控件
    ent.bind('<3>', lambda e: rclickm.post(e.x_root, e.y_root));


root.mainloop()
Image
imgobj = PhotoImage(file='myimage.gif')
label['image'] = imgobj

from PIL import ImageTk, Image
myimg = ImageTk.PhotoImage(Image.open('myimage.png'))

Canvas
canvas = Canvas(parent, width=500, height=400, background='gray75')
# 线
canvas.create_line(10, 10, 200, 50, fill='red', width=3)
canvas.create_line(10, 10, 200, 50, 90, 150, 50, 80)
canvas.itemconfigure(id, fill='blue', width=2)
# 矩形
canvas.create_rectangle(10, 10, 200, 50, fill='red', outline='blue')
# 椭圆
canvas.create_oval(10, 10, 200, 150, fill='red', outline='blue')
# 多边形
canvas.create_polygon(10, 10, 200, 50, 90, 150, 50, 80, 120, 55, fill='red', outline='blue')
# 饼、弧形
canvas.create_arc(10, 10, 200, 150, fill='yellow', outline='black', start=45, extent=135, width=5)
# Image
myimg = PhotoImage(file='pretty.png')
canvas.create_image(10, 10, image=myimg, anchor='nw')
# Text
canvas.create_text(100, 100, text='A wonderful story', anchor='nw', font='TkMenuFont', fill='red')
# Widget
b = ttk.Button(canvas, text='Implode!')
canvas.create_window(10, 10, anchor='nw', window=b)

# event bindings
canvas.tag_bind(id, '<1>', ...)

# tags
c.addtag('rectangle', 'withtag', 2)
c.addtag('polygon', 'withtag', 'rectangle')
c.dtag(2, 'polygon')
c.gettags(2)
c.find_withtag('drawing')

# scroll
h = ttk.Scrollbar(root, orient=HORIZONTAL)
v = ttk.Scrollbar(root, orient=VERTICAL)
canvas = Canvas(root, scrollregion=(0, 0, 1000, 1000), yscrollcommand=v.set, xscrollcommand=h.set)
h['command'] = canvas.xview
v['command'] = canvas.yview

widget - TreeView
tree = ttk.Treeview(parent)
tree = ttk.Treeview(root, columns=('size', 'modified'))
tree['columns'] = ('size', 'modified', 'owner')
tree.column('size', width=100, anchor='center')
tree.heading('size', text='Size')

# Inserted at the root, program chooses id:
tree.insert('', 'end', 'widgets', text='Widget Tour')
 
# Same thing, but inserted as first child:
tree.insert('', 0, 'gallery', text='Applications')

# Treeview chooses the id:
id = tree.insert('', 'end', text='Tutorial')

# Inserted underneath an existing node:
tree.insert('widgets', 'end', text='Canvas')
tree.insert(id, 'end', text='Tree')

# move widgets under gallery
tree.move('widgets', 'gallery', 'end')  

tree.detach('widgets')
tree.delete('widgets')

tree.item('widgets', open=TRUE)
isopen = tree.item('widgets', 'open')

其他
s = ttk.Separator(parent, orient=HORIZONTAL)
lf = ttk.Labelframe(parent, text='Label') # 也叫 groupbox 

# stack window layout
p = ttk.Panedwindow(parent, orient=VERTICAL)
f1 = ttk.Labelframe(p, text='Pane1', width=100, height=100)
p.add(f1)

# notebook, tabs
n = ttk.Notebook(parent)
f1 = ttk.Frame(n)   # first page, which would get widgets gridded into it
f2 = ttk.Frame(n)   # second page
n.add(f1, text='One')
n.add(f2, text='Two')

Window

window = Toplevel(parent)  # create new top window
window.destroy()  # 销毁

oldtitle = window.title()
window.title('New title')

window.geometry('300x200-5+40')
window.minsize(200,100)
window.maxsize(500,500)
window.resizable(FALSE,FALSE)

window.update_idletasks()  # 强制刷新,获取最新的size
print(window.geometry())

window.attributes("-alpha", 0.5) # 窗口透明度0-1
window.attributes("-fullscreen", 1) # 全屏幕
window.attributes("-topmost", 1) # top window

thestate = window.state()
window.state('normal')
window.iconify()
window.deiconify()
window.withdraw()

window.protocol("WM_DELETE_WINDOW", callback)  # 拦截关闭消息
print("width=", str(root.winfo_screenwidth()) + " height=", str(root.winfo_screenheight()))

对话框窗口:

from tkinter import filedialog
filename = filedialog.askopenfilename()
filename = filedialog.asksaveasfilename()
dirname = filedialog.askdirectory()

from tkinter import colorchooser
colorchooser.askcolor(initialcolor='#ff0000')

from tkinter import messagebox
messagebox.showinfo(message='Have a good day')
messagebox.askyesno(
	   message='Are you sure you want to install SuperVirus?'
	   icon='question' title='Install')

自定义对话框:

ttk.Entry(root).grid()   # something to interact with
def dismiss ():
    dlg.grab_release()
    dlg.destroy()

dlg = Toplevel(root)
ttk.Button(dlg, text="Done", command=dismiss).grid()
dlg.protocol("WM_DELETE_WINDOW", dismiss) # intercept close button
dlg.transient(root)   # dialog window is related to main
dlg.wait_visibility() # can't grab until window appears, so we wait
dlg.grab_set()        # ensure all input goes to our window
dlg.wait_window()     # block until window is destroyed

fonts

from tkinter import font
font.names()
# ('fixed', 'oemfixed', 'TkDefaultFont', 'TkMenuFont', 'ansifixed', 'systemfixed', 'TkHeadingFont', 
# 'device', 'TkTooltipFont', 'defaultgui', 'TkTextFont', 'ansi', 'TkCaptionFont', 'system', 'TkSmallCaptionFont', 'TkFixedFont', 'TkIconFont')

f = font.nametofont('TkTextFont')
f.actual()  # {'family': '.AppleSystemUIFont', 'size': 13, 'weight': 'normal', 'slant': 'roman', 'underline': 0, 'overstrike': 0}
f.metrics() # {'ascent': 13, 'descent': 3, 'linespace': 16, 'fixed': 0}
f.measure('The quick brown fox')

# set font for label
highlightFont = font.Font(family='Helvetica', name='appHighlightFont', size=12, weight='bold')
ttk.Label(root, text='Attention!', font=highlightFont).grid()

Theme

和CSS不同,效果类似。相对比较复杂。

>>> s = ttk.Style()
>>> s.theme_names()
('aqua', 'step', 'clam', 'alt', 'default', 'classic')

>>> s.theme_use()
'aqua'

s.theme_use('themename')

# 第三方主题  awthemes-*.zip.tcli
# https://wiki.tcl-lang.org/page/List+of+ttk+Themes
root.tk.call('lappend', 'auto_path', '/full/path/to/awthemes-9.3.1')
root.tk.call('package', 'require', 'awdark')
root.tk.call('source', '/full/path/to/themefile.tcl')

# 定制
s.configure('Emergency.TButton', font='helvetica 24', foreground='red', padding=10)
s.map('TButton', 
    background=[('disabled','#d9d9d9'), ('active','#ececec')],
    foreground=[('disabled','#a3a3a3')],
    relief=[('pressed', '!disabled', 'sunken')])

s.lookup('TButton', 'font')
s.element_options('Button.label')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/602973.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言贪吃蛇

注 &#xff1a;本文是基于链表实现贪吃蛇游戏 1.Win32 API 本篇文章中实现贪吃蛇会用到一些Win32 API的知识&#xff0c;接下来简单做下介绍 1.1 Win32 API Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外&#xff0c; 它同时也是⼀个 很大的服务中…

熟悉Redis吗,那Redis的过期键删除策略是什么

对于Redis&#xff0c;我们业务开发一般都只关心Redis键值对的查询、修改操作&#xff0c;可能因为懒或者只想能用就行&#xff0c;呵呵。很少关心键值对存储在什么地方、键值对过期了会怎么样、Redis有没什么策略处理过期的键、Redis处理过期键又有什么作用&#xff1f;但这些…

《深入Linux内核架构》第4章 进程虚拟内存(1)

目录 4.1 简介 4.2 进程虚拟地址空间 4.2.1 进程地址空间分布 4.2.2 建立布局 本专栏文章将有70篇左右&#xff0c;欢迎关注&#xff0c;订阅后续文章。 第3章讲了两点&#xff1a;物理内存的管理&#xff0c;内核虚拟地址管理。 本章讲&#xff1a;用户进程的虚拟地址空间…

黄金投资怎么判断走势好坏?

投资黄金&#xff0c;就是押注于这一贵金属价格的变动。判断黄金价格的走势好坏&#xff0c;对于投资者来说至关重要。这需要从宏观经济指标、技术分析指标和市场情绪等多元化角度进行综合分析。 宏观经济指标 货币政策&#xff1a;中央银行的货币政策&#xff0c;尤其是利率决…

必应bing国内广告如何开户,怎么收费?

搜索引擎广告作为直接触达潜在客户的有效途径之一&#xff0c;日益受到企业的重视&#xff0c;必应Bing作为全球第二大搜索引擎&#xff0c;在中国市场同样拥有庞大的用户群体&#xff0c;为企业提供了不可忽视的广告投放平台。 一、必应bing国内广告开户流程 1、需求分析与咨…

国产PLC海为如何与电脑通信

前言 这几天接触到了国产海为PLC&#xff0c;做一个记录&#xff01;学习一下&#xff01; 串口联机 步骤 1&#xff1a;使用 USB 转 485 线连接 A8&#xff08;RS485 通讯口&#xff09;和电脑&#xff1b; 步骤 2&#xff1a;打开 Haiwell happy PLC 编程软件&#xff0c…

vcenter7安装nsx

登录控制台 Get services

视频汇聚边缘网关EasyCVR硬件设备无法访问域名,解析失败该如何处理?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。视频汇聚融合管理平台EasyCVR既具备传统安防视…

YOLOv9全网最新改进系列:YOLOv9完美融合标准化的注意力模块NAM,高效且轻量级的归一化注意力机制,助力目标检测再上新台阶!

YOLOv9全网最新改进系列&#xff1a;YOLOv9完美融合标准化的注意力模块NAM&#xff0c;高效且轻量级的归一化注意力机制&#xff0c;助力目标检测再上新台阶&#xff01;&#xff01;&#xff01; YOLOv9原文链接戳这里&#xff0c;原文全文翻译请关注B站Ai学术叫叫首er B站全…

细说夜莺监控系统告警自愈机制

虽说监控系统最侧重的功能是指标采集、存储、分析、告警&#xff0c;为了能够快速恢复故障&#xff0c;告警自愈机制也是需要重点投入建设的&#xff0c;所有可以固化为脚本的应急预案都可以使用告警自愈机制来快速驱动。夜莺开源项目从 v7 版本开始内置了告警自愈模块&#xf…

千元投影仪高性价比机型又出新机?大眼橙C1D上市引领市场新潮流

近年来投影仪技术不断更新迭代&#xff0c;家用智能投影仪市场正迎来一场革新风暴。最明显的就是各家品牌都更快地推出自家的投影仪新品&#xff0c;4月底&#xff0c;极米推出了play5&#xff0c;大眼橙推出了c1d&#xff0c;小明推出了newq3pro……都是千元价位的投影仪新品&…

RabbitMQ基础入门

初识MQ 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&#xff0c;但是你却不能跟多个人同…

Linux的目录结构

什么是路径 在Linux系统中&#xff0c;"路径"指的是文件系统中文件或目录的位置。路径可以是绝对的或相对的。 绝对路径&#xff1a;从根目录&#xff08;即 / &#xff09;开始&#xff0c;描述从根目录到目标文件或目录的完整路径。例如&#xff0c;/usr/local/bi…

SWAT模型【建模方法、实例应用、高级进阶技能】实践

第一部分&#xff1a;SWAT模型实践部分 一、SWAT模型及应用介绍 1.1 面源污染概要 1.2 SWAT模型及应用 1.3 SWAT模型原理 1.4 SWAT模型输入文件 1.5 ArcGIS与SWAT关系 二、SWAT模型中GIS必备技术 2.1 GIS软件平台 2.2 ArcGIS10.6安装和注意事项 2.3 ArcGIS入门 2.…

IT外包能在企业上云时提供什么帮助?

在云计算不断发展的背景下&#xff0c;企业对IT部门的要求日益提高&#xff0c;越来越多的企业开始考虑将IT系统迁移到云上。因此&#xff0c;IT外包也成为企业成功上云的重要支持之一。IT外包在企业上云时具体能提供什么帮助&#xff1f;本文将对此进行详细阐述。 业务重心转移…

Linux磁盘逻辑卷LVM丢失

一.原因&#xff1a;服务器异常断电&#xff0c;重启服务器之后&#xff0c;服务所在的磁盘丢失&#xff0c;逻辑卷也不存在。 二.解决方法&#xff1a; 2.1&#xff09;执行以下命令查看lvm配置文件备份内容&#xff1a; more /etc/lvm/backup/datavg01 datavg是之前使…

ubuntu20文件安装和卸载cuda11.6

搜索cuda 11.6 nvidia&#xff0c;进入官网https://developer.nvidia.com/cuda-11-6-0-download-archive 选择linux --> runfile 用安装包安装 wget https://developer.download.nvidia.com/compute/cuda/11.6.0/local_installers/cuda_11.6.0_510.39.01_linux.run sudo s…

【数据分享】2021-2024年我国主要城市逐月轨道交通运营数据

以地铁为代表的轨道交通是大城市居民的主要交通出行方式之一&#xff0c;轨道交通的建设和运营情况也是一个城市发展水平的重要体现。本次我们为大家带来的是2021-2024年我国主要城市的逐月的轨道交通运营数据&#xff01;目前最新数据到2024年2月&#xff0c;数据也会继续更新…

Java类型转换、运算符、流程控制语句你真的懂了吗?

类型转换&#xff1a; 1.数据类型转换之隐式转换&#xff08;表示数据范围从小到大&#xff09; 小的数据类型&#xff0c;和大的数据类型运算&#xff0c;小的会提升为大的之后&#xff0c;再进行运算特殊关注&#xff1a;byte short char 三种数据在运算的时候&#xff0c;不…

OceanBase学习1:分布式数据库与集中式数据库的差异

目录 1. 传统集中式数据库 2. 数据库中间件的分库分表 3. 分布式数据库的基本特点及对比分析 4. OceanBase和传统数据库的对比 5. 小结 1. 传统集中式数据库 优点 成熟稳定:经过近40年的发展&#xff0c;应用到各行各业&#xff0c;产品技术非常成熟稳定行业适配性强:适配…
最新文章