date: 2017-12-12 22:00:00
status: public
title: 'GUI编程,Tkinter库和布局'
tags: Python, Tkinter,GUI,layout
GUI编程
最近看了下Python的GUI编程,官方Wiki地址是TkInter - Python Wiki,上面有很多文档,如果要深入这块,这些文档会很有帮助的。
这里简单说下Tkinter。Tkinter是Python的标准GUI(Graphical User Interface)包,它是基于Tcl/Tk的面向对象层。这里可以简单理解为Tkinter提供了一些函数,通过简单地import tkinter模块,你就可以使用这些函数构建图形窗口。虽然Tkinter不是Python唯一的GUI库,但是是最常用的。就我而言,我只要在之后做爬虫时能做一些简单的界面就可以,所以Tkinter能满足我的需求。
布局Layout
我以为布局其实是整个开发过程中最重要的部分,Tkinter提供了两种布局的方法:pack和grid布局管理器,除此之外还提供了用于合并插件(widget)的Frame和用于微调插件位置的place布局管理器。
一般来说,如果你的界面设计包含:
- 一个插件占满整个窗口或Frame(或其他容器)
- 插件是一个接着一个排列的(上下或左右并列)
这两种情况推荐使用pack布局管理器,pack管理器实现起来很简单,也正因为如此,它在实现具有复杂结构的界面时操作起来就很复杂了(不是不能实现)。
另一个grid布局管理器会在逻辑上把整个界面分割成网格状,就像一个二维数组一样,从左上角(row=0, column=0)开始,向下向右延伸。实际过程中,我们在设计界面时,总是会有意无意的将插件分类,将一些有关的插件放入一个容器,然后再将容器进行排列。这条思路其实和grid的布局管理器的思路不谋而合,所以说,我个人十分推荐使用grid布局管理器。
place布局管理器通常是和pack或者grid管理器一起使用的(pack和grid管理器不能混合使用)。它可以通过指定(x=x0, y=y0)的方式直接将一个插件放置在一个容器如Frame、LabelFrame或者窗口中。
在整个界面设计过程中,所有的坐标轴都是以左上角作为原点,x轴向右延伸,y轴向下延伸,也就是说右下角代表了整个坐标轴的最大值。
到这里,我总结一下我的GUI开发的思路:
- 将你的要实现的程序拆分成若干个功能块,想好每个功能块要用到什么插件;
- 将这些功能块以网格状的形式排列起来(这里某个功能块是可以占据多行或者多列的,也就是说功能块A可以是grid(row=0, column=0)+grid(row=0, column=1)合起来,这是逻辑上的表达,实际代码用columnspan实现),排列好后,给每个功能块确定一下索引值,大概估算一下每个功能块的大小。
- 功能块对应到代码就是Frame或LabelFrame,然后我们针对每个块编写代码向功能块里添加插件(widgets),同时通过place管理器来调整每个插件相对于功能块的位置。
Tkinter库
剩下的就是具体实现的问题了,比如说怎么实现功能块?怎么往里面添加widgets?怎么调整widgets的外观?怎么关联两个widget事件?等等等等。
我不打算把这widgets啊,事件啊,属性啊,一一列在这里,完全没有必要。因为这些widgets的配置项其实大同小异,我们要做到的仅仅是了解:(1)widgets都有哪些;(2)具体的参数去哪里查。
针对(1),我准备了一个程序在下面,它实现了几乎所有的插件,并且把它们排列在一个窗口里,代码包含了几乎所有的常用配置项,如果有什么额外需求,比如说调整边框大小之类的,可以通过查阅文档自行修改代码即可。
针对(2), 我推荐两个文档,一个是上文所提的官方wiki,第二是一个外国小伙子写的An Introduction to Tkinter,他只写完了TK部分,不过已经足够用了。
一个例子
本来我是打算写个计算器作为GUI编程的入门程序,后来我在一个一个试widget代码时,突然想到不如直接试着把所有的widgets放到一个窗口里,排列起来,因此有了如下程序。
另一个值得一提的一点是,我也在网上看到了一段写GUI的代码,它是用类的方式实现的,这个类继承于Application父类,然后在这个类的__init__
方法中实现整个GUI代码。我不确定哪一种才是标准写法,但只要你熟悉这些代码,改一下应该都不难。
效果图
完整代码
import tkinter as tk
import tkinter.messagebox
root = tk.Tk()
root.title('Widgets')
width = 960
height = 800
screenwidth = root.winfo_screenwidth()
screenheight = root.winfo_screenheight()
size = '%dx%d+%d+%d' % (width, height, (screenwidth - width)/2, (screenheight - height)/2)
root.geometry(size) # width*height + pos_x + pos_y
# label
# 最上方的那个黄条,后面的插件如果有事件都会修改这个黄条的值
myStr = tk.StringVar()
myStr.set('Label Default')
isHit = False
label = tk.Label(root,
textvariable = myStr,
bg = 'yellow', # =background
font = ('Microsoft YaHei', 20),
width = 60, # 本例这里两个值单位是字节,不是像素
height = 1
)
label.grid(row=0, column=0,columnspan=6)
# button
# 每种插件都会包含在一个LabelFrame里
lf_button = tk.LabelFrame(root, width=96, height=96, text='Button')
lf_button.grid(row=1, column=0, sticky='w',padx=10)
def button_click():
global isHit
if isHit == False:
isHit = True
myStr.set('Label #1')
else:
isHit = False
myStr.set('Label #2')
button = tk.Button(lf_button,
text='Mod',
width=10,
height=3,
command=button_click
)
button.place(x=6,y=1)
# Scale
lf_Scale = tk.LabelFrame(root, width=460, height=120, text='Scale - HORIZONTAL')
lf_Scale.grid(row=2, column=0, sticky='w',padx=10, columnspan=4)
def scale_click(v):
myStr.set('You have selected '+ v)
scale = tk.Scale(lf_Scale,
label='Scroll Me',
from_ = 0,
to = 100,
orient = tk.HORIZONTAL,
length = 440,
showvalue = 1,
tickinterval = 10,
resolution = 1,
command = scale_click
)
scale.place(x=1,y=1)
# entry and text
lf_entry_text = tk.LabelFrame(root, width=160, height=240, text='Entry and Text')
lf_entry_text.grid(row=1, column=4, rowspan=2, padx = 6, sticky='w')
entry = tk.Entry(lf_entry_text, relief='solid')
entry.place(x=6, y=1)
def button2_click():
var = entry.get()
textbox.insert('insert',var)
def button3_click():
var = entry.get()
textbox.insert('end',var)
button2 = tk.Button(lf_entry_text,
text='inset curr',
command=button2_click
)
button2.place(x=6,y=32)
button3 = tk.Button(lf_entry_text,
text='inset end',
command=button3_click
)
button3.place(x=80,y=32)
textbox = tk.Text(lf_entry_text,
width=20,
height=10,
relief='solid'
)
textbox.place(x=6,y=72)
# Listbox
lf_Listbox = tk.LabelFrame(root, width=260, height=240, text='Listbox')
lf_Listbox.grid(row=1, column=5, rowspan=2, sticky='w')
myList = tk.StringVar()
myList.set(['default0','default1','default2'])
listbox = tk.Listbox(lf_Listbox,
listvariable=myList,
height=11
)
# 为Listbox关联scrollbar
scrollbar = tk.Scrollbar(lf_Listbox,width=30, orient="vertical",command=listbox.yview)
listbox.configure(yscrollcommand=scrollbar.set)
scrollbar.place(x=120, y=1, height=201)
for item in range(100):
listbox.insert(0,item)
listbox.delete(0)
def button4_click():
val = listbox.get(listbox.curselection())
myStr.set(val)
button4 = tk.Button(lf_Listbox,
text='display',
width=10,
height=2,
command=button4_click
)
listbox.place(x=6, y=1)
button4.place(x=164, y=1)
# Radio Button
lf_Radio = tk.LabelFrame(root, width=96, height=96, text='Radio')
lf_Radio.grid(row=1, column=2, sticky='w')
varRadio = tk.StringVar()
def radiobutton_click():
myStr.set('You have selected ' + varRadio.get())
radio1 = tk.Radiobutton(lf_Radio,
text='A',
value='A',
variable = varRadio,
command=radiobutton_click
)
radio2 = tk.Radiobutton(lf_Radio,
text='B',
value='B',
variable = varRadio,
command=radiobutton_click
)
radio1.place(x=6, y=0)
radio2.place(x=6, y=30)
# Check Button
lf_Check = tk.LabelFrame(root, width=96, height=96, text='Check')
lf_Check.grid(row=1, column=1, sticky='w')
varCheck1 = tk.IntVar()
varCheck2 = tk.IntVar()
def checkbutton_click():
if (varCheck1.get() == 1) & (varCheck2.get() == 1):
myStr.set("Apple and Banana")
elif (varCheck1.get() == 1) & (varCheck2.get() == 0):
myStr.set("Apple")
elif (varCheck1.get() == 0) & (varCheck2.get() == 1):
myStr.set("Banana")
else:
myStr.set("Nothing")
checkbutton1 = tk.Checkbutton(lf_Check,
text = 'Apple',
variable = varCheck1,
onvalue = 1,
offvalue = 0,
command = checkbutton_click
)
checkbutton2 = tk.Checkbutton(lf_Check,
text = 'Banana',
variable = varCheck2,
onvalue = 1,
offvalue = 0,
command = checkbutton_click
)
checkbutton1.place(x=6, y=0)
checkbutton2.place(x=6, y=30)
# Menubar
menubar = tk.Menu(root)
filemenu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label='File', menu=filemenu)
def dojob():
pass
filemenu.add_command(label='New', command=dojob)
filemenu.add_command(label='Open', command=dojob)
filemenu.add_command(label='Save', command=dojob)
filemenu.add_separator()
filemenu.add_command(label='Exit', command=root.quit)
editmenu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label='Edit', menu=editmenu)
editmenu.add_command(label='Cut', command=dojob)
editmenu.add_command(label='Copy', command=dojob)
editmenu.add_command(label='Paste', command=dojob)
submenu = tk.Menu(editmenu)
editmenu.add_cascade(label='Submenu', menu=submenu, underline=0)
submenu.add_command(label="Submenu1", command=dojob)
root.config(menu=menubar)
# Message Box
lf_MessageBox = tk.LabelFrame(root, width=96, height=96, text='MessageBox')
lf_MessageBox.grid(row=1, column=3, sticky='w')
def show_msg():
tk.messagebox.showinfo(title='HI', message='Yahaha')
button4 = tk.Button(lf_MessageBox,
text='Bing',
width=10,
height=3,
command=show_msg
)
button4.place(x=6,y=2)
# Menu Button
lf_MenuButton = tk.LabelFrame(root, width=96, height=96, text='MenuButton')
lf_MenuButton.grid(row=3, column=0, padx=10,sticky='w')
def menubutton_click1():
myStr.set('You selected Apple.')
def menubutton_click2():
myStr.set('You selected Banana.')
def menubutton_click3():
myStr.set('You selected Coconut.')
menubutton = tk.Menubutton(lf_MenuButton,
text="Fruits",
relief='raised'
)
menubutton.grid()
menubutton.menu = tk.Menu(menubutton, tearoff=0)
menubutton["menu"] = menubutton.menu
menubutton.menu.add_command(label='Apple', command=menubutton_click1)
menubutton.menu.add_command(label='Banana', command=menubutton_click2)
menubutton.menu.add_command(label='Coconut', command=menubutton_click3)
menubutton.place(x=6,y=2)
# Option Menu
lf_OptionMenu = tk.LabelFrame(root, width=96, height=96, text='OptionMenu')
lf_OptionMenu.grid(row=3, column=1, sticky='w')
Options = ['Apple', 'Banana', 'Coconut']
op_var = tk.StringVar()
op_var.set(Options[0])
optionmenu = tk.OptionMenu(lf_OptionMenu,
op_var,
*Options
)
def change_dropdown(*args):
myStr.set("You have selected " + op_var.get())
op_var.trace('w', change_dropdown)
optionmenu.place(x=6,y=2)
# Spin Box
lf_SpinBox = tk.LabelFrame(root, width=96, height=96, text='SpinBox')
lf_SpinBox.grid(row=3, column=2, sticky='w')
spinbox = tk.Spinbox(lf_SpinBox,
from_=0,
to=10,
width=8
)
spinbox.place(x=6,y=2)
# Paned Window
lf_PanedWindow = tk.LabelFrame(root, width=570, height=96, text='PanedWindow')
lf_PanedWindow.grid(row=3, column=3, columnspan=3, sticky='w')
panedwindow = tk.PanedWindow(lf_PanedWindow, width=560)
pw_left = tk.Label(panedwindow, text="left pane")
panedwindow.add(pw_left)
pw_mid = tk.Label(panedwindow, text="mid pane")
panedwindow.add(pw_mid)
pw_right = tk.Label(panedwindow, text="right pane")
panedwindow.add(pw_right)
panedwindow.place(x=6,y=2)
# Canvas
lf_Canvas = tk.LabelFrame(root, width=935, height=410, text='Canvas')
lf_Canvas.grid(row=4, column=0, columnspan=6, padx=10, sticky='w')
canvas = tk.Canvas(lf_Canvas,
bg = 'grey',
heigh = 380,
width = 900
)
x0, y0, x1, y1 = 0,30,0,300
line = canvas.create_line(x0, y0, x1, y1)
oval = canvas.create_oval(x0+50, y0+50, x1+150, y1+50, fill='black')
arc = canvas.create_arc(x0+160, y0, x1+310, y1, start = 30, extent = 150, fill = 'black')
rect = canvas.create_rectangle(560, 30, 870, 250)
poly = canvas.create_polygon(560, 220, 220, 310, 230, 190)
canvas.place(x=6,y=2)
root.mainloop()