I've been trying to bind a callback to a menu item selection, instead of using the command= functionality of the add_command method.
However, it seems that no matter what I try it will only give me a proper index of the menu item ("Menu 1" and "Menu 2") when they are select, instead of the index of the buttons of the menu. When the button is pressed in will just print None.
This is my current test code, but I've been trying a bunch of different stuff.
import tkinter as tk
def menucallback(event):
print(root.call(event.widget, "index", "active"))
root = tk.Tk()
# create menu
menubar = tk.Menu(root)
menu1 = tk.Menu(menubar, tearoff=0)
menu1.add_command(label="Button 1")
menu1.add_command(label="Button 2")
menubar.add_cascade(label="Menu 1", menu=menu1)
menu2 = tk.Menu(menubar, tearoff=0)
menu2.add_command(label="Button 6")
menu2.add_command(label="Button 7")
menubar.add_cascade(label="Menu 2", menu=menu2)
tk.Tk.config(root, menu=menubar)
# bind to function
menubar.bind("<<MenuSelect>>", menucallback)
root.mainloop()
In case it matters, I'm on Windows 7 with Python 3.4
If you want the event to trigger on the dropdown menus, you need to add the same binding to each menu.
The reason you get none when selecting the menu item is likely because the state of the menu changes before the callback is called (ie: there is no active item after you click).
It is maybe a little bit late for you, but I was on a point I thought I would need it.
In fact I do not, but maybe someone else needs it:
import tkinter as tk
def menucallback(event):
check = root.call(event.widget, "index","active")
if check != "none":
menu_index.set(check)
def foo():
display.configure(text=menu_index.get())
index = menu_index.get()
if index == 0:
display.configure(bg='blue')
if index == 1:
display.configure(bg='red')
display.wait_variable(menu_index)
foo()
root = tk.Tk()
display = tk.Label(root, text='default')
display.pack(fill='x')
menu_index = tk.IntVar()
menubar =tk.Menu(root)
menu1 = tk.Menu(menubar, tearoff=0)
menu1.add_command(label="Button1")
menu1.add_command(label="Button2")
menubar.add_cascade(label='Menu1', menu=menu1)
menu1.bind('<<MenuSelect>>', menucallback)
tk.Tk.config(root, menu=menubar)
foo()
root.mainloop()
Finally found a usefull place for that code. It can be used to create scrolling buttons on the menu.
Note
This code seems to be Windows only solution.
Related
I'm barely new in Python, so forgive me if my codes aren't written in a correct 'pythonic way'...
I want to save the user's choice from a listbox. The function is defined in the BES_library module (I need the function to stay here in the module and not in the script!):
##BES_library
def quit(root, listbox, choice):
#get the user's choice
choice = listbox.get(listbox.curselection())
print(choice)
#quit the ListBox
root.destroy()
root.quit()
return choice
And this is my script:
import tkinter as tk
import BES_library as BES_lib
if __name__ == '__main__':
SheetsNames = ['Foglio1', 'Foglio2', 'Foglio3']
SheetName = 'lalala'
root = tk.Tk()
root.title("Select the worksheet to be opened")
listbox = tk.Listbox(root, selectmode= 'single')
for item in SheetsNames:
listbox.insert("end", item)
listbox.pack()
button = tk.Button(root, text='Select', command= lambda: BES_lib.quit(root, listbox, SheetName) )
button.pack()
root.geometry("450x300+120+120")
root.mainloop()
print(SheetName)
#now I want to work with the SheetName chosen by the user
The user is supposed to select one of the options from the listbox and click the 'Select' button. Upon clicking the button, I want to attribute his choice to a variable as a string and quit the listbox.
The question is, how can I use what the user entered? The button doesn't save the return value of functions.
I tried also this code, but nothing seems to be changed:
import tkinter as tk
import BES_library as BES_lib
if __name__ == '__main__':
SheetsNames = ['Foglio1', 'Foglio2', 'Foglio3']
root = tk.Tk()
root.title("Select the worksheet to be opened")
SheetName = tk.StringVar()
listbox = tk.Listbox(root, selectmode= 'single')
for item in SheetsNames:
listbox.insert("end", item)
listbox.pack()
button = tk.Button(root, text='Select', command= lambda: BES_lib.quit(root, listbox, SheetName) )
button.pack()
root.geometry("450x300+120+120")
root.mainloop()
print(SheetName.get())
#now I want to work with the SheetName chosen by the user
Here's a method that eliminates the need to use the quit method, at all. Your quit method didn't make sense, anyway. It claims to remove the listbox but it destroys the entire root of the app. You also have a misconception ~ you are trying to print something after calling root.mainloop. Nothing you do after root.mainloop is ever going to get hit. root.mainloop needs to be the last line of your program.
Using my method, The listbox and button are placed in a frame. Every time a user clicks on the listbox it stores the selection. Calling sheetframe.pack() shows the selection frame and clicking select unpacks/hides it. In this way, select isn't selecting anything. The selection was already recorded as soon as a listbox item was clicked.
import tkinter as tk
#init root
root = tk.Tk()
root.title("Select the worksheet to be opened")
root.geometry("450x300+120+120")
#init vars
sheetlist = ['Foglio1', 'Foglio2', 'Foglio3']
sheetname = 'lalala'
#container for listbox and button
sheetframe = tk.Frame(root)
listbox = tk.Listbox(sheetframe, selectmode='single', listvariable=tk.StringVar(value=sheetlist))
listbox.pack()
#unpack/hide sheetframe on click
tk.Button(sheetframe, text='Select', command=sheetframe.pack_forget).pack()
#store a listbox selection a soon as it is clicked
def selection(event):
global sheetname
sheetname = listbox.get('anchor')
print(f'{sheetname} selected')
#bind on ButtonRelease so anchor is definitely set
listbox.bind('<ButtonRelease-1>', selection)
#show sheetframe
sheetframe.pack()
root.mainloop()
Working on a project in which I use Tkinter in order to create a GUI that gives a list of software in a drop-down and when a particular software is chosen, it takes you to a separate window where a user's name will be entered and they would be added to a database. With the code I have so far, I am able to link a "submit" button on the second window to a function that prints a confirmation message as a test to make sure the button works. My issue now is trying to get the input from the entry field and link the input to the "Submit" button but I can't seem to find a way to do so. I was wondering if I could get some advice on how to go about this. Would classes need to be used in order to make it work? or can I stick with functions and keep the code relatively simple?
I have added the code for my program below.
import tkinter as tk
from tkinter import *
from tkinter import ttk
root = tk.Tk() # Main window
root.title("Software Licences")
root.geometry("300x300")
frame = ttk.Frame(root, padding="50 0 50 50")
frame.pack(fill=tk.BOTH, expand=True)
tkvar = StringVar()
choices = ['Imagenow', # Dropdown menu with software options
'FileMakerPro',
'Acrobat',
'Office',
'Lotus Notes']
tkvar.set('Acrobat') # Shows default dropdown menu option when program is opened
popupMenu = OptionMenu(frame, tkvar, *sorted(choices))
popupLabel = ttk.Label(frame, text="Choose Software")
popupLabel.pack()
popupMenu.pack()
def software_pages(): # In this function is the 2nd window with for each individual software
top = Toplevel()
top.title("Software Licences")
top.geometry("300x300")
myLabel = Label(top, text=tkvar.get()).pack()
employee_entrylbl = Label(top, text="Employee name").pack()
employee_entry = Entry(top, width=25, textvariable=tk.StringVar) # Entry field for adding user's name
employee_entry.pack() # Entry field is displayed
if tkvar.get() == "Acrobat": # for each if statement, button command is link to the functions
# defined below
button = ttk.Button(top, text="Submit", command=add_to_acrobat).pack()
elif tkvar.get() == "Imagenow":
button = ttk.Button(top, text="Submit", command=add_to_imagenow).pack()
elif tkvar.get() == "FileMakerPro":
button = ttk.Button(top, text="Submit", command=add_to_filemakerpro).pack()
elif tkvar.get() == "Office":
button = ttk.Button(top, text="Submit", command=add_to_office).pack()
else:
button = ttk.Button(top, text="Submit", command=add_to_lotusnotes).pack()
exit_button = ttk.Button(top, text="Exit", command=top.destroy).pack() # Exit button for second window
add_emp_button = ttk.Button(frame, text="Next", command=software_pages) # "Next" button in the main window takes the
# user to the second window
add_emp_button.pack()
# Functions below are linked to the button commands of each software in the second window function defined earlier.
# They print out specified messages that confirm the user had been added
def add_to_acrobat():
return print("User added to Acrobat")
def add_to_lotusnotes():
print("User added to IBM")
def add_to_imagenow():
print("User added to imagenow")
def add_to_office():
print("User added to 365")
def add_to_filemakerpro():
print("User added to FMP")
def click_button(): # Function for Exit button for main window
root.destroy()
exit_button = ttk.Button(frame, text="Exit", command=click_button) # Exit button for main window
exit_button.pack()
root.mainloop()
You can pass parameters to the command of tkinter.command using partial from the functools module.
in your case:
button = ttk.Button(top, text="Submit", command=partial(add_to_acrobat, employee_entry)).pack()
in the above line, I send the employee_entry(Which holds your desired text) to the add_to_acrobat function
and the add_acrobat function should look like this:
def add_to_acrobat(e):
print(e.get())
return print("User added to Acrobat")
Hope it helps
I have a button called view and when it is clicked on it will create a new window with a list in a listbox and a list of checkboxes that correspond to the listbox. When switching through the items in the listbox you will see that the listbox items are independent of each other and will have their own check box values. As of right now it will remember the checkbox values for each listbox item except when you close the created window. When you close the second window that was created all of that information disappears when you try and open the window back up again. I need a way to create a second window, do everything it does now with the listbox and checkboxes, but when closed it can be opened again and pick up where you left off.
For example, if I highlight the first item in the listbox and check the first checkbox, I should be able to close that window and open it again and when the first item in the listbox is highlighted, I see that there is a check in the first checkbox.
import tkinter
from tkinter import *
def myfunction(event):
canvas1.configure(scrollregion=canvas1.bbox("all"))
def onselect(evt):
# Note here that Tkinter passes an event object to onselect()
w = evt.widget
x = 0
index = int(w.curselection()[0])
value = w.get(index)
print('You selected item %d: "%s"' % (index, value))
for y in enable:
for item in list_for_listbox:
checkbuttons[item][y][1].grid_forget()
checkbuttons[value][y][1].grid(row=x, column=0)
# Label(frame2, text="some text").grid(row=x, column=1)
x += 1
def printcommand():
for item in list_for_listbox:
for y in enable:
print(item + " [" + y + "] " + str(checkbuttons[item][y][0].get()))
def create_new_window():
global new_window
new_window = tkinter.Toplevel()
new_window.geometry("750x500")
new_window_commands()
master = tkinter.Tk()
master.title("Checkboxes test")
master.geometry("750x500")
button1 = Button(master, command =create_new_window,text="View")
button1.place(x=50,y=250)
def new_window_commands():
# enable = ['button 1', 'button 2', 'button 3', 'button 4', 'button 5', 'button 6', 'button 7']
global list_for_listbox
global enable
global checkbuttons
global canvas1
enable = []
for x_number_of_items in range(1, 15):
enable.append("button " + str(x_number_of_items))
list_for_listbox = ["one", "two", "three", "four"]
listbox = Listbox(new_window)
listbox.place(x=5, y=5, width=100, height=10 + 16*len(list_for_listbox))
listbox.update()
frame1 = Frame(new_window, borderwidth=1, relief=GROOVE, highlightthickness=1, highlightbackground="black",
highlightcolor="black")
frame1.place(x=listbox.winfo_width() + 10, y=5, width=300, height=listbox.winfo_height())
canvas1 = Canvas(frame1)
frame2 = Frame(canvas1, height=500)
scrollbar1 = Scrollbar(frame1, orient="vertical", command=canvas1.yview)
canvas1.configure(yscrollcomman=scrollbar1.set)
scrollbar1.pack(side="right", fill="y")
canvas1.pack(side="left")
canvas1.create_window((0, 0), window=frame2, anchor='nw')
frame2.bind("<Configure>", myfunction)
printbutton = Button(new_window, text="Print", command=printcommand)
printbutton.place(x=100, y=250)
checkbuttons = {}
for item in list_for_listbox:
listbox.insert(END, item)
checkbuttons[item] = (dict())
for y in enable:
temp_var = BooleanVar()
checkbuttons[item][y] = [temp_var, Checkbutton(frame2, text=y, variable=temp_var)]
listbox.bind('<<ListboxSelect>>', onselect)
print(enable)
mainloop()
printcommand()
With your current structure, the simplest fix would be:
Only create the new_window once.
withdraw() new_window instead of letting it close each time.
Open the same instance of new_window again when called upon.
You'll need to implement the following:
# Default your new_window to None
new_window = None
def create_new_window():
global new_window
# If new_window doesn't exist, create a new one
if not new_window:
new_window = tkinter.Toplevel()
new_window.geometry("750x500")
# add a new protocol to redirect on closing the window.
new_window.protocol("WM_DELETE_WINDOW", hide_window)
new_window_commands()
else:
# if new_window already exist, just unhide it
new_window.deiconify()
# add a new function for when window is closing
def hide_window():
global new_window
new_window.withdraw()
You might also want to add the same protocol method under master so that when it closes, destroy both master and new_window object:
master.protocol('WM_DELETE_WINDOW', destroy_all)
def destroy_all():
global master
global new_window
master.destroy()
new_window.destroy()
If possible, for your next tkinter code I would suggest considering an object oriented approach though. I will see if I can provide a short sample here later.
As a side note, while I understand a lot of documentations in tkinter uses the from tkinter import * approach, I would discourage this practice and advise to import tkinter as tk instead (or as you already did, import tkinter, which accomplishes the same thing). See relevant answer here
Here's a quick sample of OOP approach in a similar vein:
import tkinter as tk
# Here the main window can be called upon as its own instance with its own instance attributes.
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.main_button = tk.Button(self, text="Creat a sub window", command=self.open_sub_window)
self.main_button.pack()
# define the things you wish to retain under the main window as an instance attribute
self.sub_check_var = tk.BooleanVar()
self.sub_entry_var = tk.StringVar()
# when creating a new window, just reference back to the main attributes you've already created.
def open_sub_window(self):
self.sub_window = tk.Toplevel()
tk.Checkbutton(self.sub_window, text="I'm a checkbox!", variable=self.sub_check_var).pack()
lbl_frm = tk.LabelFrame(self.sub_window, text="I am an entry!")
lbl_frm.pack()
tk.Entry(lbl_frm, text=self.sub_entry_var).pack()
gui = Window()
gui.mainloop()
Note this is but one way to do it. You just need to feel around to get comfortable with your implementation, there's no right/wrong way to do things.
I have a code where I have a drop down menu and what I need to do is that when I select an entry from the drop down list (ex: Send an email) and press on go, I need this to populate another tk window (child window).
I know I am doing something wrong but can not comprehend how to overcome this, I have been searching for a while but I am unable to find a solution or guidance on how to complete this.
Thanks in advance for your help with this!
from tkinter import *
root = Tk()
root.geometry("400x100")
#========================================
#Entry area to enter the number
labelmain = Label(root,text="Please enter number:")
labelmain.pack()
entryvar = StringVar(root)
entrymain = Entry(root, textvariable=entryvar,width=30)
entrymain.pack()
#========================================
#Create option drop down list:
lst = ["Save details to DB", "Send an email", "Copy format", "email", "View report"]
ddl = StringVar(root)
ddl.set(lst[0])
option = OptionMenu(root, ddl, *lst)
option.pack()
#========================================
#Function to get the values from drop down list
def ok():
print("value is: " + ddl.get())
#root.quit()
#=========================================
#Button to process the selection:
btnmain = Button(root,text="Go", command=ok)
btnmain.pack()
#=========================================
if ddl.get() == "Send an email":
samepmrdb = Tk()
samepmrdb.mainloop()
root.mainloop()
You are checking the value of ddl right after you open up your window. As you said in your question, you want some stuff happen after pressing the button so you need to put those codes under the command of said button.
Also, a tkinter app should only have one Tk() instance and one mainloop. When you want to open another window, you should use Toplevel().
def ok():
print("value is: " + ddl.get())
if ddl.get() == "Send an email":
samepmrdb = Toplevel()
#now you can populate samepmrdb as you like
If all you're looking to do is find a way to update a second tkinter window with the selection from an OptionMenu on the first tkinter window this can be achieved easily using the below code:
from tkinter import *
class App:
def __init__(self, master):
self.master = master
self.top = Toplevel(master)
self.master.withdraw()
self.var = StringVar()
self.var.set("Example1")
self.option = OptionMenu(self.top, self.var, "Example1", "Example2", "Example3", "Example4")
self.button = Button(self.top, text="Ok", command=lambda:self.command(self.var))
self.label = Label(self.master)
self.option.pack()
self.button.pack()
self.label.pack()
def command(self, var):
self.master.deiconify()
self.label.configure(text=var.get())
self.label.pack()
root = Tk()
app = App(root)
root.mainloop()
This creates a Toplevel widget which contains an OptionMenu and Button widget. The Button widget will then output the selection from the OptionMenu widget when pressed.
This kind of logic can be used for all sorts of things and it's relatively simple to pass information from one window to another, provided this is what your question is asking.
Is it possible to change the label of an item in a menu with tkinter?
In the following example, I'd like to change it from "An example item" (in the "File" menu) to a different value.
from tkinter import *
root = Tk()
menu_bar = Menu(root)
file_menu = Menu(menu_bar, tearoff=False)
file_menu.add_command(label="An example item", command=lambda: print('clicked!'))
menu_bar.add_cascade(label="File", menu=file_menu)
root.config(menu=menu_bar)
root.mainloop()
I found the solution myself in the Tcl manpages:
Use the entryconfigure() method like so, which changes the value after it has been clicked:
The first parameter 1 has to be the index of the item you want to change, starting from 1.
from tkinter import *
root = Tk()
menu_bar = Menu(root)
def clicked(menu):
menu.entryconfigure(1, label="Clicked!")
file_menu = Menu(menu_bar, tearoff=False)
file_menu.add_command(label="An example item", command=lambda: clicked(file_menu))
menu_bar.add_cascade(label="File", menu=file_menu)
root.config(menu=menu_bar)
root.mainloop()
I do not know if that used to be different on 2.7, but it does not work on 3.4 anymore.
On python 3.4 you should start counting entries with 0 and use entryconfig.
menu.entryconfig(0, label = "Clicked!")
http://effbot.org/tkinterbook/menu.htm
Check this dynamic menu example. The main feature here is that you don't need to care about a serial number (index) of your menu item. No index (place) of your menu is needed to track. Menu item could be the first or the last, it doesn't matter. So you could add new menus without index tracking (position) of your menus.
The code is on Python 3.6.
# Using lambda keyword and refresh function to create a dynamic menu.
import tkinter as tk
def show(x):
""" Show your choice """
global label
new_label = 'Choice is: ' + x
menubar.entryconfigure(label, label=new_label) # change menu text
label = new_label # update menu label to find it next time
choice.set(x)
def refresh():
""" Refresh menu contents """
global label, l
if l[0] == 'one':
l = ['four', 'five', 'six', 'seven']
else:
l = ['one', 'two', 'three']
choice.set('')
menu.delete(0, 'end') # delete previous contents of the menu
menubar.entryconfigure(label, label=const_str) # change menu text
label = const_str # update menu label to find it next time
for i in l:
menu.add_command(label=i, command=lambda x=i: show(x))
root = tk.Tk()
# Set some variables
choice = tk.StringVar()
const_str = 'Choice'
label = const_str
l = ['dummy']
# Create some widgets
menubar = tk.Menu(root)
root.configure(menu=menubar)
menu = tk.Menu(menubar, tearoff=False)
menubar.add_cascade(label=label, menu=menu)
b = tk.Button(root, text='Refresh menu', command=refresh)
b.pack()
b.invoke()
tk.Label(root, textvariable=choice).pack()
root.mainloop()