美文网首页GUI 设计Python
6 tkinter 自定义画图工具

6 tkinter 自定义画图工具

作者: 水之心 | 来源:发表于2020-01-15 16:56 被阅读0次

    本文创建了一个自定义的画图工具:

    from tkinter import ttk, Tk
    from tkinter import Canvas, Menu
    from tkinter import StringVar
    
    
    class Graph(Canvas):
        '''创建图形元素(简称 graph),包括矩形框,椭圆形,线段,矩形框,如果您想要画“点”,可以选中矩形框或者椭圆形。
        '''
    
        def __init__(self, master=None, cnf={}, **kw):
            super().__init__(master, cnf, **kw)
            self.master = master
            self.master.title('计算机视觉')
            self.color_options = ('red', 'blue', 'black', 'white', 'green')
            self._init_params()
            self.create_menu()
            self.change()
    
        def _create_variable(self):
            '''菜单变量'''
            self.image_var = StringVar()
            self.graph_var = StringVar()
    
        def _init_params(self):
            self.configure(width=400, height=400)
            self.current_id = None
            self.color = 'black'
            self.x = self.y = 0  # 记录鼠标左键的坐标
            self.color = 'blue'  # graph 对象的颜色
            self.width = 2  # graph 的宽度
            self._create_variable()
    
        def create_menu(self):
            self.master.option_add('*tearOff', False)  # 表示菜单不能移除窗口
            menu_bar = Menu(self.master)  # 创建菜单栏
            self.master['menu'] = menu_bar  # 或者 root.config(menu=menubar)
            painter_bar = Menu(menu_bar)
            pencil_menu = Menu(painter_bar)
            modify_menu = Menu(painter_bar)
            color_menu = Menu(painter_bar)
    
            menu_bar.add_cascade(label='创作', menu=painter_bar)
            painter_bar.add_cascade(label='画笔', menu=pencil_menu)
            painter_bar.add_cascade(label='修改', menu=modify_menu)
    
            kw_menu = {
                'variable': self.graph_var,
                'command': self.bind_graph
            }
    
            pencil_options = ('矩形框', '椭圆形', '线段')
            [pencil_menu.add_radiobutton(label=pencil, **kw_menu)
             for pencil in pencil_options]
    
            modify_options = ('移动选中的元素', '移动整个画布', '移动全部矩形框')
            [modify_menu.add_radiobutton(label=modify, **kw_menu)
             for modify in modify_options]
    
        def update_xy(self, event):
            '''按压鼠标左键'''
            self.x = event.x
            self.y = event.y
    
        def set_color(self, new_color):
            self.color = new_color
    
        def palette(self, loc, color):
            kw = {
                'fill': color,
                'tags': ('调色板', f'{color}调色板')
            }
            return self.create_rectangle(loc, **kw)
    
        def change(self):
            # 创建 3 个颜色选择块
            red_id = self.palette((10, 10, 20, 20), "red")
            blue_id = self.palette((10, 25, 20, 35), "blue")
            black_id = self.palette((10, 40, 20, 50), "black")
            # 添加标记
            self.addtag('被选中的调色板', 'withtag', black_id)
            # 绑定事件
            self.tag_bind(red_id, "<Button-1>", lambda x: self.set_color("red"))
            self.tag_bind(blue_id, "<Button-1>", lambda x: self.set_color("blue"))
            self.tag_bind(black_id, "<Button-1>",
                          lambda x: self.set_color("black"))
    
        def select_graph(self, event):
            '''按压鼠标右键'''
            self.configure(cursor="target")
            self.update_xy(event)
            # 获取当前鼠标指示的对象的 id
            self.current_id = self.find_withtag('current')
    
        def get_bbox(self, event):
            x0, y0 = self.x, self.y  # 左上角坐标
            x1, y1 = event.x, event.y  # 右下角坐标
            bbox = x0, y0, x1, y1
            return bbox
    
        def draw_graph(self, event):
            '''释放鼠标左键'''
            self.configure(cursor="arrow")
            bbox = self.get_bbox(event)
            self.create_graph(bbox)
    
        def bind_graph(self, event=None):
            graph = self.graph_var.get()
            if '移动' in graph:
                self.unbind('<ButtonRelease-1>')
                self.unbind('<1>')
                self.bind('<1>', self.select_graph)
                self.bind('<ButtonRelease-1>', self.move_graph)
            else:
                self.unbind('<ButtonRelease-1>')
                self.unbind('<1>')
                self.bind('<1>', self.update_xy)
                self.bind("<ButtonRelease-1>", self.draw_graph)
    
        def move_graph(self, event):
            print(self.current_id)
            new_x, new_y = event.x, event.y
            x_move = new_x - self.x
            y_move = new_y - self.y
            self.move(self.current_id, x_move, y_move)
    
        def create_graph(self, bbox):
            '''创建图形
            参数
            ========
            bbox: x0,y0,x1,y1
            '''
            tags = 'graph'
            kw = {
                'width': self.width,
                'tags': tags
            }
            graph = self.graph_var.get()
            if graph == '矩形框':
                self.create_rectangle(bbox, outline=self.color, **kw)
                self.addtag_withtag('rect', 'graph')
            elif graph == '椭圆形':  # 外接矩形的四个坐标
                self.create_oval(bbox, outline=self.color, **kw)
                self.addtag_withtag('oval', 'graph')
            elif graph == '线段':
                self.create_line(bbox, fill=self.color, **kw)
                self.addtag_withtag('line', 'graph')
    
        def layout(self):
            '''布局'''
            # 将整个画布铺满屏幕
            self.master.columnconfigure(0, weight=1)
            self.master.rowconfigure(0, weight=1)
            self.grid(column=0, row=0, sticky='nwes')
    
    
    if __name__ == '__main__':
        root = Tk()
        self = Graph(root)
        self.layout()
        root.mainloop()
    

    下面直接看效果图:

    图1 画图的效果图

    直接点击图中的颜色小方框,可以切换不同的画笔颜色。

    该工具主要有两个菜单栏选项:“画笔”,“修改”。“画笔” 暂时仅仅提供 3 个形状:

    图2 切换不同是画笔

    图1 中的各个图形的“作画”方法是:选中您想要画的形状,拖动鼠标左键,释放鼠标左键便会生成图形元素。

    修改选项仅仅实现了移动选中的元素这一功能。

    图3 移动选中的图元素

    视觉效果更好的优化

    由于 Tcl 支持:'⬜','⚪', '⸺', '•', '⯀', '⎔', '▅' 等 UTF-8 字符(具体参考维基百科的 Unicode/List of useful symbolsGeometric Shapes 以及 FileFormat.Info 的 Unicode Characters in the 'Symbol, Other' Category

    代码如下:

    from tkinter import ttk, Tk
    from tkinter import Canvas, Menu
    from tkinter import StringVar
    
    
    class Graph(Canvas):
        '''创建图形元素(简称 graph),包括矩形框(可以是方形点),椭圆形(圆形点),线段
    
        '''
    
        def __init__(self, master=None, cnf={}, **kw):
            super().__init__(master, cnf, **kw)
            self.master = master
            self.master.title('计算机视觉')
            self.color_options = ('red', 'blue', 'black', 'white', 'green')
            self._init_params()
            self.create_menu()
            self.change()
    
        def _create_variable(self):
            '''菜单变量'''
            self.image_var = StringVar()
            self.graph_var = StringVar()
    
        def _init_params(self):
            self.configure(width=400, height=400)
            self.current_id = None
            self.color = 'black'
            self.x = self.y = 0  # 记录鼠标左键的坐标
            self.color = 'blue'  # graph 对象的颜色
            self.width = 2  # graph 的宽度
            self._create_variable()
    
        def create_menu(self):
            self.master.option_add('*tearOff', False)  # 表示菜单不能移除窗口
            menu_bar = Menu(self.master)  # 创建菜单栏
            self.master['menu'] = menu_bar  # 或者 root.config(menu=menubar)
            painter_bar = Menu(menu_bar)
            pencil_menu = Menu(painter_bar)
            modify_menu = Menu(painter_bar)
            color_menu = Menu(painter_bar)
    
            menu_bar.add_cascade(label='创作', menu=painter_bar)
            painter_bar.add_cascade(label='画笔', menu=pencil_menu)
            painter_bar.add_cascade(label='修改', menu=modify_menu)
    
            kw_menu = {
                'variable': self.graph_var,
                'command': self.bind_graph
            }
    
            pencil_options = ('⬜', '⚪', '⸺')
            [pencil_menu.add_radiobutton(label=pencil, **kw_menu)
             for pencil in pencil_options]
    
            modify_options = ('移动选中的元素',
                              '移动全部的元素',
                              '移动全部的图元素',
                              '删除全部 ⯀',
                              '删除全部 ●',
                              '移动全部 ⬜',
                              '移动全部 ⚪',
                              '移动全部 ⸺'
                              )
            [modify_menu.add_radiobutton(label=modify, **kw_menu)
             for modify in modify_options]
            self.unbind('<ButtonRelease-1>')
            self.unbind('<1>')
    
        def update_xy(self, event):
            '''按压鼠标左键'''
            self.x = event.x
            self.y = event.y
    
        def set_color(self, new_color):
            self.color = new_color
    
        def palette(self, loc, color):
            kw = {
                'fill': color,
                'tags': ('调色板', f'{color}调色板')
            }
            return self.create_rectangle(loc, **kw)
    
        def change(self):
            # 创建 3 个颜色选择块
            red_id = self.palette((10, 10, 20, 20), "red")
            blue_id = self.palette((10, 25, 20, 35), "blue")
            black_id = self.palette((10, 40, 20, 50), "black")
            # 添加标记
            self.addtag('被选中的调色板', 'withtag', black_id)
            # 绑定事件
            self.tag_bind(red_id, "<1>", lambda x: self.set_color("red"))
            self.tag_bind(blue_id, "<1>", lambda x: self.set_color("blue"))
            self.tag_bind(black_id, "<1>", lambda x: self.set_color("black"))
    
        def select_graph(self, event):
            '''按压鼠标右键'''
            self.configure(cursor="target")
            self.update_xy(event)
            # 获取当前鼠标指示的对象的 id
            self.current_id = self.find_withtag('current')
    
        def get_bbox(self, event):
            x0, y0 = self.x, self.y  # 左上角坐标
            x1, y1 = event.x, event.y  # 右下角坐标
            bbox = x0, y0, x1, y1
            return bbox
    
        def draw_graph(self, event):
            '''释放鼠标左键'''
            self.configure(cursor="arrow")
            bbox = self.get_bbox(event)
            self.create_graph(bbox)
            rect_ids = self.find_withtag('rect')
            oval_ids = self.find_withtag('oval')
            line_ids = self.find_withtag('line')
            point_ids = self.find_withtag('point')
            print(rect_ids, oval_ids, line_ids, point_ids, bbox)
    
        def move_graph(self, event, graph_id_or_tag):
            x_move, y_move = self.move_strides(event)
            self.move(graph_id_or_tag, x_move, y_move)
    
        def bind_graph(self, event=None):
            graph = self.graph_var.get()
            self.unbind('<ButtonRelease-1>')
            self.unbind('<1>')
            if graph == '移动选中的元素':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, self.current_id)
                self.bind('<ButtonRelease-1>', move_action)
            elif graph == '移动全部的元素':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'all')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部的图元素':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'graph')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部 ⬜':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'rect')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部 ⚪':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'oval')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部 ⸺':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'line')
                self.bind('<ButtonRelease-1>',  move_action)
            else:
                self.bind('<1>', self.update_xy)
                self.bind("<ButtonRelease-1>", self.draw_graph)
    
        def move_strides(self, event):
            new_x, new_y = event.x, event.y
            x_move = new_x - self.x
            y_move = new_y - self.y
            print(x_move, y_move)
            return x_move, y_move
    
        def create_graph(self, bbox):
            '''创建图形
            参数
            ========
            bbox: x0,y0,x1,y1
            '''
            start = bbox[:2]
            end = bbox[2:]
            kw = {
                'width': self.width,
            }
            tags = {'graph'}
            #all_graph = {'rect', 'oval', 'line'}
            graph = self.graph_var.get()
            if graph == '⬜':  # 矩形框
                tags.add('rect')
                if start == end:
                    tags.add('point')
                self.create_rectangle(bbox, outline=self.color,
                                      tags=tuple(tags), **kw)
            elif graph == '⚪':  # 椭圆形,bbox 指定外接矩形的四个坐标
                tags.add('oval')
                if start == end:
                    tags.add('point')
                self.create_oval(bbox, outline=self.color, tags=tuple(tags), **kw)
            elif graph == '⸺':  # 线段
                tags.add('line')
                self.create_line(bbox, fill=self.color, tags=tuple(tags), **kw)
    
        def layout(self):
            '''布局'''
            # 将整个画布铺满屏幕
            self.master.columnconfigure(0, weight=1)
            self.master.rowconfigure(0, weight=1)
            self.grid(column=0, row=0, sticky='nwes')
    
    
    if __name__ == '__main__':
        root = Tk()
        self = Graph(root)
        self.layout()
        root.mainloop()
    

    该代码实现对矩形框、椭圆形、线段的绘制与移动,但是对于“点”(point)的移动和删除还暂时没有实现。

    下面可直接看效果图:

    图4 “画笔”展示 图5 “修改”选项 图6 随机画了一些图形

    可以看出,使用“线段”画笔也可以多边形。

    需要注意的是:代码中的 bbox 表示鼠标左键移动的方向向量。

    实现“点”与矩形框和椭圆形的分离,同时支持对点的移动:

    from tkinter import ttk, Tk
    from tkinter import Canvas, Menu
    from tkinter import StringVar
    
    
    class Graph(Canvas):
        '''创建图形元素(简称 graph),包括矩形框(可以是方形点),椭圆形(圆形点),线段
    
        '''
    
        def __init__(self, master=None, cnf={}, **kw):
            super().__init__(master, cnf, **kw)
            self.master = master
            self.master.title('计算机视觉')
            self.color_options = ('red', 'blue', 'black', 'white', 'green')
            self._init_params()
            self.create_menu()
            self.change()
    
        def _create_variable(self):
            '''菜单变量'''
            self.image_var = StringVar()
            self.graph_var = StringVar()
    
        def _init_params(self):
            self.configure(width=400, height=400)
            self.current_id = None
            self.color = 'black'
            self.x = self.y = 0  # 记录鼠标左键的坐标
            self.color = 'blue'  # graph 对象的颜色
            self.width = 2  # graph 的宽度
            self._create_variable()
    
        def create_menu(self):
            self.master.option_add('*tearOff', False)  # 表示菜单不能移除窗口
            menu_bar = Menu(self.master)  # 创建菜单栏
            self.master['menu'] = menu_bar  # 或者 root.config(menu=menubar)
            painter_bar = Menu(menu_bar)
            pencil_menu = Menu(painter_bar)
            modify_menu = Menu(painter_bar)
            color_menu = Menu(painter_bar)
    
            menu_bar.add_cascade(label='创作', menu=painter_bar)
            painter_bar.add_cascade(label='画笔', menu=pencil_menu)
            painter_bar.add_cascade(label='修改', menu=modify_menu)
    
            kw_menu = {
                'variable': self.graph_var,
                'command': self.bind_graph
            }
    
            pencil_options = ('⬜', '⚪', '⸺', '⯀', '●')
            [pencil_menu.add_radiobutton(label=pencil, **kw_menu)
             for pencil in pencil_options]
    
            modify_options = ('移动选中的元素',
                              '移动全部的元素',
                              '移动全部的图元素',
                              '移动全部 ⯀',
                              '移动全部 ●',
                              '移动全部 ⬜',
                              '移动全部 ⚪',
                              '移动全部 ⸺'
                              )
            [modify_menu.add_radiobutton(label=modify, **kw_menu)
             for modify in modify_options]
            self.unbind('<ButtonRelease-1>')
            self.unbind('<1>')
    
        def update_xy(self, event):
            '''按压鼠标左键'''
            self.x = event.x
            self.y = event.y
    
        def set_color(self, new_color):
            self.color = new_color
    
        def palette(self, loc, color):
            kw = {
                'fill': color,
                'tags': ('调色板', f'{color}调色板')
            }
            return self.create_rectangle(loc, **kw)
    
        def change(self):
            # 创建 3 个颜色选择块
            red_id = self.palette((10, 10, 20, 20), "red")
            blue_id = self.palette((10, 25, 20, 35), "blue")
            black_id = self.palette((10, 40, 20, 50), "black")
            # 添加标记
            self.addtag('被选中的调色板', 'withtag', black_id)
            # 绑定事件
            self.tag_bind(red_id, "<1>", lambda x: self.set_color("red"))
            self.tag_bind(blue_id, "<1>", lambda x: self.set_color("blue"))
            self.tag_bind(black_id, "<1>", lambda x: self.set_color("black"))
    
        def select_graph(self, event):
            '''按压鼠标右键'''
            self.configure(cursor="target")
            self.update_xy(event)
            # 获取当前鼠标指示的对象的 id
            self.current_id = self.find_withtag('current')
    
        def get_bbox(self, event):
            x0, y0 = self.x, self.y  # 左上角坐标
            x1, y1 = event.x, event.y  # 右下角坐标
            bbox = x0, y0, x1, y1
            return bbox
    
        def draw_graph(self, event):
            '''释放鼠标左键'''
            self.configure(cursor="arrow")
            bbox = self.get_bbox(event)
            self.create_graph(bbox)
            rect_ids = self.find_withtag('rect')
            oval_ids = self.find_withtag('oval')
            line_ids = self.find_withtag('line')
            rect_point_ids = self.find_withtag('rect_point')
            oval_point_ids = self.find_withtag('oval_point')
            print(rect_ids, oval_ids, line_ids,
                  rect_point_ids, oval_point_ids, bbox)
    
        def move_graph(self, event, graph_id_or_tag):
            x_move, y_move = self.move_strides(event)
            self.move(graph_id_or_tag, x_move, y_move)
    
        def bind_graph(self, event=None):
            graph = self.graph_var.get()
            self.unbind('<ButtonRelease-1>')
            self.unbind('<1>')
            if graph == '移动选中的元素':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, self.current_id)
                self.bind('<ButtonRelease-1>', move_action)
            elif graph == '移动全部的元素':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'all')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部的图元素':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'graph')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部 ⬜':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'rect')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部 ⚪':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'oval')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部 ⸺':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'line')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部 ⯀':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'rect_point')
                self.bind('<ButtonRelease-1>',  move_action)
            elif graph == '移动全部 ●':
                self.bind('<1>', self.select_graph)
                def move_action(e): return self.move_graph(e, 'oval_point')
                self.bind('<ButtonRelease-1>',  move_action)
            else:
                self.bind('<1>', self.update_xy)
                self.bind("<ButtonRelease-1>", self.draw_graph)
    
        def move_strides(self, event):
            new_x, new_y = event.x, event.y
            x_move = new_x - self.x
            y_move = new_y - self.y
            return x_move, y_move
    
        def create_graph(self, bbox):
            '''创建图形
            参数
            ========
            bbox: x0,y0,x1,y1
            '''
            start = bbox[:2]
            end = bbox[2:]
            kw = {
                'width': self.width,
            }
            tags = {'graph'}
            #all_graph = {'rect', 'oval', 'line'}
            graph = self.graph_var.get()
            if start == end:
                if graph == '⯀':
                    tags.add('rect_point')
                    self.create_rectangle(bbox, outline=self.color,
                                          tags=tuple(tags), **kw)
                elif graph == '●':
                    tags.add('oval_point')
                    self.create_oval(bbox, outline=self.color,
                                     tags=tuple(tags), **kw)
            elif graph == '⬜':  # 矩形框
                tags.add('rect')
                self.create_rectangle(bbox, outline=self.color,
                                      tags=tuple(tags), **kw)
            elif graph == '⚪':  # 椭圆形,bbox 指定外接矩形的四个坐标
                tags.add('oval')
                if start == end:
                    tags.add('point')
                self.create_oval(bbox, outline=self.color, tags=tuple(tags), **kw)
            elif graph == '⸺':  # 线段
                tags.add('line')
                self.create_line(bbox, fill=self.color, tags=tuple(tags), **kw)
    
        def layout(self):
            '''布局'''
            # 将整个画布铺满屏幕
            self.master.columnconfigure(0, weight=1)
            self.master.rowconfigure(0, weight=1)
            self.grid(column=0, row=0, sticky='nwes')
    
    
    if __name__ == '__main__':
        root = Tk()
        self = Graph(root)
        self.layout()
        root.mainloop()
    

    更加健壮的代码见 基于 tkinter 开发图形操作案例

    相关文章

      网友评论

        本文标题:6 tkinter 自定义画图工具

        本文链接:https://www.haomeiwen.com/subject/kkgtzctx.html