Python学习笔记 - tkinter: 多个Button绑定类似函数的简单写法

Python学习笔记 - tkinter: 多个Button绑定类似函数的简单写法

在tkinter编程中,常会遇到这样的问题:多个Button绑定了一个类似的回调函数。例如,我们要编写一个做选择题的程序,设置了4个按钮,点击各个按钮,就分别给选择答案列表加上一个A, B, C, D。使用传统代码:

import tkinter  # 导入tkinter模块

items = []  # 初始化选项列表

root = tkinter.Tk()  # 创建窗口
root.title('普通代码程序')  # 设置标题
root.geometry('500x300')  # 设置窗口大小为500x300
root.resizable(width=False, height=False)  # 禁止修改窗口大小


def choose_a():
    items.append('A')


def choose_b():
    items.append('B')


def choose_c():
    items.append('C')


def choose_d():
    items.append('D')


tkinter.Button(root, text='A选项', command=choose_a).pack()  # root参数的完整形式是master=root
tkinter.Button(root, text='B选项', command=choose_b).pack()
tkinter.Button(root, text='C选项', command=choose_c).pack()
tkinter.Button(root, text='D选项', command=choose_d).pack()  # 更多选项以此类推


def show():
    '''输出已选择的选项列表'''
    print(items)


tkinter.Button(master=root, text='输出已选选项', command=show).pack()

root.mainloop()  # 窗口持久化

在上述代码中,4个choose函数过于麻烦。而且,当选项数量不确定时,这种方法即不可行。因此,使用lambda关键字。
lambda关键字可以声明一个匿名函数。例如,在按钮:

def func():
    print('Hello, world!')


tkinter.Button(master, command=func)  # 注意master要填上父容器的实例

等价于下列代码:

def func(text: str):
    print(text)


tkinter Button(master, command=lambda: func(text='Hello, world!'))

因此, 最开始的选择题代码, 可以改为:

import tkinter  # 导入tkinter模块

items = []  # 初始化选项列表

root = tkinter.Tk()  # 创建窗口
root.title('lambda代码程序1')  # 设置标题root.geometry('500x300')  # 设置窗口大小为500x300root.resizable(width=False, height=False)  # 禁止修改窗口大小


def choose(item):
    items.append(item)


tkinter.Button(root, text='A选项', command=lambda: choose('A')).pack()  # 选项按钮
tkinter.Button(root, text='B选项', command=lambda: choose('B')).pack()
tkinter.Button(root, text='C选项', command=lambda: choose('C')).pack()
tkinter.Button(root, text='D选项', command=lambda: choose('D')).pack()


def show():
    '''输出已选择的选项列表'''
    print(items)


tkinter.Button(master=root, text='输出已选选项', command=show).pack()
root.mainloop()  # 窗口持久化

当然,因为choose函数只有一行代码构成,因此可以写成:

import tkinter  # 导入tkinter模块
items = []  # 初始化选项列表

root = tkinter.Tk()
root.title('lambda代码程序2')  # 设置标题root.geometry('500x300')  # 设置窗口大小为500x300
root.resizable(width=False, height=False)  # 禁止修改窗口大小

tkinter.Button(root, text='A选项', command=lambda: items.append('A')).pack()
tkinter.Button(root, text='B选项', command=lambda: items.append('B')).pack()
tkinter.Button(root, text='C选项', command=lambda: items.append('C')).pack()
tkinter.Button(root, text='D选项', command=lambda: items.append('D')).pack()


def show():
    print(items)


tkinter.Button(root, text='输出已选选项', command=show).pack()

root.mainloop()  # 窗口持久化

for循环改进tkinter.Button的定义:

import tkinter  # 导入tkinter模块
items = []  # 初始化选项列表root = tkinter.Tk()  # 创建窗口

root = tkinter.Tk()
root.title('lambda+for代码程序')  # 设置标题root.geometry('500x300')  # 设置窗口大小为500x300
root.resizable(width=False, height=False)  # 禁止修改窗口大小

