本文主要介绍几个比较实用的例子。先载入一些包:

from tkinter import Tk, ttk, PhotoImage
from tkinter import Menu, StringVar, filedialog, , Listbox 

1 可绑定“动作”的按钮#

绑定按钮,令其输出欢迎信息:

class App(Tk):
    def __init__(self):
        super().__init__()
        # 创建带命令的按钮
        self.btn = ttk.Button(self, text="点我!",
                              command=self.say_hello)
        # 布局按钮
        self.btn.grid(padx=120, pady=30)

    def say_hello(self):
        print("欢迎进入 tkinter 世界!")


if __name__ == "__main__":
    app = App()
    app.title("tkinter 的应用")
    app.mainloop()

效果图:

图1 带命令的按钮

2 带图片的按钮#

RELIEFS = ['sunken', 'raised', 'groove', 'ridge', 'flat']


class ButtonsApp(Tk):
    def __init__(self):
        super().__init__()
        self.img = PhotoImage(file="python.gif")
        self.btn = ttk.Button(self, text="带图片的按钮",
                              image=self.img, compound='left',
                              command=self.disable_btn)
        self.btn.grid(row=0, column=2)
        for i, RELIEF in enumerate(RELIEFS):
            temp = ttk.Frame(self, relief=RELIEF,
                             borderwidth=5, width=50, height=50)
            label = ttk.Label(temp, text=RELIEF)
            label.grid(row=0, column=0)
            temp.grid(row=1, column=i, padx=10, pady=10)

    def disable_btn(self):
        self.btn.config(state='disabled')


if __name__ == "__main__":
    app = ButtonsApp()
    app.mainloop()

显示效果:

图2 带图片的按钮

该例子将图片放置在文字的左侧,且绑定了一个命令,该命令实现按钮的失活操作。第二行显示了不同的 relief 的 ttk.Frame。

3 可跟踪 ttk.Entry 的“变量”#

class App(Tk):
    def __init__(self):
        super().__init__()
        self.var = StringVar()
        self.var.trace("w", self.show_message)
        self.entry = ttk.Entry(textvariable=self.var)
        self.btn = ttk.Button(text="清除",
                              command=lambda: self.var.set(""))
        self.label = ttk.Label()
        self.entry.grid()
        self.btn.grid()
        self.label.grid()

    def show_message(self, *args):
        value = self.var.get()
        text = f"你好, {value}!" if value else ''
        self.label.config(text=text)


if __name__ == "__main__":
    app = App()
    app.mainloop()

效果图:

图3 可跟踪 ttk.Entry 的“变量”

该例子实时跟踪文本框的输入,并显示在第 3 行。

4 验证 ttk.Entry#

import re

class App(Tk):
    def __init__(self):
        super().__init__()
        self.pattern = re.compile("^\w{0,10}$")
        self.label = ttk.Label(text="输入您的用户名")
        vcmd = (self.register(self.validate_username), "%i", "%P")
        self.entry = ttk.Entry(validate="key",
                              validatecommand=vcmd,
                              invalidcommand=self.print_error)
        self.label.pack()
        self.entry.pack(anchor='w', padx=10, pady=10)

    def validate_username(self, index, username):
        print("修改 " + index)
        return self.pattern.match(username) is not None

    def print_error(self):
        print("无效的用户名")


if __name__ == "__main__":
    app = App()
    app.mainloop()

验证输入的字符是否满足条件。

5 Listbox(列表框)#

该组件有一个比较重要的选项 selectmode 用来确定可以选择多少项,以及鼠标拖动的影响选择:

  • 'browse':通常,只能从列表框中选择一行。如果单击一个项目,然后拖动到不同的行,选择将会跟随鼠标,是默认的。

  • 'single':你只能选择一行,不能拖动。

  • 'multiple':您可以同时选择任意数量的行。点击在任意直线上,无论它是否被选中。不能拖动。

  • 'extended': 您可以一次选择任何相邻的多个选项。能拖动。支持Shift和Control(如Windows下的快捷键)。

