这几天一直在挣扎,所以希望能得到一些帮助。我已经阅读了这里的许多解释,包括Bryan Oakley的一些解释,但我没有找到这个场景的概述。
问题:我正在尝试使用tkinter widgets来收集和存储几个用户输入以供以后使用。需要大约50个用户输入。我的想法是将它们存储在字典中,并将字典组织成多个层次。有各种类型的小部件对应于请求的输入类型,因此使用条目、单选按钮列表、复选框列表等
思路是在类Application中构造用户界面,向类实例提供默认字典,然后让每个小部件访问字典中要更新的一个或多个特定键/值。
我不知道如何从一个小部件中更新/修改一个多层次字典中的特定值,这个小部件也在小部件布局结构的2或3层深处。
我不局限于字典的想法…只是用于组织信息。如果json或其他结构使这更容易,我可以适应。
当前代码示例…
import tkinter as tk
from tkinter import filedialog
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# ... default dict ... goal is to allow user to change these values
# ... using tkinter input widgets (Entry, Radiobutton, Checkbox, ...)
# ... and store updated dictionary in external text file
# ... there are ~50 different values that need to be managed
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
default_config = {
'general': {
'logging_level': 'DEBUG'},
'functions': {
'input': {
'input_dir': '../data/raw/',
'input_file': 'my_input.csv'},
'output': {
'output_file': 'my_output.csv'},
}
}
class Application(tk.Frame):
def __init__(self, this_config, **kwargs):
self.this_config = this_config
self.root = tk.Tk()
tk.Frame.__init__(self, self.root)
self.grid()
# manage different sets of inputs in different Frame columns
self.file_mgmt_column = tk.Frame(self)
self.file_mgmt_column.grid()
# one of several user input widgets in this frame
self.input_dir = InputDir(self.file_mgmt_column, **self.this_config)
self.input_dir.grid()
# ... exit from all
self.quit = Quit(self.root)
self.quit.grid()
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# ... quit button
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
class Quit(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(**kwargs)
self.quit = tk.Button(parent, text="Quit", command=parent.quit)
self.quit.grid()
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# ... input directory selection
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
class InputDir(tk.Frame):
def __init__(self, parent, **lx_config):
# ... this version receives the dict as **kwargs, but also desire to supply widget formatting
# ... definition through **kwargs, to have common formatting across all widgets
self.current_config = lx_config
tk.Frame.__init__(self, parent)
self.input_dir_label = tk.Label(self, text = 'input directory (raw):')
self.input_dir_label.grid()
# this is hard-coding in the specific dictionary keys to the display the current text value
# need a way to define this set of keys at method call from Application __init__ level
self.input_dir = tk.StringVar()
self.input_dir.set(self.current_config['functions']['input']['input_dir'])
self.input_dir_entry = tk.Entry(self, width = 75,
textvariable=self.input_dir)
self.input_dir_entry.grid()
self.select_button = tk.Button(self,
text = 'Select directory',
command = self.select_folder)
self.select_button.grid()
self.select_button.bind('<1>',
lambda event, obj=self.input_dir,
val=self.current_config['functions']['input']['input_dir']:
self.select_folder(event, obj, val))
def select_folder(self, event, obj, val):
self.input_dir = filedialog.askdirectory()
obj.set(self.input_dir)
# ... ??????????????????????
# ... how to make a change to default config['functions']['input']['input_dir']
# ... in this case, it is text field (file directory)
# ... for other inputs, may be a list of multiple text values, e.g.,
# ... a list of files
# ... ??????????????????????
val = self.input_dir # does not change value in primary dict
print('event : %s' % event)
print('selected folder() ... %s' % self.input_dir)
print('updated value ... %s' % str(val))
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# ... main app
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
if __name__ == "__main__":
app = Application(default_config)
app.mainloop()
print(default_config['functions']['input'])
# ... after user inputs have been received ... then take next steps with updated dict ...
几点注释:
- 将您的小部件配置与您试图编辑的配置字典分开。两者混合只会引起疼痛。
- 为保持部件外观一致,使用
ttk
(from tkinter import ttk
)和样式。 典型的网格布局是将标签放在左列,而要编辑的值放在右列。你只使用
.grid()
把每个项目放在一个单独的行(见我的例子,这样做的方法)。和修改后的示例:
import tkinter as tk
from tkinter import filedialog
from tkinter import ttk
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# ... default dict ... goal is to allow user to change these values
# ... using tkinter input widgets (Entry, Radiobutton, Checkbox, ...)
# ... and store updated dictionary in external text file
# ... there are ~50 different values that need to be managed
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
default_config = {
'general': {
'logging_level': 'DEBUG'},
'functions': {
'input': {
'input_dir': '../data/raw/',
'input_file': 'my_input.csv'},
'output': {
'output_file': 'my_output.csv'},
},
'example': {
'some_value': 'anything',
'some_bool': True,
},
}
class ConfEntry:
"""Like tk.Entry, but value is auto-stored to a dictionary element"""
def __init__(self, parent, label, config, item, **kwargs):
self.config = config
self.item = item
self.label = tk.Label(parent, text=label, justify=tk.LEFT)
self.var = tk.StringVar(parent, value=self.config[self.item])
self.entry = tk.Entry(parent, textvariable=self.var)
self.var.trace_add('write', self.update_value)
def grid(self, row, column=0):
self.label.grid(row=row, column=column, sticky='w')
self.entry.grid(row=row, column=column+1)
def update_value(self, *_ignored):
self.config[self.item] = self.var.get()
class ConfCheckbutton:
"""Like tk.Checkbutton, but value is auto-stored to a dictionary element"""
def __init__(self, parent, label, config, item, **kwargs):
self.config = config
self.item = item
self.label = tk.Label(parent, text=label, justify=tk.LEFT)
self.var = tk.BooleanVar(parent, value=self.config[self.item])
self.cb = tk.Checkbutton(parent, variable=self.var)
self.var.trace_add('write', self.update_value)
def grid(self, row, column=0):
self.label.grid(row=row, column=column, sticky='w')
self.cb.grid(row=row, column=column+1)
def update_value(self, *_ignored):
self.config[self.item] = self.var.get()
class Application(tk.Frame):
def __init__(self, this_config, **kwargs):
self.this_config = this_config
self.root = tk.Tk()
tk.Frame.__init__(self, self.root)
self.grid()
# manage different sets of inputs in different Frame columns
self.file_mgmt_column = tk.Frame(self)
self.file_mgmt_column.grid()
# one of several user input widgets in this frame
self.input_dir = InputDir(self.file_mgmt_column, **self.this_config)
self.input_dir.grid()
ttk.Separator(self).grid(row=0, column=1, rowspan=100, padx=4, sticky='ns')
self.example_column = tk.Frame(self)
self.example_column.grid(row=0, column=2)
lbl = tk.Label(self.example_column, text='Examples in this Column', justify=tk.CENTER)
lbl.grid(row=0, column=0, columnspan=2, sticky='ew')
self.example_value = ConfEntry(self.example_column, 'Example', self.this_config['example'], 'some_value')
self.example_value.grid(row=1)
self.example_bool = ConfCheckbutton(self.example_column, 'Example Bool', self.this_config['example'], 'some_bool')
self.example_bool.grid(row=2)
# ... exit from all
self.quit = Quit(self.root)
self.quit.grid()
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# ... quit button
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
class Quit(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(**kwargs)
self.quit = tk.Button(parent, text="Quit", command=parent.quit)
self.quit.grid()
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# ... input directory selection
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
class InputDir(tk.Frame):
def __init__(self, parent, **lx_config):
# ... this version receives the dict as **kwargs, but also desire to supply widget formatting
# ... definition through **kwargs, to have common formatting across all widgets
self.current_config = lx_config
tk.Frame.__init__(self, parent)
self.input_dir_label = tk.Label(self, text='input directory (raw):')
self.input_dir_label.grid()
# this is hard-coding in the specific dictionary keys to the display the current text value
# need a way to define this set of keys at method call from Application __init__ level
self.input_dir = tk.StringVar()
self.input_dir.set(self.current_config['functions']['input']['input_dir'])
self.input_dir_entry = tk.Entry(self, width=75,
textvariable=self.input_dir)
self.input_dir_entry.grid()
self.select_button = tk.Button(self,
text='Select directory',
command=self.select_folder)
self.select_button.grid()
self.select_button.bind('<1>',
lambda event, obj=self.input_dir,
val=self.current_config['functions']['input']['input_dir']:
self.select_folder(event, obj, val))
def select_folder(self, event, obj, val):
self.input_dir = filedialog.askdirectory()
obj.set(self.input_dir)
# ... ??????????????????????
# ... how to make a change to default config['functions']['input']['input_dir']
# ... in this case, it is text field (file directory)
# ... for other inputs, may be a list of multiple text values, e.g.,
# ... a list of files
# ... ??????????????????????
val = self.input_dir # does not change value in primary dict
print('event : %s' % event)
print('selected folder() ... %s' % self.input_dir)
print('updated value ... %s' % str(val))
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# ... main app
# ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
if __name__ == "__main__":
app = Application(default_config)
app.mainloop()
print(default_config['functions']['input'])
print(default_config['example'])
# ... after user inputs have been received ... then take next steps with updated dict ...