我在 GitHub 放置一个名为 xinetzone / pychaos 的项目,该项目以 tkinter 为基础研究如何使用 Python 开发 GUI 接口。该项目已经提供了 PyPI 接口,您可以使用如下命令进行安装:
pip install tkinterx
下面我们逐步展开此库的使用细节。
1 更加友好的画图操作
tkinterx
提供了 CanvasMeta
来代替 tkinter 的 Canvas
进行画图。
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
提供了一个统一的 2D 画图接口,create_graph(graph_type, directions, color='blue', width=1, tags=None, **kwargs)
。
-
graph_type
:指定画图的类型,有 'rectangle', 'oval', 'line', 'arc', 'polygon'。 -
directions
:指定您要画图形的对角线的方向向量,其中
与
分别表示图形的左上角与右下角坐标。graph_type 为
'polygon'
的图形则可以*points
形式指定directions
的值。 -
color
:表示图形的颜色。 -
width
:表示图形的宽度。 -
tags
:表示图对象的标识 id 绑定的标签信息。比如['line', 'graph']
,('test', 'graph')
,'line'
,'line graph'
。需要注意的是,像'1'
、'1 2 2'
这种纯数字的标签是无效的。如果tags
为None
则默认添加标签[graph_type, 'graph']
-
fill
:表示图形的填充颜色,由于'line'
无法填充,所以此参数对于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)
可以用于创建“行数据”,即 text: key
形式的 ttk 小部件。其中 text
、key
分别使用 ttk.Label
与 ttk.Entry
小部件。对于 key
如果设定为 *path
、*dir
则会在您使用鼠标点击其对应的 text
时分别打开文件选择器与文件夹选择器。
WindowMeta
中用户传入的值均被记录在其 table
属性字典之中,可以被其他窗体获取。除此之外,WindowMeta
还有两个关键的实例方法 run
(与 ok 按钮绑定)与 create_widget
(用于创建小部件),需要使用者自行重载,就像 Window
之中的那样设计即可。
为了在不同窗体之间传递用户传入的信息,还需要借助 ask_window
函数,比如 Root
的实例方法 ask_table
中的 bunch = ask_window(self, Window)
操作。
3 按行或者列创建图形对象
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
的参数均是: direction, num, stride=10, width=1, tags=None, **kw
。其中 num
表示行数或者列数,stride
表示图形间隔的像素个数。其余参数同 CanvasMeta
的 draw_graph
函数的参数。
在 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') # 自定义标签
self.grid(row=0, column=0)
root.mainloop()
输出界面:
也可以画出规则的图形:
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 创建几何画板
首先,创建了一个用于选择图形的形状和颜色的面板:
from tkinter import Tk
from tkinterx.graph.canvas_design import Selector
root = Tk()
select = Selector(root, background='skyblue')
select.grid()
root.mainloop()
界面图:
创建画图面板
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
设定了一些鼠标与键盘的事件绑定。使用实例变量 record_bbox=[x0, y0, x1, y1]
追踪画布上的鼠标位置,其中 (x0, y0)
记录点击鼠标左键触发的位置,而 (x1, y1)
则记录鼠标移动的实时位置。当鼠标左键释放后,(x0, y0)
设定为 ['none']*2
。该画笔支持使用 F1
清空画布;支持 Ctrl+a
选中画布全部图形,然后使用鼠标进行整体移动;支持使用鼠标左键选中图形并拖动到其他位置;支持将鼠标移动到图形内,使用 Del
按键进行删除。
使用鼠标作图的过程中,图形的边框是加粗的虚线,离开图形之后,变成实线。可以通过下方的选择器切换不同颜色(可自定义)的画笔以及画的图形形状,也支持使用键盘的方向键移动鼠标指针位置选中的图形。