Python GUI Cookbook —— 定制 widgets

原文链接:Python GUI Cookbook —— 定制 widgets

创建消息框——信息、警告和错误

消息框是一种用于给用户反馈的弹出式窗口,它可能是提示性的,也可能是显示潜在问题的,还有可能是指出灾难性错误的。

首先导入模块

#!/usr/bin/env python3

import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
from tkinter import Menu
from tkinter import messagebox as msg
[...]

创建消息框的回调函数,此时我们点击 Help|About

[...]
# Display a message box
def _msgBox():
    msg.showinfo('Python Message Info Box', 
        'A Python GUI created using tkinter:\n The year is 2017.')

# Add another Menu to the Menu Bar and an item
help_menu = Menu(menu_bar, tearoff=0)
help_menu.add_command(label='About', command=_msgBox)
menu_bar.add_cascade(label='Help', menu=help_menu)


name_entered.focus()

window.mainloop()

将消息换成警告

[...]
# Display a message box
def _msgBox():
    # msg.showinfo('Python Message Info Box', 
    #   'A Python GUI created using tkinter:\n The year is 2017.')
    msg.showwarning('Python Message Warning Box', 
        'A Python GUI Cretaed using tkinter:'
        '\nWarning: There might be a bug in this code.')
[...]

显示一个错误信息

[...]
# msg.showinfo('Python Message Info Box', 
    #   'A Python GUI created using tkinter:\n The year is 2017.')
    # msg.showwarning('Python Message Warning Box', 
    #   'A Python GUI Cretaed using tkinter:'
    #   '\nWarning: There might be a bug in this code.')
    msg.showerror('Python Message Error Box', 
        'A Python GUI created using tkinter:'
        '\nError: Houston ~ We DO have a serious PROBLEM!')
[...]

创建多选框

[...]
# Display a message box
def _msgBox():
    # msg.showinfo('Python Message Info Box', 
    #   'A Python GUI created using tkinter:\n The year is 2017.')
    # msg.showwarning('Python Message Warning Box', 
    #   'A Python GUI Cretaed using tkinter:'
    #   '\nWarning: There might be a bug in this code.')
    # msg.showerror('Python Message Error Box', 
    #   'A Python GUI created using tkinter:'
    #   '\nError: Houston ~ We DO have a serious PROBLEM!')
    answer = msg.askyesnocancel('Python Message Mutil Choice Box', 
        'Are you sure you really wish to do this?')
    print(answer)
[...]

然后我们就可以用

If answer == True:
    <do something>

来实现一些功能了

创建独立的消息框

这里我们将做一个顶层窗口的消息框

先创建一个简单的窗口看看效果:

from tkinter import messagebox as msg
msg.showinfo('', 'Python GUI created using tkinter:\nThe year is 2017')

这会产生下面这两个窗口

可以看到这并不是我们想要的

通过下面代码去掉额外的窗口

from tkinter import messagebox as msg
from tkinter import Tk

root = Tk()
root.withdraw()
msg.showinfo('', 'Python GUI created using tkinter:\n The year is 2017')

创建 tkinter 窗体的标题

import tkinter as tk

window = tk.Tk()
window.title('Python GUI')

更换根窗口的图标

[...]

window.iconbitmap('卷纸.ico')

name_entered.focus() # Place cursor into name Entry
# Start GUI
window.mainloop()

使用 spin box

[...]
from tkinter import Spinbox
[...]
# Adding a Spinbox widget
spin = Spinbox(mighty, from_=0, to=10)
spin.grid(column=0, row=2)

# Using a scrolled text control
scrol_w = 30
scrol_h = 3

scr = scrolledtext.ScrolledText(mighty, width=scrol_w, height=scrol_h, wrap=tk.WORD)
scr.grid(column=0, row=3, sticky='WE', columnspan=3)
[...]

接下来对该 widget 定制一番:减小宽度,增加边框(borderwidth,bd)

spin = Spinbox(mighty, from_=0, to=10, width=5, bd=8)

