在定义类时将事件绑定到类的方法并用该方法调用类外部函数且将属性作为实参

在编写扫雷游戏时,可用按钮组成矩阵。鼠标右击、双击和左击按钮事件都要有自己的事件函数。例如右击某按钮,该按钮显示红旗。不可能为每一按钮都编写一个右击按钮事件函数,一般只编写一个函数,其参数是行列号,通过行列号找到该按钮的引用地址,让指定按钮显示红旗。博文“用python tkinter组件实现扫雷游戏”就是采用这个思路。以下代码说明了该游戏具体实现方法,当左击(右击)按钮,label组件显示:鼠标左(右)键单击[行号][列号]。

from tkinter import * 
def do_job1(evt,x,y):       #鼠标右键单击事件函数
    label1['text']='鼠标右键单击'+ str(x)+str(y)
def do_job(x,y):            #鼠标左键单击事件函数,参数是lambda表达式传递的行列号
    label1['text']='鼠标左键单击'+str(x)+str(y)              
root = Tk()
root.title('扫雷')
root.geometry("200x200") 
root.resizable(width=False,height=False)                
label1=Label(root,text='0',bd='5',fg='red',font=("Arial",15))
label1.place(x=10,y=5,width=200,height=30)
buttons={}                  #字典记录按钮的引用地址,键值是行列号
for m in range(2):    
    for n in range(2):
        def but_RDclick(event,x=m,y=n): #鼠标右击事件函数,参数x,y默认值是按钮的行列数
            do_job1(event,x,y)          #所有事件函数都调用同一函数
        b1=Button(root,command=lambda x=m,y=n:do_job(x,y))
        b1.place(x=40+n*32,y=45+m*32,width=30,height=30)
        b1.bind("<Button-3>", but_RDclick)  #鼠标右击按钮事件绑定事件函数为but_RDclick
        buttons[m,n]=b1
root.mainloop()

鼠标左击事件函数是在创建Button类对象时用command指定的,行列参数用lambda表达式传递。参见博文“python3.8的tkinter按钮事件函数实现多个参数”。鼠标右击按钮事件用button.bind()方法和函数but_RDclick绑定,该函数有2个参数都有默认值为该按钮的行列数。请注意每个按钮都定义了一个事件函数,显然这要占用较多资源,在WinXP扫雷游戏中最多有480个按钮,无论如何都是对计算机资源的极大消耗,这是该方法最大缺点。参见博文“python3.8的tkinter按钮事件函数实现多个参数另一种方法”。
换一个思路,如果从Button类派生一个新类,在新类定义中将鼠标右击事件绑定到类中方法,新类增加2个属性:按钮位置的行号和列号,这两个属性在构造函数中被赋值,用类中方法调用类外部函数且将类对象的2个行和列号属性作为实参,使其能找到按钮引用地址,以便在外部修改其属性和行为。下边程序说明了具体实现方法,当左击(右击或右键双击)按钮,label组件显示:鼠标左(右)键单(双击)击[行号][列号]。

from tkinter import * 
class MyButton(Button):
    def __init__(self,master,command,row, col,f1):      #构造函数,下句调用基类的构造函数
        super().__init__(master=master,command=command,bg="Silver",fg='red',font=("Arial",20))
        self.row = row                                  #按钮所在位置的行数
        self.col = col                                  #按钮所在位置的列数
        self.stateOfmine=0  #-1=有雷,-2=有雷标🚩,-3=有雷标❔,0=无雷未打开,1=无雷已打开,2=无雷标🚩,3=无雷标❔
        self.mines=-1       #相邻雷数量:-1未计算相邻地雷数,0-8相邻按钮下的雷数
        self.f1=f1          #记录参数f1传递的外部函数的引用地址
        self.bind('<Button-3>',self.do_job2)            #绑定鼠标右键单击事件的事件函数
        self.bind('<Double-Button-3>',self.do_job1)     #绑定鼠标右键双击事件的事件函数
    def do_job1(self,event):                            #鼠标右键双击击事件函数
        label1['text']='鼠标右键双击'+ str(self.row)+str(self.col)               
    def do_job2(self,event):                            #鼠标右键单击事件函数
        self.f1(event,self.row,self.col)                #调用外部函数,参数为:事件参数,该按钮的行号,列号
def do_job(x,y):                                        #鼠标左键单击事件函数,参数是lambda表达式传递的行列号
    label1['text']='鼠标左键单击'+str(x)+str(y)              
def do_job4(evt,x,y):                        #被MyButton类函数do_job2调用的函数,x,y是函数do_job2传递的行列号,
    label1['text']='鼠标右键单击'+str(x)+str(y)          #根据行列号从字典buttons得到按钮引用地址
root = Tk()
root.title('扫雷')
root.geometry("200x200") 
root.resizable(width=False,height=False)                
label1=Label(root,text='0',bd='5',fg='red',font=("Arial",15))
label1.place(x=10,y=5,width=200,height=30)
buttons={}                                              #字典记录按钮的引用地址,键值是行列号
for m in range(2):    
    for n in range(2):
        b1=MyButton(root,command=lambda x=m,y=n:do_job(x,y),row=m,col=n,f1=do_job4)
        b1.place(x=40+n*32,y=45+m*32,width=30,height=30)
        buttons[m,n]=b1
b2=Button(root,text='Button类不受影响')                              #Button类不受影响
b2.place(x=10+n*32,y=100+m*32,width=120,height=30)
root.mainloop()

可能有人要问,这样做每个按钮不也是都有一个事件函数吗?的确每个按钮都有一个事件函数,但这个事件函数是同一个函数,即鼠标右键单击按钮事件函数do_job2是唯一的,右键双击按钮事件函数do_job1也是唯一的。这是因为在用类创建了多个对象后,每个对象的数据是独立的,不同对象数据不同。但并不会为每个对象创建方法的副本,类中的所有方法都是唯一的,所有类对象共享类中方法,方法的第一个参数是对象的引用self,通过self可以找到该对象的属性和数据。换句话讲,类的不同对象调用类的同一个方法,通过对象的引用self,修改对象自己的数据和行为。本例第15行,调用外部函数f1,它的实参self.row和self.col就是该按钮的行列号,使外部函数f1通过关键值行列号从字典buttons得到该行列处的按钮的引用地址,使f1可以在外部修改按钮的数据。该方法的思路可用于那些有很多同一类对象的游戏,例如打飞机。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值