当前位置: 首页 > news >正文

lesson26-2:使用Tkinter打造简易画图软件优化版

目录

前言 

功能概览

核心代码解析

程序架构设计

菜单系统实现

画布与绘图核心

数据持久化实现

使用指南

基本操作流程

状态提示

功能扩展建议

总结

整体预览及解析

一、整体结构(鸟瞰图)

二、成员变量一览(大脑地图)

三、四大私有初始化函数

1️⃣ __init_menu() —— 菜单树构建

2️⃣ __init_canvas() —— 画布

3️⃣ __init_event() —— 事件绑定

4️⃣ __init_status() —— 状态栏

四、三大鼠标事件(绘图核心)

🔹 __mouse_down(e)

🔹 __mouse_move(e)

🔹 __mouse_up(e)

五、撤销 & 清空

🔸 __undo()

🔸 __clear()

六、保存 & 加载(持久化)

🔸 __save_data()

🔸 __load_data()

七、数据流向图(一图胜千言)


前言 

在GUI编程的世界里,Tkinter作为Python自带的标准库,以其简洁易用的特点成为入门者的首选。本文将深入解析一段基于Tkinter开发的简易画图软件代码,带你了解如何从零开始构建一个具备基本绘图功能的桌面应用。该软件支持直线、矩形、椭圆等基本图形绘制,提供颜色选择、文件保存/加载、撤销/清空等核心功能,适合作为Tkinter事件处理与图形编程的学习案例。

功能概览

这款画图软件的核心功能围绕"绘制-编辑-保存"的工作流设计,主要包含以下模块:

  • 图形绘制系统:支持直线、矩形、椭圆三种基础图形,通过鼠标拖拽实现动态绘制
  • 颜色管理:内置红绿蓝三色快速选择及自定义颜色拾取器
  • 文件操作:采用pickle序列化实现图形数据的保存(.zzy格式)与加载
  • 编辑工具:提供撤销(单步)和清空画布功能
  • 交互反馈:状态栏实时显示当前操作状态,提升用户体验

核心代码解析

程序架构设计

代码采用面向对象思想,通过Draw类封装所有功能,主要包含以下组件初始化方法:

class Draw:
def __init__(self):
self.__root = tkinter.Tk()
self.__root.title('画图画画图')
self.__init_menu() # 初始化菜单栏
self.__init_canvas() # 初始化画布
self.__init_event() # 绑定事件处理
self.__init_status() # 初始化状态栏# 核心状态变量
self.__current_select_shape = None # 当前选择图形
self.__current_select_color = "black" # 当前选择颜色
self.__start_x, self.__start_y = None, None # 鼠标起始坐标
self.__current_shape_id = None # 当前绘制图形ID
self.__shape_datas = [] # 存储所有图形数据

菜单系统实现

菜单栏采用层级结构设计,通过tkinter.Menu实现:

def __init_menu(self):
self.__main_menu = tkinter.Menu(master=self.__root)# 文件菜单:包含打开/保存功能
self.__file_menu = tkinter.Menu(master=self.__main_menu, tearoff=False)
self.__file_menu.add_command(label="打开", command=self.__load_data)
self.__file_menu.add_command(label="保存", command=self.__save_data)# 图形菜单:选择绘制图形类型
self.__shape_menu = tkinter.Menu(master=self.__main_menu, tearoff=False)
self.__shape_menu.add_command(label="直线", command=lambda: self.__set_current_select_shape(LINE))
# 矩形、椭圆菜单项类似...# 颜色菜单:预设颜色+自定义选择
self.__color_menu = tkinter.Menu(master=self.__main_menu, tearoff=False)
self.__color_menu.add_command(label="红色", command=lambda: self.__set_current_select_color("#FF0000"))
# 绿色、蓝色及自定义颜色项...# 其他菜单:撤销/清空/退出
self.__other_menu = tkinter.Menu(master=self.__main_menu, tearoff=False)
# 菜单项添加...self.__root.config(menu=self.__main_menu)

画布与绘图核心

画布组件是绘图功能的核心载体,通过绑定鼠标事件实现交互绘制:

def __init_canvas(self):
self.__canvas = tkinter.Canvas(width=600, height=400, background="lightgray")
self.__canvas.pack(fill="both", expand=True)# 鼠标事件处理
def __mouse_down(self, e):
if self.__current_select_shape is not None:
self.__start_x = e.x
self.__start_y = e.y
# 根据选择的图形类型创建初始图形
if self.__current_select_shape == LINE:
self.__current_shape_id = self.__canvas.create_line(
self.__start_x, self.__start_y, e.x, e.y, fill=self.__current_select_color)def __mouse_move(self, e):
# 拖拽鼠标时更新图形坐标
if self.__current_select_shape is not None:
self.__canvas.coords(self.__current_shape_id, self.__start_x, self.__start_y, e.x, e.y)def __mouse_up(self, e):
# 释放鼠标时保存图形数据
if self.__current_select_shape is not None:
self.__shape_datas.append(
(self.__current_shape_id, self.__current_select_shape, 
self.__start_x, self.__start_y, e.x, e.y, self.__current_select_color))

数据持久化实现

通过pickle模块实现图形数据的序列化与反序列化:

def __save_data(self):
file_path = filedialog.asksaveasfilename(
title='保存为', defaultextension='.zzy', filetypes=[('新画图', '*.zzy')])
if file_path:
with open(file_path, "wb") as f:
pickle.dump(self.__shape_datas, f)
messagebox.showinfo("操作", "保存成功")def __load_data(self):
file_path = filedialog.askopenfilename(filetypes=[('新画图', '*.zzy')])
if file_path:
self.__clear()
with open(file_path, "rb") as f:
self.__shape_datas = pickle.load(f)
# 重新绘制加载的图形
for data in self.__shape_datas:
if data[1] == LINE:
self.__canvas.create_line(data[2], data[3], data[4], data[5], fill=data[-1])
# 矩形、椭圆绘制类似...

使用指南

基本操作流程

  1. 选择图形:从"图形"菜单中选择直线、矩形或椭圆
  2. 选择颜色:通过"颜色"菜单选择预设颜色或自定义颜色
  3. 绘制图形:在画布上按住鼠标左键拖拽,释放后完成绘制
  4. 文件操作:通过"文件"菜单保存(.zzy格式)或加载绘图文件
  5. 编辑操作:使用"其他"菜单中的撤销(单步)和清空功能

状态提示

状态栏会实时显示当前操作状态,例如:

  • "选择直线,点击开始绘制"
  • "选择矩形,点击开始绘制"
  • "选择图形,开始绘制"(未选择图形时)

功能扩展建议

该简易画图软件可从以下方向进行功能扩展:

  1. 图形扩展:添加圆形、多边形、文本等更多图形元素
  2. 样式增强:支持线条粗细调整、填充颜色、虚线样式等
  3. 编辑功能:实现图形移动、缩放、旋转,多级撤销/重做
  4. 快捷键支持:为常用操作添加键盘快捷键
  5. 图像导出:支持导出为PNG/JPG等通用图像格式
  6. 图层管理:实现图形分层绘制与管理

总结

本文通过解析基于Tkinter的简易画图软件代码,展示了如何利用Python的标准GUI库实现基本的图形绘制功能。该案例涵盖了Tkinter的核心知识点,包括窗口组件布局、事件绑定、画布绘图、菜单系统及文件操作等。通过学习这段代码,不仅可以掌握Tkinter的基本使用方法,还能理解GUI应用的事件驱动编程思想。

对于Python初学者来说,这个项目是一个很好的实践案例,既有趣味性又能锻炼编程能力。你可以基于此代码进行二次开发,逐步添加更复杂的功能,最终打造属于自己的完整画图应用。

# 程序入口
if __name__ == "__main__":
draw = Draw()
draw.main_loop()

通过运行上述代码,你将获得一个功能完整的简易画图软件 

整体预览及解析

