用python实现win10画图程序画选择框、复制、剪切、粘贴及拖动选择框内图形或粘贴后图形到指定位置

34 篇文章 2 订阅
4 篇文章 0 订阅

本文介绍用python实现win10画图程序剪贴功能的方法。win10画图程序本质上是一个位图处理程序。其最主要的功能是画各种图形,包括线、矩形、椭圆(圆)和各种多边形等,并将各种图形保存到位图中。该程序的剪贴功能是把选定区域的位图移到指定区域,一般包括复制、剪切和粘贴功能。首先要用鼠标拖动画矩形作为选定区域,可直接用鼠标拖动该选定区域位图到指定区域;也可用复制功能把该选定区域位图保存,供粘贴使用;剪切和复制功能相同,但要删除选定区域位图,即将选定区域修改为背景色;粘贴功能是把复制、剪切的位图重新放到指定位置,一般是先放到固定位置,然后用鼠标拖粘贴后的图形到指定位置。
要用python实现win10画图程序的剪贴功能,需要创建PIL Image类实例用来记录所画的所有图形的位图,称为总位图。要创建Canvas类实例,用来显示总位图。两者宽、高背景颜色都必须相同。
剪贴功能首先要用鼠标拖动画选择框,作为复制和剪切的区域,这个选择框是一个canvas上的矩形类实例,用selectRec记录其左上角和右下角的坐标,具体实现方法前边博文已经详细介绍过了。程序需用鼠标拖动画选择框和用鼠标拖动图形到指定位置,两者都要响应鼠标左键按下事件、鼠标左键松开事件和鼠标左键被按下时移动鼠标事件,即都要调用鼠标事件处理函数StartMove、StopMove和OnMotion,但两者完成的功能不同,在鼠标事件处理函数中需有不同的语句。因此用一个全局变量state记录这两个状态值,画选择框为’SelRec’,是初始值,即程序运行后首先画选择框,用鼠标拖动图形是’paste’。
当单击复制按钮,执行复制功能,见第35-37条语句。其中image_crop 是一个全局变量,用来保存复制或剪切所得到位图图形。selectRec也是一个全局变量,为复制和剪切的区域。
当单击剪切按钮,执行剪切功能,见第23-33条语句。首先执行复制功能,然后创建一个和被复制位图相同尺寸的位图,背景色为Canvas的背景色,将其粘贴到总位图的被剪切处,即清除剪切处的图形,然后将新的总位图在Canvas上显示。这时选择框内图形应被删除,选择框也要被删除。显然此时复制和剪切按钮应无法使用。
当单击粘贴按钮,执行粘贴功能,见第39-52条语句。因要用鼠标拖动粘贴位图到指定位置,这时不能直接把图形粘贴到总位图中,而是用粘贴位图在canvas上创建一个图形类实例,用鼠标拖动图形类实例到指定位置后,才把图形粘贴到总位图中。此时要进入粘贴状态’paste’,以区别画选择框状态’SelRec’。请注意,单击粘贴按钮事件处理函数有2个参数,默认值都为0。当单击按钮粘贴时,是把图形类实例放在固定位置(10,10),让使用者拖动。但在拖动选择框内图形时,要把图形类实例放在选择框处,即把选择框左上角坐标作为实参。
请注意复制、剪切和粘贴按钮的状态,什么时候可用,什么时候不可用。如选择框不存在或在粘贴状态(state==‘paste’)时,复制和剪切按钮不可用,否则可用。在粘贴状态(state==‘paste’)时粘贴按钮不可用,其它情况都可用。
现在看一下鼠标左键按下事件处理函数StartMove。在state为’paste’时,是处理用鼠标拖动图形程序。如鼠标点击处在图形内,拖动图形,如鼠标点击处在图形外,结束拖动图形,把拖动图形粘贴到总位图中被拖动后所在位置,并在Canvas显示,同时删除canvas上的图形对象和矩形框,令state=‘SelRec’,粘贴按钮可用。而当在state为’SelRec’时,如鼠标点击点在用来复制或剪切的矩形内,表示要拖动选择框中图形到指定位置,要先调用剪切按钮事件处理函数crop(),执行复制功能,并清除选择框中图形;然后调用粘贴按钮事件函数paste(selectRec[0],selectRec[1]),请注意,调用该函数的两个实参,要把图形类实例放在选择框处,看起来图形未发生变化,但实际上,此时已做好了被拖动的准备,令state==‘paste’,再次进入鼠标左键按下事件处理函数StartMove后,要执行被拖动语句。如鼠标点击点在用来复制或剪切的矩形外,则完成画选择框功能。
鼠标移动事件处理函数changeCursor(),是为了改变鼠标光标形状,鼠标在被拖动图形内或在选择框内,光标为通用的表示移动的光标,否则为默认光标。
win10画图程序剪贴有些功能本程序未实现。第一,选择框是闪动的,这可以在另一线程中每隔0.5秒发一事件,在事件处理函数中,改变选择框断续线设置达到动画效果。第二,选择框可被拖动改变尺寸,即鼠标移到选择框上下左右边,光标变为上下或左右箭头,按下左键并上下或左右移动,可拖动边线移动,鼠标移到选择框的4个顶点,鼠标改变,按下左键并移动,可拖动选择框顶点移动,从而改变选择框尺寸。实现方法是在选择框每边中点和四个角放置小矩形,有不同tag值,通过为不同tag值绑定鼠标移动事件,在其事件处理函数可改变光标形状;为不同tag值绑定鼠标左键按下并移动事件,在其事件处理函数中完成改变选择框尺寸的功能。
通过前边及本篇博文,可以实现win10画图程序大部分功能。有些功能虽然未提到,使用PIL大部分都能实现。例如存取位图文件、橡皮、调整大小、旋转、画任意曲线、填充、取色、添加字符等,有兴趣可以试一试。而用博文“用方法create_bitmap在Canvas上生成xbm文件图像创建win10风格工具栏”中的方法可设计出和win10类似的工具栏。
用python实现win10画图程序的剪贴功能的完整程序如下:

import tkinter as tk
from PIL import Image,ImageTk,ImageDraw
root = tk.Tk()
root.geometry('400x350')
cv = tk.Canvas(root, width=400, height=300, bg='white')
cv.pack()

image1=Image.new("RGB",(400, 300),'white')#总位图和cv宽、高和背景色必须相同,用来记录所画图形
img = ImageTk.PhotoImage(image=image1)
mainImage=cv.create_image(200,150,image=img)    #在cv上显示image1,此时image1为空(白色)

draw = ImageDraw.Draw(image1)                       #将用draw在image1上画图
draw.line([220, 20, 280, 80], fill='red',width=3)   #画线
draw.ellipse((100,100, 200, 200), fill ='red',outline='green')  #画圆

img = ImageTk.PhotoImage(image=image1)
cv.itemconfig(mainImage,image=img)               #在cv上显示所画的圆和线   

selectRec=(0,0,0,0)               #用来复制和剪切的矩形。下句image_crop记录复制或剪切得到的位图
image_crop=Image.new("RGB", (0, 0), 'white')    #初始宽和高都为0,粘贴时判断宽和高都为0,不粘贴
state='SelRec'    #画或已画完矩形用来复制和剪切,另一状态为'paste',粘贴后用鼠标移动黏贴的图形

def crop():     #剪切按钮的事件处理函数
    global image_crop,selectRec,img,state
    image_crop =image1.crop(box=selectRec)      #复制selectRec指定矩形内的图形
    image2=Image.new("RGB",(image_crop.width,image_crop.height),'white') #建宽高和复制图形相同位图  
    image1.paste(image2,selectRec)              #将image2粘贴到image1的被剪切处,即清除剪切处的图形
    img = ImageTk.PhotoImage(image=image1)      #显示清除剪切处的图形后的图形
    cv.itemconfig(mainImage,image=img)           #img必须是全局变量
    cv.delete('S')                              #删除用来复制和剪切的矩形
    but1.config(state="disabled")               #因无复制和剪切的矩形,复制和剪切按钮不能使用
    but2.config(state="disabled")
    state='SelRec'
    
def copy():                         #复制按钮的事件处理函数
    global image_crop,selectRec
    image_crop =image1.crop(box=selectRec)    
    
def paste(x=10,y=10):               #粘贴按钮的事件处理函数
    global img,img1,state
    if image_crop.width==0 and image_crop.height==0:    #判断宽和高都为0,初始状态,不粘贴
        return
    cv.delete('S')                      #删除用来复制或剪切的矩形
    but1.config(state="disabled")       #复制按钮不能使用
    but2.config(state="disabled")       #剪切按钮不能使用
    but3.config(state="disabled")       #粘贴按钮不能使用
    image2 = Image.new("RGB", (image_crop.width, image_crop.height), 'white')#建新位图,image_crop是复制的位图
    image2.paste(image_crop,(0,0,image_crop.width,image_crop.height))        #将复制的位图复制到新位图
    img1 = ImageTk.PhotoImage(image=image2)
    cv.create_image(x,y,image=img1,anchor="nw",tags=('S')) #在Canvas上创建位图对象方便移动,下句矩形显示复制的位图外轮廓
    cv.create_rectangle(x,y,x+image_crop.width,y+image_crop.height,tags=('S','S1'),dash=(3,5))   #注意tags=('S')
    state='paste'           #进入粘贴状态    
    
