Searching a tkinter dataframe - python-3.x

What I am attempting to create is a program that will first create a pandas dataframe. Then, it will make a tkinter window with an input entry frame, a button, and a text box. For the code I have below, when the button is pressed, I get an output that shows the header of the dataframe and the row that was "searched".
import pandas
from tkinter import *
#creates the dataframe
summer17=pandas.read_excel("summer17.xlsx","list")
window = Tk() #start of the main window
#function that will search the dataframe column "company" for any matches
def search_df():
search_result=summer17[summer17['Company'].str.contains("CAPS")]
t1.insert(END,search_result)
#Creates the entry box
e1_value=StringVar()
e1=Entry(window)
e1.grid(row=0,column=0)
#Creates a button
b1=Button(window,width=10,text='search',command=search_df)
b1.grid(row=0,column=1)
#Creates a text box
t1=Text(window,height=5,width=80)
t1.grid(row=0,column=2)
window.mainloop() #end of the main window
That works all and well, however, I want the user to be able to input a value into the entry box and press the button and search for that entry. So I change the function to be
def search_df():
search_result=summer17[summer17['Company'].str.contains(e1_value.get())]
t1.insert(END,search_result)
If I leave this blank, it returns the entire data frame (as I may or may not expect). However, if I put CAPS in the entry box and press the button, It still returns the entire dataframe.
My guess is that when I am getting the value from the entry box, there is a variable miss match, but I'm not sure how I would correct that.

i used one of my files to create the dataframe.
you need to add the e1_value to the entry by using the textvariable parameter.
I added a bind between the enter key and your function therefore you don't have to press the button, you can press the enter key instead. To do that, i used the bind function. This function binds a widget and a tkinter event. it executes the chosen function and passes a parameter (which is the event).
However the command paramter of the widget button does not pass any parameter when it executes the chosen function (the event is always a left clic). That's why your function takes *event as a parameter, event can be None. (i used *event but event=None works too, and i don't know which way is the most pythonic way, sorry)
PS : You should use import tkinter as tk because you may have some conflict with variable and function names if you use from tkinter import *
import pandas
import tkinter as tk
#creates the dataframe
summer17=pandas.read_csv("User_AD_Commun01_2017-07-26_15-01.csv",
sep=";",
encoding="latin1")
window = tk.Tk() #start of the main window
#function that will search the dataframe column "company" for any matches
def search_df(*event):
search_result=summer17.loc[summer17['company'].str.contains(e1_value.get(),
na=False, #ignore the cell's value is Nan
case=False)] #case insensitive
t1.insert(tk.END,search_result)
#Creates the entry box and link the e1_value to the variable
e1_value=tk.StringVar()
e1=tk.Entry(window, textvariable=e1_value)
e1.grid(row=0,column=0)
#execute the search_df function when you hit the "enter" key and put an event
#parameter
e1.bind("<Return>", search_df)
#Creates a button
b1=tk.Button(window,
width=10,
text='search',
command=search_df)
b1.grid(row=0,column=1)
#Creates a text box
t1=tk.Text(window,height=5,width=80)
t1.grid(row=0,column=2)
window.mainloop() #end of the main window

Related

How to restrict inputting text only for a specified time in Tkinter Text widget

I am trying to implement a text widget in tkinter which will allow input text for only a specified time (here 5 secs) and then capture the typed text, without using a submit button calling a function.
I want the time to start as soon as user starts typing and shall prevent user inputting any longer after 5secs. The text that was inputted thus far shall be catured.
I tried the below code which is not working. I tried looking in the documentation and did web search and many stackoverflow discussion threads. I couldn't find an answer. Appreciate inputs on a solution.
from tkinter import *
my_window = Tk()
type_txt = Text()
type_txt.grid(row=0, column=0)
type_txt.focus()
type_txt.after(5000, type_txt.configure(state=DISABLED))
typed_text = type_txt.get("1.0", END)
print(typed_text)
my_window.mainloop()
You can bind <key> event to a function, then inside the callback to disable the text box 5 seconds later using .after().
from tkinter import *
my_window = Tk()
type_txt = Text()
type_txt.grid(row=0, column=0)
type_txt.focus()
def disable_textbox():
type_txt.configure(state=DISABLED)
typed_text = type_txt.get("1.0", END)
print(typed_text)
def start_typing(event):
# disable <Key> binding
type_txt.unbind('<Key>')
# disable text box 5 seconds later
type_txt.after(5000, disable_textbox)
type_txt.bind('<Key>', start_typing)
my_window.mainloop()

Python Tkinter: Creating checkbuttons from a list

