I am trying to do a dynamic search function, where the user also can select multiple items in a list. If we consider this example (I added this part, selectmode=MULTIPLE):
from Tkinter import *
# First create application class
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.create_widgets()
# Create main GUI window
def create_widgets(self):
self.search_var = StringVar()
self.search_var.trace("w", self.update_list)
self.entry = Entry(self, textvariable=self.search_var, width=13)
self.lbox = Listbox(self,selectmode=MULTIPLE, width=45, height=15)
self.entry.grid(row=0, column=0, padx=10, pady=3)
self.lbox.grid(row=1, column=0, padx=10, pady=3)
self.btn = ttk.Button(self, text="Select", command=self.Select)
self.btn.grid(column=1, row=1)
# Function for updating the list/doing the search.
# It needs to be called here to populate the listbox.
self.update_list()
def update_list(self, *args):
search_term = self.search_var.get()
# Just a generic list to populate the listbox
lbox_list = ['Adam', 'Lucy', 'Barry', 'Bob',
'James', 'Frank', 'Susan', 'Amanda', 'Christie']
self.lbox.delete(0, END)
for item in lbox_list:
if search_term.lower() in item.lower():
self.lbox.insert(END, item)
def Select(self):
reslist = list()
selecion = self.lbox.curselection()
for i in selecion:
entered = self.lbox.get(i)
reslist.append(entered)
print reslist
root = Tk()
root.title('Filter Listbox Test')
app = Application(master=root)
print 'Starting mainloop()'
app.mainloop()
The search function works perfectly fine, however, once a search has been done and an item has been selected, the selections is not saved since the lbox.delete function is used in update_list. Is there a way to keep each item selected while using the search function?
This is what I came up with. I don't know if you need it anymore but I'll post it anyway.
I've basically added code to set values as selected if they are in user selection list after the listbox is refreshed on a change in Entry widget, and to remove it from user selection if user deselects irrespective of what is currently in the listbox.
from tkinter import *
sel=list()
# First create application class
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.create_widgets()
def CurSelet(self,evt):
global sel
temp=list()
for i in self.lbox.curselection():
temp.append(self.lbox.get(i))
allitems=list()
for i in range(self.lbox.size()):
allitems.append(self.lbox.get(i))
for i in sel:
if i in allitems:
if i not in temp:
sel.remove(i)
for x in self.lbox.curselection():
if self.lbox.get(x) not in sel:
sel.append(self.lbox.get(x))
def select(self):
global sel
s=', '.join(map(str,sel))
self.cursel.set('Current Selection: '+s)
# Create main GUI window
def create_widgets(self):
self.search_var = StringVar()
self.search_var.trace("w", lambda name, index, mode: self.update_list())
self.entry = Entry(self, textvariable=self.search_var, width=13)
self.lbox = Listbox(self, selectmode=MULTIPLE,width=45, height=15)
self.lbox.bind('<<ListboxSelect>>',self.CurSelet)
self.entry.grid(row=0, column=0, padx=10, pady=3)
self.lbox.grid(row=1, column=0, padx=10, pady=3)
self.btn=Button(self,text='Okay', command=self.select, width=20)
self.btn.grid(row=2,column=0, padx=10, pady=3)
self.cursel=StringVar()
self.lb1=Label(self,textvariable=self.cursel)
self.lb1.grid(row=3,column=0,padx=10,pady=3)
# Function for updating the list/doing the search.
# It needs to be called here to populate the listbox.
self.update_list()
def update_list(self):
global sel
global l
search_term = self.search_var.get()
# Just a generic list to populate the listbox
lbox_list = ['Adam', 'Lucy', 'Barry', 'Bob',
'James', 'Frank', 'Susan', 'Amanda', 'Christie']
self.lbox.delete(0, END)
for item in lbox_list:
if search_term.lower() in item.lower():
self.lbox.insert(END, item)
allitems=list()
for i in range(self.lbox.size()):
allitems.append(self.lbox.get(i))
for i in sel:
if i in allitems:
self.lbox.select_set(self.lbox.get(0, "end").index(i))
root = Tk()
root.title('Filter Listbox Test')
Label(root, text='Search enabled').pack()
app = Application(master=root)
print('Starting mainloop()')
app.mainloop()
Related
I have a code that gets keyinputs and show it on a entrybox in the toplevel window of the main window. In the main window I have a listbox and I am wishing to get the keyinput that is shown on the entrybox to be enlisted when the confirm button is pressed on toplevel.
I tried several ways to get evt.keysym to my listbox but all failed.
class EntryBox(tk.Entry):
def __init__(self, master, cnf = {}, **kw):
kw = tk._cnfmerge((kw, cnf))
kw['justify'] = kw.get('justify', 'center')
kw['width'] = 15
kw['state'] = 'readonly'
super(EntryBox, self).__init__(master=master, **kw)
self.unbind_class('Entry', '<BackSpace>')
self.unbind_class('Entry', '<Key>')
self.bind_class(self, '<Key>', self._display)
def _display(self, evt):
self['state'] = 'normal'
self.delete('0', 'end')
self.insert('0', str(evt.keysym))
self['state'] = 'readonly'
class Keyboard:
def __init__(self):
self.kb = tk.Toplevel()
kb_frame = ttk.Frame(self.kb)
kb_frame.grid(column=0, row=1, pady=(7, 19))
ttk.Label(kb_frame, text='Enter Key').grid(column=0, row=0, pady=4)
entry = EntryBox(kb_frame)
entry.grid(column=0, row=1)
# Confirm button
self.co_button = ttk.Button(self.kb, text='Confirm')
self.co_button.grid(column=0, row=2)
class Main:
def __init__(self):
self.win = tk.Tk()
# listbox
lb_frame = tk.Frame(self.win)
lb_frame.grid(column=0, row=0)
scrollbar = tk.Scrollbar(lb_frame, orient='vertical')
scrollbar.grid(column=1, row=0, sticky='NS', pady=(12, 4))
listbox = tk.Listbox(lb_frame, selectmode='extended', width=25,
height=16,
yscrollcommand=scrollbar.set, activestyle='none')
listbox.grid(column=0, row=0, sticky='NSEW', padx=(6, 0), pady=(12, 4))
scrollbar.config(command=listbox.yview)
# button to open toplevel
bt_frame = ttk.Frame(self.win)
bt_frame.grid(column=2, row=0, rowspan=2)
self.kb_button = ttk.Button(bt_frame, text='KeyBoard', command=KeyBoard)
self.kb_button.grid(column=0, row=0)
main = Main()
main.win.mainloop()
To get values from one class to another class you've to link them. Inheriting the Widgets directly to the class will help you a lot in establishing a connection between Tk() window and Toplevel() Window.
One more thing when a Keyboard window is already opened disable the button by configure state = 'disabled' so the user won't open another one by mistake and when Keyboard window is closed re-enable the button by state = 'normal'.
Here is the complete code:
import tkinter as tk
import tkinter.ttk as ttk
class EntryBox(tk.Entry):
def __init__(self, master, cnf = {}, **kw):
kw = tk._cnfmerge((kw, cnf))
kw['justify'] = kw.get('justify', 'center')
kw['width'] = 15
kw['state'] = 'readonly'
super(EntryBox, self).__init__(master=master, **kw)
self.bind_class(self, '<Key>', self._display)
def _display(self, evt):
self['state'] = 'normal'
self.delete('0', 'end')
self.insert('0', str(evt.keysym))
self['state'] = 'readonly'
class Keyboard(tk.Toplevel):
def __init__(self, master=None, cnf={}, **kw):
super(Keyboard, self).__init__(master=master, cnf=cnf, **kw)
self.master = master
kb_frame = ttk.Frame(self)
kb_frame.grid(column=0, row=1, pady=(7, 19))
ttk.Label(kb_frame, text='Enter Key').grid(column=0, row=0, pady=4)
self.entry = EntryBox(kb_frame)
self.entry.grid(column=0, row=1)
# This protocol calls the function when clicked on 'x' on titlebar
self.protocol("WM_DELETE_WINDOW", self.Destroy)
# Confirm button
self.co_button = ttk.Button(self, text='Confirm', command=self.on_press)
self.co_button.grid(column=0, row=2)
def on_press(self):
key = self.entry.get()
# Condition to not have duplicate values, If you want to have duplicate values remove the condition
if key not in self.master.lb.get('0', 'end') and key:
# Insert the Key to the listbox of mainwindow
self.master.lb.insert('end', key)
def Destroy(self):
# Enable the button
self.master.kb_button['state'] = 'normal'
# Then destroy the window
self.destroy()
class Main(tk.Tk):
def __init__(self):
super(Main, self).__init__()
bt_frame = ttk.Frame(self)
bt_frame.grid(column=2, row=0, rowspan=2)
self.kb_button = ttk.Button(bt_frame, text='KeyBoard', command=self.on_press)
self.kb_button.grid(column=0, row=0)
self.lb = tk.Listbox(bt_frame)
self.lb.grid(column=0, row=1)
def on_press(self):
# Creating a toplevel window and passed self as master parameter
self.Kb = Keyboard(self)
# Disabled the button
self.kb_button['state'] = 'disabled'
main = Main()
main.mainloop()
I'm building a desktop application that lets you insert some data into a form and then the data is displayed in a series (3) of Treeview widgets.
This is the form that I'm using to enter new data:
It's in a Toplevel widget. When the Add button is pressed the new data is stored in a file and it also should insert the new data in the corresponding Treeview Widget.
This is the root window:
It's comprised of 3 Treeview widgets. The purpose of the application is to give the user the opportunity to sort candidates into the right Treeview widget.
The issue that I'm facing is that when the Add button is pressed the new data is not shown in the Treeview widget and no errors are given. I think it may be an issue of class instantiation. This is an excerpt from my app, please see below a Minimal, Complete and Verifiable example
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
...
# frame and menu classes are instantiated here
self.FrameList = {ViableCandidates: ViableCandidates(self),
NotViableCandidates: NotViableCandidates(self),
InProgressCandidates: InProgressCandidates(self)}
...
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
...
# menu code is here
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
...
# code for the small Toplevel window
...
# this is the code that I use to add the new item to Treeview when the Add button is pressed
if CandidateInfo['status'] == 'Viable':
app.InstanceLinker(ViableCandidates).AddtoList()
elif CandidateInfo['status'] == 'Not Viable':
app.InstanceLinker(NotViableCandidates).AddtoList()
else:
app.InstanceLinker(InProgressCandidates).AddtoList()
# ViableCandidates, NotViableCandidates, InProgressCandidates are created with the same pattern
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
global Counter
tk.Frame.__init__(self, parent)
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
title = tk.Label(self, text="Candidates In Progress", font="Verdana 10 bold")
title.grid(row=0, column=0, sticky='nesw')
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns=('Name', 'Date'), selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('Name', width=150, minwidth=10, stretch=tk.YES)
self.tree.column('Date', width=80, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('Name', text='Name', anchor=tk.W)
self.tree.heading('Date', text='Date', anchor=tk.W)
if Counter < 4:
Counter += 1
self.PopulateList()
def PopulateList(self):
selection = Database().SelectFromDB('name, date', "status = 'In progress'")
for i in range(len(selection)):
name = list(selection[i])[0]
date = adjusttotimezone(list(selection[i])[1])
self.tree.insert("", i, name, text=i + 1)
self.tree.set(name, 'Name', name)
self.tree.set(name, 'Date', date)
CandidateCounter['InProgressCandidates'] = i
def AddtoList(self):
CandidateCounter['InProgressCandidates'] += 1
print('I was here')
self.tree.insert("", CandidateCounter['InProgressCandidates'], CandidateInfo['name'],
text=CandidateCounter['InProgressCandidates'])
self.tree.set(CandidateInfo['name'], 'Name', CandidateInfo['name'])
selection = Database().SelectFromDB('date', "name = '" + CandidateInfo['name'] + "'")
date = adjusttotimezone(list(selection[0])[0])
self.tree.set(CandidateInfo['name'], 'Date', date)
app = MainApp()
app.mainloop()
When the "Add" button is pressed there are no errors and "I was here" is printed so the AddtoList method is instantiated, but there are no new items added to Treeview. I did check if the variables that I'm using to create the new Treeview item hold the correct data and they do.
EDIT: This is a Minimal, Complete and Verifiable example:
import tkinter as tk
from tkinter import ttk
Bigbadtext = ''
Counter = 0
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
self.MainWindow = tk.Tk.__init__(self, *args, **kwargs)
menu = GUIMenu(self)
self.config(menu=menu)
frame = InProgressCandidates(self)
frame.grid(row=0, column=1, sticky='nesw')
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
tk.Menu.__init__(self, parent)
addcandidates = tk.Menu(self, tearoff=0)
self.add_cascade(label='Add Candidates', menu=addcandidates)
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
saysomething = tk.Entry(self)
saysomething.grid(row=1, column=0)
def addbutton():
global Bigbadtext
Bigbadtext = saysomething.get()
app.InstanceLinker(InProgressCandidates).AddtoList()
okbutton = ttk.Button(self, text='Add', command=addbutton)
okbutton.grid(row=2, column=0)
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns='something', selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('something', width=150, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('something', text='Say something', anchor=tk.W)
def AddtoList(self):
global Counter
Counter += 1
print('I was here')
self.tree.insert("", Counter, Bigbadtext, text=Counter)
self.tree.set(Bigbadtext, 'something', Bigbadtext)
app = MainApp()
app.mainloop()
The problem is that you are creating two treeview widgets, and then adding items to the one that is invisible.
You create one here:
frame = InProgressCandidates(self)
Then you create another one here:
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
Since you've already created one, the one you created should be what goes in self.FrameList:
self.FrameList = {InProgressCandidates:frame}
It is not really an answer but I up voted the question because it solved me a problem. I wanted to add items to the widget but did not want to show it to the user until I finished to populate the tree. But each insert showed right away. Now I create 2 identical widgets, one visible and the other is not, and once it is populated I change between them. Thus even a mistake can have a benefit.
I have two listboxes on tkinter, what I'd like to do is double-click the item in one listbox, add it to the list, and have it show up in the other listbox. Currently, the adding to list part is working, but it's not showing up in the other listbox for some reason.
import tkinter as tk
testList = ["dog", "cat", "orange"]
newList = []
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = HomePage
class HomePage(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.config(width=300, bg='white', height=500, relief='sunken', borderwidth=2)
self.reportNames = tk.StringVar(value=tuple(testList))
self.lbox = tk.Listbox(self, listvariable=self.reportNames, height=15, width=50, borderwidth=2)
self.lbox.pack()
self.lbox.bind('<Double-1>', lambda x: addButton.invoke())
addButton = tk.Button(self, text='Select', command=self.selection)
addButton.pack()
self.testNames = tk.StringVar(value=newList)
self.lbox2 = tk.Listbox(self, listvariable=self.testNames, height=15, width=50, borderwidth=2)
self.lbox2.pack()
def selection(self):
addThis = self.lbox.selection_get()
print(self.lbox.selection_get())
newList.append(addThis)
print(newList)
if __name__ == "__main__":
global app
app = SampleApp()
sidebar = HomePage(app)
sidebar.pack(expand=False, fill='both', side='left', anchor='nw')
app.geometry("1200x700")
app.mainloop()
Your StringVar testNames won't track changes made to newList, you need to update the StringVar every time newList changes.
def selection(self):
addThis = self.lbox.selection_get()
print(self.lbox.selection_get())
newList.append(addThis)
print(newList)
# update StringVar
self.testNames.set(newList)
I have a python program that accesses databases for stuff that needs to be displayed on a screen. It's purpose is to display when a person's order is ready to be picked up. I have all the code to get the data and display it on the window. However, I need every 60 seconds to re-query the databases as some orders will be picked up and need to disappear from the list and some need to be added. I just don't know how to do this, as it appears that once the app.mainloop() is called, it takes human interaction with the window to make something happen. Any assistance would be greatly appreciated... Sorry for being long winded!
Here is an example I threw together to show you some basics of how you can use after() to check ever so many seconds to update your tracker.
Let me know if you have any questions.
import tkinter as tk
class Example(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry("600x400")
self.current_ticket_number = 1
self.data = [[97, "Mike"], [98, "Kaite"], [99, "Tom"]]
self.display_frame = tk.Frame(self)
self.display_frame.grid(row=2, column=0, columnspan=3, sticky="nsew")
self.lbl1 = tk.Label(self, text="Next ticket number: {}".format(self.current_ticket_number))
self.lbl1.grid(row=0, column=0)
self.lbl2 = tk.Label(self, text="Customer Name: ".format(self.current_ticket_number))
self.lbl2.grid(row=0, column=1)
self.entry1 = tk.Entry(self)
self.entry1.grid(row=0, column=2)
tk.Button(self, text="Refresh List", command=self.refresh).grid(row=1, column=0, pady=5)
tk.Button(self, text="Submit new ticket", command=self.new_ticket).grid(row=1, column=1, pady=5)
self.timed_refresh()
def new_ticket(self):
x = self.entry1.get().strip()
if x != "":
self.data.append([self.current_ticket_number, x])
#self.refresh() # you could do self.refresh() here if you want to update as soon as you create a ticket
#I left it out though so you can see how after() works below.
if self.current_ticket_number >= 99:
self.current_ticket_number = 1
else:
self.current_ticket_number += 1
def refresh(self):
self.display_frame.destroy()
self.display_frame = tk.Frame(self)
self.display_frame.grid(row=2, column=0, columnspan=3, sticky="nsew")
for ndex, item in enumerate(self.data):
tk.Label(self.display_frame, text=r"Order #{} is ready for {}.".format(item[0], item[1])).grid(row=ndex, column=1)
tk.Button(self.display_frame, text=r"Remove Ticket".format(item[0], item[1]), command=lambda x=ndex: self.remove_ticket(x)).grid(row=ndex, column=0)
def remove_ticket(self, ndex):
self.data.pop(ndex)
self.refresh()
def timed_refresh(self):
#this after statement is set for every 6 seconds
self.after(6000, self.timed_refresh)
self.refresh()
if __name__ == "__main__":
Example().mainloop()
Here is what I ended up with. Seems to do what I need it to do. Thanks for all the help, it was spot on.
class Application(tk.Frame):
def __init__(self,master=None):
self.createWidgets()
def createWidgets(self):
tk.Frame.__init__(self)
self.pack()
for b in range(0,int(len(myItems)/2)):
#print (myItems[b])
self.btn = tk.Button(self)
self.btn["text"] = myItems[b,0]
# self.btn["command"] = (lambda tck=b, binst=btn : pickUp(tck, binst))
# self.btn["command"] = lambda ticketNo=myItems[b,1] : self.pickUp(ticketNo)
self.btn.pack(fill='x')
def pp(self) :
#print('Im in pp')
self.destroy()
getArrowDataAndUpdateSQLite()
myItems = getDisplayData()
app.createWidgets()
app.master.after(30000, self.pp)
app = Application()
app.master.title('Customer Order Status')
app.master.after(30000,app.pp)
app.mainloop()
I am trying to create a GUI for an auto-complete prototype and am new to tkinter. I want to get the entire input when Space is pressed but I am unable to do so. The idea is to get all the entries in the text box so that I can do some analysis inside a function call.
This is the code:
def kp(event):
app.create_widgets(1)
import random
def getFromScript(text):
#########THIS IS A PLACE HOLDER FOR ACTUAL IMPLEMENTATION
i= random.randint(1,100)
return ['hello'+str(i),'helou'+text]
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.create_widgets(0)
# Create main GUI window
def create_widgets(self,i):
self.search_var = StringVar()
self.search_var.trace("w", lambda name, index, mode: self.update_list(i))
self.lbox = Listbox(self, width=45, height=15)
if i==0:
self.entry = Entry(self, textvariable=self.search_var, width=13)
self.entry.grid(row=0, column=0, padx=10, pady=3)
self.lbox.grid(row=1, column=0, padx=10, pady=3)
# Function for updating the list/doing the search.
# It needs to be called here to populate the listbox.
self.update_list(i)
def update_list(self,i):
search_term = self.search_var.get()#### THIS LINE SHOULD READ THE TEXT
# Just a generic list to populate the listbox
if(i==0):
lbox_list = ['Excellent','Very Good','Shabby', 'Unpolite']
if(i==1):
lbox_list = getFromScript(search_term)####### PASS TEXT HERE
self.lbox.delete(0, END)
for item in lbox_list:
if search_term.lower() in item.lower():
self.lbox.insert(END, item)
root = Tk()
root.title('Filter Listbox Test')
root.bind_all('<space>', kp)
app = Application(master=root)
app.mainloop()
Any kind of help is highly appreciable. Thanks in advance
Problem is you are creating a new StringVar on each create_widgets call.
Create StringVar in your __init__.
class Application(Frame):
def __init__(self, master=None):
...
self.search_var = StringVar()
...