2

I'm going to need a list control with multiple selection and a way for the user to edit the displayed list.

Judging by Python - python-list - ttk Listbox, ttk.Treeview is the new black way to display a list and a replacement for Tkinter.Listbox.

Is there some stock/recommended way provided to incorporate list editing, too?

This is typically done with three buttons "add"/"edit"/"delete" somewhere around the control (the first two may cause a small window with an edit control to pop up), or a list entry itself becomes an editor e.g. on a double click. Implementing either logic by hand would be nontrivial.

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • I think that using placer you can overlay an `Entry` over the listbox item, and allow the user edit its content, and that shouldn't be too much difficult. There is a bit of resemblance to https://stackoverflow.com/questions/22011509/tkinter-popup-and-text-processing-for-autocomplete/22020864#22020864 which I think is interesting despite being a different technique. – progmatico Feb 19 '18 at 21:54
  • Related: https://stackoverflow.com/questions/18562123/how-to-make-ttk-treeviews-rows-editable . It discusses the 2nd option specifically. – ivan_pozdeev Feb 19 '18 at 22:14

1 Answers1

2

Tk and thus Tkinter only provide base widgets; reusable combinations of them are beyond their scope. The abandoned Tix package offered a mechanism for and a collection of these, called "mega-widgets", but the idea didn't catch on apparently.

I did it my way the following way (using the Model-View-Presenter design pattern).

It looks like this:

how it looks

import tkinter
from tkinter import ttk
from tkinter import W, E


class View_EditableList(object):
    def __init__(self,root,root_row):
        """ List with buttons to edit its contents.
        :param root: parent widget
        :param roow_row: row in `root'. The section takes 2 rows.
        """

        self.list = tkinter.Listbox(root, selectmode=tkinter.EXTENDED)
        self.list.grid(row=root_row, sticky=(W, E))
        root.rowconfigure(root_row, weight=1)

        self.frame = ttk.Frame(root)
        self.frame.grid(row=root_row+1, sticky=(W, E))

        self.add = ttk.Button(self.frame, text="+", width=5)
        self.add.grid(row=0, column=1)
        self.edit = ttk.Button(self.frame, text="*", width=5)
        self.edit.grid(row=0, column=2)
        self.del_ = ttk.Button(self.frame, text="-", width=5)
        self.del_.grid(row=0, column=3)
        self.up = ttk.Button(self.frame, text=u"↑", width=5)
        self.up.grid(row=0, column=4)
        self.down = ttk.Button(self.frame, text=u"↓", width=5)
        self.down.grid(row=0, column=5)
        self.frame.grid_columnconfigure(0, weight=1)
        self.frame.grid_columnconfigure(6, weight=1)


class Presenter_EditableList(object):
    def __init__(self,view,root):
        """

        :param view: View_EditableList
        :param root: root widget to be used as parent for modal windows
        """
        self.root = root
        self.view = view
        view.add.configure(command=self.add)
        view.edit.configure(command=self.edit)
        view.del_.configure(command=self.del_)
        view.up.configure(command=self.up)
        view.down.configure(command=self.down)

    def add(self):
        w=View_AskText(self.root)
        self.root.wait_window(w.top)
        if w.value:
            self.view.list.insert(self.view.list.size(),w.value)

    def edit(self):
        l=self.view.list
        try:
            [index]=l.curselection()
        except ValueError:
            return
        w=View_AskText(self.root,l.get(index))
        self.root.wait_window(w.top)
        if w.value:
            l.delete(index)
            l.insert(index,w.value)

    def del_(self):
        l=self.view.list
        try:
            [index]=l.curselection()
        except ValueError:
            return
        l.delete(index)
        l.select_set(max(index,l.size()-1))

    def up(self):
        l = self.view.list
        try:
            [index] = l.curselection()
        except ValueError:
            return
        if index>0:
            v = l.get(index)
            l.delete(index)
            l.insert(index-1,v)
            l.select_set(index-1)

    def down(self):
        l = self.view.list
        try:
            [index] = l.curselection()
        except ValueError:
            return
        if index<l.size()-1:
            v = l.get(index)
            l.delete(index)
            l.insert(index+1,v)
            l.select_set(index+1)

    def getlist(self):
        return [self.view.list.get(i) for i in range(self.view.list.size())]

    def setlist(self,list_):
        self.view.list.delete(0,tkinter.END)
        for i,v in enumerate(list_):
            self.view.list.insert(i,v)

# supplemental class; it's in another file in my actual code
class View_AskText(object):
    """
    A simple dialog that asks for a text value.
    """
    def __init__(self, master, value=u""):
        self.value = None

        top = self.top = tkinter.Toplevel(master)
        top.grab_set()
        self.l = ttk.Label(top, text=u"Value:")
        self.l.pack()
        self.e = ttk.Entry(top)
        self.e.pack()
        self.b = ttk.Button(top, text='Ok', command=self.save)
        self.b.pack()

        if value: self.e.insert(0, value)
        self.e.focus_set()
        top.bind('<Return>', self.save)

    def save(self, *_):
        self.value = self.e.get()
        self.top.destroy()


root = tkinter.Tk()
view = View_EditableList(root, 5)
Presenter_EditableList(view, root)
root.mainloop()
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152