I am building a gui in tkinter with a list task_list = [].
Tasks are appended to/deleted from the list in the gui.
I want a window with checkboxes for every item in the list.
So if there's 10 items in the list, there should also be 10 checkboxes.
If there's 5 items in the list, there should be 5 corresponding checkboxes.
Can this be done?
I can't find anything on it
Thanks!
Here.
from tkinter import *
task_list=["Call","Work","Help"]
root=Tk()
Label(root,text="My Tasks").place(x=5,y=0)
placement=20
for tasks in task_list:
Checkbutton(root,text=str(tasks)).place(x=5,y=placement)
placement+=20
root.mainloop()
Using grid.
from tkinter import *
task_list=["Call","Work","Help"]
root=Tk()
Label(root,text="My Tasks").grid(row=0,column=0)
placement=3
for tasks in task_list:
Checkbutton(root,text=str(tasks)).grid(row=placement,column=0,sticky="w")
placement+=3
root.mainloop()
Here is my code for this issue:
from tkinter import Tk, Checkbutton, IntVar, Frame, Label
from functools import partial
task_list = ['Task 1', 'Task 2', 'Task 3', 'Work', 'Study']
def choose(index, task):
print(f'Selected task: {task}' if var_list[index].get() == 1 else f'Unselected task: {task}')
root = Tk()
Label(root, text='Tasks').grid(column=0, row=0)
frame = Frame(root)
frame.grid(column=0, row=1)
var_list = []
for index, task in enumerate(task_list):
var_list.append(IntVar(value=0))
Checkbutton(frame, variable=var_list[index],
text=task, command=partial(choose, index, task)).pack()
root.mainloop()
First I would like to mention that it is possible to mix layout manager methods like in this example. The main window uses grid as layout management method and I have gridded a frame to the window, but notice that Checkbuttons are getting packed, that is because frame is a different container so it is possible to use a different layout manager, which in this case makes it easier because pack just puts those checkbuttons one after another.
The other stuff:
There is the task list which would contain the tasks.
Then I have defined a function choose() this function prints out something. It depends on a variable. The comparison happens like this: print out this if value is this else print out this. It is just an if/else statement in one line and all it checks is if the IntVar in that list in that specific index is value 1 so "on". And there are two argument this function takes in: index and task. The index is meant to get the according IntVar from the var_list and the task is meant to display what tasks was chosen or unchosen.
Then the simple root = Tk() and root.mainloop() at the end.
Then is the label that just explains it.
Then the frame and here You can see that both label and frame were gridded using .grid()
Then the var_list = [] just creates an empty list.
Then comes the loop:
It loops over each item in the task_list and extracts the index of that item in the list and the value itself. This is done by using enumerate() function.
Each iteration appends a IntVar(value=0) to the var_list and since this appending happens at the same time as the items are read from the task_list the index of that IntVar in the list is the same as the current item in the task_list so that same index can be used for access.
Then a Checkbutton is created, its master is the frame (so that .pack() can be used) and the text=task so it corresponds to task name, the variable is set as a specific item in the var_list by index and this all has to be done so that a reference to that IntVar is kept. Then comes command=partial(choose, index, task) which may seem confusing but all partial does is basically this function will now execute always with the variables just given so those variables will always be the same for this function for this Checkbutton. And the first argument of partial is the function to be executed and next are arguments this function takes in. Then the Checkbutton gets packed.
If You have any questions ask.
Useful sources:
About partial() (though there are other sources too)
About Checkbutton (other sources about this too)
One line if/else statements

Tkinter Listbox will not return selected element