示例:

DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday",
        "Friday", "Saturday", "Sunday"]
MODES = ['single', 'browse', 'multiple', 'extended']


class ListApp(Tk):
    def __init__(self):
        super().__init__()
        self.list = Listbox()
        self.list.insert(0, *DAYS)
        self.print_btn = ttk.Button(self, text="Print selection",
                                    command=self.print_selection)
        self.btns = [self.create_btn(m) for m in MODES]

        self.list.pack()
        self.print_btn.pack(fill='both')
        for btn in self.btns:
            btn.pack(side='left')

    def create_btn(self, mode):
        def cmd(): return self.list.config(selectmode=mode)
        return ttk.Button(self, command=cmd,
                          text=mode.capitalize())

    def print_selection(self):
        selection = self.list.curselection()
        print([self.list.get(i) for i in selection])


if __name__ == "__main__":
    app = ListApp()
    app.mainloop()

效果图见:

图4 列表框

6 鼠标触发的事件#

class App(ttk.Frame):
    def __init__(self, master=None, **kw):
        super().__init__(master, **kw)
        style = ttk.Style()
        style.configure("BG.TFrame", foreground="black", background="green")
        self.configure(style="BG.TFrame", height=100, width=100)
        self.bind_mul()
        self.grid(padx=50, pady=50)

    def bind_mul(self):
        event_names = ("<Button-1>", "<Double-Button-1>",
                       "<ButtonRelease-1>", "<B1-Motion>",
                       "<Enter>", "<Leave>")
        [self.bind(event_name, self.print_event) for event_name in event_names]

    def print_event(self, event):
        position = f"(x={event.x}, y={event.y})"
        print(event.type, "event", position)


if __name__ == "__main__":
    root = Tk()
    app = App(root)
    root.mainloop()

图5 鼠标触发的事件

7 键盘触发的事件#

class App(Tk):
    def __init__(self):
        super().__init__()
        entry = ttk.Entry(self)
        entry.bind("<FocusIn>", self.print_type)
        entry.bind("<Key>", self.print_key)
        entry.grid(padx=20, pady=20)

    def print_type(self, event):
        print(event.type)

    def print_key(self, event):
        print(
            f"Symbol: {event.keysym},Code: {event.keycode}, Char: {event.char}")


if __name__ == "__main__":
    app = App()
    app.mainloop()

效果图:

图6 键盘触发的事件

8 自定义标题栏图标#

class App(Tk):
    def __init__(self):
        super().__init__()
        self.title("自定义标题栏图标")
        self.iconbitmap("python.ico")
        # width x height ±x±y
        self.geometry("400x200+50+50")

if __name__ == "__main__":
    app = App()
    app.mainloop()

width 和 height(通常以像素为单位)非常不言自明。 “±x”(水平位置)用正负号指定,因此“+25”表示窗口的左边缘应距屏幕左边缘25个像素,而“-50”表示右边缘窗口的宽度应距屏幕右边缘50像素。 同样,“y”(垂直)位置为“+10”表示窗口的上边缘应比屏幕顶部低10个像素,而“-100”表示窗口的底部边缘应比屏幕顶部高100像素屏幕底部。

图7 自定义标题栏图标

9 自定义列表框#

class ListFrame(ttk.Frame):
    def __init__(self, master, items=[]):
        super().__init__(master)
        self.list = Listbox(self)
        self.scroll = ttk.Scrollbar(self, orient='vertical',
                                    command=self.list.yview)
        self.list.config(yscrollcommand=self.scroll.set)
        self.list.insert(0, *items)
        self.list.pack(side='left')
        self.scroll.pack(side='left', fill='y')

    def pop_selection(self):
        index = self.list.curselection()
        if index:
            value = self.list.get(index)
            self.list.delete(index)
            return value

    def insert_item(self, item):
        self.list.insert('end', item)


