How to say nothing is selected by using curselection in Tkinter - python

I have a code that uses Listbox.curselection():
self.index = int(self._Listbox.curselection()[0])
I would like to raise an error window when nothing is selected in the Listbox.
Any feedback would be appreciated.
Thanks!

I'm not 100% sure what the problem is. If there are no items selected, the self._Listbox.curselection() should return an emtpy list. Since you then grab index 0, it should throw an IndexError.
Demo Code:
from Tkinter import *
master = Tk()
listbox = Listbox(master)
listbox.pack()
listbox.insert(END, "a list entry")
for item in ["one", "two", "three", "four"]:
listbox.insert(END, item)
def callback():
items = map(int, listbox.curselection())
if(len(items) == 0):
print "No items"
else:
print items
button = Button(master,text="press",command=callback)
button.pack()
mainloop()
Based on the behavior of the code above (nothing selected returns an empty list), your code should throw an IndexError when you don't have anything selected ... now you just need to handle the exception:
try:
self.index = int(self._Listbox.curselection()[0])
except IndexError:
tkMessageBox.showwarning("Oops","Need to select something")
Finally, I suppose I'll leave a link to some documentation on the standard Tkinter dialogs (tkMessageBox module)

Related

How to insert a scrollbar to a menu?

I want to add another widget, in this case a scale widget, to a menu widget in tkinter.
Right now the only solutions I see are creating a new command and open a new window with the scale widget or creating the scale widget elsewhere. Both don't seem too appealing to me.
Any ideas how to archive this are welcome :)
You cant add a scrollbar to it, but I have coded something similar to this. Its a hacky way and maybe its hard to understand but I can try to explain.
Note as Bryan mentioned in the linked Thread, this seems to be a a Windows only solution.
import tkinter as tk
def my_first_function():
print('first')
def my_second_function():
print('second')
def check_for_scroll(event):
check = root.call(event.widget, "index","active")
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
if check == file_menu.index('end'):
scroll_down()
root.after(100,lambda e=event:check_for_scroll(e))
def scroll_up():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
previous_command_label = list(dict_of_commands)[i-1]
previous_command = list(dict_of_commands.values())[i-1]
if i != 0: #avoid to get the last as first
file_menu.delete(index_of_first_command) #first before pull down button
file_menu.insert_command(index_of_first_command,
label=previous_command_label,
command=previous_command)
except Exception as e:
print(e)
def scroll_down():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_last_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
next_command_label = list(dict_of_commands)[i+1]
next_command = list(dict_of_commands.values())[i+1]
file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
label=next_command_label,
command=next_command)
except:
pass
space = ' '
dict_of_commands = {'first' : my_first_function,
'second': my_second_function}
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.bind('<<MenuSelect>>', check_for_scroll)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()
So this code creates your window and a menubar on it as usal:
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()
Important for you, is this line here:
file_menu.bind('<<MenuSelect>>', check_for_scroll)
This line binds the event MenuSelect and it happens/triggers if your cursor hovers over a command of your menu. To this event I have bound a function called check_for_scroll and it looks like this:
def check_for_scroll(event):
check = root.call(event.widget, "index","active")
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
if check == file_menu.index('end'):
scroll_down()
root.after(100,lambda e=event:check_for_scroll(e))
The line below checks for the index of the command that has triggered the event. With this we check if its button of our interest like the first or last, with the arrows.
check = root.call(event.widget, "index","active")
if its the first for example this code here is executed:
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
it calls/triggers the function scroll_up and uses then the after method of tkinter to retrigger itself, like a loop. The scroll_up function is build like the scroll_down just in the opposite direction. Lets have a closer look:
def scroll_up():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
previous_command_label = list(dict_of_commands)[i-1]
previous_command = list(dict_of_commands.values())[i-1]
if i != 0: #avoid to get the last as first
file_menu.delete(index_of_first_command) #first before pull down button
file_menu.insert_command(index_of_first_command,
label=previous_command_label,
command=previous_command)
except Exception as e:
print(e)
In this function we need to know the first and the last position of commands, because we want to delete one and insert another on that position/index. To achieve this I had created a dictionary that contains the label and the the function of the command item of tkinter like below. (This could be created dynamically, but lets keep it for another question)
dict_of_commands = {'first' : my_first_function,
'second': my_second_function}
So we iterate over this enumerated/indexed dictionary in our function and check if the k/key/label is our item of interest. If true, we get the previous_command by listing the dictionary keys and get the extract the key before by this line:
next_command_label = list(dict_of_commands)[i+1]
similar to the value of the dictionary with this line:
next_command = list(dict_of_commands.values())[i+1]
After all we can delete one and insert one where we like to with this:
file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
label=next_command_label,
command=next_command)
I know that this code can improved by a lot but it seems hard enough to understand as it is. So dont judge me please.
I hope this solves your question, even if it isnt in the way you wanted to. But this code avoids you from hardly code a own menubar.
If there are questions left on my answer, let me know.

