Python:使用UI树展示字典的字典和其他控件的值
我有三个字典(dict),一个字典列出了所有可用的选项,另外两个字典提供了一部分选择(一个是默认选项,另一个是用户选择的选项)。我使用Python自带的JSON解析器来获取这三个字典。
我想在用户界面(UI)左边显示一个树形结构,这个树是基于字典中的键来构建的;在右边,我希望能显示一个下拉框、按钮、列表框或者其他合适的控件,以便对该键的数据进行操作。之所以需要树形结构,是因为我实际上是在处理一个字典的字典,并且我想允许折叠功能。
到目前为止,我研究了tkinter、tkinter的ttk和tix库,它们可以创建树形结构,但似乎不支持在右边配置列表。我还看到一些例子是从Python的IDLE中借用的树形结构。
- 有没有提供这种功能的图形用户界面(GUI)工具包,还是说没有这样的工具包,我必须自己设计一个?
- 如果我必须自己设计,有没有推荐的GUI工具包比tk更好?
- 如果推荐的工具包不提供这种功能,有没有关于GUI设计的基础教程?
如果可能的话,我希望这个GUI工具包能够跨平台兼容(支持*nix和Windows),并且可以自由分发。此外,出于好奇,有没有关于使用tk创建自定义控件的教程?我尝试查找,但总是被引导到tk的控件使用,而不是控件设计的内容 :s
作为一个简单的例子,我暂时省略了额外的字典,只有以下内容:
import json
import tkinter as tk
from tkinter import ttk
from pprint import pprint as pprint
def JSONTree(Tree, Parent, Dictionery, TagList = []):
for key in Dictionery :
if isinstance(Dictionery[key],dict):
Tree.insert(Parent, 'end', key, text = key)
TagList.append(key)
JSONTree(Tree, key, Dictionery[key], TagList)
pprint(TagList)
elif isinstance(Dictionery[key],list):
Tree.insert(Parent, 'end', key, text = key) # Still working on this
else :
Tree.insert(Parent, 'end', key, text = key, value = Dictionery[key])
if __name__ == "__main__" :
# Setup the root UI
root = tk.Tk()
root.title("JSON editor")
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
# Setup Data
Data = {"firstName": "John",
"lastName": "Smith",
"gender": "man",
"age": 32,
"address": {"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"},
"phoneNumbers": [{ "type": "home", "number": "212 555-1234" },
{ "type": "fax", "number": "646 555-4567" }]}
# Setup the Frames
TreeFrame = ttk.Frame(root, padding = "3")
TreeFrame.grid(row = 0, column = 0, sticky = tk.NSEW)
# Setup the Tree
tree = ttk.Treeview(TreeFrame, columns = ('Values'))
tree.column('Values', width = 100, anchor = 'center')
tree.heading('Values', text = 'Values')
JSONTree(tree, '', Data)
tree.pack(fill=tk.BOTH, expand = 1)
# Limit windows minimum dimensions
root.update_idletasks()
root.minsize(root.winfo_reqwidth(),root.winfo_reqheight())
root.mainloop()
2 个回答
我对John Gaines Jr.的回答进行了修改,以便处理列表。我在做的事情中不需要编辑功能或标签列表,所以把它们去掉了。不过,这些功能当然可以再加回来。由于列表可能会导致键的重复,我用UUID(通用唯一识别码)替换了键,同时在树形视图的左侧仍然显示原始键的文本。
更新于2021年7月18日 - 我添加了第一个答案中提到的编辑功能。不过,我的Tk布局技能并不是特别出色。
# https://gist.github.com/wware/a1d90a3ca3cbef31ed3fbb7002fd1318
import json
import uuid
import Tkinter as tk
import ttk
from pprint import pprint as pprint
# opt_name: (from_, to, increment)
IntOptions = {
'age': (1.0, 200.0, 1.0),
}
def close_ed(parent, edwin):
parent.focus_set()
edwin.destroy()
def set_cell(edwin, w, tvar):
value = tvar.get()
w.item(w.focus(), values=(value,))
close_ed(w, edwin)
def edit_cell(e):
w = e.widget
if w and len(w.item(w.focus(), 'values')) > 0:
edwin = tk.Toplevel(e.widget)
edwin.protocol("WM_DELETE_WINDOW", lambda: close_ed(w, edwin))
edwin.wait_visibility()
edwin.grab_set()
edwin.overrideredirect(1)
opt_name = w.focus()
(x, y, width, height) = w.bbox(opt_name, 'Values')
edwin.geometry('%dx%d+%d+%d' % (width, height, x/4, y))
value = w.item(opt_name, 'values')[0]
tvar = tk.StringVar()
tvar.set(str(value))
ed = None
if opt_name in IntOptions:
constraints = IntOptions[opt_name]
ed = tk.Spinbox(edwin, from_=constraints[0], to=constraints[1],
increment=constraints[2], textvariable=tvar)
else:
ed = tk.Entry(edwin, textvariable=tvar)
if ed:
ed.config(background='LightYellow')
#ed.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.W, tk.E))
ed.pack()
ed.focus_set()
edwin.bind('<Return>', lambda e: set_cell(edwin, w, tvar))
edwin.bind('<Escape>', lambda e: close_ed(w, edwin))
def JSONTree(Tree, Parent, Dictionary):
for key in Dictionary :
uid = uuid.uuid4()
if isinstance(Dictionary[key], dict):
Tree.insert(Parent, 'end', uid, text=key)
JSONTree(Tree, uid, Dictionary[key])
elif isinstance(Dictionary[key], list):
Tree.insert(Parent, 'end', uid, text=key + '[]')
JSONTree(Tree,
uid,
dict([(i, x) for i, x in enumerate(Dictionary[key])]))
else:
value = Dictionary[key]
if isinstance(value, str) or isinstance(value, unicode):
value = value.replace(' ', '_')
Tree.insert(Parent, 'end', uid, text=key, value=value)
if __name__ == "__main__" :
# Setup the root UI
root = tk.Tk()
root.title("JSON editor")
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
# Setup Data
Data = {
"firstName": "John",
"lastName": "Smith",
"gender": "male",
"age": 32,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"},
"phoneNumbers": [
{"type": "home", "number": "212 555-1234" },
{"type": "fax",
"number": "646 555-4567",
"alphabet": [
"abc",
"def",
"ghi"]
}
]}
# Setup the Frames
TreeFrame = ttk.Frame(root, padding="3")
TreeFrame.grid(row=0, column=0, sticky=tk.NSEW)
# Setup the Tree
tree = ttk.Treeview(TreeFrame, columns=('Values'))
tree.column('Values', width=100, anchor='center')
tree.heading('Values', text='Values')
tree.bind('<Double-1>', edit_cell)
tree.bind('<Return>', edit_cell)
JSONTree(tree, '', Data)
tree.pack(fill=tk.BOTH, expand=1)
# Limit windows minimum dimensions
root.update_idletasks()
root.minsize(root.winfo_reqwidth(), root.winfo_reqheight())
root.mainloop()
好吧,这段代码看起来不太美观,我也不太想把这样的代码放到实际使用中,但它确实能工作。为了让它更合理、更适合生产环境,我可能会把JSONTree做成一个类,把所有这些代码放到方法里。这样可以简化和清理代码,也能减少在事件处理程序中传递对象的次数。
import json
import tkinter as tk
from tkinter import ttk
from pprint import pprint as pprint
# opt_name: (from_, to, increment)
IntOptions = {
'age': (1.0, 200.0, 1.0),
}
def close_ed(parent, edwin):
parent.focus_set()
edwin.destroy()
def set_cell(edwin, w, tvar):
value = tvar.get()
w.item(w.focus(), values=(value,))
close_ed(w, edwin)
def edit_cell(e):
w = e.widget
if w and len(w.item(w.focus(), 'values')) > 0:
edwin = tk.Toplevel(e.widget)
edwin.protocol("WM_DELETE_WINDOW", lambda: close_ed(w, edwin))
edwin.grab_set()
edwin.overrideredirect(1)
opt_name = w.focus()
(x, y, width, height) = w.bbox(opt_name, 'Values')
edwin.geometry('%dx%d+%d+%d' % (width, height, w.winfo_rootx() + x, w.winfo_rooty() + y))
value = w.item(opt_name, 'values')[0]
tvar = tk.StringVar()
tvar.set(str(value))
ed = None
if opt_name in IntOptions:
constraints = IntOptions[opt_name]
ed = tk.Spinbox(edwin, from_=constraints[0], to=constraints[1],
increment=constraints[2], textvariable=tvar)
else:
ed = tk.Entry(edwin, textvariable=tvar)
if ed:
ed.config(background='LightYellow')
#ed.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.W, tk.E))
ed.pack()
ed.focus_set()
edwin.bind('<Return>', lambda e: set_cell(edwin, w, tvar))
edwin.bind('<Escape>', lambda e: close_ed(w, edwin))
def JSONTree(Tree, Parent, Dictionery, TagList=[]):
for key in Dictionery :
if isinstance(Dictionery[key], dict):
Tree.insert(Parent, 'end', key, text=key)
TagList.append(key)
JSONTree(Tree, key, Dictionery[key], TagList)
pprint(TagList)
elif isinstance(Dictionery[key], list):
Tree.insert(Parent, 'end', key, text=key) # Still working on this
else:
Tree.insert(Parent, 'end', key, text=key, value=Dictionery[key])
if __name__ == "__main__" :
# Setup the root UI
root = tk.Tk()
root.title("JSON editor")
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
# Setup Data
Data = {
"firstName": "John",
"lastName": "Smith",
"gender": "man",
"age": 32,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"},
"phoneNumbers": [
{ "type": "home", "number": "212 555-1234" },
{ "type": "fax", "number": "646 555-4567" },
]}
# Setup the Frames
TreeFrame = ttk.Frame(root, padding="3")
TreeFrame.grid(row=0, column=0, sticky=tk.NSEW)
# Setup the Tree
tree = ttk.Treeview(TreeFrame, columns=('Values'))
tree.column('Values', width=100, anchor='center')
tree.heading('Values', text='Values')
tree.bind('<Double-1>', edit_cell)
tree.bind('<Return>', edit_cell)
JSONTree(tree, '', Data)
tree.pack(fill=tk.BOTH, expand=1)
# Limit windows minimum dimensions
root.update_idletasks()
root.minsize(root.winfo_reqwidth(), root.winfo_reqheight())
root.mainloop()