Change the label's text everytime a button is pressed - python-3.x

I'm doing a PYTHON table using FOR, in TKINTER, and I would like every time a new number is placed in ENTRY, the label changes to the new table. For example, a number will be placed in the ENTRY and the person will click the TAB button, when it is clicked, the table will appear, but if the person wants another number, and click again, the new table will go down from the previous one. My solution was to create a button that erases the previous table, but when the button is pressed, only the last multiplication is deleted. I would like to know how I click the tabuada button, and the previous one erases the new one without using another button .Get the code and a photo below, Thanks. Obs.: First photo shows reset button working, but it just erase the last multiplication,second photo shows the whole multiplication.
from tkinter import *
import tkinter as tk
win=tk.Tk()
win.title('Table')
lb=Label(win,text='Type a number:',font='Helvetica 12 bold')
lb.pack()
e=Entry(win)
e.pack()
def click():
global c
c=e.get()
print('requested number ',c)
for b in (range(0, 11)):
global lb2
lb2=Label(text='{} x {} = {} '.format(c, b, int(b)*int(c)))
lb2.pack()
def reset():
lb2['text'] = ' '
bt1=Button(win,text='GO',bg='lightblue',command=click)
bt1.pack()
bt2=Button(win,text='RESET',bg='lightblue',command=reset)
bt2.pack()
win.mainloop()
erasing:
whole multiplication:

Here are some fixes for your code; it was not entirely clear what you exactly meant by: "the new table will go down from the previous one.", so I went with having the new table replacing the previous one.
c was not defined in your code and that threw an exception.
I placed the construction of the label inside a function make_label that is called from the main block, and from click(), to rebuild it when a new number is requested.
reset was missing a call to pack on the label, to update the text displayed.
I think that should help you get started in the right direction; let me know if something is unclear.
edit:
I modified reset so the label is destroyed and re-created from view, thus removing the growth in size of the window.
from tkinter import *
import tkinter as tk
win=tk.Tk()
win.title('Table')
lb=Label(win,text='Type a number:',font='Helvetica 12 bold')
lb.grid(row=0, column=0)
lb2 = Label(text='')
e=Entry(win)
e.grid(row=1, column=0)
c = 2
def click():
global c
c = e.get()
print('requested number ', c)
reset()
make_label(c)
def make_label(c):
global lb2
txt = []
for b in (range(0, 11)):
txt.append('{} x {} = {} '.format(c, b, int(b)*int(c)))
text = '\n'.join(txt)
lb2 = Label(text=text)
lb2.grid(row=4, column=0)
def reset():
global lb2
lb2.destroy()
lb2 = Label()
lb2.grid(row=4, column=0)
make_label(c)
bt1=Button(win,text='GO',bg='lightblue',command=click)
bt1.grid(row=2, column=0)
bt2=Button(win,text='RESET',bg='lightblue',command=reset)
bt2.grid(row=3, column=0)
win.mainloop()

Related

ttk Treeview, get item's bbox after scrolling into view

I am working on an editable tkinter.ttk.Treeview subclass. For editing I need to place the edit widget on top of a choosen "cell" (list row/column). To get the proper coordinates, there is the Treeview.bbox() method.
If the row to be edited is not in view (collapsed or scrolled away), I cannot get its bbox obviously. Per the docs, the see() method is meant to bring an item into view in such a case.
Example Code:
from tkinter import Tk, Button
from tkinter.ttk import Treeview
root = Tk()
tv = Treeview(root)
tv.pack()
iids = [tv.insert("", "end", text=f"item {n}") for n in range(20)]
# can only get bbox once everything is on screen.
n = [0]
def show_bbox():
n[0] += 1
iid = iids[n[0]]
b = tv.bbox(iid)
if not b:
# If not visible, scroll into view and try again
tv.see(iid)
# ... but this still doesn't return a valid bbox!?
b = tv.bbox(iid)
print(f"bbox of item {n}", b)
btn = Button(root, text="bbox", command=show_bbox)
btn.pack(side="bottom")
root.mainloop()
(start, then click the button until you reach an invisible item)
The second tv.bbox() call ought to return a valid bbox, but still returns empty string. Apparently see doesnt work immediately, but enqeues the viewport change into the event queue somehow. So my code cannot just proceed synchronously as it seems.
How to solve this? Can see() be made to work immediately? If not, is there another workaround?
The problem is that even after calling see, the item isn't visible (and thus, doesn't have a bounding box) until it is literally drawn on the screen.
A simple solution is to call tv.update_idletasks() immediately after calling tv.see(), which should cause the display to refresh.
Another solution is to use tv.after to schedule the display of the box (or the overlaying of an entry widget) to happen after mainloop has a chance to refresh the window.
def print_bbox(iid):
bbox = tv.bbox(iid)
print(f"bbox of item {iid}", bbox)
def show_bbox():
n[0] += 1
iid = iids[n[0]]
tv.see(iid)
tv.after_idle(print_bbox, iid)

