为了520能够博得女友欢心,我设计了一个用来表白的程序,主要用到了tkinter的画布和各种鼠标事件,今天就来分享给大家。(借鉴要谨慎,可不要只用它来糊弄女盆友哦)

程序的设计是用tkinter模拟手机操作,实现手机桌面的展示、滑动和下拉,然后点击指定位置,可以弹出新的内容。其原理是用canvas显示图片,所谓的滑动和点击等等,其实就是图片的更换;下拉则是利用鼠标事件来移动图片,实现和手机下拉相同的效果(不到半屏自动回位,超过半屏自动落下,落下后拖动底端回位)。

桌面是犯罪嫌疑人——偷心贼

下拉的效果:

部分效果如上所示,点击信息、拨号和相册,可以分别弹出新的图片(把表白的内容放在这里);点击home键则是返回(home键是一个显示了图片的button按钮);桌面还可以滑动,滑动后切换成另一张背景图(我本人),然后对应绑定了不同的鼠标事件,点击后会显示与之前不同的图片。(所有图片都是我用自己的手机截图的)

程序逻辑里,最重要的部分就是几个模式的区分,我用flag来定义,具体如下:flag=0是初始状态、点击屏幕其他位置和下拉未到半屏回位后的状态;flag=1是点击屏幕顶端下拉;flag=2是下拉超过半屏后自动落下,和此时再点击其他位置;flag=3是落下后点击屏幕底端回位;flag=5是切换屏幕。

程序中创建了两个canvas,一个用来显示外壳(固定不动),另一个用来显示屏幕(触发各种事件)。在这两个画布中,获取鼠标位置event.x和event.y是分开计的,所以我们只需要用到显示屏幕的画布中的位置。根据信息、拨号和相册的位置,在点击时设置对应的flag值,触发各自的事件(其实就是显示新的图片)。

另外,所有屏幕图片的大小都是400*700,所以桌面的初始位置在(200,350)附近,而下拉图片的初始位置是(200,-350),此时是在画布外的,不会显示出来。触发下拉事件时,它就会跟随鼠标移动。(这也是创建两个canvas的原因,如果只用一个,由于屏幕不是在画布的顶端,因此下拉图片不会被隐藏起来;而如果它属于其中一个画布,但初始位置在画布外,那么就会隐藏)

下面是完整代码和简单注释:

from tkinter import *
import tkinter as tk
from PIL  import Image, ImageTk
import time
import cv2

flag=0  #状态标志
root = Tk()
root.title("手机")
root.geometry("450x840+500+0")
root.overrideredirect(True)
canvas2 = Canvas(root,width=450, height=840,bg='white',highlightthickness=0)
canvas = Canvas(root,width=400, height=700,bg='black',highlightthickness=0)
f=0 #记录之前的flag,当前事件结束后返回前一个状态
canvas2.place(x=0,y=0)  #手机壳
canvas.place(x=24,y=80)  #屏幕

#左键点击home键,清除刚才显示的图片(即返回桌面),同时开始计时
def onLeftButton(event):
    global start
    start=time.time()
    canvas.delete('c')
    
#鼠标左键抬起home键,结束计时,超过3秒则退出程序(关机),否则退回之前的flag
def ButtonUp(event):
    global end,start,flag
    end=time.time()
    flag=f  
    if end-start>=3:
        root.destroy()
#获得鼠标位置,并设置相应的flag
def callback(event):
    global x,y,flag
    
    x = event.x
    y = event.y
    if flag!=2 and 0<y<10:
        flag=1
    if flag==2 and y>680:
        flag=3

#双击“信息”、“拨号”或“相册”的位置,显示新的图片
#两个桌面对应不同的图片,用flag来区分
def DoubleButton(event):
    if flag==5:
        if 30<x<90 and 30<y<90:
            canvas.create_image(200,350,image=var7,tags="c")
        elif 120<x<180 and 600<y<660:
            canvas.create_image(200,350,image=var8,tags="c")
    else:
        if 30<x<90 and 30<y<90:
            canvas.create_image(200,350,image=var3,tags="c")
        elif 120<x<180 and 600<y<660:
            canvas.create_image(200,350,image=var4,tags="c")
        elif 220<x<270 and 600<y<660:
            canvas.create_image(200,350,image=var5,tags="c")

