Python : Display a Dict of Dicts using a UI Tree for the keys and any other widget for the values
Question: I have three dicts, one providing a list of all the available options, and two
providing a subset of choices (one set for defaults and one for user choices).
I get the three dicts using python's built in JSON parser.

I want display, in a UI, a tree on the left that is based on the keys in the
dicts, on the right I would like to display either a combobox, a button, a
listbox or some other appropriate widget to manipulate the data for that key.
I need the tree since I'm really working with a dict of dicts and I want to
allow folding.

So far I have looked into tkinter, tkinter's ttk and tix libraries and they
allow trees but don't allow configurable lists on the right it seems. I've
also seen some examples where the tree is borrowed from python's IDLE.

  1. Is there a GUI toolkit that provides such functionality or is there no such thing and I have to design my own ?
  2. If I have to design my own is there any GUI toolkit that you would recommend over tk ?
  3. Is there a basic tutorial on GUI design for the recommended toolkit if it doens't provide this kind of thing ?

I'd prefer it if the GUI toolkit was cross platform compatible (*nix and win)
and free to distribute if possible. Out of interest is there a tutorial on
creating custom widgets with tk, I have tried looking but I keep getting
directed to tk's widget use instead of widget design :s

As a minimal example I've dropped the extra dicts for now and have the
following :

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


Answer: OK, so it's not really pretty, nor would I feel very good about putting code
like this into production, but it does work. To make it more sane and
production quality, I'd probably make JSONTree a class with all this code
wrapped up into methods. This would allow some simplification and cleanup of
the code and a little less passing around of objects to event handlers.

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