populating one combobox based on another combo box using tkinter python

from tkinter import *
from tkinter.ttk import Combobox
v1=[]
root = Tk()
root.geometry('500x500')
frame1=Frame(root,bg='#80c1ff',bd=5)
frame1.place(relx=0.5,rely=0.1,relwidth=0.75,relheight=0.1,anchor='n')
lower_frame=Frame(root,bg='#80c1ff',bd=10)
lower_frame.place(relx=0.5,rely=0.25,relwidth=0.75,relheight=0.6,anchor='n')
v=[]
def maincombo():
Types=["MA","MM","MI","SYS","IN"]
combo1=Combobox(frame1,values=Types)
combo1.place(relx=0.05,rely=0.25)
combo2=Combobox(frame1,values=v)
combo2.bind('<<ComboboxSelected>>', combofill)
combo2.place(relx=0.45,rely=0.25)
def combofill():
if combo1.get()=="MA":
v=[1,2,3,45]
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
if combo1.get()=="MM":
v=[5,6,7,8,9]
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
maincombo()
root.mainloop()
I want to populate the one combobox based on selection of other combobox I,e types.But failed to do so with simple functions.
Looking at you code, most of what you need is already there. The changes I have made are as follows:
Bound to combo1 rather than combo2 (as combo1 is the one you want to monitor)
Set combo1 and combo2 as global variables (so they can be used in the combofill method)
Set the combofill method to accept the event arg (it would raise a TypeError otherwise)
Use the .config method on combo2 rather than creating a new one each time
Set combo2 to be empty when neither "MA" or "MM" are selected
Here is my implementation of that:
from tkinter import *
from tkinter.ttk import Combobox
v1=[]
root = Tk()
root.geometry('500x500')
frame1=Frame(root,bg='#80c1ff',bd=5)
frame1.place(relx=0.5,rely=0.1,relwidth=0.75,relheight=0.1,anchor='n')
lower_frame=Frame(root,bg='#80c1ff',bd=10)
lower_frame.place(relx=0.5,rely=0.25,relwidth=0.75,relheight=0.6,anchor='n')
v=[]
def maincombo():
global combo1, combo2
Types=["MA","MM","MI","SYS","IN"]
combo1=Combobox(frame1,values=Types)
combo1.place(relx=0.05,rely=0.25)
combo1.bind('<<ComboboxSelected>>', combofill)
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
def combofill(event):
if combo1.get()=="MA":
v=[1,2,3,45]
elif combo1.get()=="MM":
v=[5,6,7,8,9]
else:
v=[]
combo2.config(values=v)
maincombo()
root.mainloop()
A couple other ideas for potential future consideration:
I would recommend using the grid manager rather than the place manager as it will stop widgets overlapping, etc. (on my system, combo2 slightly covers combo1)
Use a dictionary rather than if ... v=... elif ... v= ... and then use the get method so you can give the default argument. For example:
v={"MA": [1,2,3,45],
"MM": [5,6,7,8,9]}. \
get(combo1.get(), [])
EDIT:
Responding to the question in the comments, the following is my implementation of how to make a "toggle combobox" using comma-separated values as requested.
As the combobox has already overwritten the value of the text area when our <<ComboboxSelected>> binding is called, I had to add a text variable trace so we could keep track of the previous value of the text area (and therefore append the new value, etc.). I am pretty sure that explanation is completely inadequate so: if in doubt, look at the code!
from tkinter import *
from tkinter.ttk import Combobox
root = Tk()
def log_last():
global last, cur
last = cur
cur = tx.get()
def append_tx(event):
if last:
v = last.split(",")
else:
v = []
v = list(filter(None, v))
if cur in v:
v.remove(cur)
else:
v.append(cur)
tx.set(",".join(v))
combo.selection_clear()
combo.icursor("end")
last, cur = "", ""
tx = StringVar()
combo = Combobox(root, textvariable=tx, values=list(range(10)))
combo.pack()
combo.bind("<<ComboboxSelected>>", append_tx)
tx.trace("w", lambda a, b, c: log_last())
root.mainloop()