#按住左键并拖动鼠标,显示下拉图片
def onLeftButtonMove(event):
    global a,flag,f   
    if flag==2:
        return
    if flag==0:
        f=5-flag #这里要么是0,要么是5
        return
    if flag==5:
        f=5-flag
        return
    #下拉栏落下后,拖动底端回位
    if flag==3:
        if event.y<=702:
            canvas.delete('a')
            canvas.create_image(200,event.y-350,image=var1,tags="a")       
            return
    #跟随鼠标下拉
    if flag==1 and event.y<=702:
        canvas.delete('a')
        canvas.create_image(200,event.y-350,image=var1,tags="a")
        return
    
#释放左键,触发下拉的动态效果(自动下落或回位)以及两个桌面的切换      
def onLeftButtonUp(event):
    global a,flag  
    if flag==0:
        if abs(event.x-x)>10:
            canvas.create_image((200,350), image=var6,tags='b')
            flag=5
            return
        else:
            return
    if flag==5:
        if abs(event.x-x)>10:
            canvas.create_image((200,350), image=var2,tags='b')
            flag=0
            return
        else:
            return
    if flag==2:
        return
    yy=event.y
    i=0
    if flag==3:
		#不到半屏自动回位
        while yy-i>=-350:
            canvas.delete('a')
            canvas.create_image(200,yy-i-350,image=var1,tags="a")
            i+=1
            root.update_idletasks()  #刷新
            root.update()

        flag=f
        return
    #超过半屏自动落下
    if yy>350:
            while yy+i<=702:
                canvas.delete('a')
                canvas.create_image(200,yy+i-350,image=var1,tags="a")
                i+=1
                root.update_idletasks()  #刷新
                root.update()

            flag=2
            return
    #点击底端,自动回位
    while yy-i>=-350:
                canvas.delete('a')
                canvas.create_image(200,yy-i-350,image=var1,tags="a")
                i+=1
                root.update_idletasks()  #刷新
                root.update()

    flag=f

#存储初始图
load = Image.open("res/0.jpg") #手机壳图片
var= ImageTk.PhotoImage(load) 
load = Image.open("res/1.png")
var1= ImageTk.PhotoImage(load)  
load = Image.open("res/2.jpg")
var2= ImageTk.PhotoImage(load) 
load = Image.open("res/3.jpg")
var3= ImageTk.PhotoImage(load)
load = Image.open("res/4.jpg")
var4= ImageTk.PhotoImage(load)
load = Image.open("res/5.jpg")
var5= ImageTk.PhotoImage(load)
load = Image.open("res/6.jpg")
var6= ImageTk.PhotoImage(load)
load = Image.open("res/7.png")
var7= ImageTk.PhotoImage(load)
load = Image.open("res/8.jpg")
var8= ImageTk.PhotoImage(load)
load = Image.open("res/bt.png")
bt= ImageTk.PhotoImage(load)

canvas.create_image((200,-350), image=var1,tags='a')  #下拉图片的初始位置,此时未进入画布当中
canvas2.create_image((225,430), image=var)  #手机壳
canvas.create_image((200,352), image=var2,tags='b')  #初始桌面
b = Button(root, image = bt, bg='black',activebackground='black')
b.place(x=165,y=800)
b.bind('<Button-1>', onLeftButton)
b.bind('<ButtonRelease-1>', ButtonUp)
canvas.bind('<B1-Motion>', onLeftButtonMove)  #按住并移动左键
canvas.bind('<ButtonRelease-1>', onLeftButtonUp)  #释放左键
canvas.bind('<Double-Button-1>', DoubleButton)  #双击左键
root.bind("<Button-1>",callback)
root.mainloop()

P.S.代码中用到的各种位置都经过了微调,达到最佳效果。

在这篇文章里,我们演示了如何用tkinter实现复杂的鼠标事件,将不同组件的鼠标事件结合起来。赶紧学会这一手,回去哄女朋友吧!