I can't show properly a list in Tkinter - python

I'm making a study project, just a basic calendar, but I encountered an error, that I can't figure out. Firstly, I input the proper date, which later I append to a list. The next step is to click the button to list all events you already add, but I can't show it proper. It gets the awful brackets, that I can't get rid of. If I try the .format, unfortunately window shows only the first element of a list. Can you help me figure it out?
def window2():
print(events)
win2 = Toplevel(root)
win2.title("Lista wydarzeń")
win2.geometry("300x200")
events_label = Label(win2, text="Wydarzenia:")
events_label.pack()
if len(events) > 0:
for x in events:
events_show_label = Label(win2, text=events)
events_show_label.pack()
else:
no_events_label = Label(win2, text="Nie dodano żadnego wydarzenia.")
no_events_label.pack(pady=10)
I get the variables by .get() and add them to the list like that
events.append(f"Nazwa: {title} \nData: {date} {time}\n")

Related

How do I only get the latest input from tkinter Text widget?

I need to get only the latest input from my text widget, and then append that character to a list.
I am using
Text.get(1.0,'end-1c')
, and it does not work because the loop constantly gets all the input, instead of only getting the latest input when there is a new latest input.
def main_screen():
start_time=time.time()
tk=Tk()
tk.title('Typing Test')
tk.geometry('800x500')
main_title=Label(tk,text='1 Minute Test',font=('Times New Roman',36))
main_title.pack(pady=5)
directions=Label(tk,text='Start Typing',font=('Times New Roman',14))
directions.pack()
base_text=Label(tk,text=randomizer(),bg='#E0E0EE',font=('Arial',14),wraplength=700,justify=LEFT)
base_text.pack(pady=10)
text_area=Text(tk,font=('Arial',14),width=63,height=7,wrap='word')
text_area.pack()
tk.update()
#WPM Calculation
target_text=randomizer()
typed_text=[]
wpm=0
errors=0
while True:
tk.update()
time_elapsed=max(time.time()-start_time,1)
wpm=round((len(typed_text)/60)/5)
if time_elapsed>=60:
break
#Problem Section
key=text_area.get(1.0,'end-1c')
typed_text.append(key)
for x in typed_text:
if x != target_text:
errors += 1
Alternatively, I tried using a variable in place of the 1.0 in .get, that would increase by one with each iteration of the loop. Next, I tried a try/except command, and put the #Problem Section into a function. I tried calling that function by binding the text area to
'<Key>'
'<KeyPress>'
'<KeyRelease>'
None of these attempts work. I used a print statement to see what those variables are with each iteration of the loop, and using the first method, it just keeps making a longer and longer string that repeats constantly, instead of updating with each new character. Trying the other ways I just got nothing, no output, but no error either. I am completely stuck, and don't know what else to try.
You can bind the text_area with a <KeyPress> event, but you need to pass the list typed_text as an argument so you can append the presses.
So you should do something like this:
text_area.bind("<KeyPress>", lambda _: getKey(_, typed_text))
while True:
tk.update()
time_elapsed = max(time.time() - start_time, 1)
wpm = round((len(typed_text) / 60) / 5)
if time_elapsed >= 60:
break
# Problem Section
for x in typed_text:
if x != target_text:
errors += 1
def getKey(event, list):
list.append(event.char)
print(list)
The text widget supports something called a "mark", which is like a bookmark. You can put a mark anywhere in the text and use it just like a normal index.
Assuming that data is only ever appended to the end of the widget, the simplest solution is to fetch a block of data and then move the mark to the end of the text that you fetched. The next time you fetch data, start at that mark.
Marks have something called "gravity" that defines which character the mark sticks to. For example, if the gravity is "left" and you set it to character "2.2", the mark will always stay adjacent to the character at index "2.2". If the gravity is "right", it will be stuck at the character following index "2.2" (eg: "2.3" or "3.0")
Here's a contrived example that will print only the latest additions to a text widget every five seconds, by tracking the last position that was used to fetch the data.
import tkinter as tk
def get_new_text():
data = text.get("last", "end-1c")
print(f"new data: >>>{data}<<<")
text.mark_set("last", "end-1c")
root.after(5000, get_new_text)
root = tk.Tk()
text = tk.Text(root, wrap="word")
text.pack(fill="both", expand=True)
text.mark_set("last", "1.0")
text.mark_gravity("last", "left")
root.after(5000, get_new_text)
root.mainloop()

How to get state of each checkbox generated through a loop, as soon as it is checked/unchecked in tkinter?