给 widget 增加实际功能

[...]
# Spinbox callback
def _spin():
    value = spin.get()
    print(value)
    scr.insert(tk.INSERT, value+'\n')

# Adding a Spinbox widget
spin = Spinbox(mighty, from_=0, to=10, width=5, bd=8, command=_spin)
[...]

还可以使用

spin = Spinbox(mighty, values=(1, 2, 4, 42, 100), width=5, bd=8, command=_spin)

widget 风格

给 spinbox 增加 bd

[...]
# Adding a Spinbox widget
spin = Spinbox(mighty, from_=0, to=10, width=5, bd=8, command=_spin)
spin.grid(column=0, row=2)

Spinbox(mighty, values=(1, 2, 4, 8, 16, 32, 64), 
    width=5, bd=20).grid(column=1, row=2)
[...]

  • 两个 spinbox 都是浮雕(relief)风,第二个具有更大的边框
  • 默认的 relief 属性是 tk.SUNKEN

relief 属性的可选值

|tk.SUNKEN|tk.RAISED|tk.FLAT|tk.GROOVE|tk.EIDGE|

[...]
# Adding a Spinbox widget
spin = Spinbox(mighty, from_=0, to=10, width=5, bd=8, command=_spin, 
    relief=tk.GROOVE)
spin.grid(column=0, row=2)

Spinbox(mighty, values=(1, 2, 4, 8, 16, 32, 64), 
    width=5, bd=8, relief='flat').grid(column=1, row=2)
[...]

使用提示(tooltips)

添加一个 tooltip 应该是一件简单的事,但这里它并不像我们想象的那么简单

首先需要添加一些面向对象(OOP)代码

import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
from tkinter import Menu
from tkinter import messagebox as msg
from tkinter import Spinbox


class ToolTip(object):
    def __init__(self, widget):
        self.widget = widget
        self.tip_window = None


    def show_tip(self, tip_text):
        if self.tip_window or not tip_text:
            return
        # get size of widget
        x, y, _cx, cy = self.widget.bbox('insert')
        # calculate to display tooltip
        x = x + self.widget.winfo_rootx() + 25
        y = y + cy + self.widget.winfo_rooty() + 25
        # create new tooltip window
        self.tip_window = tw = tk.Toplevel(self.widget)
        # remove all Window Manager (wm) decorations
        tw.wm_overrideredirect(True)
        tw.wm_geometry('+%d+%d' % (x, y))

        label = tk.Label(tw, text=tip_text, justify=tk.LEFT,
            background='#ffffe0', relief=tk.SOLID, bd=1)
        label.pack(ipadx=1)


    def hide_tip(self):
        tw = self.tip_window
        self.tip_window = None
        if tw:
            tw.destroy()


def create_ToolTip(widget, text):
    tooltip = ToolTip(widget)
    def enter(event):
        tooltip.show_tip(text)
    def leave(event):
        tooltip.hide_tip()

    widget.bind('', enter)
    widget.bind('', leave)

[...]

给一些 widget 添加 tooltips

[...]
# Adding a Spinbox widget
spin = Spinbox(mighty, from_=0, to=10, width=5, bd=8, command=_spin, 
    relief=tk.GROOVE)
spin.grid(column=0, row=2)

create_ToolTip(spin, 'This is a Spinbox widget')

Spinbox(mighty, values=(1, 2, 4, 8, 16, 32, 64), 
    width=5, bd=8, relief='flat').grid(column=1, row=2)

# Using a scrolled text control
scrol_w = 30
scrol_h = 3

scr = scrolledtext.ScrolledText(mighty, width=scrol_w, height=scrol_h, wrap=tk.WORD)
scr.grid(column=0, row=3, sticky='WE', columnspan=3)

# Add a Tooltip to the ScrolledText widget
create_ToolTip(scr, 'This is a ScrolledText widget')
[...]

添加进度条

[...]
from time import sleep
[...]