class App(Tk):
    def __init__(self):
        super().__init__()
        months = ["January", "February", "March", "April",
                  "May", "June", "July", "August", "September",
                  "October", "November", "December"]
        self.frame_a = ListFrame(self, months)
        self.frame_b = ListFrame(self)
        self.btn_right = ttk.Button(self, text=">",
                                    command=self.move_right)
        self.btn_left = ttk.Button(self, text="<",
                                   command=self.move_left)

        self.frame_a.pack(side='left', padx=10, pady=10)
        self.frame_b.pack(side='right', padx=10, pady=10)
        self.btn_right.pack(expand=True, ipadx=5)
        self.btn_left.pack(expand=True, ipadx=5)

    def move_right(self):
        self.move(self.frame_a, self.frame_b)

    def move_left(self):
        self.move(self.frame_b, self.frame_a)

    def move(self, frame_from, frame_to):
        value = frame_from.pop_selection()
        if value:
            frame_to.insert_item(value)


if __name__ == "__main__":
    app = App()
    app.mainloop()

图8 可转移的列表框

10 不同颜色的标签#

class App(Tk):
    def __init__(self):
        super().__init__()
        colors = ("yellow", "orange", "red", "green", "blue")
        labels = [ttk.Label(text=f'label_{color}', background=color)
                  for color in colors]
        opts = {'ipadx': 20, 'ipady': 10, 'fill': 'both'}
        labels[0].pack(side='top', **opts)
        labels[1].pack(side='top', **opts)
        labels[2].pack(side='left', **opts)
        labels[3].pack(side='left', **opts)
        labels[4].pack(side='left', **opts)


if __name__ == "__main__":
    app = App()
    app.mainloop()

效果图:

图9 不同颜色的标签

Grid 布局:

class App(Tk):
    def __init__(self):
        super().__init__()
        colors = ("yellow", "orange", "red", "green", "blue")
        labels = [ttk.Label(text=color, background=color)
                  for color in colors]

        opts = {'ipadx': 10, 'ipady': 10, 'sticky': 'nswe'}
        for k, lb in enumerate(labels):
            lb.grid(row=self.set_row(k),
                    column=self.set_column(k),
                    rowspan=2 if k in (2, 3) else None,
                    columnspan=3 if k == 4 else None,
                    **opts)

    def set_row(self, k):
        if k == 1:
            return 1
        elif k == 4:
            return 2
        else:
            return 0

    def set_column(self, k):
        if k == 2:
            return 1
        elif k == 3:
            return 2
        else:
            return 0


if __name__ == "__main__":
    app = App()
    app.mainloop()

图10 Grid布局

class App(Tk):
    def __init__(self):
        super().__init__()
        colors = ("yellow", "orange", "red", "green", "blue")
        labels = [ttk.Label(text=color, background=color)
                  for color in colors]
        labels[0].place(relwidth=0.25, relheight=0.25)
        labels[1].place(x=100, anchor='n',
                        width=100, height=50)
        labels[2].place(relx=0.5, rely=0.5, anchor='center',
                        relwidth=0.5, relheight=0.5)
        labels[3].place(in_=labels[2], anchor='nw',
                        x=2, y=2, relx=0.5, rely=0.5,
                        relwidth=0.5, relheight=0.5)
        labels[4].place(x=200, y=200, anchor='se',
                        relwidth=0.25, relheight=0.25)


if __name__ == "__main__":
    app = App()
    app.mainloop()

图11 place 布局

11 创建个人信息记录表单#

