tkinter Canvas 實現 手繪畫板 功能

下面代碼簡單實現了手繪畫板功能,其實也是Canvas的教程,後面都給加了註釋。
這裏通過列表來存儲筆畫,實現撤銷恢復功能,右鍵有菜單。

截圖

import tkinter as tk
from tkinter import ttk

class tkinter_example(object):
    def __init__(s):
        s.win = tk.Tk()               # 創建主窗口
        s.win.title("tkinter Canvas") # 窗口標題
        s.win.withdraw()              # 隱藏窗口

        s.win.update_idletasks()      # 刷新窗口
        s.width, s.height = 600,400   #獲取此時窗口大小
        s.Canvas()                    # 添加Canvas界面

        #窗口位置居中
        s.win.geometry('%dx%d+%d+%d' % (s.width, s.height, 
                      (s.win.winfo_screenwidth()  - s.width)/2,
                      (s.win.winfo_screenheight() - s.height)/2))

        s.win.resizable(0,0)     # 阻止GUI大小調整
        s.win.deiconify()        # 顯示窗口
        s.win.mainloop()         # 顯示主窗口

    def Canvas(s):
        #新建畫布界面
        canvas = tk.Canvas(s.win, width=s.width, height=s.height, highlightthickness=0, bg='#AFEEEE')  
        canvas.grid()
        #鼠標中鍵滾動事件
        canvas.bind("<MouseWheel>", lambda event: print("向上滾動") if event.delta > 0 else print("向下滾動"))
        #指定tag點擊事件響應
        canvas.tag_bind('other','<Button-1>', lambda event: print("other 點擊相應"))

        #畫一條直線提供所要繪製的直線連接的兩個點座標
        canvas.create_line(0, 30, s.width, 30, fill="#476042", dash = (4, 4))  #加了dash就是虛線,不加就是實線

        #畫一個矩形,提供兩個點的座標: 第一個點爲左上角座標, 第二個點爲右下角座標, outline邊框顏色,fill填充顏色
        canvas.create_rectangle(5, 5, 45, 25, outline="#476042", fill="#476042", tags = "other")

        #寫文字,文字將以此座標爲中心進行繪製,也寫 anchor 屬性來改變文字繪製的對齊方式. 比如:anchor = 'nw'
        canvas.create_text(25, 15, text="Python", fill="#ffffff")

        #畫一個橢圓,提供橢圓外切矩形兩個頂點,同畫矩形
        canvas.create_oval(50, 5, 100, 25, fill="#476042", tags = "other")

        #畫一個正圓,提供正圓外切正方形兩個頂點,同畫矩形
        canvas.create_oval(105, 5, 125, 25, fill="#476042", tags = "other")

        #繪製多邊形
        points1 = [130, 25, 160, 25, 145,  5]#三角形
        canvas.create_polygon(points1, outline="#ff0000", fill='#ff0000', width=1, tags = "other")
  
        #繪製圖片,貼圖(這裏的貼圖必須是 全局 或者和 mainloop在同一個函數下,否則會被清除導致不顯示)
        s.image = tk.PhotoImage(file = tk.__file__.split("tkinter")[0] + 'test\\imghdrdata\\python.gif')
        canvas.create_image(180, 10, anchor = 'nw', image = s.image, tags = "other")

        #畫弧線(座標,start = 開始方向角,extent = 結束方向角)
        canvas.create_arc((200, 5, 245, 50), start = 0, extent = 120, fill = "blue", tags = "other")

        #繪製Bitmap
        bitmaps = ["error", "gray75", "gray50", "gray25", "gray12", "hourglass", "info", "questhead", "question", "warning"]
        nsteps = len(bitmaps)
        step_x = int((s.width-250) / nsteps)
        for i in range(0, nsteps):
           canvas.create_bitmap(250 + (i+1)*step_x - step_x/2,15, bitmap=bitmaps[i])

        #創建一個可在 canvas 上手動繪圖的效果,通過兩點畫線段的方式
        draw_point = ['', '']  #用於儲存拖拉鼠標時的點
        revoke     = [ ] #用於儲存每次鼠標繪圖操作的ID供撤銷用[[...],[...],[...]]
        recover    = [ ] #用於儲存每次鼠標繪圖的點構成的列表供恢復
        clear      = [ ] #用於記錄是否使用過清空,因爲列表可變,支持全局修改,所以用列表記錄
        def _canvas_draw(event):
            if not event: #鬆開鼠標左鍵時執行,清空記錄點
                draw_point[:] = ['','']  #[:]只改變draw_point指向的列表的內容,不是重新賦值一個新的列表所以修改值全局通用
                return
            point = [event.x, event.y]   #此次傳遞的點座標
            if draw_point==['','']:      #按下鼠標左鍵開始拖動時執行
                draw_point[:] = point    #保存拖動的第一個點
                if len(revoke) < len(recover):
                    recover[len(revoke):] = [] #用於使用過撤銷後再繪圖,清除撤銷點後的恢復數據
                clear[:] = []
                revoke.append([])        #新建一個撤銷記錄列表
                recover.append([])       #新建一個恢復記錄列表
                recover[-1].extend(point)#在新建的恢復記錄列表裏記錄第一個點
            else:
                revoke[-1].append(
                    canvas.create_line(draw_point[0], draw_point[1], event.x, event.y, fill="#476042", width=1,
                    tags = "line")
                          )      #繪製的線段並保存到撤銷記錄的末次列表
                draw_point[:] = point    #保存拖動點,覆蓋上一次
                recover[-1].extend(point)#保存此次傳遞的點座標到恢復記錄的末次列表
        canvas.bind("<B1-Motion>", _canvas_draw) #設定拖動鼠標左鍵畫線段
        canvas.bind("<ButtonRelease-1>", lambda event:_canvas_draw(0)) #設定鬆開鼠標左鍵清除保存的點

        #添加撤銷和恢復功能rev撤銷,rec恢復
        def _canvas_re(rev=0, rec=0):
            if rev and revoke: #撤銷執行
                for i in revoke.pop(-1): canvas.delete(i) #pop彈出最後一個撤銷列表,刪除圖像
            elif rec and recover and (len(revoke) != len(recover)): #恢復執行,恢復列表需要大於撤銷列表
                if clear:
                    for i in recover: revoke.append([canvas.create_line(i , fill="#476042", width=1, tags = "line")])
                    clear[:] = []
                else:
                    revoke.append([canvas.create_line(recover[len(revoke)], fill="#476042", width=1, tags = "line")])

        #清空功能
        def _canvas_clear():
            canvas.delete("line") #清除 tags = "line"的圖像
            revoke[:] = []
            clear.append(1)

        #添加右鍵菜單
        menu = tk.Menu(s.win, tearoff=0)    #不加 tearoff=0 的會出現可彈出選項
        menu.add_command(label="撤銷", command = lambda:_canvas_re(rev=1))
        menu.add_command(label="恢復", command = lambda:_canvas_re(rec=1))
        menu.add_command(label="清空", command = _canvas_clear)
        canvas.bind("<Button-3>", lambda event: menu.post(event.x_root,event.y_root))#右鍵激活菜單

        # 創建一個Button對象,默認設置爲居中對齊
        bt1 = ttk.Button(canvas,text = '撤銷',command = lambda:_canvas_re(rev=1))
        #修改button在canvas上的對齊方式
        canvas.create_window((5, s.height-20), window = bt1, anchor = 'w')
        bt2 = ttk.Button(canvas,text = '恢復',command = lambda:_canvas_re(rec=1))
        canvas.create_window((s.width-90, s.height-20), window = bt2, anchor = 'w')
        bt3 = ttk.Button(canvas,text = "清空", command = _canvas_clear)
        canvas.create_window((s.width/2-43, s.height-20), window = bt3, anchor = 'w')

tkinter_example()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章