Trying to locate and delete string in listbox in Python tktinter

Hi I am trying to see if text is in my listbox and then have it deleted but for some reason i get the error
return self.tk.call(self._w, 'cget', '-' + key)
TypeError: can only concatenate str (not "int") to str
This is my code:
from tkinter import *
import tkinter as tk
root = tk.Tk()
listboxz = Listbox(root,height=40,width=150,bg='pink')
listboxz.pack(side=tk.LEFT,fill='both',expand=True)
listboxz.insert(END, 'hi','bye','see')
if 'bye' in listboxz:
listboxx.delete()
root.mainloop()
You can use listboxz.get(0, 'end') to get the list of items:
items = listboxz.get(0, 'end')
then use items.index() to find the item you want to delete:
i = items.index('bye')
listboxz.delete(i)
If the item cannot be found, ValueError will be raised, so better use try / except:
try:
items = listboxz.get(0, 'end')
i = items.index('bye')
listboxz.delete(i)
except ValueError:
# item not found, do nothing
pass

OptionsMenu display only first item in each list?

Here I have a basic OptionsMenu and a list with lists inside of it for my Menu options. What I need to do is display only the first item of the sub lists in the options menu but when that item is selected to pass the 2nd item in the sub list to a function.
Currently I can display all of the sub list and pass all of the sub list to a function. The main problem I am having is trying to display JUST the first item "ID 1" or "ID 2" in the drop down menu.
import tkinter as tk
root = tk.Tk()
def print_data(x):
print(x[1])
example_list = [["ID 1", "Some data to pass later"], ["ID 2", "Some other data to pass later"]]
tkvar = tk.StringVar()
tkvar.set('Select ID')
sellection_menu = tk.OptionMenu(root, tkvar, *example_list, command=print_data)
sellection_menu.config(width=10, anchor='w')
sellection_menu.pack()
root.mainloop()
This is what I get:
What I want:
As you can see in the 2nd image only the "ID 1" is displayed in the menu and the data for that ID is printed to console.
I cannot find any documentation or post abut this issue so it may not be possible.
The only way I can think about is this
import tkinter as tk
root = tk.Tk()
do a for loop that takes the first index and that is the ID
sellection_menu = tk.OptionMenu(root, tkvar,
*[ID[0] for ID in example_list],
command=print_data)
search for the given index but this is slow and not so good if you are using a lot of data
def print_data(x):
for l in example_list:
if x == l[0]:
print(l[1])
break
example_list = [["ID 1", "Some data to pass later"],
["ID 2", "Some other data to pass later"]]`

Select from Listbox populated by selecting from another Listbox

I have two Listboxes. Clicking on an item of the first Listbox inserts information in the second one.
When I click on one of the inserted items, I get an error.
The listboxes are defined as:
list_1 = Listbox(root,selectmode=SINGLE)
list_2 = Listbox(root,selectmode=SINGLE)
To get the selected item:
list_1.bind('<<ListboxSelect>>',CurSelect)
which refers to:
def CurSelect(evt):
list_2.delete(0,END)
selected = list_1.get(list_1.curselection())
for i in range(2):
list_2.insert(END,i)
Clicking on one item of list_1 inserts items in list_2.
If I select an item of List_2 this appears:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\XXX\Anaconda3\lib\tkinter\__init__.py", line 1699, in __call
__
return self.func(*args)
File "unbenannt.py", line 28, in CurSelect
selected = liste.get(liste.curselection())
File "C:\Users\XXX\Anaconda3\lib\tkinter\__init__.py", line 2792, in get
return self.tk.call(self._w, 'get', first)
_tkinter.TclError: bad listbox index "": must be active, anchor, end, #x,y, or a
number
I had this kind of problem when selecting in the first Listbox, but solved it with << ListboxSelect>>.
Previously clicking items in the second Listbox worked, although I didn't change anything since then.
Full Code Example:
from tkinter import *
class Code():
def __init__(self):
Code.Window(self)
def Window(self):
root = Tk()
scrollbar = Scrollbar(root)
scrollbar.grid(row=4,rowspan=3,column=1,sticky=N+S+W)
liste = Listbox(root,selectmode=SINGLE,width=12,yscrollcommand=scrollbar.set)
liste.grid(row=4,rowspan=3,column=0)
for j in range(2):
liste.insert(END,j+5)
scrollbar.config(command=liste.yview)
scrollbar_2 = Scrollbar(root)
scrollbar_2.grid(row=4,rowspan=3,column=3,sticky=N+S+W)
eintrag = Listbox(root,selectmode=SINGLE,yscrollcommand=scrollbar_2.set)
eintrag.grid(row=4,rowspan=3,column=2,sticky=W)
def CurSelect(evt):
eintrag.delete(0,END)
selected = liste.get(liste.curselection())
for i in range(2):
eintrag.insert(END,str(i)+str(selected))
liste.bind('<<ListboxSelect>>',CurSelect)
root.mainloop()
Code()
This example doesn't make anything useful, but the problem appears anyway.
The problem is that by default only one listbox can have a selection at a time. When you select something in the second listbox, the selection is removed from the first listbox. When that happens, your binding fires but your bound function assumes that liste.curselection() returns a non-empty string.
The simplest solution is to allow both listboxes to have a selection at the same time. You do that by setting the exportselection attribute to False:
liste = Listbox(..., exportselection=False)
...
eintrag = Listbox(..., exportselection=False)
The error raises because when the right-hand-side list box, eintrag, gets focus, any item selected in the left-hand-side list box,liste, gets deselected and that invokes the event callback for '<<ListboxSelect>>' in which it is assumed that liste.curselection() is never empty, which is untrue in this particular case and thus liste.get(liste.curselection()) throws the error as it tries to get an item that is in the index of "".
Nesting entire event handler with an if resolves the issue:
def CurSelect(evt):
if liste.curselection(): # To call below statements only when liste has a selected item
eintrag.delete(0,END)
selected = liste.get(liste.curselection())
for i in range(2):
eintrag.insert(END,str(i)+str(selected))

Python Tkinter class inheritance problems

I'm trying to manipulate a listbox in Tkinter but I'm having some troubles. I used to have everything in one class, on one page, and it worked fine. I separated the methods into different classes on two different pages (one for displaying things, one for modifying them) and now I'm having some issues.
I'm getting the following error AttributeError: Actions has no attribute 'listbox'. I'm assuming it's something inheritance related because it worked fine before I split it into two files.
Here's the first file
from Tkinter import *
import Tkinter
import SortActions
class MakeList(Tkinter.Listbox):
def BuildMainWindow(self):
menubar = Frame(relief=RAISED,borderwidth=1)
menubar.pack()
mb_file = Menubutton(menubar,text='file')
mb_file.menu = Menu(mb_file)
mb_file.menu.add_command(label='open', command = self.BuildListbox)
mb_file.pack(side=LEFT)
mb_edit = Menubutton(menubar,text='edit')
mb_edit.menu = Menu(mb_edit)
mb_edit.pack(padx=25,side=RIGHT)
mb_file['menu'] = mb_file.menu
mb_edit['menu'] = mb_edit.menu
return
def BuildListbox(self):
self.listbox = Tkinter.Listbox()
index = SortActions.Actions()
self.listbox.bind('<<ListboxSelect>>', index.GetWindowIndex)
MoveItem = SortActions.Actions()
self.listbox.bind('<B1-Motion>', index.MoveWindowItem)
for item in ["one", "two", "three", "four"]:
self.listbox.insert(END, item)
self.listbox.insert(END, "a list entry")
self.listbox.pack()
#print self.listbox.get(0, END)
return
if __name__ == '__main__':
start = MakeList()
start.BuildMainWindow()
mainloop()
And the second file, the one that I'm having issues with
from FileSort import MakeList
class Actions(MakeList):
#gets the current item that was clicked in the window
def GetWindowIndex(self, event):
w = event.widget
self.curIndex = int(w.curselection()[0])
#moves the current item in the window when clicked/dragged
def MoveWindowItem(self, event):
i = self.listbox.nearest(event.y) #here is where the error is occurring
print i
I assumed since I inherit the MakeList class I should have access. I also tried changing it so I directly accessed MakeList (an object) but instead of the error saying "Actions instance has no...." it said "MakeList has no attribute..."
I posted something previously but I accidentally ran an older version of the code, so I was referencing the wrong error. Sorry if you saw that post. It's gone now
As I see it, there's no reason for the Actions to be in a class ...
#SortActions.py
#gets the current item that was clicked in the window
def GetWindowIndex(self, event):
w = event.widget
self.curIndex = int(w.curselection()[0])
#moves the current item in the window when clicked/dragged
def MoveWindowItem(self, event):
i = self.nearest(event.y) #here is where the error is occurring
print i
Now you can use the actions:
...
def BuildListbox(self):
#self.listbox = Tkinter.Listbox() #??? This has no master widget ...
#Since this is already a listbox, there's no point in building another ...
self.bind('<<ListboxSelect>>', lambda e:SortActions.GetWindowIndex(self,e))
self.bind('<B1-Motion>', lambda e:SortActions.MoveWindowItem(self,e)
for item in ("one", "two", "three", "four"):
self.insert(END, item)
self.insert(END, "a list entry")
self.pack()

Categories

Resources