Checkbutton和Entry(框)之间的通信

时间:2018-11-21 16:43:10

标签: python tkinter

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的新手,不确定我想做什么。

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

enter image description here

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

有可能吗?

1 个答案:

答案 0 :(得分:1)

有可能,但是让我在当前代码的开头加上一些注意事项:

  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)

请记住我提到的行为-如果Checkbox为空,我有点自由地清除了Entry。但是,如果您不希望这样做,则需要对处理进行一些修改。

注意trace_add的写入方式。始终使用三个默认参数传递回调函数,即变量名称,变量索引(如果有)和操作(see this great answer from 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