def StartMove(event):
    global first_x,first_y,selectRec,state,img       
    if state=='paste':               #此状态是拖动图形,鼠标点击处在图形内,拖动图形,在图形外,结束拖动
        x1,y1,x2,y2=cv.bbox('S1')    #得到被拖动图形所在矩形左上角右下角坐标,下句判断单击点是否在该矩形内  
        if event.x>min(x1,x2) and event.x<max(x1,x2) and event.y>min(y1,y2) and event.y<max(y1,y2):
            first_x,first_y = event.x,event.y    #如在矩形内,拖动图形,否则,结束拖动        
        else:       #结束拖动图形,把拖动图形粘贴到被拖动位置,并在Canvas显示
            image1.paste(image_crop,(x1,y1,x1+image_crop.width,y1+image_crop.height))
            img = ImageTk.PhotoImage(image=image1)
            mainImage=cv.create_image(200,150,image=img)
            cv.delete('S')      #删除canvas上的图形对象和矩形框
            state='SelRec'      #回到画用来复制或剪切的矩形状态
            selectRec=(0,0,0,0)
            but3.config(state="normal")

    if state=='SelRec':     #在此状态下,如鼠标点击点在用来复制或剪切的矩形内,拖动该图形
        x1,y1,x2,y2=selectRec
        if event.x>min(x1,x2) and event.x<max(x1,x2) and event.y>min(y1,y2) and event.y<max(y1,y2):
            crop()
            paste(selectRec[0],selectRec[1])
            first_x,first_y = event.x,event.y            
        else:               #否则,在另一处画用来复制或剪切的矩形
            selectRec=(0,0,0,0)
            cv.delete('S')
            first_x,first_y = event.x,event.y
        
def StopMove(event):
    global first_x,first_y,selectRec,state
    if state=='SelRec':
        cv.delete('S')    
        cv.create_rectangle(first_x,first_y,event.x,event.y,tags=('S'),dash=(3,5))
        selectRec=(first_x,first_y,event.x,event.y)
        if ((abs(event.x-first_x)+abs(event.y-first_y))<6):     #如用来复制或剪切的矩形太小
            cv.delete('S')                                      #删除这个矩形
            selectRec=(0,0,0,0)
            but1.config(state="disabled")
            but2.config(state="disabled")
        else:                                                   #否则运行复制或剪切
            but1.config(state="normal")         #but1['state']="disabled"也可
            but2.config(state="normal")
            
def OnMotion(event): 
    global first_x,first_y,selectRec
    if state=='paste':
        cv.move('S',event.x-first_x,event.y-first_y)
        first_x,first_y = event.x,event.y
    if state=='SelRec':
        cv.delete('S')
        cv.create_rectangle(first_x,first_y,event.x,event.y,tags=('S'),dash=(3,5))

def changeCursor(event):            #改变鼠标光标形状
    if state=='paste':
        x1,y1,x2,y2=cv.bbox('S1')   #下句鼠标在被拖动图形内,光标为通用的表示移动的光标
        if event.x>min(x1,x2) and event.x<max(x1,x2) and event.y>min(y1,y2) and event.y<max(y1,y2):
            cv.config(cursor="fleur")
        else:
            cv.config(cursor="")        #恢复默认光标
    if but1['state']!="disabled":       #如复制按钮可用,表示选择框存在
        x1,y1,x2,y2=selectRec           #鼠标在选择框内,光标为通用的表示移动的光标
        if event.x>min(x1,x2) and event.x<max(x1,x2) and event.y>min(y1,y2) and event.y<max(y1,y2):
            cv.config(cursor="fleur")
        else:
            cv.config(cursor="")

but1=tk.Button(root,command=copy,text='复制',state="disabled")
but1.pack(side="left")
but2=tk.Button(root,command=crop,text='剪切',state="disabled")
but2.pack(side="left")
but3=tk.Button(root,command=paste,text='粘贴')
but3.pack(side="left")                  #,cursor='sb_v_double_arrow'

cv.bind("<ButtonPress-1>",StartMove)    #绑定鼠标左键按下事件
cv.bind("<ButtonRelease-1>",StopMove)   #绑定鼠标左键松开事件
cv.bind("<B1-Motion>", OnMotion)        #绑定鼠标左键被按下时移动鼠标事件
cv.bind("<Motion>",changeCursor)        #绑定鼠标移动事件
root.mainloop()
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值