# update progressbar in callback loop
def run_progressbar():
    progress_bar['maximum'] = 100
    for i in range(101):
        sleep(0.05)
        # increment progressbar
        progress_bar['value'] = i
        # have to call update() in loop
        progress_bar.update()
    # reset/clear progressbar
    progress_bar['value'] = 0

def start_progressbar():
    progress_bar.start()

def stop_progressbar():
    progress_bar.stop()

def stop_after_second():
    window.after(500, progress_bar.stop)

# Create a container to hold labels
buttons_frame = ttk.LabelFrame(mighty2, text=' ProgressBar ')
buttons_frame.grid(column=0, row=3)

# Place labels into the container element
ttk.Button(buttons_frame, text=' Run Progressbar ', 
    command=run_progressbar).grid(column=0, row=0, sticky=tk.W)
ttk.Button(buttons_frame, text='Start Progressbar', 
    command=start_progressbar).grid(column=0, row=1, sticky=tk.W)
ttk.Button(buttons_frame, text='Stop immediately', 
    command=stop_progressbar).grid(column=0, row=2, sticky=tk.W)
ttk.Button(buttons_frame, text='Stop after second', 
    command=stop_after_second).grid(column=0, row=3, sticky=tk.W)

# Add a Progressbar to Tab 2
progress_bar = ttk.Progressbar(tab2, orient='horizontal', 
    length=286, mode='determinate')
progress_bar.grid(column=0, row=4, pady=2)

[...]

使用 canvas widget

[...]
tabControl = ttk.Notebook(window)

# Create a tab
tab1 = ttk.Frame(tabControl)
# Add a tab
tabControl.add(tab1, text='Tab 1')
tab2 = ttk.Frame(tabControl)
tabControl.add(tab2, text='Tab 2')
tab3 = ttk.Frame(tabControl)
tabControl.add(tab3, text='Tab 3')
[...]

# Tab 3 control
tab3_frame = tk.Frame(tab3, bg='blue')
tab3_frame.pack()
for orange_color in range(2):
    canvas = tk.Canvas(tab3_frame, width=150, height=80,
        highlightthickness=0, bg='orange')
    canvas.grid(row=orange_color, column=orange_color)

[...]

Full Version Code

#!/usr/bin/env python3

import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
from tkinter import Menu
from tkinter import messagebox as msg
from tkinter import Spinbox
from time import sleep


class ToolTip(object):
    def __init__(self, widget):
        self.widget = widget
        self.tip_window = None


    def show_tip(self, tip_text):
        if self.tip_window or not tip_text:
            return
        # get size of widget
        x, y, _cx, cy = self.widget.bbox('insert')
        # calculate to display tooltip
        x = x + self.widget.winfo_rootx() + 25
        y = y + cy + self.widget.winfo_rooty() + 25
        # create new tooltip window
        self.tip_window = tw = tk.Toplevel(self.widget)
        # remove all Window Manager (wm) decorations
        tw.wm_overrideredirect(True)
        tw.wm_geometry('+%d+%d' % (x, y))

        label = tk.Label(tw, text=tip_text, justify=tk.LEFT,
            background='#ffffe0', relief=tk.SOLID, bd=1)
        label.pack(ipadx=1)


    def hide_tip(self):
        tw = self.tip_window
        self.tip_window = None
        if tw:
            tw.destroy()


def create_ToolTip(widget, text):
    tooltip = ToolTip(widget)
    def enter(event):
        tooltip.show_tip(text)
    def leave(event):
        tooltip.hide_tip()

    widget.bind('', enter)
    widget.bind('', leave)


# Create instance
window = tk.Tk()

# Add a title
window.title('Python GUI')

tabControl = ttk.Notebook(window)

# Create a tab
tab1 = ttk.Frame(tabControl)
# Add a tab
tabControl.add(tab1, text='Tab 1')
tab2 = ttk.Frame(tabControl)
tabControl.add(tab2, text='Tab 2')
tab3 = ttk.Frame(tabControl)
tabControl.add(tab3, text='Tab 3')

