Is there any solution of write-protecting a tkinter OptionMenu while retaining the possibility to inspect the available Options?
Background: I have a tkinter OptionMenu containing a selection of files that the user can "quick-load" into the application. However it might be that the user does not have permission to load new files.
I now indicate this by putting the OptionMenu in disabled state. But then the dropdown cannot be expanded anymore; meaning that the user cannot look at the available files.
Yes, it is possible to disable the menu and still be able to open it just to see the list. The menu used in OptionMenu is tkinter Menu() and you can access it.
Example:
Op = OptionMenu(root, var, 'First', 'Second', 'Third')
Op.pack()
# Op_Menu is the Menu() class used for OptionMenu
Op_Menu = Op['menu']
Then you can do anything with the Op menu same as Menu()
In your case , How to disable?
We can use menu.entryconfig(index, options) to configure state = 'disabled' / 'normal' as per the user.
Example:
import tkinter as tk
root = tk.Tk()
root.geometry('250x250+100+100')
str = tk.StringVar()
str.set('Select')
Op = tk.OptionMenu(root, str, "First", "Second", "Third")
Op.pack()
# This will disable the First and Third entries in the Op
# state = 'disable' / 'normal'
Op['menu'].entryconfig(0, state='disable')
Op['menu'].entryconfig("Third", state='disable')
entries = Op['menu'].index('end') # This will get the total no. of entries.
# If you want to disable all of the entries uncomment below 2 lines.
# for i in range(entries+1):
# Op['menu'].entryconfig(i, state='disable')
root.mainloop()
For better understanding on how the Menu() is defined inside OptionMenu class can check the source code of OptionMenu() class. (from line 3959)
You can disable each entry of the menu instead of disabling the optionmenu totally using menu.entryconfigure(<index>, state='disabled').
The menu of an optionmenu is stored in the 'menu' property:
import tkinter as tk
root = tk.Tk()
var = tk.StringVar(root)
opmenu = tk.OptionMenu(root, var, *['item %i' % i for i in range(5)])
opmenu.pack()
menu = opmenu['menu']
for i in range(menu.index('end') + 1):
menu.entryconfigure(i, state='disabled')
So you can view all the items in the menu but they are not clickable.
Related
I have a GUI that uses a class that calls a function that provides a popup window.
The popup box has a button that gets a directory from the user using askdirectory() from tkinter.filedialog, a pair of radio buttons(RadioG, RadioY), and checkbox(state_checkbox) that I want to update the two different variables(driveletter and statevalue) . I've set the drive letter to "G:" and that value is held correctly, but doesn't update if I select the "Y:" Radio button. I have the IntVar.set(1), but that checkbox does not populate as checked and checking/unchecking it keeps the value at 1 and does not update it to 0 when unchecked.
Any advice on what I'm missing to keep my values from updating is appreciated.
relevant section of code below:
class preferences(Preferences):
def save_preferences(self):
# Used to reset the folder location desired by the user
self.prefdialog = tk.Tk()
self.prefdialog.title('Set Preferences')
self.driveletter = tk.StringVar()
self.driveletter.set("G:")
self.statevalue = tk.IntVar()
self.statevalue.set(1)
self.fpath = '\\Desktop'
self.RadioG = tk.Radiobutton(self.prefdialog, text="G:", variable=self.driveletter,
value="G:", command=lambda: print(self.driveletter.get()))
self.RadioG.grid(row=3,column=0,sticky=tk.W,pady=4)
self.RadioY = tk.Radiobutton(self.prefdialog, text="Y:", variable=self.driveletter,
value="Y:", command=lambda: print(self.driveletter.get()))
self.RadioY.grid(row=3,column=1,sticky=tk.W,pady=4)
self.state_checkbox = tk.Checkbutton(
self.prefdialog, text="Check to use state folders",
variable=self.statevalue, command=lambda: print(self.statevalue.get()))
self.state_checkbox.grid(row=4,column=0,sticky=tk.W,pady=4)
self.prefdialog.mainloop()
For posterity: Looks like my issue was using Tk() twice. Per Bryan since I'm trying to create a popup window, I should be calling Toplevel(), not a second instance of Tk(). I also definitely called mainloop() twice, once for my main window and once for this popup window.
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
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()
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()
I've being building a basic UI using Tkinter, and I noticed that cmd + a (or Select all command) is not enabled.
How do I enable all the shortcuts in tkinter especially for entry text field.
This is my code :
entry1 = ttk.Entry(root, width = 60)
entry1.pack()
If tkinter doesn't define the shorcuts you want you can define your own by binding keyboard events.
import tkinter as tk
import tkinter.ttk as ttk
def callback(ev):
ev.widget.select_range(0, 'end')
root = tk.Tk()
entry = ttk.Entry(root)
entry.pack()
entry.bind('<Command-a>', callback)
root.mainloop()
I think Command is the correct prefix for the cmd key but I don't have a mac to test. In windows it binds to the control key.
#Goyo already answered your question. I want to share my contribution as I do not see interest in selecting the text of the Entry widget's text and not doing anything else with it. So I am going to provide you a dirty MCVE to show how you are going to use the selected text: a) either you will delete it or b) you will copy it.
For a), the following function will do the job:
def select_text_or_select_and_copy_text(e):
e.widget.select_range(0, 'end')
It will work under the condition you bind the corresponding events described by the function's name to the entry widget:
entry.bind('<Control-a>', select_text_or_select_and_copy_text)
entry.bind('<Control-c>', select_text_or_select_and_copy_text)
For b), you can use this function:
def delete_text(e):
e.widget.delete('0', 'end')
And bind the Delete event to the entry widget:
entry.bind('<Delete>', delete_text)
I tried this MCVE on Ubuntu and it works:
import tkinter as tk
import tkinter.ttk as ttk
def select_text_or_select_and_copy_text(e):
e.widget.select_range(0, 'end')
def delete_text(e):
e.widget.delete('0', 'end')
root = tk.Tk()
entry = ttk.Entry(root)
entry.pack()
entry.bind('<Control-a>', select_text_or_select_and_copy_text)
entry.bind('<Control-c>', select_text_or_select_and_copy_text)
entry.bind('<Delete>', delete_text)
root.mainloop()