Problems in Python getting multiple selections from Tkinter listbox - python

This is the same problem I posed earlier today and which a couple of you tried to help me with, but I can't get it to work. All I want to do is to populate "ichose" with the multiple selections I make when I click on the listbox.
import Tkinter as tk
from Tkinter import *
global ichose
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master=master
self.grid()
self.ichose = ()
self.l = Listbox(self, height=10, selectmode=EXTENDED)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
self.l.grid(column=0, row=0, sticky=(N,W,E,S))
self.l.bind("Double-Button-1", self.entered)
s = Scrollbar(self, orient=VERTICAL, command=self.l.yview)
s.grid(column=0, row=0, sticky=(N,S,E))
self.l['yscrollcommand'] = s.set
for i in range(1,101):
self.l.insert('end', 'Line %d of 100' % i)
def entered(self, event):
self.ichose = self.selection_get()
self.ichose = ('hello')
root=tk.Tk()
root.title('Listbox Problem')
root.geometry('200x200')
app=App(root)
root.mainloop()
print app.ichose
Whatever I do, "ichose" comes out as an empty tuple ().
It's clear that the function "entered" is never called because I never see the test string 'hello'.
I also don't know what the various options are as in "Double-Button-", "<>" etc. Where can I find a list and explanation of each one?
If somebody could please just modify my program so the "print ichose" works, I'd be really grateful. You can see from my program that I don't really know what I'm doing but am keen to learn. Thank you.

I've finally found the answer to my own question. This is REALLY useful if you want to capture multiple responses from a listbox. I've commented a lot. Hope it helps!
import Tkinter as tk
from Tkinter import *
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master=master
self.grid()
self.ichose = []
self.l = Listbox(self, height=10, selectmode=MULTIPLE)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
self.l.grid(column=0, row=0, sticky=(N,W,E,S))
s = Scrollbar(self, orient=VERTICAL, command=self.l.yview)
s.grid(column=0, row=0, sticky=(N,S,E))
self.l['yscrollcommand'] = s.set
for i in range(1,101):
self.l.insert('end', 'Line %d of 100' % i)
# Create Textbox that will display selected items from list
self.selected_list = Text(self,width=20,height=10,wrap=WORD)
self.selected_list.grid(row=12, column=0, sticky=W)
# Now execute the poll() function to capture selected list items
self.ichose = self.poll()
def poll(self):
items =[]
self.ichose = []
# Set up an automatically recurring event that repeats after 200 millisecs
self.selected_list.after(200, self.poll)
# curselection retrieves the selected items as a tuple of strings. These
# strings are the list indexes ('0' to whatever) of the items selected.
# map applies the function specified in the 1st parameter to every item
# from the 2nd parameter and returns a list of the results. So "items"
# is now a list of integers
items = map(int,self.l.curselection())
# For however many values there are in "items":
for i in range(len(items)):
# Use each number as an index and get from the listbox the actual
# text strings corresponding to each index, and append each to
# the list "ichose".
self.ichose.append(self.l.get(items[i]))
# Write ichose to the textbox to display it.
self.update_list()
return self.ichose
def update_list(self):
self.selected_list.delete(0.0, END)
self.selected_list.insert(0.0, self.ichose)
root=tk.Tk()
root.title('Listbox Multi-Capture')
root.geometry('200x340')
app=App(root)
root.mainloop()
print app.ichose

# ----------------[ Listbox EXAMPLE ]----------------
self.sarcCountries = (
"Bangladesh",
"India",
"Pakistan",
"Nepal",
"Bhutan",
"Sri Lanka",
"Afghanistan"
)
self.listData = StringVar(value = self.sarcCountries)
self.listbox = Listbox(
master = self,
height = 10,
listvariable = self.listData,
selectmode = MULTIPLE,
selectbackground = "#BC80CC"
)
self.listbox.bind("<<ListboxSelect>>", self.OnListboxSelectionChanged)
self.listbox.pack(fill = BOTH, expand = 0, padx = 10, pady = 10)
# ----------------[ Listbox EXAMPLE ]----------------
def OnListboxSelectionChanged(self, val):
# NOTE: If your listbox's select mode is MULTIPLE, then you may use this portion of code
selections = val.widget.curselection()
print("---------------------------")
if (selections != ()):
for index in selections:
print(self.sarcCountries[int(index)])
print("---------------------------")

