2

我有三个字典,一个提供所有可用选项的列表,两个提供选择的子集(一组用于默认值,一组用于用户选择)。我使用 python 内置的 JSON 解析器获得了三个字典。

我想在 UI 中显示左侧的树,它基于字典中的键,在右侧我想显示组合框、按钮、列表框或其他适当的小部件来操作数据那把钥匙。我需要这棵树,因为我真的在使用 dicts 的 dicts 并且我想允许折叠。

到目前为止,我已经研究了 tkinter、tkinter 的 ttk 和 tix 库,它们允许树,但不允许在右侧显示可配置列表。我还看到了一些从 python 的 IDLE 借用树的例子。

  1. 是否有提供此类功能的 GUI 工具包,或者没有这样的东西,我必须自己设计?
  2. 如果我必须自己设计,是否有任何 GUI 工具包可以推荐超过 tk ?
  3. 如果推荐的工具包不提供这种东西,是否有关于 GUI 设计的基本教程?

如果 GUI 工具包是跨平台兼容的(*nix 和 win)并且如果可能的话可以免费分发,我更喜欢它。出于兴趣,是否有关于使用 tk 创建自定义小部件的教程,我尝试过寻找,但我一直被定向到 tk 的小部件使用而不是小部件设计:s

作为一个最小的例子,我现在已经放弃了额外的 dicts 并具有以下内容:

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()
4

2 回答 2

4

I've modified John Gaines Jr.'s answer to handle lists. I didn't need editing or the Taglist for what I'm doing so I removed them. They could certainly be added back. Since lists can introduce duplication of keys, I replaced the keys with UUIDs while still showing the original key as text on the left side of the treeview.

UPDATE 07/18/2021 - I've added editing as found in the first answer. Alas my Tk layout skills aren't too impressive.

# 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()
于 2014-03-28T20:46:42.603 回答
3

好的,所以它不是很漂亮,将这样的代码投入生产我也不会感觉很好,但它确实有效。为了使其更加理智和生产质量,我可能会将 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()
于 2012-05-03T05:15:29.897 回答