for item in ('A', 'B', 'C', 'D'):  # 循环定义
    tkinter.Button(root, text=item, command=lambda: items.append(item)).pack()


def show() -> None:  # 顺便说一句, -> None表示返回None. 也可以是bool, tuple, tkinter.Misc等等
    print(items)


tkinter.Button(root, text='输出已选选项', command=show).pack()

root.mainloop()  # 窗口持久化

总之, 可以写一个含有共同内容的function, 把不同内容作为参数,再用lambda关键字改写。
注:感谢**@xiansr**同志的提醒,之前的lambda写法确实不可用,目前已经修改,谢谢。
其实,在其他时候也可以使用lambda。这里仍然使用tkinter作为示例:

import tkinter  # 导入tkinter模块
import tkinter.messagebox as msg  # 导入消息框模块

root = tkinter.Tk()  # 定义一个窗口
root.title('lambda示例窗口')
root.geometry('500x300')
root.resizable(width=False, height=False)


def show(event: tkinter.Event, key: str) -> None:  # event是tkinter.Misc中bind传入参数所留的参数位置,不留位置会报错
    msg.showinfo(title='单击了按钮', message='单击了%s按钮' % key)  # 也可改为'单击了{}按钮'.format(key)


root.bind('<KeyPress-A>', lambda: show(key='A'))  # 绑定了A按钮事件,当A键被按下时执行
root.bind('<KeyPress-B>', lambda: show(key='B'))
root.bind('<KeyPress-C>', lambda: show(key='C'))

root.mainloop()  # 窗口持久化

当然,如果你对Python已经比较熟悉,可以用类来实现。
关于类中__call__方法的说明:

class Class:
    # Python2.x必须写class Class(object):
    def __init__(self, func, *args, **kwargs):
        # 函数
        self.func = func
        # 参数
        self.args = args
        # 键值对形式的参数
        self.kwargs = kwargs
    
    def __call__(self):
        '''
        PyCharm可能默认为
        def __call__(self, *args, **kwargs):
        把形参的*args和**kwargs去掉即可
        '''
        self.func(self.args, self.kwargs)
        # return self.func(self.args, self.kwargs)

生成实例时,会自动调用__init__方法,这时把参数func, args, kwargs传入,当实例被调用时,执行__call__方法,请看下面的例子:

In[01]: # 定义类
        class Homework:
            def __new__(self):
                # 只是演示需要,实际不需要自定义此方法。
                # 生成实例时自动形成,次序在__init__之前。
                super().__new__()
                print('已拿到作业表单,准备布置作业')

            def __init__(self, subject, page):
                # 科目
                self.sub = subject
                # 练习册页数
                self.page = page
                print('已布置作业')
            
            def __call__(self, data):
                print('提交{}作业,练习册第{}页,提交时间{}')
                # 有无返回值均可
                return True

In[02]: example = Homework('数学', 31)
         已拿到作业表单,准备布置作业
         已布置作业
In[03]: call = example('10月29日')
        提交数学作业,练习册第31页,提交时间1029日
In[04]: call
Out[03]: True

由此可见,当示例被调用时(即Class('__init__参数')('__call__参数')),会执行类的__call__方法,返回值即__call__方法的返回值(默认为None)。
注:当没有__call__方法时,如果实例被调用,将会抛出异常,提示Uncallable
因此,上文中的show函数,可以改为:

class Show:
    def __call__(self, event: tkinter.Event)
        msg.showinfo(title='单击了按钮', message='单击了%s按钮' % self.key)  # 也可改为'单击了{}按钮'.format(key)

    def __init__(self, key: str):
        self.key: str = key

bind中的lambda语句可改为:

root.bind('<KeyPress-A>', Show('A'))
root.bind('<KeyPress-B>', Show('B'))
root.bind('<KeyPress-C>', Show('C'))

调用实例的实际原理是:

Show('C')(event)  # event由root.bind自动传入

编者技术有限,如有不足敬请指教!

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值