"""
画图软件功能:菜单文件打开保存图形直线矩形椭圆...颜色红色绿色蓝色自定义其他撤销清除退出画布状态栏
"""import tkinter
from tkinter import colorchooser
from tkinter import filedialog
from tkinter import messagebox
import pickleLINE = "line"
RECT = "rect"
OVAL = "oval"class Draw:def __init__(self):self.__root = tkinter.Tk()self.__root.title('画图画画图')self.__init_menu()self.__init_canvas()self.__init_event()self.__init_status()self.__current_select_shape = Noneself.__current_select_color = "black"self.__start_x = Noneself.__start_y = Noneself.__current_shape_id = Noneself.__shape_datas = []def __save_data(self):file_path = filedialog.asksaveasfilename(title='保存为',defaultextension='.zzy',filetypes=[('新画图', '*.zzy')])if file_path:with open(file_path, "wb") as f:pickle.dump(self.__shape_datas, f)messagebox.showinfo("操作", "保存成功")def __load_data(self):file_path = filedialog.askopenfilename(title='请选择文件',filetypes=[('新画图', '*.zzy')])if file_path:self.__clear()with open(file_path, "rb") as f:self.__shape_datas = pickle.load(f)for data in self.__shape_datas:if data[1] == LINE:self.__current_shape_id = self.__canvas.create_line(data[2], data[3], data[4], data[5],fill=data[-1])elif data[1] == RECT:self.__current_shape_id = self.__canvas.create_rectangle(data[2], data[3], data[4], data[5],outline=data[-1])elif data[1] == OVAL:self.__current_shape_id = self.__canvas.create_oval(data[2], data[3], data[4], data[5],outline=data[-1])def __set_current_select_shape(self, shape):self.__current_select_shape = shapeif shape is None:self.__status.config(text="选择图形,开始绘制")elif shape == LINE:self.__status.config(text="选择直线,点击开始绘制")elif shape == RECT:self.__status.config(text="选择矩形,点击开始绘制")elif shape == OVAL:self.__status.config(text="选择椭圆,点击开始绘制")def __set_current_select_color(self, color):self.__current_select_color = colorprint(f"当前选择颜色{self.__current_select_color}")def __choose_color(self):result = colorchooser.askcolor()if result[1]:self.__set_current_select_color(result[1])def __init_menu(self):self.__main_menu = tkinter.Menu(master=self.__root)self.__file_menu = tkinter.Menu(master=self.__main_menu, tearoff=False)self.__file_menu.add_command(label="打开", command=self.__load_data)self.__file_menu.add_command(label="保存", command=self.__save_data)self.__main_menu.add_cascade(menu=self.__file_menu, label="文件")self.__shape_menu = tkinter.Menu(master=self.__main_menu, tearoff=False)self.__shape_menu.add_command(label="直线", command=lambda: self.__set_current_select_shape(LINE))self.__shape_menu.add_command(label="矩形", command=lambda: self.__set_current_select_shape(RECT))self.__shape_menu.add_command(label="椭圆", command=lambda: self.__set_current_select_shape(OVAL))self.__main_menu.add_cascade(menu=self.__shape_menu, label="图形")self.__color_menu = tkinter.Menu(master=self.__main_menu, tearoff=False)self.__color_menu.add_command(label="红色", command=lambda: self.__set_current_select_color("#FF0000"))self.__color_menu.add_command(label="绿色", command=lambda: self.__set_current_select_color("#00FF00"))self.__color_menu.add_command(label="蓝色", command=lambda: self.__set_current_select_color("#0000FF"))self.__color_menu.add_separator()self.__color_menu.add_command(label="自定义", command=self.__choose_color)self.__main_menu.add_cascade(menu=self.__color_menu, label="颜色")self.__other_menu = tkinter.Menu(master=self.__main_menu, tearoff=False)self.__other_menu.add_command(label="撤销", command=self.__undo)self.__other_menu.add_command(label="清空", command=self.__clear)self.__other_menu.add_command(label="退出", command=self.__root.destroy)self.__main_menu.add_cascade(menu=self.__other_menu, label="其他")self.__root.config(menu=self.__main_menu)def __init_canvas(self):self.__canvas = tkinter.Canvas(width=600, height=400, background="lightgray")self.__canvas.pack(fill="both", expand=True)def __mouse_down(self, e):if self.__current_select_shape is not None:self.__start_x = e.xself.__start_y = e.yif self.__current_select_shape == LINE:self.__current_shape_id = self.__canvas.create_line(self.__start_x, self.__start_y, e.x, e.y,fill=self.__current_select_color)elif self.__current_select_shape == RECT:self.__current_shape_id = self.__canvas.create_rectangle(self.__start_x, self.__start_y, e.x, e.y,outline=self.__current_select_color)elif self.__current_select_shape == OVAL:self.__current_shape_id = self.__canvas.create_oval(self.__start_x, self.__start_y, e.x, e.y,outline=self.__current_select_color)def __mouse_move(self, e):if self.__current_select_shape is not None:self.__canvas.coords(self.__current_shape_id, self.__start_x, self.__start_y, e.x, e.y)def __mouse_up(self, e):if self.__current_select_shape is not None and self.__start_x is not None and self.__start_y is not None:self.__shape_datas.append((self.__current_shape_id, self.__current_select_shape, self.__start_x, self.__start_y, e.x, e.y,self.__current_select_color))print(self.__shape_datas)self.__start_x = Noneself.__start_y = Noneself.__current_shape_id = Nonedef __undo(self):if self.__shape_datas:last_shape = self.__shape_datas.pop()last_shape_id = last_shape[0]self.__canvas.delete(last_shape_id)print(self.__shape_datas)def __clear(self):self.__shape_datas.clear()self.__canvas.delete("all")def __init_event(self):self.__canvas.bind("<Button-1>", func=self.__mouse_down)self.__canvas.bind("<B1-Motion>", func=self.__mouse_move)self.__canvas.bind("<ButtonRelease-1>", func=self.__mouse_up)self.__canvas.bind("<ButtonRelease-3>", func=lambda e: self.__set_current_select_shape(None))def __init_status(self):self.__status = tkinter.Label(master=self.__root, text="选择图形,开始绘制")self.__status.pack(side="left")def main_loop(self):self.__root.mainloop()draw = Draw()
draw.main_loop()

