Home > Blockchain >  Tkinter ListboxSelect callback always processes last item in the list
Tkinter ListboxSelect callback always processes last item in the list

Time:03-19

If the ListboxSelect callback is executed without having an entry selected, i.e the click in the respective listbox has been placed below the last item in the empty space, the "curselection" method always returns the index of the last item in the listbox. Even more, if one tries to use the default "browse" selection method for the listbox and moves the mouse cursor over the listbox while holding mouse button pressed, any move will execute the callback. I tried to find a hint on how to fix it... no luck though.

from tkinter import *

class simple( Frame ):
  def __init__( self, parent = None ):
    Frame.__init__( self )
    self.master.title( 'DEMO' )
    self.master.bind( '<Control-q>', quit )
    self.pack()

    self.create_widgets()

  def create_widgets( self ):
    words = ['An','"empty"','selection','processes','the', 'last', 'item','in','this','list' ]

    self.lb = Listbox ( self, width = 12, height = 25, selectmode = SINGLE, exportselection = False )
    self.lb.pack ( side = LEFT )
    self.lb.bind ( '<<ListboxSelect>>', self.process_item )

    Label ( self, anchor = W, text = '\n\nClick here\n\n<--\n\nin the emtpy space...' ).pack ( side = RIGHT )

    for w in words:
      self.lb.insert ( 0, w )


  def process_item ( self, event ):
    selection = self.lb.curselection ( )
    self.do_stuff_with_item ( self.lb.get ( selection ) )
    self.lb.delete ( selection )


  def do_stuff_with_item ( self, item ):
    print ( item )




def engage():
  root = Tk()
  sw = simple( root )
  sw.pack()
  root.mainloop()


if __name__ == '__main__':
  engage()

I tried it with python 2.7, 3.6 ( on Linux ) and with python 3.8 on Win

CodePudding user response:

It was indeed tricky to achieve what You've asked for.
Default behavior of Listbox is to select last item when it gains focus.

To fool it, You can add an empty word as a last item of Your words list. This will make it invisible in a Listbox. Then in Your process_item callback, check if selected value is empty (optionally if it's last value as well). If so, remove focus from Listbox and return from Your callback. This will look as if nothing happened.

Here is modified code:

from tkinter import *


class simple(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self)
        self.master.title('DEMO')
        self.master.bind('<Control-q>', quit)
        self.pack()

        self.create_widgets()

    def create_widgets(self):
        # Words with dummy "" word in the end
        words = ['An', '"empty"', 'selection', 'processes', 'the', 'last', 'item', 'in', 'this', 'list', ""]

        self.lb = Listbox(self, width=12, height=25, selectmode=SINGLE, exportselection=False)
        self.lb.pack(side=LEFT)
        self.lb.bind('<<ListboxSelect>>', self.process_item)

        Label(self, anchor=W, text='\n\nClick here\n\n<--\n\nin the emtpy space...').pack(side=RIGHT)

        self.lb.insert(0, *words)

    def process_item(self, event):
        selection = self.lb.curselection()
        item = self.lb.get(selection)
        if item == "":
            # It's last dummy item, remove focus and return
            self.master.after(0, self.master.focus)
            return

        # Real item was clicked
        self.do_stuff_with_item(item)
        self.lb.delete(selection)

    def do_stuff_with_item(self, item):
        print(item)


def engage():
    root = Tk()
    sw = simple(root)
    sw.pack()
    root.mainloop()


if __name__ == '__main__':
    engage()
  • Related