我在 GitHub 放置一個名為 xinetzone / pychaos 的項目,該項目以 tkinter 為基礎(chǔ)研究如何使用 Python 開發(fā) GUI 接口。該項目已經(jīng)提供了 PyPI 接口,您可以使用如下命令進(jìn)行安裝:
pip install tkinterx
下面我們逐步展開此庫的使用細(xì)節(jié)。
1 更加友好的畫圖操作
tkinterx 提供了 CanvasMeta 來代替 tkinter 的 Canvas 進(jìn)行畫圖。
def test_Meta():
from tkinter import Tk
from tkinterx.graph.canvas import CanvasMeta
root = Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
self = CanvasMeta(root)
kw = {
'color': 'purple',
'dash': 2,
'width': 3,
}
self.create_graph('line', [20, 20, 100, 200], **kw)
self.create_graph('oval', [50, 80, 100, 200], fill='red', **kw)
self.create_graph('rectangle', [170, 80, 220, 200], fill='yellow', **kw)
self.create_graph('arc', [180, 100, 250, 260],
tags='test',
fill='lightblue', style='chord', **kw)
self.create_graph('polygon', [(270, 80), (220, 170), (230, 90)], fill='blue', **kw)
self.create_graph('polygon', ((70, 80), (20, 70), (30, 90)), fill='purple', **kw)
self.grid(row=0, column=0)
print((self.gettags(1)))
print((self.find_withtag('test')))
root.mainloop()
CanvasMeta 提供了一個統(tǒng)一的 2D 畫圖接口,create_graph(graph_type, directions, color='blue', width=1, tags=None, **kwargs)。
-
graph_type:指定畫圖的類型,有 'rectangle', 'oval', 'line', 'arc', 'polygon'。 -
directions:指定您要畫圖形的對角線的方向向量,其中
與
分別表示圖形的左上角與右下角坐標(biāo)。graph_type 為
'polygon'的圖形則可以*points形式指定directions的值。 -
color:表示圖形的顏色。 -
width:表示圖形的寬度。 -
tags:表示圖對象的標(biāo)識 id 綁定的標(biāo)簽信息。比如['line', 'graph'],('test', 'graph'),'line','line graph'。需要注意的是,像'1'、'1 2 2'這種純數(shù)字的標(biāo)簽是無效的。如果tags為None則默認(rèn)添加標(biāo)簽[graph_type, 'graph'] -
fill:表示圖形的填充顏色,由于'line'無法填充,所以此參數(shù)對于graph_type為'line'的圖形無效。
下圖1 展示了圖形效果:

2 可傳遞值的窗體
先看一個例子:
import json
from tkinter import Tk, StringVar, ttk
from tkinterx.meta import WindowMeta, ask_window, askokcancel, showwarning
class Window(WindowMeta):
def __init__(self, master=None, cnf={}, **kw):
super().__init__(master, cnf, **kw)
def create_widget(self):
self.add_row('Please enter your name:', 'name')
self.add_row('Please enter your age:', 'age')
self.add_row('Enter your information saving path:', 'save_path')
def save(self, path):
table = self.table.todict()
with open(path, 'w') as fp:
json.dump(table, fp)
def run(self):
self.withdraw()
name = self.table['name']
age = self.table['age']
save_path = str(self.table['save_path'])
if '' in [name, age, save_path]:
showwarning(self)
else:
self.save(save_path)
askokcancel(self)
class Root(Tk):
def __init__(self):
super().__init__()
self.label_var = StringVar()
self.create_widgets()
self.layout()
def create_buttons(self):
style = ttk.Style()
style.configure("C.TButton",
foreground="green",
background="white",
relief='raise',
justify='center',
font=('YaHei', '10', 'bold'))
self.table_button = ttk.Button(self, text='Fill in your name and age:',
command=self.ask_table,
style="C.TButton")
def create_widgets(self):
self.create_buttons()
self.label = ttk.Label(self, textvariable=self.label_var)
def ask_table(self):
bunch = ask_window(self, Window)
name, age = bunch['name'], bunch['age']
self.label_var.set(f"{name}: {age}")
def layout(self):
self.table_button.pack()
self.label.pack()
if __name__ == "__main__":
root = Root()
root.geometry('300x200')
root.mainloop()
輸出的界面為:

該例子使用了可定制的窗體: WindowMeta,該類存在實例方法 add_row(text, key) 可以用于創(chuàng)建“行數(shù)據(jù)”,即 text: key 形式的 ttk 小部件。其中 text、key 分別使用 ttk.Label 與 ttk.Entry 小部件。對于 key 如果設(shè)定為 *path、*dir 則會在您使用鼠標(biāo)點擊其對應(yīng)的 text 時分別打開文件選擇器與文件夾選擇器。
WindowMeta 中用戶傳入的值均被記錄在其 table 屬性字典之中,可以被其他窗體獲取。除此之外,WindowMeta 還有兩個關(guān)鍵的實例方法 run(與 ok 按鈕綁定)與 create_widget(用于創(chuàng)建小部件),需要使用者自行重載,就像 Window 之中的那樣設(shè)計即可。
為了在不同窗體之間傳遞用戶傳入的信息,還需要借助 ask_window 函數(shù),比如 Root 的實例方法 ask_table 中的 bunch = ask_window(self, Window) 操作。
3 按行或者列創(chuàng)建圖形對象
from tkinterx.param import ParamDict
from tkinterx.graph.canvas import CanvasMeta
from tkinter import Tk
class SimpleGraph(CanvasMeta):
color = ParamDict()
shape = ParamDict()
def __init__(self, master, shape, color, cnf={}, **kw):
'''The base class of all graphics frames.
:param master: a widget of tkinter or tkinter.ttk.
'''
super().__init__(master, cnf, **kw)
self.color = color
self.shape = shape
def draw(self, direction, width=1, tags=None, **kw):
kw.update({'color': self.color, 'width': width, 'tags': tags})
return self.create_graph(self.shape, direction, **kw)
def add_row(self, direction, num, stride=10, width=1, tags=None, **kw):
x0, y0, x1, y1 = direction
stride = x1 - x0 + stride
for k in range(num):
direction = [x0+stride*k, y0, x1+stride*k, y1]
self.draw(direction, width=width, tags=tags, **kw)
def add_column(self, direction, num, stride=5, width=1, tags=None, **kw):
x0, y0, x1, y1 = direction
stride = y1 - y0 + stride
for k in range(num):
direction = [x0, y0+stride*k, x1, y1+stride*k]
self.draw(direction, width=width, tags=tags, **kw)
if __name__ == "__main__":
root = Tk()
self = SimpleGraph(root, 'rectangle', 'red')
self.add_row([15, 15, 40, 40], 10)
self.add_column([15, 45, 40, 80], 5)
self.grid()
root.mainloop()
效果圖:

注意 add_row 與 add_column 的參數(shù)均是: direction, num, stride=10, width=1, tags=None, **kw。其中 num 表示行數(shù)或者列數(shù),stride 表示圖形間隔的像素個數(shù)。其余參數(shù)同 CanvasMeta 的 draw_graph 函數(shù)的參數(shù)。
在 tkinterx 中定制了一個可以修改形狀、顏色、填充、輪廓寬度的工具:
from tkinter import Tk
from tkinterx.graph.canvas_design import SimpleGraph
root = Tk()
self = SimpleGraph(root, 'rectangle', 'yellow', width=1, fill=None, background='pink')
self.add_row([25, 25, 40, 40], 10, 20)
self.fill = 'blue' # 修改填充顏色
self.add_column([40, 80, 100, 100], 5, 30, tags='TY') # 自定義標(biāo)簽
self.grid(row=0, column=0)
root.mainloop()
輸出界面:

也可以畫出規(guī)則的圖形:
from tkinter import Tk
from tkinterx.graph.canvas_design import RegularGraph
root = Tk()
self = RegularGraph(root, 'circle', 'yellow', width=7, fill=None, background='pink')
self.fill = 'red'
self.draw([140, 140], 40, tags='DF', activedash=7,
activeoutlinestipple='error', activeoutline='red')
self.add_row([75, 45], 20, 10)
self.width = 0
self.fill = 'blue'
self.add_column([40, 80], 20, 5)
self.shape = 'square'
self.width = 5
self.add_column([240, 20], radius=10, num=7, stride=25)
self.grid(row=0, column=0)
root.mainloop()

4 創(chuàng)建幾何畫板
首先,創(chuàng)建了一個用于選擇圖形的形狀和顏色的面板:
from tkinter import Tk
from tkinterx.graph.canvas_design import Selector
root = Tk()
select = Selector(root, background='skyblue')
select.grid()
root.mainloop()
界面圖:

創(chuàng)建畫圖面板
from tkinterx.graph.painter import GraphMeta
from tkinter import Tk
root = Tk()
selector = Selector(root, background='skyblue')
self = GraphMeta(root, selector)
self.bind_drawing()
selector.grid()
self.grid()
root.mainloop()
效果圖:

from tkinter import Tk
from tkinterx.graph.canvas_design import Selector
from tkinterx.graph.painter import GraphPainter
class DrawingWindow(Tk):
def __init__(self, **win_kw):
super().__init__(**win_kw)
self.selector = Selector(self, background='skyblue', width=350, height=90)
self.painter = GraphPainter(self, self.selector, background='pink')
self.painter.bind_drawing()
self.painter.bind_master()
def layout(self, row=0, column=0):
self.selector.grid(row=row, column=column)
self.painter.grid(row=row+1, column=column, sticky='nesw')
self = DrawingWindow()
self.layout(row=0, column=0)
self.mainloop()
DrawingWindow 設(shè)定了一些鼠標(biāo)與鍵盤的事件綁定。使用實例變量 record_bbox=[x0, y0, x1, y1] 追蹤畫布上的鼠標(biāo)位置,其中 (x0, y0) 記錄點擊鼠標(biāo)左鍵觸發(fā)的位置,而 (x1, y1) 則記錄鼠標(biāo)移動的實時位置。當(dāng)鼠標(biāo)左鍵釋放后,(x0, y0) 設(shè)定為 ['none']*2。該畫筆支持使用 F1 清空畫布;支持 Ctrl+a 選中畫布全部圖形,然后使用鼠標(biāo)進(jìn)行整體移動;支持使用鼠標(biāo)左鍵選中圖形并拖動到其他位置;支持將鼠標(biāo)移動到圖形內(nèi),使用 Del 按鍵進(jìn)行刪除。
使用鼠標(biāo)作圖的過程中,圖形的邊框是加粗的虛線,離開圖形之后,變成實線??梢酝ㄟ^下方的選擇器切換不同顏色(可自定義)的畫筆以及畫的圖形形狀,也支持使用鍵盤的方向鍵移動鼠標(biāo)指針位置選中的圖形。