# Pack to make visible
tabControl.pack(expand=1, fill='both')


# Tab 1
# LabelFrame using tab 1 as the parent
mighty = ttk.LabelFrame(tab1, text=' Mighty Python ')
mighty.grid(column=0, row=0, padx=8, pady=4)

# Modify adding a Label using mighty as the parent instead of window
ttk.Label(mighty, text='Enter a name:').grid(column=0, row=0, sticky='W')

# Modify Button Click Function
def click_me():
    action.configure(text='Hello ' + name.get() + ' ' + number_chosen.get())

# Adding a Textbox Entry widget
name = tk.StringVar()
name_entered = ttk.Entry(mighty, width=12, textvariable=name)
name_entered.grid(column=0, sticky='W') # align left/West

# Adding a Button
action = ttk.Button(mighty, text='Click Me!', command=click_me)
action.grid(column=2, row=1)

ttk.Label(mighty, text='Choose a number:').grid(column=1, row=0)
number = tk.StringVar()
number_chosen = ttk.Combobox(mighty, width=12, textvariable=number, state='readonly')
number_chosen['values'] = (1, 2, 4, 8, 16, 32, 64, 128, 256, 562, 1024)
number_chosen.grid(column=1, row=1)
number_chosen.current(10)


# Spinbox callback
def _spin():
    value = spin.get()
    print(value)
    scr.insert(tk.INSERT, value+'\n')

# Adding a Spinbox widget
spin = Spinbox(mighty, from_=0, to=10, width=5, bd=8, command=_spin, 
    relief=tk.GROOVE)
spin.grid(column=0, row=2)

create_ToolTip(spin, 'This is a Spinbox widget')

Spinbox(mighty, values=(1, 2, 4, 8, 16, 32, 64), 
    width=5, bd=8, relief='flat').grid(column=1, row=2)

# Using a scrolled text control
scrol_w = 30
scrol_h = 3

scr = scrolledtext.ScrolledText(mighty, width=scrol_w, height=scrol_h, wrap=tk.WORD)
scr.grid(column=0, row=3, sticky='WE', columnspan=3)

# Add a Tooltip to the ScrolledText widget
create_ToolTip(scr, 'This is a ScrolledText widget')


# Tab 2
# We are creating a container frame to hold all other widgets
mighty2 = ttk.LabelFrame(tab2, text=' The Snake ')
mighty2.grid(column=0, row=0, padx=8, pady=4)

# Creating three checkbuttons
chVarDis = tk.IntVar()
check1 = tk.Checkbutton(mighty2, text='Disabled', variable=chVarDis, state='disabled')
check1.select()
check1.grid(column=0, row=1, sticky=tk.W)

chVarUn = tk.IntVar()
check2 = tk.Checkbutton(mighty2, text='UnChecked', variable=chVarUn)
check2.deselect()
check2.grid(column=1, row=1, sticky=tk.W)

chVarEn = tk.IntVar()
check3 = tk.Checkbutton(mighty2, text='Enabled', variable=chVarEn)
check3.deselect()
check3.grid(column=2, row=1, sticky=tk.W)

# GUI Callback function
def checkCallback(*ignoredArgs):
    if chVarUn.get():
        check3.configure(state='disabled')
    else:
        check3.configure(state='normal')
    if chVarEn.get():
        check2.configure(state='disabled')
    else:
        check2.configure(state='normal')

# trace the state of the two checkbutton
chVarUn.trace('w', lambda unused0, unused1, unused2 : checkCallback())
chVarEn.trace('w', lambda unused0, unused1, unused2 : checkCallback())

# First, we change our Radiobutton global variables into a list
colors = ['Blue', 'Gold', 'Red']

# We have also changed the callback function to be zero-based, using the list
# instead of module-level global variables
# Radiobutton callback
def radCall():
    radSel = radVar.get()
    if radSel == 0:
        window.configure(background=colors[0])
    elif radSel == 1:
        window.configure(background=colors[1])
    elif radSel == 2:
        window.configure(background=colors[2])

