首页 > 解决方案 > Checkbutton 和 Entry(box) 之间的通信

问题描述

from tkinter import *
import pandas as pd

df = pd.DataFrame({'item': list('abcde'), 'default_vals': [2,6,4,5,1]})

def input_data(df):
    box = Tk()
    height = str(int(25*(df.shape[0]+2)))
    box.geometry("320x" + height)
    box.title("my box")

    #initialise
    params, checkButtons, intVars = [], [], []
    default_vals = list(df.default_vals)
    itemList = list(df.item)

    for i,label in enumerate(itemList):
        Label(box, text = label).grid(row = i, sticky = W)
        params.append(Entry(box))
        params[-1].grid(row = i, column = 1)
        params[-1].insert(i, default_vals[i])
        intVars.append(IntVar())
        checkButtons.append(Checkbutton(variable = intVars[-1]))
        checkButtons[-1].grid(row = i, column = 3)

    def sumbit(event=None):  
        global fields, checked
        fields = [params[i].get() for i in range(len(params))]
        checked = [intVars[i].get() for i in range(len(intVars))]
        box.destroy()

    #add submit button
    box.bind('<Return>', sumbit) 
    Button(box, text = "submit",
           command = sumbit).grid(row = df.shape[0]+3, sticky = W)                                          
    box.focus_force() 
    mainloop()        

    return fields, checked

我是 tkinter 的新手,不确定我想做什么。

目前,我的脚本(这里简化为函数而不是类)构建了一个框,其中包含在字段中输入的所有默认值:

在此处输入图像描述

相反,我想从空字段开始,一旦单击相应的 checkButton 将获得默认值(应该仍然能够像现在一样通过字段手动更改它),而且,一旦在给定中输入任何值字段,对应的checkButton被选中。

这些可能吗?

标签: pythontkinter

解决方案


这是可能的,但让我在我的解决方案前对您当前的代码提出一些注意事项:

  1. 很少建议进行星号导入 ( from tkinter import *),因为您无法控制导入命名空间的内容。最好明确导入您需要的内容作为参考:

    import tkinter as tk
    
    tk.Label() # same as if you wrote Label()
    tk.IntVar() # same as if you called IntVar()
    
  2. 您想要的行为虽然可能,但不一定是用户友好的。当用户已经输入了某些内容并取消选中该复选框时会发生什么?或者如果选择了复选框然后用户删除了信息会发生什么?这些可能是您要考虑的事情。

话虽如此,解决方案是trace在变量上添加回调函数。您还需要StringVar()Entry盒子添加一个,因为您想要一个双向连接:

# add strVars as a list of StringVar() for your Entry box
params, checkButtons, intVars, strVars = [], [], [], []

在您的 迭代期间enumerate(itemList),添加以下内容:

# Create new StringVar()
strVars.append(StringVar())

# add a trace callback for tracking changes over the StringVar()
strVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_strVar(idx))

# update your Entry to set textvariable to the new strVar
params.append(Entry(box, textvariable=strVars[-1]))


# similarly, add a trace for your IntVar
intVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_intVar(idx))

在迭代小部件创建之前,您需要定义两个trace回调函数 :

def trace_intVar(idx):

    # if Checkbox is checked and Entry is empty...     
    if intVars[idx].get() and not params[idx].get():

        # prefill Entry with default value
        params[idx].insert(0, df.default_vals[idx])

def trace_strVar(idx):

    # if Entry has something...
    if strVars[idx].get():

        # and Checkbox is not checked...
        if not intVars[idx].get():

            # Set the checkbox to checked.
            intVars[idx].set(True)

    # but if Entry is empty...
    else:

        # Set the Checkbox to uncheck.
        intVars[idx].set(False)

记住我提到了这种行为——我冒昧地清除了CheckboxifEntry是空的。但是,如果您不想这样做,则需要稍微修改处理。

注意trace_add书写方式。回调函数始终使用三个默认参数传递,即变量名称、变量索引(如果有)和操作(请参阅 Bryan Oakley 的这个好答案)。由于在这种情况下我们不需要任何参数(我们不能将变量名反向引用到变量之间的链接索引lists),我们必须手动用另一个回调来包装回调lambda并忽略三个参数:

lambda var,        # reserve first pos for variable name
       var_idx,    # reserve second pos for variable index
       oper,       # reserve third pos for operation
       idx=i:      # pass in i by reference for indexing point
trace_intVar(idx)  # only pass in the idx

在这种情况下,您不能只传递值lambda...: trace_intVar(i)i不是引用传递。相信我,我以前犯过这个错误。因此,我们传递另一个参数idx,其默认设置为i,现在将通过引用传递。

如果trace_add不起作用,请trace('w', ...)改用。


为了繁荣,这是您问题的完整实施解决方案:

from tkinter import *
import pandas as pd

df = pd.DataFrame({'item': list('abcde'), 'default_vals': [2,6,4,5,1]})

def input_data(df):
    box = Tk()
    height = str(int(25*(df.shape[0]+2)))
    box.geometry("320x" + height)
    box.title("my box")

    #initialise
    params, checkButtons, intVars, strVars = [], [], [], []
    default_vals = list(df.default_vals)
    itemList = list(df.item)

    def trace_intVar(idx):        
        if intVars[idx].get() and not params[idx].get():
            params[idx].insert(0, df.default_vals[idx])

    def trace_strVar(idx):
        if strVars[idx].get():
            if not intVars[idx].get():
                intVars[idx].set(True)
        else:
            intVars[idx].set(False)


    for i,label in enumerate(itemList):
        Label(box, text = label).grid(row = i, sticky = W)
        strVars.append(StringVar())
        strVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_strVar(idx))
        params.append(Entry(box, textvariable=strVars[-1]))
        params[-1].grid(row = i, column = 1)
        #params[-1].insert(i, default_vals[i])  # <-- You don't need this any more
        intVars.append(IntVar())
        intVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_intVar(idx))
        checkButtons.append(Checkbutton(variable = intVars[-1]))
        checkButtons[-1].grid(row = i, column = 3)



    def sumbit(event=None):  
        global fields, checked
        fields = [params[i].get() for i in range(len(params))]
        checked = [intVars[i].get() for i in range(len(intVars))]
        box.destroy()

    #add submit button
    box.bind('<Return>', sumbit) 
    Button(box, text = "submit",
           command = sumbit).grid(row = df.shape[0]+3, sticky = W)                                          
    box.focus_force() 
    mainloop()        

    return fields, checked

推荐阅读