一、整体结构(鸟瞰图)

模块作用
常量区 (LINE/RECT/OVAL)用字符串标识当前要画的形状,避免魔法字符串。
类 Draw唯一顶层类,把窗口、菜单、画布、状态栏、事件、数据全部包在一起。
私有方法 __xxx以双下划线开头,表示只在类内部使用,外部不应调用。

二、成员变量一览(大脑地图)

变量类型/初始值作用
__roottk.Tk()根窗口
__canvastk.Canvas(...)600×400 的画布,真正画图形的地方
__current_select_shapeNone"line"/"rect"/"oval"当前选中的形状
__current_select_color"black"当前选中的颜色
__start_x/yNoneint鼠标按下时的坐标
__current_shape_idNoneint正在绘制的图形的 canvas id
__shape_datas[]核心数据仓库,保存所有已绘制图形的完整信息(用于撤销/保存/加载)

三、四大私有初始化函数

1️⃣ __init_menu() —— 菜单树构建

  • 文件

    • 打开 → __load_data()

    • 保存 → __save_data()

  • 图形

    • 直线/矩形/椭圆 → 只改 __current_select_shape

  • 颜色

    • 红/绿/蓝 + 自定义 → 只改 __current_select_color

  • 其他

    • 撤销 → __undo()

    • 清空 → __clear()

    • 退出 → root.destroy

tearoff=False 禁用可撕拉菜单,防止界面分离。


2️⃣ __init_canvas() —— 画布

  • 宽高 600×400,背景浅灰。

  • pack(fill="both", expand=True) 让画布随窗口一起拉伸。


3️⃣ __init_event() —— 事件绑定

事件回调说明
<Button-1>__mouse_down左键按下:开始画
<B1-Motion>__mouse_move左键拖动:实时预览
<ButtonRelease-1>__mouse_up左键松开:完成图形并记录
<ButtonRelease-3>lambda右键松开:取消当前形状选择

4️⃣ __init_status() —— 状态栏

最底部放一条 Label,提示用户当前选中了什么形状/颜色。


