Python Forum
[Tkinter] listbox selection
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] listbox selection
#1
Hi,
I have a listbox for MULTIPLE selection.
def getElement(event):
    selection = event.widget.curselection()
the variable "selection" becomes a vector as you select more lines. OK.
Like so:
Output:
(10, 11, 12, 15)
My question: "selection" gets sorted automatically, for every line you click.
The information of which line was selected first is lost. (Especially the first selection)
Yes, I can create a new variable to save this info, it would be nicer if I can prevent it from sorting.
thx,
Paul
It is more important to do the right thing, than to do the thing right.(P.Drucker)
Better is the enemy of good. (Montesquieu) = French version for 'kiss'.
Reply
#2
The info is not sorted, it is just generated fresh each time you call curselection(). The listbox has no memory of selection order, only what is selected. If selection order is important, you will need to update the list choices to reflect the order. That will give you a chronological list and provide feedback to the user. Something like this:
import tkinter as tk

class OrderedListbox(tk.Listbox):
    """A list box that moves unselected items below selected items"""
    UNSPECIFIED = object()

    def __init__(self, *args, items=None, command=None, **kvargs):
        super().__init__(*args, selectmode=tk.MULTIPLE, **kvargs)
        self.bind('<<ListboxSelect>>', self._update)
        self.config(items=items, command=command)

    def config(self, items=UNSPECIFIED, command=UNSPECIFIED, **kvargs):
        """
        Implement items and command options.
        items=iterable : Use to set list items
        command=callable : Use to set function called when selection changes
        """
        if items is not self.UNSPECIFIED:
            self._items = items or []
            self.delete(0, tk.END)
            self.insert(tk.END, *self._items)
        if command is not self.UNSPECIFIED:
            self.command = command

    def _update(self, _):
        """Callback function when user changes selection"""
        # Get list of selected items
        selection = self.selection()

        # Move unselected items after selected items
        items = selection + [item for item in self._items if item not in selection]
        self.config(items=items)
        if len(selection) > 0:
            self.selection_set(0, len(selection)-1)
        else:
            super().selection_clear(0, tk.END)

        # Execute callback function with currently selected items
        if self.command is not None:
            self.command(self.selection())

    def selection_clear(self):
        """Clear selection"""
        super().selection_clear(0, tk.END)

    def selection(self):
        """Return list of selected items"""
        return [self.get(i) for i in self.curselection()]

class MainWindow(tk.Tk):
    def __init__(self, *args, **kvargs):
        super().__init__(*args, **kvargs)
        self.list = OrderedListbox(self, items=list('ABCDEFG'), width=5)
        self.list.pack(padx=10, pady=10)
        button = tk.Button(self, text="Clear", command=self.list.selection_clear)
        button.pack(padx=10, pady=10)

window = MainWindow()
window.list.config(command=lambda x:print(x))
window.mainloop()
Usually I see two lists when the the interface supports order AND selection. There is a list for selecting items and a second list for ordering the selected items, and some buttons for moving values in and out of the selected items list.
Reply
#3
Thanks for the elaborate answer.
I understand the selection is not "sorted", it just looks that way because it
updates the list every time from top to bottom.
I'll start by remembering the first selection. As the app develops, I may need to expand.
thx,
Paul
It is more important to do the right thing, than to do the thing right.(P.Drucker)
Better is the enemy of good. (Montesquieu) = French version for 'kiss'.
Reply
#4
Did you try running it?

Fixed config/configure so you can set other options after creating the widget.
import tkinter as tk

class OrderedListbox(tk.Listbox):
    """A list box that moves unselected items below selected items"""
    UNSPECIFIED = object()

    def __init__(self, *args, items=None, command=None, **kvargs):
        super().__init__(*args, selectmode=tk.MULTIPLE, **kvargs)
        self.bind('<<ListboxSelect>>', self._update)
        self.config(items=items, command=command)

    def config(self, items=UNSPECIFIED, command=UNSPECIFIED, **kvargs):
        """
        Implement items and command options.
        items=iterable : Use to set list items
        command=callable : Use to set function called when selection changes
        """
        if items is not self.UNSPECIFIED:
            self._items = items or []
            self.delete(0, tk.END)
            self.insert(tk.END, *self._items)
        if command is not self.UNSPECIFIED:
            self.command = command
        super().config(**kvargs)

    def configure(self, *args, **kvargs):
        """config for those who like to type"""
        self.config(*args, **kvargs)

    def _update(self, _):
        """Callback function when user changes selection"""
        # Get list of selected items
        selection = self.selection()

        # Move unselected items after selected items
        items = selection + [item for item in self._items if item not in selection]
        self.config(items=items)
        if len(selection) > 0:
            self.selection_set(0, len(selection)-1)
        else:
            self.selection_clear(0, tk.END)

        # Execute callback function with currently selected items
        if self.command is not None:
            self.command(selection)

    def selection(self):
        """Return list of selected items"""
        return [self.get(i) for i in self.curselection()]

class MainWindow(tk.Tk):
    def __init__(self, *args, **kvargs):
        super().__init__(*args, **kvargs)
        self.list = OrderedListbox(self, width=10)
        self.list.config(items=list('ABCDEFG'), bg="red", selectbackground="green")
        self.list.pack(padx=10, pady=10)
        button = tk.Button(self, text="Clear", command=lambda: self.list.selection_clear(0, tk.END))
        button.pack(padx=10, pady=10)

window = MainWindow()
window.list.config(command=lambda x:print(x))
window.mainloop()
Reply
#5
I ran this second version and it goes a long way to what I want to achieve.
But physically moving the selected items is not necessary.
What you cannot know is what I, (eventually "the users") am doing.
From earlier posts in the bar forum, you may know that I am trying to speed up
data entry of historical genealogy documents. Imagine 100.000 19th century prayer cards,
OCR-scanned, in python and shown line by line in the listbox. Now I want to select items
in a particular order (Name, place birth, date birth, etc..) I do not want to
shuffle the lines around, because then the text makes no sense any more,
and users are lost. What I will do is keep your idea of a shadow list,
but remove the re-ordering.
If you have any other ideas that could help me, keep them coming.
Gribouillis has already sent me some magic flux that was very helpful.
thx,
Paul
It is more important to do the right thing, than to do the thing right.(P.Drucker)
Better is the enemy of good. (Montesquieu) = French version for 'kiss'.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020