OptionMenu widget not updating when StringVar is changed until hovered - python

I need a menu that can respond to items being clicked by running code then switch the text back to a default text.
Currently, my implementation works but the default text is only displayed when the cursor hovers over the menu after clicking.
I have searched but I could not find anything related to this problem, although maybe that is because I am unsure as to what exactly is causing this.
Here is the code to reproduce this behaviour:
from tkinter import *
root = Tk()
default_text = 'select an item'
def thing_selected(self, *args):
#other stuff happens here
var.set(default_text)
var = StringVar(root)
var.set(default_text)
var.trace('w', thing_selected)
menu = OptionMenu(root, var, *['Pizza','Lasagne','Fries','Fish'])
menu.pack()
root.mainloop()
Here is a gif representing the outcome:
I would expect the text at the top to be updated instantaneously, but it only updates when the cursor has hovered over the widget
I am looking for some way to trigger a hover event on the widget or I am open to suggestions for any other methods of accomplishing this.

You could take a different route and use the command attribute of the OptionMenu:
import tkinter as tk
root = tk.Tk()
default_text = 'select an item'
def thing_selected(selected):
#other stuff happens here
print(var.get())
var.set(default_text)
print(var.get())
var = tk.StringVar()
var.set(default_text)
options = ['Pizza','Lasagne','Fries','Fish']
menu = tk.OptionMenu(root, var, *options, command = thing_selected)
menu.pack()
root.mainloop()

Related

Tkinter pack_forget() and destroy() don't work

I have a problem with these two functions that don't work as expected.
I want to use a selection form drop-down menù and based on the selection two different set of checkbox are displayed in the root window. To do so, I use this code:
from tkinter import *
import tkinter as tk
def evaluation(form):
form.pack_forget()
form.destroy()
form=Frame(root)
GotPlatform=platform.get()
for elem in PlatformOption[GotPlatform]:
OptionButtonSel[elem]=Checkbutton(form, text=elem)
OptionButtonSel[elem].pack()
form.pack(fill="both", expand = 1)
PlatformOption={'Option1':["option1-1","option1-2"],'Option2':["option2-1","option2-2"]}
OptionButtonSel={}
root = tk.Tk()
f1=Frame(root)
menuBar = tk.Menu(root)
menu1 = tk.Menu(root)
submenu = tk.Menu(root)
platform = StringVar()
submenu.add_radiobutton(label="Option1", value="Option1", variable=platform,command=lambda:evaluation(f1))
submenu.add_radiobutton(label="Option2", value="Option2", variable=platform,command=lambda:evaluation(f1))
menuBar.add_cascade(label="Options", menu=menu1)
menu1.add_cascade(label="Select option", menu=submenu)
root.config(menu=menuBar)
root.mainloop()
The code works but whenever I change the options fron drop-down menù, the checkboxes option are stacked and aren't overwritten as expected.
This puzzles me since I have used this other code and it works as expected:
from tkinter import Tk, Frame, Menu, Label
def show_frame1():
forget_all()
f1.pack(fill="both", expand = 1)
def show_frame2():
forget_all()
f2.pack(fill="both", expand = 1)
def forget_all():
f1.pack_forget()
f2.pack_forget()
root = Tk()
f1 = Frame(root)
f2 = Frame(root)
Label(f1, text="MENU SELECTED 1").pack()
Label(f2, text="MENU SELECTED 2").pack()
menubar=Menu(root)
subMenu=Menu(menubar, tearoff = 0)
menubar.add_cascade(label = 'MENU', menu=subMenu)
subMenu.add_command(label = 'SUBMENU1', command = show_frame1)
subMenu.add_command(label = 'SUBMENU2', command = show_frame2)
root.config(menu = menubar)
root.mainloop()
Aside from addind destroy(), the usage of frame and pack_forget() seems identical to me.
I use Python 3.10.1, IDE Spyder last version, Windows 8.
The root of the problem is that you create a form that never appears on the screen. You create it, but never call pack on it. You then pass this form to the function every time the button is clicked. In other words, you keep passing the original form, not the new one that is recreated.
The best solution is for your main code to call pack, place, or grid on the form, and then in the function you can delete the children of the form without deleting the form itself.
So, add the following shortly after creating f1:
f1.pack(fill="both", expand=True)
Next, modify evaluation to destroy the children rather than the form itself:
def evaluation(form):
for child in form.winfo_children():
child.destroy()
GotPlatform=platform.get()
for elem in PlatformOption[GotPlatform]:
OptionButtonSel[elem]=Checkbutton(form, text=elem)
OptionButtonSel[elem].pack()
It gives the effect that it doesn't work because the widget is invisible to the screen. This topic however is further explained below
pack_forget() not working
Just to prove my theory I did some other research about the topic and
https://www.geeksforgeeks.org/python-forget_pack-and-forget_grid-method-in-tkinter/
explains the concept of pack_forget() perfectly