四、三大鼠标事件(绘图核心)

🔹 __mouse_down(e)

  • 如果已选中形状(非 None):

    • 记录起点 (start_x, start_y)

    • 立刻在画布上 创建一条长度为 0 的图形(直线/矩形/椭圆),得到 __current_shape_id

    • 颜色使用 __current_select_color

🔹 __mouse_move(e)

  • 只要鼠标移动,就用 canvas.coords(...) 实时改变刚才那条图形的终点坐标,实现“橡皮筋”效果。

🔹 __mouse_up(e)

  • 图形最终定型,把一条完整记录塞进 __shape_datas
    (id, 形状常量, x0, y0, x1, y1, 颜色)

  • 立刻打印到终端方便调试。

  • 清空 start_x/start_y/current_shape_id,为下一次绘制做准备。


五、撤销 & 清空

🔸 __undo()

  • __shape_datas 弹出最后一条记录 → 拿到 idcanvas.delete(id)

  • 真正删除了画布上的图形,也删除了内存里的记录。

  • 再次打印剩余数据,方便验证。

🔸 __clear()

  • 直接 canvas.delete("all") 清空画布

  • __shape_datas.clear() 把列表也清空 → 撤销栈归零。


六、保存 & 加载(持久化)

🔸 __save_data()

  • filedialog.asksaveasfilename 弹出保存窗口,默认扩展名 .zzy(自定义二进制 pickle)。

  • __shape_datas 原封不动 pickle 到文件。

  • 成功后用 messagebox.showinfo 弹窗提示。

🔸 __load_data()

  • 弹窗让用户选 .zzy 文件 → 反序列化得到 __shape_datas

  • __clear() 清空画布和列表,防止旧数据残留。

  • 遍历列表,根据每条记录的 (形状, x0, y0, x1, y1, 颜色) 重新在画布上画一遍,实现“打开”功能。


七、数据流向图(一图胜千言)

用户操作│├─菜单:选择形状/颜色 → 修改 __current_select_shape / __current_select_color│├─鼠标:按下 → 创建图形 id│         ││         └─移动 → 实时更新坐标│                  ││                  └─松开 → 记录到 __shape_datas│├─撤销 → 弹出 & delete│└─保存 → pickle.dump(__shape_datas)│└─加载 → pickle.load → 重新绘制

 

http://www.dtcms.com/a/302538.html

相关文章:

  • 深入解析MIPI C-PHY (五) MIPI C-PHY 与 A-PHY 的对比分析
  • 重生之我在暑假学习微服务第三天《Docker-上篇》
  • 【Unity笔记】Unity Camera.cullingMask 使用指南:Layer 精准控制、XR 多视图与性能提升
  • ERC20 和 XCM Precompile|详解背后技术逻辑
  • 学习Python中Selenium模块的基本用法(2:下载浏览器驱动)
  • js的学习2
  • JavaScript:数组常用操作方法的总结表格
  • Webhook技术深度解析:从原理到实现全指南
  • Item17:以独立语句将newed对象置入智能指针
  • MDM五十万台设备高并发场景解决方案【后台管理】
  • Taro 位置相关 API 介绍
  • C# 状态机以及状态机编程模式
  • Java设计模式-通俗举例
  • 【智慧物联网平台】编译jar环境 Linux 系统Maven 安装——仙盟创梦IDE
  • Leaflet 综合案例-聚类图层控制
  • django ManyToManyField 如何添加数据
  • Django缓存机制详解:从配置到实战应用
  • MGRE 实验
  • Django 视图详解(View):处理请求与返回响应的核心
  • Linux IPC实战:管道与命名管道的进程对话术
  • 语音识别数据增强
  • llama系列
  • 1688寻源通接口接入要点||电商API接口
  • 电脑ip地址在哪里看
  • 如何提升 TCP 传输数据的性能?详解
  • 信息收集工具ARL资产侦察灯塔系统搭建教程
  • 最新的前端技术和趋势(2025)
  • STM32启动流程
  • 防水医用无人机市场报告:现状、趋势与洞察
  • 无人机喷洒系统技术要点与难点解析