在编写扫雷游戏时,可用按钮组成矩阵。鼠标右击、双击和左击按钮事件都要有自己的事件函数。例如右击某按钮,该按钮显示红旗。不可能为每一按钮都编写一个右击按钮事件函数,一般只编写一个函数,其参数是行列号,通过行列号找到该按钮的引用地址,让指定按钮显示红旗。博文“用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可以在外部修改按钮的数据。该方法的思路可用于那些有很多同一类对象的游戏,例如打飞机。