Related

Get Variables from Checkbox in Python

I tried to create a multiple checkboxes and get the information whether they are checked or not. I tried to use tkinter for this purpose. The number of checkboxes would be variable. Up to now, I found a way to create the checkboxes with the following code. With this, 10 checkboxes are created at which people can tick any of them
class Example(tk.Frame):
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.vsb = tk.Scrollbar(self, orient="vertical")
self.text = tk.Text(self, width=40, height=20,
yscrollcommand=self.vsb.set)
self.vsb.config(command=self.text.yview)
self.vsb.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
n=10
for i in range(n):
cb = tk.Checkbutton(self, text="Modul %s" % i)
self.text.window_create("end", window=cb)
self.text.insert("end", "\n")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
However, the information are not saved in any variable. If I add variable to be dumped in the cb, the code will check every checkboxes. The edited code section is as follow (sorry that I couldn't highlight the addition):
class Example(tk.Frame):
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.vsb = tk.Scrollbar(self, orient="vertical")
self.text = tk.Text(self, width=40, height=20,
yscrollcommand=self.vsb.set)
self.vsb.config(command=self.text.yview)
self.vsb.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
n=10
var1 = IntVar()
val =[]
for i in range(n):
cb = tk.Checkbutton(self, text="Modul %s" % i, variable=var1)
self.text.window_create("end", window=cb)
self.text.insert("end", "\n") # to force one checkbox per line
val.append(var1.get())
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Could you help what can I add to the code in order to be able to get the checked Modules by the user? for instance I will get in "val" variable a list with [1, 0, 1, 0, 0, 0, 1, 0, 0, 0] if somebody tick module 0, 2, and 6
I look forward for your feedback.
Are you looking for something like this?
import tkinter as tk
def done():
result = []
for var in variables:
result.append(var.get())
print(result)
root = tk.Tk()
variables = []
check_buttons = []
for i in range(10):
var = tk.IntVar(root)
check_button = tk.Checkbutton(root, text="Modul %i" % i, variable=var)
check_button.pack()
variables.append(var)
check_buttons.append(check_button)
done_button = tk.Button(root, text="Done", command=done)
done_button.pack()
root.mainloop()
I created 10 variables in a list called variables and connected them to each Checkbutton
This is quite simple, the mistake is you are creating one IntVar for all the checkbuttons, what you should be having is different IntVar for different buttons. Then what I want to add is, instead of val being the values from IntVar let it be the IntVar themselves, so later it can be reused. Here I am using bind to print the list, you can create a button and add command option to it.
self.val = [] # self.val if you want to access it outside __init__, or just val
for i in range(n):
var1 = IntVar()
cb = tk.Checkbutton(self, text="Modul %s" % i, variable=var1)
self.text.window_create("end", window=cb)
self.text.insert("end", "\n") # to force one checkbox per line
self.val.append(var1)
self.text.bind('<Return>',lambda e:print([x.get() for x in self.val])) # Dummy function to just print a new list
What is [x.get() for x in self.val] ? It is simple List Comprehension which means:
temp = [] # Any empty list
for x in self.val:
temp.append(x.get())
Here is another way of doing (tho probably other answers will better suit You depending on what You know):
from tkinter import Tk, Checkbutton, IntVar
from functools import partial
def func(n):
print(f'Selected {n + 1}') if variables[n].get() == 1 else print(f'Deselected {n + 1}')
variables = []
root = Tk()
for i in range(10):
variables.append(IntVar())
Checkbutton(root, text=f'Option {i + 1}', variable=variables[i], command=partial(func, i)).pack()
root.mainloop()
(By suggestion #CoolCloud You can replace the shown command with this: command=lambda i=i: func(i), that should also work, also then You don't have to import functools)
However this is a pretty compact setup.
Explanation:
First there is an empty list, where all the Checkbox variables will be stored.
Then in range 10 which means that 10 Checkboxes will be created we do the following:
First we append a variable to the list that will get assigned to each Checkbutton
Then it creates a Checkbutton and adds to it the variable by using indexing and immediately packs it.
About that command:
The partial basically makes it so that the function given in its argument will always get executed with that variable. More about that here
func() function:
print number of option when selected, otherwise print the number when deselected (adding the correct 'selected: no' and 'deselected: no'). It can also be written as:
if all_checkboxes[n][0].get() == 1:
print(f'Selected {n + 1}')
else:
print(f'Deselected {n + 1}')
Hope this helps, if You have any questions ask them.
Source for information about Checkbox and its attributes and arguments.

python - binding to a tkinter widget is called twice if set() is called on a StringVar()

I have an entry, a listbox(dropdown) and another listbox. Whenever more than 3 characters are typed inside the entry. A completion list is looked up and inserted to the dropdown and the dropdown is shown. If an item is selected from the dropdown. It's value should fill the entry and the entry should get the focus again and the cursor should go to the end of the entry. And then, when Enter key is pressed the value of the entry should be inserted to the other listbox.
I've developed a code for that with much help from this utility and the code works perfectly fine. Except, I realized that whenever I select an option from the dropdown the corresponding method is called twice(I get two prints in the console from the same thing). But if I select the first option of the dropdown, it's called once(which is what should have actually happened in the other case) but the focus does not go to the entry (which is a problem).
Here is my code:
from tkinter import *
class Autocomplete(Frame, object):
def __init__(self, width, height, entries, *args, **kwargs):
super(Autocomplete, self).__init__(*args, **kwargs)
self._entries = entries
self.listbox_height = height
self.entry_width = width
self.text = StringVar()
self.entry = Entry(
self,
textvariable=self.text,
width=self.entry_width
)
self.frame = Frame(self)
self.listbox = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width
)
self.dropdown = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width,
background="#cfeff9"
)
def build(self):
self.text.trace("w", lambda name, index, mode, text=self.text: self._update_autocomplete())
self.entry.bind("<Return>", lambda event,: self._add_course())
self.entry.focus_set()
self.entry.pack()
self.frame.pack()
self.listbox.grid(column=0, row=0, sticky=N)
self.dropdown.bind("<<ListboxSelect>>", self._select_entry)
self.dropdown.grid(column=0, row=0, sticky=N)
self.dropdown.grid_forget()
return self
def _update_autocomplete(self):
self.dropdown["height"] = self.listbox_height
self.dropdown.delete(0, END)
text = self.text.get()
if len(text) < 3:
self.dropdown.grid_forget()
return
else:
for entry in self._entries:
if text.lower() in entry.strip().lower():
self.dropdown.insert(END, entry)
listbox_size = self.dropdown.size()
if not listbox_size:
self.dropdown.insert(END, "No results found for '{}'")
self.dropdown["height"] = 1
else:
if listbox_size <= self.dropdown["height"]:
self.dropdown["height"] = listbox_size
self.dropdown.grid(column=0, row=0, sticky=N)
def _select_entry(self, event):
widget = event.widget
value = widget.get(int(widget.curselection()[0]))
print(value)
self.text.set(value)
self.entry.focus_set()
self.entry.icursor(END)
def _add_course(self):
self.listbox.insert(END, self.text.get())
So what am I missing here?
By the way any general improvement to the code will also be much appreciated.
And here is how I call it:
from tkinter import *
from autocomplete import Autocomplete
from main import *
courses = load_courses_from_file("courses.txt")
root = Tk()
autocomplete_frame = Autocomplete(
60,
10,
list(set(course.name + ", " + course.instructor for course in courses))
).build().pack()
mainloop()
The selection of the listbox changes when you click on the item -- this is the default behavior of the listbox. This causes the entry widget value to change, which triggers a call to _update_autocomplete. That function deletes everything in the listbox, causing the selection to change again.