I'm trying add/remove an item (text of checkbox) to/from a list whenever a checkbox is checked/unchecked in tkinter.
My idea was to add a command to the checkbutton, like:
cb = Checkbutton(master,...,command=some_fun)
but I cannot think of a way to define the function. I was thinking the function should contain the widget attribute cget('text'), but the problem is I have many checkboxes made with the help of a loop.
I guess the question is: how can I reference the checkbox whose state got changed and is therefore calling the function some_fun?
The way I generated the checkboxes is:
cb_identities = []
for i in range(cb_max_num):
cb = Checkbutton(frame_data,bg="white")
cb_identities.append(cb)
And then I'm dynamically changing them depending on some radiobuttons:
def fun_chck(): #shows or hides checkbuttons based on radiobutton input
data = read_data(rb_var.get())
for i in range(cb_max_num):
cbname = (cb_identities[i])
if len(data)-1 < i:
cbname.grid_forget()
else:
cbname.config(text=data[i]) #I would place some_fun here, which gets text option of checked box
cbname.grid(row=i,column=1,sticky=W)
Update! I managed with the following code for anyone interested:
cb_var_init = [0] * cb_max_num #create the initial list of inactive checkbuttons, all 0
input_params=[] #list which needs to be populated/depopulated based on checkbutton state
def get_data(data): #populates a list with parameter from checked checkbuttons,
global cb_var_init
cb_var_list = list(map(lambda var: var.get(),list(cb_var.values())))
for i in range(len(data)):
if cb_var_list[i] > cb_var_init[i]:
input_params.append(data[i])
elif cb_var_list[i] < cb_var_init[i]:
input_params.remove(data[i])
cb_var_init = cb_var_list
return(input_params)
cb_var is a dictionary of IntVars, and data is a list of checkbuttons' names.
As for the command on each checkbutton, I used cbname.config(text=data[i],command=lambda: get_data(data)) as suggested in another topic for functions with arguments.
Now each time I check a checkbutton, I immediately get a list of parameters which should show in the next Frame, which is dynamically updated.

Tkinter: Opening a URL via Dropdown. Need to specify Item from List

Another one of these puzzles where Im sure I got the right idea but I cant seem to implement it.
Im trying to make a dropdown that instantly opens the URL to whatever column 1 of the list says but I'm not sure if I maybe need a dictionary and call it from that, do I need to define the list by columns...
So I figured out how to open up URLS from a TreeView but now Im figuring out how to open the URL from a Dropdown. I want the {spec} to be populated by only the first item from the list (Pepperoni) in this case.
QUICKJUMP_1 = [
["--", "--"],
["Pepperoni", "Pizza"],
]
def openGoogle():
_spec_ = QUICKJUMP_1
url = f'https://google.com/{_spec_}'
webbrowser.open(url)
quickjump = StringVar()
quickjump.set(QUICKJUMP_1[0])
spec = ttk.Combobox(root, width=30, textvariable=quickjump, state="")
spec['values'] = [item[1] for item in QUICKJUMP_1]
spec.place(x=300, y=400)
spec.current(0)
spec.bind("<<ComboboxSelected>>", openGoogle)
EDIT:
I updated the code as it was figured out how to make it actually open up the URL but as it stands right now all it currently does is just opens up the first part of the list whatever part of the list the "spec = QUICKJUMP" points it to i.e [1] = item 2.
So I need someway to tell it to pick from the drop box and only the first part.
Since the code given is not a working code, I will write down an example and you can hopefully figure it out from it:
from tkinter import *
from tkinter import ttk
import webbrowser
root = Tk()
base_url = 'www.google.com/{}'
options = ['--','Peperoni Pizza']
def open_url(e): # e.widget is the widget that triggers the event, here, combobox
if e.widget.get() != options[0]: # If the value is not '--'
value = e.widget.get()
just_the_flavour = value.split(' ')[0] # This will split the text 'Peperoni Pizza' into two items in a list
# It does this where there is ' ', ie, a blank space, so the list will be ['Peperoni','Pizza']
# Then we take just the first item from it use [0]
url = base_url.format(just_the_flavour)
webbrowser.open(url)
combobox = ttk.Combobox(root,values=options)
combobox.pack()
combobox.current(0) # Set the first value to the first in the list
combobox.bind('<<ComboboxSelected>>',open_url) # Bind to the function
root.mainloop()
I have explained it on the comments to understand it on the go.

How to destroy comboboxes if they are available in python gui?

