这是一个基于 tkinter 开发的交互式绘图应用程序,支持多种图形绘制模式和丰富的自定义选项。
项目概述
使用 tkinter 库构建的绘图应用,通过鼠标拖动实现自由绘制和几何图形绘制功能。
核心功能
主要组件
DrawApp 类:主要应用程序类
画布区域:白色背景的绘图画布,支持鼠标交互
侧边栏:图形类型、线条粗细和操作按钮
底部区域:颜色选择面板
绘图模式
自由绘制:freehand_draw() - 鼠标拖动连续绘制线条
直线绘制:preview_shape() - 绘制直线段
矩形绘制:拖动绘制矩形
圆形绘制:拖动绘制椭圆形
功能特性
颜色选择:提供8种预设颜色(黑色、红色、蓝色等)
粗细调节:线条粗细范围1-10像素,通过滑块控制
撤销功能:undo_last() - 撤销最后绘制的图形
清除画布:clear_canvas() - 清空所有绘制内容
界面设计
布局结构
左侧边栏:包含图形类型选择、线条粗细调节和操作按钮
中央画布:主要绘图区域,白色背景
底部面板:颜色选择区域,显示当前颜色和可选颜色
交互方式
鼠标事件绑定:
<ButtonPress-1>:按下事件,记录起始位置
<B1-Motion>:拖动事件,实时绘制预览
<ButtonRelease-1>:释放事件,确认绘制
技术实现
核心机制
图形栈管理:使用 shapes_stack 列表管理绘制元素,支持撤销功能
预览绘制:在 preview_shape() 中实现图形预览效果
事件处理:完整的鼠标事件处理链,实现流畅的绘制体验
界面组件
标签框架:使用 tk.LabelFrame 创建分组区域
滑块控件:tk.Scale 实现线条粗细调节
按钮控件:彩色按钮提供直观的操作反馈
运行方式
运行 canvas拖动画图.py 文件启动应用,使用 if __name__ == "__main__" 确保独立运行。

运行效果:

源码:

import tkinter as tk