The code is as follows:
import tkinter as tk
plc=tk.Tk()
choices=["A","B","C]
choicesvar=tk.StringVar(value=choices)
l=tk.Listbox(master=plc,listvariable=choicesvar)
l.pack()
selected=l.get(l.curselection())
plc.mainloop()
The idea is to get what element is currently selected. But all I'm getting is TclError: bad listbox index "": must be active, anchor, end, #x,y, or a number.
.curselections
returns an empty tuple if nothing is selected. You are trying to get the user-selected input even before the user selects anything.
one way to overcome this is to set the default selection to the first row using .selection_set(0)
...
l=tk.Listbox(master=plc,listvariable=choicesvar)
l.pack()
l.selection_set(0)
print(l.get(l.curselection()))
...
But since you want to get user selected row, bind the <ButtonRelease> to an event handler then get the selected rows. something as shown below.
import tkinter as tk
def handler(event):
print([l.get(index) for index in l.curselection()])
plc=tk.Tk()
choices=["A","B","C"]
choicesvar=tk.StringVar(value=choices)
l=tk.Listbox(master=plc,listvariable=choicesvar, selectmode=tk.MULTIPLE)
l.pack()
l.bind('<ButtonRelease>', handler)
plc.mainloop()

get value from event in an entrybox

#I want to put the selected item from combobox in the merk_entry. and the selected item from the listbox(omschrijving) in model_entry.
.get() or insert aren't working in this function.
I tried both of them. Also Comboboxselected.
I dont know how fix this.
Thanks in advance.
from tkinter import *
from tkinter.ttk import Combobox
window=Tk()
window.geometry("500x500")
wagenmerk=["BMW","Mercedes","Audi"]
Bmw=["Bmw 1 reeks","Bmw 5 reeks Berline","Bmw 7 reeks"]
Mercedes=["A-klasse","B-klasse","Eqc","C-klasse"]
Audi=["A1","A3","A4"]
def toon_info(evt):
teller=1
merk_info=automerk_entry.get()
print(merk_info)
if merk_info == "BMW":
omschrijving.delete(0,END)
for line in Bmw:
omschrijving.insert(teller,Bmw[teller-1])
teller+=1
elif merk_info=="Mercedes":
omschrijving.delete(0,END)
for line in Mercedes:
omschrijving.insert(teller,Mercedes[teller-1])
teller+=1
elif merk_info=="Audi":
omschrijving.delete(0,END)
for line in Audi:
omschrijving.insert(teller,Audi[teller-1])
teller+=1
automerk_text=Label(text="automerk")
merk_text=Label(text="merk")
model_text=Label(text="model")
prijs_text=Label(text="prijs")
automerk_text.place(x=15,y=70)
merk_text.place(x=280,y=100)
model_text.place(x=280,y=120)
prijs_text.place(x=280,y=140)
merk=StringVar()
model=StringVar()
prijs=StringVar()
merk_entry=Entry(textvariable=merk,width="25")
model_entry=Entry(textvariable=model,width="25")
prijs_entry=Entry(textvariable=prijs,width="25")
merk_entry.place(x=320,y=100)
model_entry.place(x=320,y=125)
prijs_entry.place(x=320,y=150)
automerk_entry=Combobox(window,values=wagenmerk,width=30)
automerk_entry.bind("<<ComboboxSelected>>",toon_info)
automerk_entry.place(x=70,y=70)
omschrijving=Listbox(window,width=30,height=10)
omschrijving.place(x=70,y=100)
#button
bereken=Button(window,text="toon",width="30",height="2",command=toon_info,bg="grey")
bereken.place(x=70,y=270)
You have not set the two Entry widgets to take the change in values. Your Comboboxselected works as expected. Do the following changes to reflect the selection of the Combobox widget and the Listbox widget to the 2 Entry widgets:
def toon_info(evt):
teller=1
merk_info=automerk_entry.get()
merk.set(merk_info) # set the StringVar variable (assigned to the merk_entry widget)
...
...
Bind the Listbox widget:
def set_item(evt):
model.set(omschrijving.get(omschrijving.curselection()[0]))
omschrijving.bind('<<ListboxSelect>>', set_item)
And lastly, the Button-bereken is not set to send or activate any event/evt. So the callback function (under the option command) has to be changed
This is how I fixed it.
def set_item(evt):
model_entry.delete(0,END)
cur_selection=omschrijving.curselection()
if len(cur_selection)>0:
model.set(omschrijving.get(cur_selection[0]))

Tkinter - How to trace expanding list of variables

What I am trying to do track when any values in a list of StringVar change, even when the list is expanding. Any additions to the list before the trace statement will result in the callback. But any additions afterward, such as when pressing a button, will not cause any callback.
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
add = tk.Button(frame,text='add Entry',command='buttonpressed')
add.grid(row=0)
add.bind('<Button-1>',add_entry)
for i in range(2):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
for i in L:
i.trace('w',lambda *arg:print('Modified'))
root.mainloop()
Modifying the first two Entry's prints out Modified, but any Entry's after the trace is run, such as the ones produced when a button is pressed, will not.
How do I make it so that trace method will run the callback for the entire list of variables even if the list is expanded?
Simple suggestion, change your add_entry function to something like this:
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[len(L)-1].trace('w',lambda *arg:print('Modified'))
Extra suggestions:
This add = tk.Button(frame,text='add Entry',command='buttonpressed') is assigning a string to command option, means it will try to execute that string when button is clicked(which will do nothing). Instead, you can assign your function add_entry to command option and it will call that function when button is clicked and you can avoid binding Mouse Button1 click to your Button(Note: No need to use argument event in function when using like this). Read more here
Python supports negative indexing of List, so you can call L[-1] to retrieve the last element in the list instead of calling L[len(L)-1]).
Once you change your add_entry function as suggested, you can reduce your code to
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry():
global L
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[-1].trace('w',lambda *arg:print('Modified'))
add = tk.Button(frame,text='add Entry',command=add_entry)
add.grid(row=0)
for i in range(2):
add_entry()
root.mainloop()

Resources