I have a function, which based on the count generates the comboboxes. I want to destroy any combobox which is available already whenever my count variable changes. I used winfo_exists to do this...but it throws an attribute error every time. Please help me with this.
Here is the code of that function:
def create(event):
count = combo.current()
print ("count")
print(count)
for i in range(1,count+2):
if (create_combo[i].winfo_exists()):
create_combo[i].destroy()
for i in range (1,count+2):
create = tk.StringVar()
create_combo[i]= ttk.Combobox(new_window_2,width = 15,textvariable = create, values = sheets)
#create_combo.set("Sheet " + str(i))
create_combo[i].grid(column = i, row =4, padx=10,pady=10)
To delete the widgets which are created in loop, can be deleted by using the method available in this link
Python Tkinter :removing widgets that were created using a for loop
This worked for me... I dont understand why winfo_exists didn't work.
Anyway Thanks!!
list_of_owner_widgets = []
def create(event):
count = combo.current()
print(count)
for widget in list_of_owner_widgets:
widget.destroy()
for i in range (1,count+2):
create = tk.StringVar()
create_combo[i]= ttk.Combobox(new_window_2,width = 15,textvariable = create, values = sheets)
list_of_owner_widgets.append(create_combo[i])
create_combo[i].grid(column = i, row =4, padx=10,pady=10)
If you wish to destroy a Python widget, be it a Checkbox in your case, you use the following code.
It is a lot easier to remove and show widgets using the .grid method!
Your code:
create_combo[i].destroy()
I assume (as I can see further down the code file) that you used the grid method. In which case I would simply change the code to:
create_combo[i].grid_forget()
Hope This Helps!
From your post:
for i in range(1,count+2):
if (create_combo[i].winfo_exists()):
create_combo[i].destroy()
And the error:
AttributeError: 'str' object has no attribute 'winfo_exists'
I can infer that:Your create_combo must be a list full of string(Instead of Combobox widget).
You could add print(create_combo) before the first for loop to check the value in create_combo.It must be a list full of string.
And it seems that your problem is not here,you should check the way how you create the create_combo.
lets assume create_combo = ['a','b','c']. So I am creating three comboboxes create_combo[0...2]. So name of the comboboxes(widgets) is a, b, c.
No,you couldn't.
If really want to get a list of comboboxes you create,you should use:
create_combo = []
for i in range(3):
t = ttk.Combobox(xxxxx)
t.grid(xxxxxx)
create_combo.append(t) # append it to your create_combo
And then,you could use:
for i in create_combo:
if i.winfo_exists(): # i should be a widget,not string
xxxxxxx # your job

Using buttons with #interact

EDIT: Re-written for clarity Sept. 8 2019 1330 UTC
In a Jupyter notebook, I want to explore dates chosen by a date picker widget. The way of doing this I already knew was to use #interact and make the datepicker widget an argument to the interact function, like this:
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date')):
But I need buttons "Next Holiday" and "Previous Holiday" to move from the selected date to the next holiday. I already have an array of holidays stored as datetime.datetime objects.
So I tried adding the button objects to the arguments to the #interact function, like this:
#interact
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date'),
prev_button = widgets.Button(description="Prev Holiday"),
next_button = widgets.Button(description="Next Holiday")):
That didn't work. Error says the line with the button widget "cannot be transformed into a widget". Hmm, I thought it already was a widget...
My next thought was to put the button code inside the function, rather than as an argument:
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date')):
prev_button = widgets.Button(description="Prev Holiday")
next_button = widgets.Button(description="Next Holiday")
prev_button.on_click(prev_holiday_callback)
next_button.on_click(next_holiday_callback)
box=widgets.HBox([prev_button,next_button])
display(box)
That got the buttons on the screen and I can push them and my callback routines run. But when the datepicker is set up (per above) as an argument to #interact, it appears that it is no longer possible to reset its date using its .value attribute. (See code above) trying to set d1.value results in an error.
I suppose I could get rid of #interact entirely, and just put the datepicker and both buttons into the mainline code. But I don't know how to reproduce the function of #interact at that point. Short of an infinite loop that waits for a click or observe event to come from one of the widgets, other than #interact, I don't know how to tell Python/Jupyter "hey, just chill out until a widget event causes a callback to wake up the code".
In case it's helpful, here are my callback routines for the buttons.
def button_click_common():
global button_flag
global button_date
global holidays
for i in range(len(holidays)):
if(holidays[i]<button_date): # haven't passed the prior holiday yet
continue
# Now holidays[i] has to either be same as or after button_date
prior_holiday = i-1 # prior holiday is the one we just passed that was prior
next_holiday = i # assume at first we are between dates
if holidays[i]==button_date:
next_holiday +=1 # if we were already on a holiday date, next becomes the following one
return prior_holiday, next_holiday
def prev_holiday_callback(_):
global button_flag
global button_date
global holidays
prior_holiday,_ = button_click_common()
button_date = holidays[prior_holiday]
button_flag = True
def next_holiday_callback(_):
global button_flag
global button_date
global holidays
_,next_holiday = button_click_common()
button_date = holidays[next_holiday]
button_flag = True
The idea here is that the callback updates button_date to the date of the next holiday, and sets the flag button_flag. Then code (not shown) in the explore_dates function was going to test the flag. But I can't figure out how to update the datepicker widget with the new date when it is defined as an argument to the #interact function.
Feels like I'm going about this wrong. Any advice or guidance welcome...
Thanks in advance!
I'm not sure I understand the exact aim you're trying to achieve, but interact works best with functions that take numeric inputs, rather than discrete inputs or python objects like dates.
I put together some code that would illustrate how I would use the forward and back buttons to update a datepicker, it's probably not exactly what you are looking for, but maybe you can describe the changes from there?
import ipywidgets as ipyw
from datetime import date
idx = 0
date_picker = ipyw.DatePicker()
date_picker.value = month_starts[idx]
start_button = ipyw.Button(description='Prev')
end_button = ipyw.Button(description='Next')
display(date_picker, start_button, end_button)
def change_date(button):
global idx
if button.description=='Prev':
if idx-1<0:
pass
else:
date_picker.value = month_starts[idx-1]
idx -= 1
elif button.description=='Next':
if idx+1>=len(month_starts):
pass
else:
date_picker.value = month_starts[idx+1]
idx += 1
start_button.on_click(change_date)
end_button.on_click(change_date)

Categories