tkinter 的拓展包:tkinterx

我在 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', [(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 画图接口,draw_graph(graph_type, direction, color='blue', width=1, tags=None, **kwargs)

  • graph_type:指定画图的类型,有 'rectangle', 'oval', 'line', 'arc', 'polygon'。
  • direction:指定您要画图形的对角线的方向向量 d = (x_0, y_0, x_1, y_1),其中 (x_0, y_0)(x_1, y_1) 分别表示图形的左上角与右下角坐标。graph_type 为 'polygon' 的图形则可以 *points 形式指定 direction 的值。
  • color:表示图形的颜色。
  • width:表示图形的宽度。
  • tags:表示图对象的标识 id 绑定的标签信息。比如 ['line', 'graph']('test', 'g'), 'line','line graph'。需要注意的是,像 '1''1 2 2' 这种纯数字的标签是无效的。如果 tagsNone 则默认添加标签 [graph_type, 'graph']
  • fill:表示图形的填充颜色,由于 'line' 无法填充,所以此参数对于 graph_type'line' 的图形无效。

下图1 展示了图形效果:

图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()

输出的界面为:

图2 可传递值的窗体

该例子使用了可定制的窗体: WindowMeta,该类存在实例方法 add_row(text, key) 可以用于创建“行数据”,即 text: key 形式的 ttk 小部件。其中 textkey 分别使用 ttk.Labelttk.Entry 小部件。对于 key 如果设定为 *path*dir 则会在您使用鼠标点击其对应的 text 时分别打开文件选择器与文件夹选择器。

WindowMeta 中用户传入的值均被记录在其 table 属性字典之中,可以被其他窗体获取。除此之外,WindowMeta 还有两个关键的实例方法 run(与 ok 按钮绑定)与 create_widget(用于创建小部件),需要使用者自行重载,就像 Window 之中的那样设计即可。

为了在不同窗体之间传递用户传入的信息,还需要借助 ask_window 函数,比如 Root 的实例方法 ask_table 中的 bunch = ask_window(self, Window) 操作。

3 按行或者列创建图形对象

import sys
sys.path.append(r"D:\study\pygui")

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):
        return self.create_graph(self.shape, direction, self.color, width, tags, **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()

效果图:

图3 按行或者列创建图形对象

注意 add_rowadd_column 的参数均是: direction, num, stride=10, width=1, tags=None, **kw。其中 num 表示行数或者列数,stride 表示图形间隔的像素个数。其余参数同 CanvasMetadraw_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()

输出界面:

图4 可以修改图形属性的工具

也可以画出规则的图形:

from tkinter import Tk
from tkinterx.graph.canvas_design import RegularGraph
root = Tk()
self = RegularGraph(root, 'circle', 'yellow', width=1, fill=None, background='pink')
self.fill = 'red'
self.draw([140, 140], 40, tags='DF', activedash=7)
self.add_row([75, 45], 20, 10)
self.fill = 'blue'
self.add_column([40, 80], 20, 5)
self.shape = 'square'
self.add_column([240, 20], radius=10, num=7, stride=25)
self.grid(row=0, column=0)
root.mainloop()
图5 画出正方形与圆形

4 创建几何画板

首先,创建了一个用于选择图形的形状和颜色的面板:

from tkinter import Tk
from tkinterx.graph.canvas_design import SelectorFrame
root = Tk()
self = SelectorFrame(root, text='Selector')
self.grid()
root.mainloop()

界面图:

图6 选择图形的形状和颜色的面板

创建画图面板

from tkinter import Tk
from tkinterx.graph.drawing import Drawing
root = Tk()
self = Drawing(root)
self.layout()
root.mainloop()

效果图:

图7 几何画板

Drawing 设定了一些鼠标与键盘的事件绑定。使用实例变量 record_bbox=[x0, y0, x1, y1] 追踪画布上的鼠标位置,其中 (x0, y0) 记录点击鼠标左键触发的位置,而 (x1, y1) 则记录鼠标移动的实时位置。当鼠标左键释放后,(x0, y0) 设定为 ['none']*2。该画笔支撑使用 F1 清空画布;支撑 Ctrl+a 选中画布全部图形,然后使用鼠标进行整体移动;支撑使用鼠标左键选中图形并拖动到其他位置;支撑将鼠标移动到图形内,使用 Del 按键进行删除。

使用鼠标作图的过程中,图形的边框是加粗的虚线,离开图形之后,变成实线。可以通过下方的选择器切换不同颜色(可自定义)的画笔以及画的图形形状,也支撑使用键盘的方向键移动鼠标指针位置选中的图形。

禁止转载,如需转载请通过简信或评论联系编辑。

推荐阅读更多精彩内容