Making Tkinter Filtering Listbox more Dynamic

This is the Tkinter window when calling addFilterList(list)
I called this function like so:
tkWindow = TkWindow()
tkWindow.addFilterList(['A','B','C','D','E','F','G','H','I','J','K','L'])
tkwindow.runwindow()
I have this TKinker class. I am stuck on ways to make this more dynamic. First the scroll bar, buttons, and listbox are hard coded to be in specific places in the window. Is there a way to get this format no matter where on the Tkinter window it appears. For example, If I have a bunch of buttons on top, I would like the this feature to appear in this format without having to go back to the code and change its row or column location.
Second: The way I set it up, there can only be one addFilterList per TkWindow because of the return value. Can someone point me in the right directions in how to alter the code so that I can return the values of multiple Listbox in one Tkinter window.
class TkWindow(object):
def __init__(self):
self.top = tk.Tk()
def addFilterList(self, list_box):
self.list_box = list_box
self.value = []
self.text_field = tk.StringVar()
self.entry = tk.Entry(self.top, textvariable=self.text_field, width=60)
self.listbox = tk.Listbox(self.top, width=40, selectmode=tk.MULTIPLE)
self.entry.grid()
self.listbox.grid(row=7)
self.text_field.trace("w", lambda name, index, mode: self.update_list())
self.button_show = tk.Button(self.top, text="Select",
command=self.selected_item)
self.button_clear = tk.Button(self.top, text="Clear",
command=self.clear)
self.scrollbar = tk.Scrollbar(self.top)
self.show_list = tk.Listbox(self.top, width=60, height=4)
self.scrollbar.grid(row=7, sticky=tk.N + tk.S + tk.E, padx=(10, 50))
self.button_show.grid(row=8, padx=(10, 300))
self.button_clear.grid(row=8, sticky=tk.E, padx=(10, 100))
self.show_list.grid()
# Add scrollbar
self.listbox.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.listbox.yview)
self.update_list()
def update_list(self):
# Used in addFilterList()
search_term = self.text_field.get()
self.listbox.delete(0, tk.END)
for item in self.list_box:
if search_term.lower() in item.lower():
self.listbox.insert(tk.END, item)
def selected_list(self):
# Used in addFilterList()
self.show_list.delete(0, tk.END)
for item in self.value:
self.show_list.insert(tk.END, item)
self.selected = self.listbox.selection_clear(0, tk.END)
def selected_item(self):
# Used in addFilterList()
self.selected = self.listbox.curselection()
for i in self.selected:
self.value.append(self.listbox.get(i))
self.selected_list()
def clear(self):
# Used in addFilterList()
self.value = []
self.show_list.delete(0, tk.END)
def return_value(self):
return self.value
def runWindow(self):
self.top.mainloop()
I'm not sure I understand your question but I will try to offer some advice. I think you are trying to do too many things in the function addFilterList. Your code is hard to read and modify as a result. You have three distinct things to be done:
Initializing the widgets
Laying out the widgets
Populating the widgets with values
I usually do #1 in the constructor. So your constructor would be, in outline:
def __init__(self):
self.top = tk.Tk()
self.entry = tk.Entry(...)
self.listbox = tk.ListBox(...)
Then I do the layout in a separate function, call it doLayout():
def doLayout(self):
self.entry.grid(...)
self.listbox.grid(...)
Now your function addFilterList can be concerned ONLY with adding a list of items to your listbox. You can change the layout without changing this function. You can add additional widgets to the window without changing this function.
If you want to have more than one FilterList, you might consider making a subclass of tk.Listbox. The functions here would set the list contents, clear the list contents, handle list selection events and so forth. Then if you decide you want two lists instead of just one, you can instantiate another instance of this class and add that to your window.