How can I change the state of a menu with a button inside tkinter?

I want to make a unit converter in tkinter. I made two drop-down menus; the first one allows the user to select the unit they want to convert from, and the second one allows them to choose the unit they want to convert to. I want to disable all the options that do not make sense in the second menu after they have selected an option in the first one (if they want to convert kilograms it would not make sense to choose centimeters in the second menu)
I have tried to use a StringVar() to change the state of the menu, but it is not working. I have no idea of what to do next. I have been using the documentation of Tutorialspoint, but I cannot find anything that works (first time using tkinter).
import tkinter as tk
root = tk.Tk()
root.geometry('600x600')
my_var = tk.StringVar()
my_var.set('active')
unit_1 = tk.Menubutton(root,text='This is the first menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_1 = tk.Menu(unit_1)
unit_1.config(menu=menu_1)
menu_1.add_command(label='Inches',command= lambda: my_var.set('disabled') )
menu_1.add_command(label='Kilograms')
unit_2 = tk.Menubutton(root,text='This is the second menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_2 = tk.Menu(unit_2)
unit_2.config(menu=menu_2)
menu_2.add_command(label='Centimeters')
menu_2.add_command(label='Pounds',state= my_var.get())
unit_1.place(relx=0.03,rely=0.08,relheight=0.04,relwidth=0.45)
unit_2.place(relx=0.52,rely=0.08,relheight=0.04,relwidth=0.45)
root.mainloop()
Here I am trying to make the button 'Inches' in the first menu to disable the button 'Pounds' in the second menu, but when I click on 'Inches' nothing happens to 'Pounds'.
tk.StringVar() is used to change text on something, for example if you want to have a button with dynamic text, you may want to use a tk.StringVar() for that.
What you want to do is something different; you want to change the configuration of a label. So you need to find the element and adjust its state:
import tkinter as tk
root = tk.Tk()
root.geometry('600x600')
my_var = tk.StringVar()
my_var.set('active')
unit_1 = tk.Menubutton(root,text='This is the first menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_1 = tk.Menu(unit_1)
unit_1.config(menu=menu_1)
menu_1.add_command(label='Inches', command=lambda: disable_pounds())
menu_1.add_command(label='Kilograms', command=lambda: disable_centimeters())
unit_2 = tk.Menubutton(root,text='This is the second menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_2 = tk.Menu(unit_2)
unit_2.config(menu=menu_2)
menu_2.add_command(label='Centimeters')
menu_2.add_command(label='Pounds',state= my_var.get())
unit_1.place(relx=0.03,rely=0.08,relheight=0.04,relwidth=0.45)
unit_2.place(relx=0.52,rely=0.08,relheight=0.04,relwidth=0.45)
def disable_pounds():
menu_2.entryconfig("Pounds", state="disabled")
menu_2.entryconfig("Centimeters", state="active")
def disable_centimeters():
menu_2.entryconfig("Pounds", state="active")
menu_2.entryconfig("Centimeters", state="disabled")
root.mainloop()

tkinter auto switch focus after write in entry

I have got 4 entry fields and I want to switch focus to next field after write one letter. So basically i want to write 4 letters, each one in another field, without touching mouse or Tab button.
from tkinter import *
root = Tk()
r1= StringVar()
r2= StringVar()
r3= StringVar()
r4= StringVar()
e1=Entry(root, textvariable=r1)
e1.pack()
e2=Entry(root, textvariable=r2)
e2.pack()
e3=Entry(root, textvariable=r3)
e3.pack()
e4=Entry(root, textvariable=r4)
e4.pack()
list=[e1,e2,e3,e4]
for i, element in enumerate(list):
lista=[r1,r2,r3,r4]
element.focus()
while lista[i].get() == "":
pass
root.mainloop()
How can I do that?
Thanks for help :D
Your original code hangs before reaching root.mainloop() because the Entry() fields will never be edited. You have to use Tk events, e.g. after packing the Entry fields, you could try this:
liste=[e1,e2,e3,e4]
e1.focus()
curre=0
def nexte(e):
global curre
curre+=1
if (curre<4):
liste[curre].focus()
else:
root.unbind("<Key>")
# we've got input into the last field, now do sth useful here...
root.bind("<Key>",nexte)
root.mainloop()
Try something like:
all_typed = False
e1.focus()
boxes = [e1, e2, e3, e4]
focused = 0
while not all_typed:
if focused > 3:
all_typed = True
elif boxes[focused].get() != "":
focused += 1
boxes[focused].focus()
Not my finest code, but should work. I'm not too familiar with TK so I don't know if this will block the app from running or anything.

Tkinter: creating an arbitrary number of buttons/widgets

So, I've got a list with entries that look like this:
Option1 Placeholder1 2 Placeholder2 0
Option2 Placeholder1 4
Option3 Placeholder1 2 Placeholder2 -2 Placeholder3 6
I have a listbox of the Options and a button that creates a new window with the values for the selected Option. What I want to do is to create n number of buttons when this new window is created, where n is the number of values of the selected Options (i.e. 2, 1 and 3 for Options 1 through 3, respectively). I want it to look something like this:
Option1
Placeholder1 [button1 containing value=2]
Placeholder2 [button2 containing value=0]
... which is of course quite simple if I just assign a button for the maximum number of n that I know will be present, but I'm wondering if there's a way to do it more arbitrarily. Obviously the same problem applies to the arbitrary number of Labels I would need to use for the value names (the 'PlaceholderX's) as well.
I've been trying to do some reading on this type of thing, variable variables, etc., and it seems it's a very big NO-NO most (if not all) of the time. Some advocate the use of dictionaries, but I don't really get how that's supposed to work (i.e. naming variables from entries/values in a dict).
Is this something that can (and should) be done, or am I better off just creating all the buttons manually?
[EDIT: added code]
from tkinter import *
import csv
root = Tk()
root.wm_title("RP")
listFrame = Frame(root, bd=5)
listFrame.grid(row=1, column=2)
listbox1 = Listbox(listFrame)
listbox1.insert(1, "Option1")
listbox1.insert(2, "Option2")
listbox1.insert(3, "Option3")
listbox1.pack()
infoFrame = Frame(root, bd=5)
infoFrame.grid(row=1, column=3)
info_message = Message(infoFrame, width=300)
info_message.pack()
# Read stats from file
stat_file = open('DiceTest.csv', 'rU')
all_stats = list(csv.reader(stat_file, delimiter=';'))
def list_selection(event):
# gets selection and info/stats for info_message
index = int(listbox1.curselection()[0])
stats = all_stats[index]
infotext = str(stats[0]) # just the name
for n in range(int((len(stats)-2)/2)): # rest of the stats
infotext += ('\n' + str(stats[n*2 + 2]) + '\t' + str(stats[n*2 + 3]))
info_message.config(text=infotext)
listbox1.bind('<ButtonRelease-1>', list_selection)
def load():
top = Toplevel()
top.geometry('300x100')
index = int(listbox1.curselection()[0])
stats = all_stats[index]
# some way to create arbitrary buttons/labels here (?)
load_button = Button(root, text='Load', command=load)
load_button.grid(row=2, column=2)
root.mainloop()
Oh, and every button should have the same command/function, which reduces whatever value currently is in the button by 2.
Figured it out! Creating the widgets dynamically with a dictionary worked just fine, but calling the correct widget on the various button presses was more difficult. This is what I had:
buttons = dict()
for k in range(len(info)):
buttons[k] = Button(top, text=info[k], command=lambda: my_function(buttons[k]))
... which would work, but all button presses would call the function with the last created button as the target. All that was needed was a few extra characters in the command part of the buttons:
buttons = dict()
for k in range(len(info)):
buttons[k] = Button(top, text=info[k], command=lambda a=k: my_function(buttons[a]))
... which I assume works because it somehow stores the value of k inside a rather than taking the last known value of k, i.e. equivalent to the last created button. Is this correct?
You can store Buttons in a list:
from tkinter import *
master = Tk()
buttons = []
n = 10
for i in range(n):
button = Button(master, text = str(i))
button.pack()
buttons.append(button)
master.mainloop()

Resources