class App(Tk):
    def __init__(self):
        super().__init__()
        style = ttk.Style()
        style.configure("W.TFrame", background="white")
        group_1 = ttk.LabelFrame(self, text="个人信息", style="W.TFrame")
        group_1.pack(padx=10, pady=5)

        ttk.Label(group_1, text="性别").grid(row=0)
        ttk.Label(group_1, text="姓名").grid(row=1)
        ttk.Entry(group_1).grid(row=0, column=1, sticky='w')
        ttk.Entry(group_1).grid(row=1, column=1, sticky='w')

        group_2 = ttk.LabelFrame(self, text="地址", style="W.TFrame")
        group_2.pack(padx=10, pady=5)

        ttk.Label(group_2, text="国籍").grid(row=0)
        ttk.Label(group_2, text="省市").grid(row=1)
        ttk.Label(group_2, text="邮编").grid(row=2)
        ttk.Entry(group_2).grid(row=0, column=1, sticky='w')
        ttk.Entry(group_2).grid(row=1, column=1, sticky='w')
        ttk.Entry(group_2, width=8).grid(row=2, column=1,
                                         sticky='w')

        self.btn_submit = ttk.Button(self, text="提交")
        self.btn_submit.pack(padx=10, pady=10, side='right')


if __name__ == "__main__":
    app = App()
    app.mainloop()

图12 创建个人信息记录表单

12 可切换背景和前景的颜色选择器#

from functools import partial

from tkinter.colorchooser import askcolor


class App(Tk):
    def __init__(self):
        super().__init__()
        self.title("Colors demo")
        text = "The quick brown fox jumps over the lazy dog"
        self.label = ttk.Label(self, text=text)
        self.fg_btn = ttk.Button(self, text="Set foreground color",
                                 command=partial(self.set_color, "fg"))
        self.bg_btn = ttk.Button(self, text="Set background color",
                                 command=partial(self.set_color, "bg"))

        self.label.pack(padx=20, pady=20)
        self.fg_btn.pack(side='left', fill='both', expand=True)
        self.bg_btn.pack(side='left', fill='both', expand=True)

    def set_color(self, option):
        color = askcolor()[1]
        print("Chosen color:", color)
        self.label.config(**{option: color})


if __name__ == "__main__":
    app = App()
    app.mainloop()

图13 可切换背景和前景的颜色选择器

13 变换字体#

class App(Tk):
    def __init__(self):
        super().__init__()
        self.title("Fonts demo")
        text = "The quick brown fox jumps over the lazy dog"
        self.label = ttk.Label(text=text)

        self.family = StringVar()
        families = ("Times", "Courier", "Helvetica")
        self.option = ttk.OptionMenu(self, self.family, *families)

        self.size = StringVar()
        self.size.trace("w", self.set_font)
        self.spinbox = ttk.Spinbox(from_=8, to=18,
                                   textvariable=self.size)

        self.family.set(families[0])
        self.size.set("10")
        self.label.pack(padx=20, pady=20)
        self.option.pack(side='left', fill='both', expand=True)
        self.spinbox.pack(side='left', fill='both', expand=True)
        self.family.trace("w", self.set_font)

    def set_font(self, *args):
        family = self.family.get()
        size = self.size.get()
        self.label.config(font=(family, size))


if __name__ == "__main__":
    app = App()
    app.mainloop()

图14 可以改变字体类型与大小

from tkinter.font import Font


class App(Tk):
    def __init__(self):
        super().__init__()
        header = Font(family='Helvetica', size=18, weight='bold')
        subtitle = Font(family="Helvetica 14 italic")
        style = ttk.Style()
        style.map("C.TButton",
                  foreground=[('pressed', 'red'), ('active', 'blue')],
                  background=[('pressed', '!disabled', 'black'), ('active', 'white')])
        self.create_label(font=header, text="This is the header")
        self.create_label(font=subtitle, text="This is the subtitle")
        self.create_label(text="This is a paragraph")
        self.create_label(text="This is another paragraph")
        self.create_button("C.TButton", text="See more")

    def create_label(self, **options):
        return ttk.Label(**options).pack(padx=20, pady=5, anchor='w')

    def create_button(self, style, **options):
        return ttk.Button(style=style, **options).pack(padx=5, pady=5, anchor='e')


