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', [(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:指定您要画图形的对角线的方向向量 d = (x_0, y_0, x_1, y_1),其中 (x_0, y_0)(x_1, y_1) 分别表示图形的左上角与右下角坐标。graph_type 为 'polygon' 的图形则可以 *points 形式指定 directions 的值。
  • color:表示图形的颜色。
  • width:表示图形的宽度。
  • tags:表示图对象的标识 id 绑定的标签信息。比如 ['line', 'graph']('test', 'graph'), '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 按行或者列创建图形对象

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

效果图:

图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=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()
图5 画出正方形与圆形

4 创建几何画板

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

from tkinter import Tk
from tkinterx.graph.canvas_design import Selector
root = Tk()
select = Selector(root, background='skyblue')
select.grid()
root.mainloop()

界面图:

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

创建画图面板

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

效果图:

图7 几何画板
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 按键进行删除。

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

图8 可修改图形的几何画板
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容