# create three Radiobuttons using one variable
radVar = tk.IntVar()

# Now we are selecting a non-existing index value for radVar
radVar.set(99)

# Now we are creating all three Radiobutton widgets within one loop
for col in range(3):
    currad = tk.Radiobutton(mighty2, text=colors[col], variable=radVar, 
        value=col, command=radVar)
    currad.grid(column=col, row=2, sticky=tk.W)

# update progressbar in callback loop
def run_progressbar():
    progress_bar['maximum'] = 100
    for i in range(101):
        sleep(0.05)
        # increment progressbar
        progress_bar['value'] = i
        # have to call update() in loop
        progress_bar.update()
    # reset/clear progressbar
    progress_bar['value'] = 0

def start_progressbar():
    progress_bar.start()

def stop_progressbar():
    progress_bar.stop()

def stop_after_second():
    window.after(500, progress_bar.stop)

# Create a container to hold labels
buttons_frame = ttk.LabelFrame(mighty2, text=' ProgressBar ')
buttons_frame.grid(column=0, row=3)

# Place labels into the container element
ttk.Button(buttons_frame, text=' Run Progressbar ', 
    command=run_progressbar).grid(column=0, row=0, sticky=tk.W)
ttk.Button(buttons_frame, text='Start Progressbar', 
    command=start_progressbar).grid(column=0, row=1, sticky=tk.W)
ttk.Button(buttons_frame, text='Stop immediately', 
    command=stop_progressbar).grid(column=0, row=2, sticky=tk.W)
ttk.Button(buttons_frame, text='Stop after second', 
    command=stop_after_second).grid(column=0, row=3, sticky=tk.W)

# Add a Progressbar to Tab 2
progress_bar = ttk.Progressbar(tab2, orient='horizontal', 
    length=286, mode='determinate')
progress_bar.grid(column=0, row=4, pady=2)


# Tab 3 control
tab3_frame = tk.Frame(tab3, bg='blue')
tab3_frame.pack()
for orange_color in range(2):
    canvas = tk.Canvas(tab3_frame, width=150, height=80,
        highlightthickness=0, bg='orange')
    canvas.grid(row=orange_color, column=orange_color)


# Exit GUI cleanly
def _quit():
    window.quit()
    window.destroy()
    exit()

# Creating a Menu Bar
menu_bar = Menu(window)
window.configure(menu=menu_bar)

# Add menu items
file_menu = Menu(menu_bar, tearoff=0)
file_menu.add_command(label='New')
file_menu.add_separator()
file_menu.add_command(label='Exit', command=_quit)
menu_bar.add_cascade(label='File', menu=file_menu)

# Display a message box
def _msgBox():
    # msg.showinfo('Python Message Info Box', 
    #   'A Python GUI created using tkinter:\n The year is 2017.')
    # msg.showwarning('Python Message Warning Box', 
    #   'A Python GUI Cretaed using tkinter:'
    #   '\nWarning: There might be a bug in this code.')
    # msg.showerror('Python Message Error Box', 
    #   'A Python GUI created using tkinter:'
    #   '\nError: Houston ~ We DO have a serious PROBLEM!')
    answer = msg.askyesnocancel('Python Message Mutil Choice Box', 
        'Are you sure you really wish to do this?')
    print(answer)

# Add another Menu to the Menu Bar and an item
help_menu = Menu(menu_bar, tearoff=0)
help_menu.add_command(label='About', command=_msgBox)
menu_bar.add_cascade(label='Help', menu=help_menu)

# Windows "ico" and Unix "xbm"
# window.iconbitmap('卷纸.ico')
img = tk.PhotoImage(file='卷纸.png')
window.tk.call('wm', 'iconphoto', window._w, img)

name_entered.focus()

window.mainloop()

参考文献

  • Python GUI Programming Cookbook - Second Edition by Burkhard A. Meier

你可能感兴趣的:(python,gui,tkinter,Tkinter)