Creating entry and buttons linked in tkinter

I'm creating a GUI where I need to create certain number of entries and buttons in Tkinter. I'd like to create all these in a for loop. As actions, when I press any of the button, it should transfer the value of the Entry to the callback of the button next to it.
This is what I've done so far but it's not working yet.
n=0
self.button = []
self.entFreq = []
for calVal in calibration:
lbl = Label(self.calFrame)
lbl.configure(text = "Set amplitud to " + calVal)
lbl.configure(background=self.bg_App, fg = "white")
lbl.grid(row=n, column=0)
self.entFreq.append(Entry(self.calFrame, width=10))
self.entFreq[n].grid(row=n, column=1, padx = 10)
#Construction Button send frequency
self.button.append(Button(self.calFrame, text="Cal", borderwidth=0, relief="groove", command = lambda n=self.entFreq[n].get(): self.get_val(n)))
self.button[n].configure(bg="#FFF3E0")
self.button[n].grid(row=n, column=2)
n+=1
def get_val(self, var):
print "Got this:", str(var)
I'm just getting blank in the var function. How to link those two?
You're putting too much code into your lambdas. You only need to pass in n, and get_val can do the rest of the work:
self.button.append(Button(..., command=lambda n=n: self.get_val(n)))
...
def get_val(self, n):
value = self.entFreq[n].get()
print "Got this:", value
You might want to consider defining a class for this set of label, entry and button, since they are designed to work together and you're making several sets.
You could, for example,pass in a label and a function to call when the user clicks the button. For example:
class LabelEntry(object):
def __init__(self, parent, text, command):
self.command = command
self.label = Label(parent, text=text)
self.entry = Entry(parent)
self.button = Button(parent, text="Cal", command=self.call_command)
def call_command(self):
value = self.entry.get()
self.command(value)
You would use it something like this:
def some_function(self, value):
print "the value is", value
...
for calVal in calibration:
le = LabelEntry(frame,
text="Set aplitud to " + calVal,
command=self.some_function)
le.label.grid(...)
le.entry.grid(...)
le.button.grid(...)