class DrawApp:
    def __init__(self, root):
        self.root = root
        self.root.title("鼠标拖动绘图工具")
        self.root.geometry("900x700")

        # 初始化绘图参数
        self.last_x = None
        self.last_y = None
        self.start_x = None
        self.start_y = None
        self.current_color = "black"
        self.line_width = 2
        self.drawing_shape = None
        self.shape_type = "freehand"
        self.shapes_stack = []

        # 创建主框架
        main_frame = tk.Frame(root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 左侧边栏框架
        sidebar_frame = tk.Frame(main_frame, width=150, relief=tk.RAISED, bd=2)
        sidebar_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
        sidebar_frame.pack_propagate(False)

        # 创建画布框架(包含画布和底部颜色选择)
        canvas_container = tk.Frame(main_frame)
        canvas_container.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 创建画布(白色背景)
        self.canvas = tk.Canvas(canvas_container, bg="white", relief=tk.RAISED, bd=2)
        self.canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        # 底部颜色选择框架(占整个窗口约25%高度)
        color_selector_frame = tk.Frame(canvas_container, height=120, relief=tk.RAISED, bd=2)  # 增加高度到120
        color_selector_frame.pack(side=tk.BOTTOM, fill=tk.X)
        color_selector_frame.pack_propagate(False)  # 固定高度

        # 在左侧边栏添加图形类型选择
        self.create_shape_selection(sidebar_frame)

        # 在左侧边栏添加线条粗细选择
        self.create_thickness_selection(sidebar_frame)

        # 在左侧边栏添加操作按钮
        self.create_operation_buttons(sidebar_frame)

        # 在底部添加颜色选择
        self.create_color_selection(color_selector_frame)

        # 绑定鼠标事件
        self.canvas.bind("<ButtonPress-1>", self.on_press)
        self.canvas.bind("<B1-Motion>", self.on_drag)
        self.canvas.bind("<ButtonRelease-1>", self.on_release)

    def create_shape_selection(self, parent):
        """创建图形类型选择区域"""
        shape_frame = tk.LabelFrame(parent, text="图形类型", font=("Arial", 10, "bold"))
        shape_frame.pack(fill=tk.X, padx=5, pady=5)

        shapes = [("自由绘制", "freehand"), ("直线", "line"), ("矩形", "rectangle"), ("圆形", "oval")]
        self.shape_buttons = {}

        for text, shape in shapes:
            btn = tk.Button(
                shape_frame,
                text=text,
                width=12,
                command=lambda s=shape: self.set_shape(s)
            )
            btn.pack(pady=2, padx=5)
            self.shape_buttons[shape] = btn

    def create_thickness_selection(self, parent):
        """创建线条粗细选择区域"""
        thickness_frame = tk.LabelFrame(parent, text="线条粗细", font=("Arial", 10, "bold"))
        thickness_frame.pack(fill=tk.X, padx=5, pady=5)

        self.thickness_var = tk.IntVar(value=self.line_width)
        thickness_scale = tk.Scale(
            thickness_frame,
            from_=1,
            to=10,
            orient=tk.HORIZONTAL,
            variable=self.thickness_var,
            command=self.change_thickness,
            length=120
        )
        thickness_scale.pack(pady=5)

    def create_operation_buttons(self, parent):
        """创建操作按钮区域"""
        operation_frame = tk.LabelFrame(parent, text="操作", font=("Arial", 10, "bold"))
        operation_frame.pack(fill=tk.X, padx=5, pady=5)

        # 撤销按钮
        self.undo_btn = tk.Button(
            operation_frame,
            text="撤销",
            command=self.undo_last,
            bg="#4CAF50",
            fg="white",
            font=("Arial", 10, "bold")
        )
        self.undo_btn.pack(pady=2, padx=5, fill=tk.X)

        # 清除画布按钮
        self.clear_btn = tk.Button(
            operation_frame,
            text="清除画布",
            command=self.clear_canvas,
            bg="#FF6B6B",
            fg="white",
            font=("Arial", 10, "bold")
        )
        self.clear_btn.pack(pady=2, padx=5, fill=tk.X)

    def create_color_selection(self, parent):
        """创建颜色选择区域"""
        # 主颜色选择区域
        color_main_frame = tk.Frame(parent)
        color_main_frame.pack(fill=tk.BOTH, expand=True, pady=10)  # 增加垂直间距

        # 颜色标题和当前颜色显示
        title_frame = tk.Frame(color_main_frame)
        title_frame.pack(side=tk.LEFT, padx=20)  # 增加水平间距

        tk.Label(title_frame, text="颜色:", font=("Arial", 12, "bold")).pack(side=tk.LEFT)  # 增大字体

        # 当前颜色显示
        self.current_color_frame = tk.Frame(title_frame, relief=tk.SUNKEN, bd=2)
        self.current_color_frame.pack(side=tk.LEFT, padx=(15, 0))

        tk.Label(self.current_color_frame, text="当前颜色:", font=("Arial", 10)).pack(side=tk.LEFT)
        self.color_indicator = tk.Label(
            self.current_color_frame,
            bg=self.current_color,
            width=5,  # 增加宽度
            height=2,  # 增加高度
            relief=tk.RAISED,
            bd=1
        )
        self.color_indicator.pack(side=tk.LEFT, padx=8, pady=5)  # 增加间距

        # 颜色按钮区域
        colors_frame = tk.Frame(color_main_frame)
        colors_frame.pack(side=tk.RIGHT, padx=20)  # 增加水平间距

        colors = ["black", "red", "blue", "green", "yellow", "orange", "purple", "brown"]
        self.color_buttons = []

        for i, color in enumerate(colors):
            btn = tk.Button(
                colors_frame,
                bg=color,
                width=4,  # 增加按钮宽度
                height=2,  # 增加按钮高度
                relief=tk.RAISED,
                bd=2,
                command=lambda c=color: self.change_color(c)
            )
            btn.pack(side=tk.LEFT, padx=4)  # 增加按钮间距
            self.color_buttons.append(btn)

    def change_color(self, color):
        """改变当前绘图颜色"""
        self.current_color = color
        self.color_indicator.config(bg=color)

    def change_thickness(self, value):
        """改变线条粗细"""
        self.line_width = int(value)

    def set_shape(self, shape):
        """设置当前绘制的图形类型"""
        self.shape_type = shape
        print(f"切换到 {shape} 模式")

    def on_press(self, event):
        """按下鼠标左键"""
        self.start_x = event.x
        self.start_y = event.y
        if self.shape_type != "freehand":
            self.drawing_shape = None

    def on_drag(self, event):
        """拖动鼠标"""
        if self.shape_type == "freehand":
            self.freehand_draw(event)
        else:
            self.preview_shape(event)

    def on_release(self, event):
        """释放鼠标"""
        if self.shape_type == "freehand":
            self.reset_last_pos(event)
        elif self.drawing_shape is not None:
            self.finalize_shape(event)
            self.shapes_stack.append(self.drawing_shape)
            self.drawing_shape = None

    def freehand_draw(self, event):
        """自由绘制模式"""
        if self.last_x and self.last_y:
            line_id = self.canvas.create_line(
                self.last_x, self.last_y,
                event.x, event.y,
                fill=self.current_color,
                width=self.line_width,
                smooth=True,
                capstyle=tk.ROUND,
                joinstyle=tk.ROUND
            )
            self.shapes_stack.append(line_id)
        self.last_x = event.x
        self.last_y = event.y

    def preview_shape(self, event):
        """预览图形"""
        if self.drawing_shape:
            self.canvas.delete(self.drawing_shape)

        x0, y0 = self.start_x, self.start_y
        x1, y1 = event.x, event.y

        if self.shape_type == "line":
            self.drawing_shape = self.canvas.create_line(x0, y0, x1, y1,
                                                        fill=self.current_color,
                                                        width=self.line_width)
        elif self.shape_type == "rectangle":
            self.drawing_shape = self.canvas.create_rectangle(x0, y0, x1, y1,
                                                              outline=self.current_color,
                                                              width=self.line_width)
        elif self.shape_type == "oval":
            self.drawing_shape = self.canvas.create_oval(x0, y0, x1, y1,
                                                         outline=self.current_color,
                                                         width=self.line_width)

    def finalize_shape(self, event):
        """确认最终图形"""
        self.canvas.itemconfig(self.drawing_shape, tags="shape")

    def reset_last_pos(self, event):
        """释放鼠标时重置起始位置"""
        self.last_x = None
        self.last_y = None

    def clear_canvas(self):
        """清除画布上所有内容"""
        self.canvas.delete("all")
        self.shapes_stack.clear()

    def undo_last(self):
        """撤销最后的操作"""
        if self.shapes_stack:
            last_item = self.shapes_stack.pop()
            self.canvas.delete(last_item)


if __name__ == "__main__":
    root = tk.Tk()
    app = DrawApp(root)
    root.mainloop()

注:可以免费领取上面代码,给个关注+收藏+点赞即可。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