связь между Checkbutton и Entry (поле) - PullRequest
0 голосов
/ 21 ноября 2018
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 голосов
/ 21 ноября 2018

Возможно, но позвольте мне предисловие к моему решению с несколькими предостережениями относительно вашего текущего кода:

  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.Функция обратного вызова всегда передается с тремя аргументами по умолчанию, а именно: Имя переменной, Индекс переменной (если есть) и Операция (см. Этот великолепный ответ от Брайана Оукли) .Так как в этом случае они нам не нужны (мы не можем обратить ссылку на имя переменной на связанный индекс между переменной 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...