ttk.treeview selection_set to the item with specific id - python

I want to edit an item in treeview by another Toplevel window and after editing want to refresh / reload items into list from database and set focus to the edited item.
The problem I am facing is to SET FOCUS TO THE EDITED ITEM IN TREEVIEW. Any help will be appreciated.
Here is the minimal sample code.
import tkinter as tk
from tkinter import ttk
class _tree(tk.Frame):
def __init__(self, *args):
tk.Frame.__init__(self, *args)
self.tree = ttk.Treeview(self, columns = ("id", "name"))
self.tree.heading("#0", text = "s.n")
self.tree.heading("#1", text = "id")
self.tree.heading("#2", text = "name")
self.tree.pack()
_items = [[52,"orange"],[61,"manggo"],[1437,"apple"]] # item with id 61 needs to be changed
sn = 1
for r in (_items):
self.tree.insert("", "end", text = str(sn), values = (r[0], r[1]))
sn += 1
self.tree.bind("<Double-Button-1>", self._item)
def _item(self, event):
global item_values
global item_id
global item_name
idx = self.tree.selection()
item_values = self.tree.item(idx)
print("item_values : %s" % item_values)
item_id = self.tree.set(idx, "#1")
item_name = self.tree.set(idx, "#2")
edit_item(self)
class edit_item(tk.Toplevel):
def __init__(self, master, *args):
tk.Toplevel.__init__(self, master)
self.master = master
global item_values
global item_name
lbl1 = tk.Label(self, text = "item name")
self.ent1 = tk.Entry(self)
btn1 = tk.Button(self, text = "update", command = self.update_item)
lbl1.place(x=0, y=10)
self.ent1.place(x=90, y=10)
btn1.place(x=90, y=100)
self.ent1.insert(0, item_name)
def update_item(self):
for i in self.master.tree.get_children():
self.master.tree.delete(i)
new_data = [[52,"orange"],[61,"mango"],[1437,"apple"]] # item with id 61 has been changed
sn = 1
for r in (new_data):
self.master.tree.insert("", "end", text = str(sn), values = (r[0], r[1]))
sn += 1
# Need to set focus on item with id 61
idx = self.master.tree.get_children(item_values['values'][0]) # HERE NEED HELP
self.master.tree.focus_set()
self.master.tree.selection_set(idx)
self.master.tree.focus(idx)
self.destroy()
def main():
root = tk.Tk()
app = _tree()
app.pack()
root.mainloop()
if __name__ == "__main__":
main()
`
I am receiving the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "test_tree.py", line 55, in update_item
idx = self.master.tree.get_children(item_values['values'][0]) # HERE NEED HELP
File "/usr/lib/python3.8/tkinter/ttk.py", line 1225, in get_children
self.tk.call(self._w, "children", item or '') or ())
_tkinter.TclError: Item 61 not found

You don't need to delete the existing values in order to update the value. You can simply use the set() method to update your treeview.
syntax:
tree.set(iid, column=None, value=None)
If you specify only iid in set method it will return items as dict.
Here is a better way to do the same.
from tkinter import ttk
import tkinter as tk
titles={'Id': [1,2,3,4,5, 6, 7, 8, 9], 'Names':['Tom', 'Rob', 'Tim', 'Jim', 'Kim', 'Kim', 'Kim', 'Kim', 'Kim']}
def update(selected_index_iid, changed):
index = treev.index(selected_index_iid)# or just index = treev.index(treev.selection())
treev.set(selected_index_iid, 1, changed) # updating the tree
titles['Names'][index] = changed #updating the dictionary
print(titles)
def clicked(event):
global titles
top = tk.Toplevel(window)
label = tk.Label(top, text='Update: ')
label.pack()
entry = tk.Entry(top)
entry.insert(0, treev.set(treev.selection())['1']) #if you only specify the iid 'set' will return dict of items, ['1'] is to select 1st column
entry.pack()
button= tk.Button(top, text='Update', command=lambda :update(treev.selection(), entry.get()))
button.pack()
window = tk.Tk()
treev = ttk.Treeview(window, selectmode ='browse')
treev.bind('<Double-Button-1>', clicked)
treev.pack(side='left',expand=True, fill='both')
verscrlbar = ttk.Scrollbar(window,
orient ="vertical",
command = treev.yview)
verscrlbar.pack(side ='right', fill ='y')
treev.configure(yscrollcommand = verscrlbar.set)
treev["columns"] = list(x for x in range(len(list(titles.keys()))))
treev['show'] = 'headings'
for x, y in enumerate(titles.keys()):
treev.column(x, minwidth=20, stretch=True, anchor='c')
treev.heading(x, text=y)
for args in zip(*list(titles.values())):
treev.insert("", 'end', values =args)
window.mainloop()

Related

tkinter: my second option menu broke the first one

I made an option menu with tkinter that worked exactly as I wanted to, but when I added a second option menu I started getting error messages and I'm not sure why. I attached the code for the first and second option menu and the error message.
Code for first option menu:
# Option Menu teams
om_team_options = []
for i in team_list:
om_team_options.append(i.name)
var_team = StringVar(root)
var_team.set(om_team_options[0])
om_team = tk.OptionMenu(frm_create_team, var_team, *om_team_options)
om_team.grid(row=0, column=1)
# - Save team button
btn_team_save = tk.Button(frm_create_team, text='Voeg team toe', #Line 92
command=lambda: add_team())
btn_team_save.grid(row=3, column=5)
def add_team():
for obj in team_list:
if var_team.get() == obj.name:
ingame_list.append(obj)
place_team(obj.name)
team_list.remove(obj)
om_team_options.remove(var_team.get())
x = om_team['menu'].index(var_team.get()) #Line 103
om_team['menu'].delete(x)
var_team.set(om_team['menu'].entrycget(0, 'label'))
def place_team(name):
cols, rows = frm_team_list.grid_size()
lbl = tk.Label(frm_team_list, text=name)
lbl.grid(row=rows, column=0)
btn = tk.Button(frm_team_list, text='-',
command=lambda: remove_team(lbl, btn, name))
btn.grid(row=rows, column=1)
def remove_team(lbl, btn, name):
for obj in ingame_list:
if name == obj.name:
team_list.append(obj)
add_om_option(name)
ingame_list.remove(obj)
lbl.destroy()
btn.destroy()
def add_om_option(name):
om_team['menu'].add_command(label=name, command=tk._setit(var_team, name))
om_team_options.append(name)
var_team.set(om_team_options[0])
code for second option menu:
# Option menu existing players
om_players_options = ['Player1', 'Player2', 'Player3']
var_players = StringVar(root)
var_players.set(om_players_options[0])
om_team = tk.OptionMenu(frm_create_team, var_players, *om_players_options)
om_team.grid(row=2, column=1)
Error message:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\max\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "C:\Users\max\Desktop\Coding\Python projects\31_Seconds\Test2.py", line 92, in <lambda>
btn_team_save = tk.Button(frm_create_team, text='Voeg team toe', command=lambda: add_team())
File "C:\Users\max\Desktop\Coding\Python projects\31_Seconds\Test2.py", line 103, in add_team
x = om_team['menu'].index(var_team.get())
File "C:\Users\max\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 3394, in index
i = self.tk.call(self._w, 'index', index)
_tkinter.TclError: bad menu entry index "Team Blauw"
Process finished with exit code 0
Edit 2: minimal runnable example:
import tkinter as tk
from tkinter import StringVar
root = tk.Tk()
root.geometry('400x600')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
# ______________________________________________________________________________________________________________________
# Game Frames
teams = tk.Frame(root, bg='yellow')
teams.grid(sticky='news')
# ______________________________________________________________________________________________________________________
# Teams Frame
# - Team class and instances
class Team:
def __init__(self, name):
self.name = str(name)
self.players = []
self.current_player = int(0)
self.points = int(0)
team_blue = Team('Team Blauw')
team_red = Team('Team Rood')
team_green = Team('Team Groen')
team_yellow = Team('Team Geel')
# - Team lists
team_list = [team_blue, team_red, team_green, team_yellow]
ingame_list = []
# - Frames in teams frame
frm_create_team = tk.Frame(teams)
frm_team_list = tk.Frame(teams)
frm_create_team.grid()
frm_team_list.grid()
# - Buttons and lables for team frame
cols, rows = frm_create_team.grid_size()
tk.Label(frm_create_team, text='Choose a team: ').grid(row=0, column=0)
tk.Label(frm_create_team, text='Existing player: ').grid(row=1, column=0)
# - Option Menu teams
om_team_options = []
for i in team_list:
om_team_options.append(i.name)
var_team = StringVar(root)
var_team.set(om_team_options[0])
om_team = tk.OptionMenu(frm_create_team, var_team, *om_team_options)
om_team.grid(row=0, column=1)
# - Save team button
btn_team_save = tk.Button(frm_create_team, text='Add Team', command=lambda: add_team())
btn_team_save.grid(row=3, column=5)
def add_team():
for obj in team_list:
if var_team.get() == obj.name:
ingame_list.append(obj)
place_team(obj.name)
team_list.remove(obj)
om_team_options.remove(var_team.get())
x = om_team['menu'].index(var_team.get())
om_team['menu'].delete(x)
var_team.set(om_team['menu'].entrycget(0, 'label'))
def place_team(name):
column, row = frm_team_list.grid_size()
lbl = tk.Label(frm_team_list, text=name)
lbl.grid(row=row, column=0)
btn = tk.Button(frm_team_list, text='-', command=lambda: remove_team(lbl, btn, name))
btn.grid(row=row, column=1)
def remove_team(lbl, btn, name):
for obj in ingame_list:
if name == obj.name:
team_list.append(obj)
add_om_option(name)
ingame_list.remove(obj)
lbl.destroy()
btn.destroy()
def add_om_option(name):
om_team['menu'].add_command(label=name, command=tk._setit(var_team, name))
om_team_options.append(name)
var_team.set(om_team_options[0])
# - Option menu existing players
om_players_options = ['Player1', 'Player2', 'Player3']
var_players = StringVar(root)
var_players.set(om_players_options[0])
om_team = tk.OptionMenu(frm_create_team, var_players, *om_players_options)
om_team.grid(row=1, column=1)
root.mainloop()

Python Tkinter Spinboxes return value using for loops

I'm trying to use X many spin boxes as there are items in a list - so have put them on screen using a for loop.
However, I don't seem able to get them to return their value when I click a button.
aStarter = ["Starter 1", "Starter 2", "Starter 3"]
def getOrders():
aStarterOrder = []
aMainOrder = []
aDessertOrder = []
for i in aStarterQuantity:
aStarterOrder.append(StarterQuantity.get())
print(aStarterOrder)
aStarterQuantity = []
StarterQuantity = IntVar()
for i in range(len(aStarter)):
lbl = Label(self, text = aStarter[i]).grid(column = 0, row = 2 + i)
StarterQuantity = Spinbox(self, from_=0, to=20, width=5, command=callback, font=Font(family='Helvetica', size=20, weight='bold')).grid(column = 1, row = 2 + i)
print(StarterQuantity.get())
I have tried appending the StarterQuantity to an array inside of the for loop:
aStarterQuantity = []
StarterQuantity = IntVar()
for i in range(len(aStarter)):
lbl = Label(self, text = aStarter[i]).grid(column = 0, row = 2 + i)
StarterQuantity = Spinbox(self, from_=0, to=20, width=5, command=callback, font=Font(family='Helvetica', size=20, weight='bold')).grid(column = 1, row = 2 + i)
aStarterQuantity.append(StarterQuantity.get())
This returns a None Type error - I can't seem to get the value out of the spin box.
Any ideas?
Here is the full code (it's very much an early WIP!)
from tkinter import *
from tkinter.font import Font
import sqlite3
DatabaseFile = 'RMS.db'
connDatabase = sqlite3.connect(DatabaseFile)
#Creating a cursor to run SQL Commands
DatabaseSelect = connDatabase.cursor()
#Selecting all Menu Data
DatabaseSelect.execute("SELECT * FROM MENU")
MenuData = DatabaseSelect.fetchall()
class Main(Tk): #This sets up the initial Frame in a Window called Main
def __init__(self): #This creates a controller to use the Frames
Tk.__init__(self) #This creates a Window within the controller
self._frame = None #This sets the frame to have a value of None - this makes sure the main Window doesn't get destroyed by the switch frame function
self.switch_frame(Home)
def switch_frame(self, frame_class): #This function is used to switch frames. Self is the master frame, frame_class is the name of the Class (page) being passed int
new_frame = frame_class(self)
if self._frame is not None:
self._frame.destroy() #Destroy any frames except the master
self._frame = new_frame #Make a new frame
self._frame.grid(column=0, row=0) #Put the frame in the top left
class Home(Frame):
def __init__(self, master):
Frame.__init__(self, master)
Label(self, text="The White Horse").grid(column = 0, row = 0)
btnPage1 = Button(self, text="Orders", command=lambda: master.switch_frame(Orders)).grid(column = 1, row = 0)
#Making a blank page with a return home button
class Orders(Frame): #This sets up a class called Page1 - Each new page will need a new class.
def __init__(self, master): #This sets up a controller to put things into the Frame
Frame.__init__(self, master) #Make the frame using the controller
#Get list of Starters from Menu Data
aStarter = ["Starter 1", "Starter 2", "Starter 3"]
aMain = []
aDessert = []
for i in MenuData:
if i[3] == "Starters":
aStarter.append(i[1])
if i[3] == "Main":
aMain.append(i[1])
if i[3] == "Dessert":
aDessert.append(i[1])
def getOrders():
aStarterOrder = []
aMainOrder = []
aDessertOrder = []
for i in aStarterQuantity:
aStarterOrder.append(StarterQuantity.get())
print(aStarterOrder)
def callback():
print(StarterQuantity.get())
#Starter Section
boxes = []
aStarterQuantity = []
StarterQuantity = IntVar()
for i in range(len(aStarter)):
lbl = Label(self, text = aStarter[i]).grid(column = 0, row = 2 + i)
StarterQuantity = Spinbox(self, from_=0, to=20, width=5, command=callback, font=Font(family='Helvetica', size=20, weight='bold')).grid(column = 1, row = 2 + i)
aStarterQuantity.append(StarterQuantity.get())
#Mains Sections
for i in range(len(aMain)):
lbl = Label(self, text = aMain[i]).grid(column = 6, row = 2 + i)
MainQuantity = Spinbox(self, from_=0, to=20, width=5, font=Font(family='Helvetica', size=20, weight='bold')).grid(column = 7, row = 2 + i)
#Dessert Section
for i in range(len(aDessert)):
lbl = Label(self, text = aDessert[i]).grid(column = 12, row = 2 + i)
DessertQuantity = Spinbox(self, from_=0, to=20, width=5, font=Font(family='Helvetica', size=20, weight='bold')).grid(column = 13, row = 2 + i)
btnHome = Button(self, text="Home", command=lambda: master.switch_frame(Home)).grid(column = 0, row = 1) #Add a Button
btnSubmitEntries = Button(self, text = "Submit", command = getOrders).grid(column = 0, row= 20)
Main = Main()
Main.geometry("1200x800")
Main.mainloop()
The problem seems to be that the grid method doesn't return anything, so StarterQuantity gets assigned None i.e. the default return value for a function.
for i in range(len(aStarter)):
lbl = Label(self, text=aStarter[i])
lbl.grid(column=0, row=2 + i)
sq_font = Font(family='Helvetica',
size=20,
weight='bold')
StarterQuantity = Spinbox(self,
from_=0,
to=20,
width=5, command=callback,
font=sq_font)
StarterQuantity.grid(column=1, row=2 + i)
aStarterQuantity.append(StarterQuantity.get())
This works in my case. (Pulled out the Font argument for Spinbox to increase readability on mobile).
Unrelated note; I don't know what code style you prefer/have to maintain and it is up to you of course, but I thought it might be helpful to mention that the naming style convention in Python (PEP8) is to use CamelCase for class names, lower_case_w_underscores for variable names and functions/methods, and SCREAMING_SNAKE for constants.

Creating a settings text file for python tkinter form widgets

I am creating a form for session variables in python and (currently) store the inputs in separate text files. How could I use a single file to hold all session variables in lists (there will be more than 2 in the final form)? What would I need to change in my code to make this more efficient? Any help is greatly appreciated.
import tkinter
from tkinter import *
from tkinter import ttk
regionList = open('regions.txt','r')
optionList = open('options.txt','r')
class MainWindow(Frame):
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
self.init_window()
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create Window Layout"""
Boxfont = ('Lucida Grande', 12)
self.label1 = Label(self, font=Boxfont,
text="Regions").grid(row=2,column=0)
self.regcombo = ttk.Combobox(self, font = Boxfont, width = 20, textvariable = varRegions)
self.regcombo.bind("<Return>", self.regcombo_onEnter)
self.regcombo.bind('<<ComboboxSelected>>',self.regcombo_onEnter)
self.regcombo['values'] = regionList.readlines()
self.regcombo.grid(row=2, column=1,sticky = W)
self.label2 = Label(self, font=Boxfont, text="Options").grid(row=4,column=0)
self.optcombo = ttk.Combobox(self, font = Boxfont, width = 20, textvariable = varOptions)
self.optcombo.bind("<Return>", self.optcombo_onEnter)
self.optcombo.bind('<<ComboboxSelected>>',self.optcombo_onEnter)
self.optcombo['values'] = optionList.readlines()
self.optcombo.grid(row=4, column=1,sticky = W)
def init_window(self):
self.master.title("User Settings")
self.pack(fill=BOTH, expand=1)
def regcombo_onEnter(self,event):
varRegions.set(varRegions.get().lower())
mytext = varRegions.get()
vals = self.regcombo.cget('values')
self.regcombo.select_range(0,END)
print(mytext)
if not vals:
self.regcombo.configure(values = (mytext.strip,))
elif mytext not in vals:
with open('regions.txt','a') as f:
f.write('\n'+ mytext)
self.regcombo.configure(values = vals + (mytext,))
f.close
return 'break'
def optcombo_onEnter(self,event):
varOptions.set(varOptions.get().lower())
mytext = varOptions.get()
vals = self.optcombo.cget('values')
self.optcombo.select_range(0,END)
print(mytext)
if not vals:
self.optcombo.configure(values = (mytext.strip,))
elif mytext not in vals:
with open('options.txt','a') as f:
f.write('\n'+ mytext)
self.optcombo.configure(values = vals + (mytext,))
f.close
return 'break'
root = tkinter.Tk()
root.geometry("600x600")
varRegions = tkinter.StringVar(root, value='')
varOptions = tkinter.StringVar(root, value='')
app = MainWindow(root)
root.mainloop()

How do I enable multiple selection of values from a combobox?

Python 3.4.3, Windows 10, Tkinter
I am attempting to create a combobox that allows for multiple selections from the dropdown. I have found similar work for listbox (Python Tkinter multiple selection Listbox), but cannot get it work with the combobox.
Is there a simple way to enable multiple selection from the dropdown of the combobox?
By design the ttk combobox doesn't support multiple selections. It is designed to allow you to pick one item from a list of choices.
If you need to be able to make multiple choices you can use a menubutton with an associated menu, and add checkbuttons or radiobuttons to the menu.
Here's an example:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
menubutton = tk.Menubutton(self, text="Choose wisely",
indicatoron=True, borderwidth=1, relief="raised")
menu = tk.Menu(menubutton, tearoff=False)
menubutton.configure(menu=menu)
menubutton.pack(padx=10, pady=10)
self.choices = {}
for choice in ("Iron Man", "Superman", "Batman"):
self.choices[choice] = tk.IntVar(value=0)
menu.add_checkbutton(label=choice, variable=self.choices[choice],
onvalue=1, offvalue=0,
command=self.printValues)
def printValues(self):
for name, var in self.choices.items():
print "%s: %s" % (name, var.get())
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Try this maybe...
import tkinter as Tkinter
import tkinter.font as tkFont
import tkinter.ttk as ttk
class Picker(ttk.Frame):
def __init__(self, master=None,activebackground='#b1dcfb',values=[],entry_wid=None,activeforeground='black', selectbackground='#003eff', selectforeground='white', command=None, borderwidth=1, relief="solid"):
self._selected_item = None
self._values = values
self._entry_wid = entry_wid
self._sel_bg = selectbackground
self._sel_fg = selectforeground
self._act_bg = activebackground
self._act_fg = activeforeground
self._command = command
ttk.Frame.__init__(self, master, borderwidth=borderwidth, relief=relief)
self.bind("<FocusIn>", lambda event:self.event_generate('<<PickerFocusIn>>'))
self.bind("<FocusOut>", lambda event:self.event_generate('<<PickerFocusOut>>'))
self._font = tkFont.Font()
self.dict_checkbutton = {}
self.dict_checkbutton_var = {}
self.dict_intvar_item = {}
for index,item in enumerate(self._values):
self.dict_intvar_item[item] = Tkinter.IntVar()
self.dict_checkbutton[item] = ttk.Checkbutton(self, text = item, variable=self.dict_intvar_item[item],command=lambda ITEM = item:self._command(ITEM))
self.dict_checkbutton[item].grid(row=index, column=0, sticky=Tkinter.NSEW)
self.dict_intvar_item[item].set(0)
class Combopicker(ttk.Entry, Picker):
def __init__(self, master, values= [] ,entryvar=None, entrywidth=None, entrystyle=None, onselect=None,activebackground='#b1dcfb', activeforeground='black', selectbackground='#003eff', selectforeground='white', borderwidth=1, relief="solid"):
if entryvar is not None:
self.entry_var = entryvar
else:
self.entry_var = Tkinter.StringVar()
entry_config = {}
if entrywidth is not None:
entry_config["width"] = entrywidth
if entrystyle is not None:
entry_config["style"] = entrystyle
ttk.Entry.__init__(self, master, textvariable=self.entry_var, **entry_config, state = "readonly")
self._is_menuoptions_visible = False
self.picker_frame = Picker(self.winfo_toplevel(), values=values,entry_wid = self.entry_var,activebackground=activebackground, activeforeground=activeforeground, selectbackground=selectbackground, selectforeground=selectforeground, command=self._on_selected_check)
self.bind_all("<1>", self._on_click, "+")
self.bind("<Escape>", lambda event: self.hide_picker())
#property
def current_value(self):
try:
value = self.entry_var.get()
return value
except ValueError:
return None
#current_value.setter
def current_value(self, INDEX):
self.entry_var.set(values.index(INDEX))
def _on_selected_check(self, SELECTED):
value = []
if self.entry_var.get() != "" and self.entry_var.get() != None:
temp_value = self.entry_var.get()
value = temp_value.split(",")
if str(SELECTED) in value:
value.remove(str(SELECTED))
else:
value.append(str(SELECTED))
value.sort()
temp_value = ""
for index,item in enumerate(value):
if item!= "":
if index != 0:
temp_value += ","
temp_value += str(item)
self.entry_var.set(temp_value)
def _on_click(self, event):
str_widget = str(event.widget)
if str_widget == str(self):
if not self._is_menuoptions_visible:
self.show_picker()
else:
if not str_widget.startswith(str(self.picker_frame)) and self._is_menuoptions_visible:
self.hide_picker()
def show_picker(self):
if not self._is_menuoptions_visible:
self.picker_frame.place(in_=self, relx=0, rely=1, relwidth=1 )
self.picker_frame.lift()
self._is_menuoptions_visible = True
def hide_picker(self):
if self._is_menuoptions_visible:
self.picker_frame.place_forget()
self._is_menuoptions_visible = False
if __name__ == "__main__":
import sys
try:
from Tkinter import Tk, Frame, Label
except ImportError:
from tkinter import Tk, Frame, Label
root = Tk()
root.geometry("500x600")
main =Frame(root, pady =15, padx=15)
main.pack(expand=True, fill="both")
Label(main, justify="left", text=__doc__).pack(anchor="w", pady=(0,15))
COMBOPICKER1 = Combopicker(main, values = [1, 2, 3, 4])
COMBOPICKER1.pack(anchor="w")
if 'win' not in sys.platform:
style = ttk.Style()
style.theme_use('clam')
root.mainloop()

Listbox filtering using a combo box

I'm trying to filter the content that is displayed in my listbox depending on the currently selected item of a combo box. I'm not sure how I can accomplish this and I have not found even a simple, plain example online. Any ideas? I'd really appreciate some help.
Here are code excerpts.
Listbox:
class MyListBox(object):
def __init__(self, frame, listbox_header, listbox_list):
self.tree = ttk.Treeview(frame, columns=listbox_header, show='headings')
yScroll = ttk.Scrollbar(frame,orient='vertical', command=self.tree.yview)
yScroll.pack(side=RIGHT, fill=Y)
self.tree.config(yscrollcommand=yScroll.set)
self.tree.pack(side=LEFT, fill=Y, expand=TRUE)
for col in listbox_header:
self.tree.heading(col, text=col.title(),command=lambda c=col: sortby(self.tree, c, 0))
self.update_rows(listbox_list)
self.tree.bind('<Double-1>', self.OnDoubleClick)
def update_rows(self, listbox_list):
items = self.tree.get_children()
for item in items:
self.tree.delete(item)
for item in listbox_list:
self.tree.insert('', 'end', values=item)
def OnDoubleClick(self, event):
item = self.tree.selection()[0]
self.Info(self.tree.item(item, 'values'))
#Single student information window. Display all courses in student listbox
def Info(self, selected):
info = Toplevel()
info.title('Student Information: %s - %s - %s' % (selected[0], selected[1], selected[2]))
info.geometry('%dx%d+%d+%d' % (WIDTH, HEIGHT, 50, 50))
student = session.query(Student).filter_by(id=selected[0]).first()
#Single student header label info
Label(info, text='Total All Attempts: %s' % student.gpa).pack()
Label(info, text='Total Best Attempts: %s' % student.best_gpa).pack()
Label(info, text='Spec GPAs: %s' % student.rules_gpas).pack()
Label(info, text='Spec GPAs Needed: %s' % student.rules_gpas_needed).pack()
#Single Student course info list
Label(info, text='\nAll Courses:').pack()
current = session.query(Course.sid, Course.term, Course.subject, Course.number,
Course.title, Course.grade, Course.grade_val, Course.hours).\
order_by(Course.term.desc(), Course.subject, Course.number, Course.grade_val.desc()).filter_by(sid=selected[0])
course_header = ['ID', 'term', 'subject', 'number', 'title', 'grade', 'grade_val', 'hours']
#setup listbox and scroll bars
tree = ttk.Treeview(info, columns=course_header, show='headings')
yScroll = ttk.Scrollbar(info, orient='vertical', command=tree.yview)
yScroll.pack(side=RIGHT, fill=Y)
tree.config(yscrollcommand=yScroll.set)
tree.pack(side=LEFT, fill=BOTH, expand=TRUE)
for col in course_header:
tree.heading(col, text=col.title(), command=lambda c=col: sortby(tree, c, 0))
tree.column(col, width=50, anchor='center')
tree.column('title', width=150, anchor='w')
for item in current:
tree.insert('', 'end', values=item)
And here is the combo box:
# This creates the drop menu (combobox)
Label(top, text='View Concentration:').pack(side=LEFT)
box_value = StringVar()
box = ttk.Combobox(top, textvariable=box_value, state='readonly')
titles = []
titles.append('All')
for rule in rules:
titles.append((rule.title))
box['values'] = titles
box.current(0)
box.pack(side=LEFT)
From what I understand, what you are trying to do is clear and repopulate the listbox every time the combobox changes. Surprisingly, it's not too difficult.
Here is my example app. It utilizes root.after to recursively check to see if the combo box has changed. Note that there is probably a way to bind the update function to the StringVar.set command, I just don't know what it is. You could also subclass with the StringVar, but regardless:
# Import
import Tkinter
import ttk
# Class
class App:
# Init
def __init__(self):
self.options = ["All", "Odd", "Even"] # Combobox elements
self.values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # Treeview elements
self.last_filter_mode = "" # Combobox change detector
self.create()
# Build GUI
def create(self):
self.root = Tkinter.Tk() # Can be substituted for Toplevel
self.root.title("Filtered Listbox")
self.frame = Tkinter.Frame(self.root)
# I like to use pack because I like the aesthetic feel
# pady is 5 so that the widgets in the frame are spaced evenly
self.frame.pack(fill="both", padx=10, pady=5)
self.filter_mode = Tkinter.StringVar(); # Combobox StringVar
# Set it to the initial value of combobox
# Also consider using self.options[0] for uniformity
self.filter_mode.set("All")
self.combobox = ttk.Combobox(
self.frame, textvariable=self.filter_mode, state="readonly",
values=self.options)
self.combobox.pack(fill="x", pady=5)
# So that the scroll bar can be packed nicely
self.treeview_frame = Tkinter.Frame(self.frame)
self.treeview_frame.pack(fill="x", pady=5)
column_headings = ["A", "B", "C"] # These are just examples
self.treeview = ttk.Treeview(
self.treeview_frame, columns=column_headings, show="headings")
self.treeview.pack(fill="y", side="left")
self.treeview_scroll = ttk.Scrollbar(
self.treeview_frame, orient="vertical", command=self.treeview.yview)
self.treeview_scroll.pack(fill="y", side="right")
self.treeview.config(yscrollcommand=self.treeview_scroll.set)
# Recursize update function called with root.after
def update(self):
filter_mode = self.filter_mode.get()
# Check for change in the filter_mode
if filter_mode != self.last_filter_mode:
items = self.treeview.get_children()
for item in items:
self.treeview.delete(item) # Clear the treeview
# Combobox options
if filter_mode == "All":
for element in self.values:
self.treeview.insert("", "end", values=(element))
if filter_mode == "Odd":
for element in filter(
lambda x: True if x % 2 != 0 else False, self.values):
self.treeview.insert("", "end", values=(element))
if filter_mode == "Even":
for element in filter(
lambda x: True if x % 2 == 0 else False, self.values):
self.treeview.insert("", "end", values=(element))
self.last_filter_mode = filter_mode # Update current filter mode
self.root.after(100, self.update) # Call this function again
def main(self):
self.update() # Start the recursive update function
self.root.mainloop() # Start the app
a = App()
a.main()
I know it's a little bit complex, and integrating it into your current code may be difficult, but if you have any questions feel free to ask! Hope this helps!

Categories

Resources