simple tkinter question - button command (display other text on click)

i've just started learning tkinter for python, and i'm trying to get the button to change its text when it's clicked on.
this seems like a very simple question, but i can't find any answers. the code i'm using at the moment doesn't work - when the window opens, it displays 'clicked!' as a label above the button immediately, before i've clicked on the button.
from tkinter import *
root = Tk()
def click():
label = Label(root, text = 'clicked!')
label.pack()
button = Button(root, text='click me', command = click())
button.pack()
root.mainloop()
To change an existing button's text (or some other option), you can call its config() method and pass it keyword arguments with new values in them. Note that when constructing the Button only pass it the name of the callback function — i.e. don't call it).
from tkinter import *
root = Tk()
def click():
button.config(text='clicked!')
button = Button(root, text='click me', command=click)
button.pack()
root.mainloop()
You're passing command = click() to the Button constructor. This way, Python executes click, then passes its return value to Button. To pass the function itself, remove the parentheses - command = click.

Text to appear as checkbox is clicked in Python Tkinter

I am coding a GUI in Python 2.7 and I am making checkboxes. I want to know how to make a text appear right beside the checkbox when it is checked and unchecked. For eg. When I check the checkbox the text beside the checkbox should be 'enable' and when I uncheck the checkbox the text should be 'disable'.
You can assign same StringVar to textvariable and variable options of Checkbutton and set onvalue='enable' and offvalue='disable'. Then whenever the state of the checkbutton changes, the text changes:
import tkinter as tk
root = tk.Tk()
var = tk.StringVar(value='disable')
tk.Checkbutton(root, textvariable=var, variable=var, onvalue='enable', offvalue='disable').pack()
root.mainloop()
There's nothing particularly difficult about this. Checkbuttons can call a command when toggled. You can change the text inside the command using the configure method of the widget.
Here's a simple example:
import tkinter as tk
def toggle(widget):
variable = widget.cget("variable")
value = int(widget.getvar(variable))
label = "enable" if value else "disable"
widget.configure(text=label)
root = tk.Tk()
for i in range(10):
cb = tk.Checkbutton(root, text="disable")
cb.configure(command=lambda widget=cb: toggle(widget))
cb.pack(side="top")
root.mainloop()

How to refresh tkinter entry value? I get a 1-click lag