if __name__ == "__main__":
    app = App()
    app.mainloop()

图15 Font 设定字体

14 设置鼠标光标#

class App(Tk):
    def __init__(self):
        super().__init__()
        self.title("Cursors demo")
        self.resizable(0, 0)
        self.label = ttk.Label(self, text="Click the button to start")
        self.btn_launch = ttk.Button(self, text="Start!",
                                     command=self.perform_action)
        self.btn_help = ttk.Button(self, text="Help",
                                   cursor="question_arrow")

        btn_opts = {"side": 'left', "expand": True, "fill": 'x',
                    "ipadx": 30, "padx": 20, "pady": 5}
        self.label.pack(pady=10)
        self.btn_launch.pack(**btn_opts)
        self.btn_help.pack(**btn_opts)

    def perform_action(self):
        self.btn_launch.config(state='disabled')
        self.btn_help.config(state='disabled')
        self.label.config(text="Working...")
        self.after(3000, self.end_action)
        self.config(cursor="watch")

    def end_action(self):
        self.btn_launch.config(state='normal')
        self.btn_help.config(state='normal')
        self.label.config(text="Done!")
        self.config(cursor="arrow")

    def set_watch_cursor(self, widget):
        widget._old_cursor = widget.cget("cursor")
        widget.config(cursor="watch")
        for w in widget.winfo_children():
            self.set_watch_cursor(w)

    def restore_cursor(self, widget):
        widget.config(cursor=widget.old_cursor)
        for w in widget.winfo_children():
            self.restore_cursor(w)


if __name__ == "__main__":
    app = App()
    app.mainloop()

图16 鼠标光标

15 tkinter.Text#

from tkinter import Text


class App(Tk):
    def __init__(self):
        super().__init__()
        self.title("Text demo")
        self.resizable(0, 0)
        self.text = Text(width=50, height=10)
        self.btn_clear = ttk.Button(text="Clear text",
                                    command=self.clear_text)
        self.btn_insert = ttk.Button(text="Insert text",
                                     command=self.insert_text)
        self.btn_print = ttk.Button(text="Print selection",
                                    command=self.print_selection)
        self.text.pack()
        self.btn_clear.pack(side='left', expand=True, pady=10)
        self.btn_insert.pack(side='left', expand=True, pady=10)
        self.btn_print.pack(side='left', expand=True, pady=10)

    def clear_text(self):
        self.text.delete("1.0", 'end')

    def insert_text(self):
        self.text.insert('insert', "Hello, world")

    def print_selection(self):
        selection = self.text.tag_ranges('sel')
        if selection:
            content = self.text.get(*selection)
            print(content)


if __name__ == "__main__":
    app = App()
    app.mainloop()

图17 Text 的示例

设定超链接:

import webbrowser
from tkinter import Text

class App(Tk):
    def __init__(self):
        super().__init__()
        self.title("Text tags demo")
        self.text = Text(self, width=50, height=10)
        self.btn_link = ttk.Button(self, text="Add hyperlink",
                                   command=self.add_hyperlink)

        self.text.tag_config("link", foreground="blue", underline=1)
        self.text.tag_bind("link", "<Button-1>", self.open_link)
        self.text.tag_bind("link", "<Enter>",
                           lambda _: self.text.config(cursor="hand2"))
        self.text.tag_bind("link", "<Leave>",
                           lambda e: self.text.config(cursor=""))

        self.text.pack()
        self.btn_link.pack(expand=True)

    def add_hyperlink(self):
        selection = self.text.tag_ranges('sel')
        if selection:
            self.text.tag_add("link", *selection)

    def open_link(self, event):
        position = f"@{event.x},{event.y} + 1c"
        index = self.text.index(position)
        prevrange = self.text.tag_prevrange("link", index)
        url = self.text.get(*prevrange)
        webbrowser.open(url)


if __name__ == "__main__":
    app = App()
    app.mainloop()

图18 设定超链接