How to add Autoscroll on insert in Tkinter Listbox?

I'm using a listbox (with scrollbar) for logging:
self.listbox_log = Tkinter.Listbox(root, height = 5, width = 0,)
self.scrollbar_log = Tkinter.Scrollbar(root,)
self.listbox_log.configure(yscrollcommand = self.scrollbar_log.set)
self.scrollbar_log.configure(command = self.listbox_log.yview)
Now, when I do:
self.listbox_log.insert(END,str)
I want the inserted element to be selected. I've tried:
self.listbox_log.selection_anchor(END)
but that doesn't work... Please suggest a solution...
AFAIK the ScrollBar widget doesn't have an auto-scroll feature, but it can be easily implemented by calling the listBox's yview() method after you insert a new item. If you need the new item to be selected then you can do that manually too using the listbox's select_set method.
from Tkinter import *
class AutoScrollListBox_demo:
def __init__(self, master):
frame = Frame(master, width=500, height=400, bd=1)
frame.pack()
self.listbox_log = Listbox(frame, height=4)
self.scrollbar_log = Scrollbar(frame)
self.scrollbar_log.pack(side=RIGHT, fill=Y)
self.listbox_log.pack(side=LEFT,fill=Y)
self.listbox_log.configure(yscrollcommand = self.scrollbar_log.set)
self.scrollbar_log.configure(command = self.listbox_log.yview)
b = Button(text="Add", command=self.onAdd)
b.pack()
#Just to show unique items in the list
self.item_num = 0
def onAdd(self):
self.listbox_log.insert(END, "test %s" %(str(self.item_num))) #Insert a new item at the end of the list
self.listbox_log.select_clear(self.listbox_log.size() - 2) #Clear the current selected item
self.listbox_log.select_set(END) #Select the new item
self.listbox_log.yview(END) #Set the scrollbar to the end of the listbox
self.item_num += 1
root = Tk()
all = AutoScrollListBox_demo(root)
root.title('AutoScroll ListBox Demo')
root.mainloop()
try to do it in this way. (I have copied from another question: How to auto-scroll a gtk.scrolledwindow?) It works fine for me.
def on_TextOfLog_size_allocate(self, widget, event, data=None):
adj = self.scrolled_window.get_vadjustment()
adj.set_value( adj.upper - adj.page_size )

Categories