I built an interface where the user fills a hierarchical form. Past values are displayed in a ttk.Treeview.
I allow the user to edit previous values by clicking on the tree. The value gets filled on the form where it can be edited and overwriten.
The problem: the value I insert on the Entry widget is only displayed the next time the user clicks it, so that it is always 1 click lagging. Please run my sample code to get a better understanding. It gets confusing because if the user clicks a value and then another, it will display the previously clicked value.
It must have something to do with the event handling routine in tkinter, but I could not find and answer.
How can I get rid of this lag?
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
def cb_clique(event):
item = tree.selection()[0]
entry1.delete(0, "end")
entry1.insert(0, item)
entry1 = tk.Entry(root, width=15)
entry1.grid(row=1,column=1)
tree = ttk.Treeview(root)
tree.bind("<Button-1>", cb_clique)
tree["columns"]=("valor")
tree.column("valor", width=200 )
tree.heading("valor", text="Valor")
tree.grid(row=3, column = 1, columnspan = 4)
tree.insert("", "end", iid = "Will display position",text = "Click me",
values=("a","b"))
tree.insert("", "end", iid = "Use position to get info",
text = "Click me", values=("a","b"))
root.mainloop()
Looks like the <Button-1> event triggers before the window notices that the selection has changed, so selection() returns the thing that was selected before your click. Try changing the event binding to <<TreeViewSelect>>.
tree.bind("<<TreeviewSelect>>", cb_clique)

tkinter: can't enter into entry widget

I don't understand why the entry boxes under rackGUI.py in my code are static/won't allow anything to be entered. I believe all the Entry objects are instantiated correctly. I specified the textvariable as instances of the StringVar(). My gut tells me the problem lies in command argument in create_button instantiation but I'm not really sure why. I thought by setting command = lambda:function the function would not be called.
Upon clicking 'New' in the menu, main.py successfully calls rackGUI.create() which successfully calls input_form(). Clicking the button 'create_button' successfully calls drawRack which prints to the shell 'test'. I also added a test where I printed the type of value for each entry box i.e., print type(rack_name.get()) and this successfully returns type 'str'.
So again the main problem is that the entry box is static.
Below is my code:
config.py
"""
config.py
"""
import Tkinter as tk
import tkMessageBox as tkmb
#setup
root = tk.Tk()
root.title("TLA Database Tool")
frame = tk.Frame(height = 300, width = 250)
frame.pack()
main.py
#main.py
from config import *
import rackGUI
def createRackTemplate():
rackGUI.create()
def loadRackTemplate():
rackGUI.load()
menubar = tk.Menu(root)
filemenu = tk.Menu(menubar)
filemenu.add_command(label = "New", command = createRackTemplate)
filemenu.add_command(label = "Load", command = loadRackTemplate)
menubar.add_cascade(label = "File", menu = filemenu)
tkmb.showinfo("Welcome", "Under File click New to create a new rack template.\n\
Click load to load rack template.")
root.config(menu = menubar)
root.mainloop()
rackGUI.py
"""
rackGUI.py
"""
from config import *
def input_form():
form_frame = tk.Frame(frame)
form_frame.pack()
tk.Label(form_frame, text = "Rack Template Name (e.g., Knox Type 4)").pack()
rack_name = tk.Entry(form_frame, textvariable = tk.StringVar())
rack_name.pack()
tk.Label(form_frame, text = "Dimensions").pack()
tk.Label(form_frame, text = "#rack rows").pack()
num_rack_rows = tk.Entry(form_frame, textvariable = tk.StringVar())
num_rack_rows.pack()
tk.Label(form_frame, text = "#nodes per row").pack()
num_slots = tk.Entry(form_frame, textvariable = tk.StringVar())
num_slots.pack()
create_button = tk.Button(form_frame, text = "Create!",\
command = lambda: drawRack(rack_name, num_rack_rows, num_slots))
create_button.pack()
def drawRack(rack_name, num_rack_rows, num_slots):
print rack_name.get(), num_rack_rows.get(), num_slots.get()
def create():
input_form()
def load():
pass
For anyone who comes here after me, my solution ended up being
root.overrideredirect(True)
Working fine on Windows, but causing this text entry problem on Mac.
I actually found the problem there. The issue seems to be the focus of the windows, since you're using a messagebox.
In my script I just had put a root.update() before opening another window (in my case a filedialog) and everything worked fine. There's an already existing issue for that: https://bugs.python.org/issue42867#msg384785

Categories

Resources