Self Made Tkinter Popup Menu Python - python

I was writing a GUI library in Python based on tkinter and I was designing and building all the widgets, but I have come to the PopUp menus.
Due that tkinter picks system menus and this can't be customized, I write the following code to make a frame where I can put my customized buttons in and works as a popup.
from tkinter import *
root = Tk()
w = Label(root, text="Right-click to display menu", width=40, height=20)
w.place(x=0)
def function1():
print('function1 activated')
# create a menu
f = Frame(root,width=80,height=60,background='green')
b2 = Button(f,text='function',command=function1)
b2.pack()
def open_popup(event):
try:
f.place(x=event.x, y=event.y)
root.after(1)
f.focus_set()
w.bind_all("<Button-1>",close_popup)
except:
print("Can't open popup menu")
def close_popup(event):
try:
f.place_forget()
root.after(1)
w.unbind_all("<Button-1>")
except:
print("Can't close popup menu")
w.bind("<Button-3>", open_popup)
b = Button(root, text="Quit", command=root.destroy)
b.pack()
root.mainloop()
Everything works well, if I clicked with the mouse right-button the popup menu appears, and if I clicked on every other part the popup menu dissapears.
The problem is that, due to bind_all when I press the button of my popup menu, function1 doesn't run and the event handler closes the popup. I have tried with only bind but this time, function1 runs and the event handler doesn't activates.
Is there anyway I can do that?
Thanks

I would do this using a tracking variable.
We can first assign None to f as a way to check if f is currently set up.
If f is not None then we create frame and button. Then when the function is activated we can run function and destroy the frame the button was in. This also destroys the button and then we set f back to None for out next use.
Take a look at the below reworked example.
Let me know if you have any questions.
from tkinter import *
root = Tk()
w = Label(root, text="Right-click to display menu", width=40, height=20)
w.place(x=0)
f = None # Tracking F to see if it is None or not.
def function1():
global f
print('function1 activated')
# add this to the end of the function to destroy the frame and reset f
if f != None:
f.destroy()
f = None
def open_popup(event):
global f
# if f is None then create frame and button else don't
if f == None:
f = Frame(root,width=80,height=60,background='green')
f.place(x=event.x, y=event.y)
b2 = Button(f,text='function',command=function1)
b2.pack()
else:
print("Can't open popup menu")
w.bind("<Button-3>", open_popup)
b = Button(root, text="Quit", command=root.destroy)
b.pack()
root.mainloop()

I found a way to do this without modifying so much the code, the idea of the tracking variable was good but doesn't solve all the problems, and this code does.
from tkinter import *
root = Tk()
w = Label(root, text="Right-click to display menu", width=40, height=20)
w.pack()
def function1():
print('function1 activated')
try:
f.place_forget()
except:
pass
# create a menu
f = Frame(root,width=80,height=60,background='green')
b2 = Button(f,text='function',command=function1)
b2.place(x=0,y=5)
def open_popup(event):
try:
f.place(x=event.x, y=event.y)
root.after(1)
f.focus_set()
except:
pass
def close_popup(event):
try:
f.place_forget()
root.after(1)
w.unbind_all("<Button-1>")
except:
pass
def enable_depopup(event):
w.bind_all("<Button-1>",close_popup)
def disable_depopup(event):
w.unbind_all("<Button-1>")
w.bind("<Button-3>", open_popup)
w.bind("<Motion>", enable_depopup)
f.bind("<Motion>", disable_depopup)
b = Button(root, text="Quit", command=root.destroy)
b.pack()
root.mainloop()
In this way, everytime I move the mouse over the parent window, the <Button-1> of the mouse is binded to close the popup menu.
And doing a trick, that is place the button of the menu a few pixels down, this let the mouse pass through the popup frame to reach the button and disable the <Button-1> binding letting me click the button.
The function of the button activate the place_forget method of the frame, so everything works correctly.

Related

How can I prevent my main window from running with a Toplevel window in python and Tkinter?

I am trying to stop the main window from running until a button has been pressed on a separate Toplevel window.
Example:
from tkinter import *
let_user_through = False
window = Tk()
def activate_main_window():
global let_user_through
let_user_through = True
frame = Toplevel()
b = Button(frame, text="Enter", command=activate_main_window).pack()
if let_user_through == True:
lbl = Label(window, text="Hello")
#bunch of code
#bunch of code
window.mainloop()
In this example, in the main window there is a label that reads: "Hello".
But I don't want people to be able to see it if they haven't pressed the button on the frame
Once the user has pressed the button, the frame will destroy itself and the main window will continue executing a bunch of code.
I'm a beginner to tkinter so i'm not sure if the answer is obvious or not. Thanks!
You can use frame.wait_window() to wait until frame is destroyed. Also you need to call frame.destroy() inside activate_main_window().
from tkinter import *
let_user_through = False
window = Tk()
def activate_main_window():
global let_user_through
let_user_through = True
frame.destroy() # need to destroy frame
# wait for root window becomes visible
# otherwise "frame" may be open behind root window
window.wait_visibility()
frame = Toplevel()
Button(frame, text="Enter", command=activate_main_window).pack()
frame.grab_set() # capture keyboard/mouse events
frame.wait_window() # wait for "frame" to be destroyed
if let_user_through:
Label(window, text="Hello").pack()
#bunch of code
#bunch of code
# should it be within the above for loop?
window.mainloop()
A small change to your code using window.withdraw and window.deiconify works for me. #acw1668 correctly pointed out an error in my original code, so here is the fix.
Your main window is invisible until user presses button.
import tkinter as tk
let_user_through = False
window = tk.Tk()
window.withdraw()
def activate_main_window():
global let_user_through
let_user_through = True
frame.destroy() # need to destroy frame
frame = tk.Toplevel()
tk.Button(frame, text="Enter", command=activate_main_window).pack()
frame.wait_window() # wait for "frame" to be destroyed
if let_user_through:
tk.Label(window, text="Hello").pack()
window.update()
window.deiconify()
#bunch of code
#bunch of code
window.mainloop()
I've created a class that removes the need for let_user_through and sets up code for any next steps.
import tkinter as tk
class invisible:
def __init__( self ):
self.window = tk.Tk()
self.window.withdraw() # make window invisible
self.frame = tk.Toplevel()
tk.Button(
self.frame, text = "Enter", command = self.activate_main_window ).pack( fill='both' )
self.frame.wait_window( ) # wait for "frame"
self.button = tk.Button( self.window, text = "Hello", command = self.remove_next )
self.button.pack( fill = 'both')
self.window.update()
self.window.deiconify() # make window visible
def activate_main_window( self ):
self.frame.destroy() # need to destroy frame
def remove_next( self ):
self.button.destroy()
tk.Label( self.window, text = "Bunch of codeA" ).pack( fill = 'both' )
tk.Label( self.window, text = "Bunch of codeB" ).pack( fill = 'both' )
tk.Label( self.window, text = "Bunch of codeC" ).pack( fill = 'both' )
# continue code initialization
if __name__ == '__main__':
make = invisible()
tk.mainloop()

Value from new window, button, and script to main script/window python+tkinter

I have a main script. When you push the button (in tkinter) you open a class with a new window and a new button.
When you click the new button (in the new window and different file) the text in the main window should be updated.
I have the following:
Main script
from tkinter import *
from kandit import Kandit
root=Tk()
def hoop():
s=Kandit()
label.configure(text=s)
button=Button(root, text="ok", command=hoop)
button.grid(row=0,column=0)
label=Label(root, text="nog niet dus")
label.grid(row=1, column=0)
Sub-script
class Kandit:
def __init__(self):
self.Master=Toplevel()
self.Button=Button(self.Master, text="check", command=self.Return())
self.Button.grid(row=0,column=0)
self.Master.mainloop()
def Return(self):
self.Keuze="nothing"
return self.Keuze #, self.Master.destroy()
except from the destroy it works until the moment I press the "check" button.
Than nothing happens.
Try this:
import tkinter as tk
class Kandit:
def __init__(self):
# Set the default value for keuze:
self.keuze = None
self.master = tk.Toplevel()
# If the user presses the "X" in the window toolbar call `_return`
self.master.protocol("WM_DELETE_WINDOW", self.destroy)
# When the button is pressed call `_return`
self.button = tk.Button(self.master, text="check", command=self._return)
self.button.grid(row=0, column=0)
# Start the mainloop. Later we will stop the mainloop
# Note it waits until the button is pressed/window closed
self.master.mainloop()
# Here we can garantee that `_return` was called
# and `self.keuze` has set to the value we need.
def _return(self):
# Set the result in a variable
self.keuze = "nothing"
self.destroy()
def destroy(self):
# Stop the mainloop so that the program can continue
self.master.quit()
# Remove the window from the screen
self.master.destroy()
def hoop():
# Create the window and wait until the button is pressed/window closed
new_window = Kandit()
# Get the result from the class
new_text = new_window.keuze
#if
# Set the label with the result
label.configure(text=new_text)
root = tk.Tk()
button = tk.Button(root, text="ok", command=hoop)
button.grid(row=0, column=0)
label = tk.Label(root, text="nog niet dus")
label.grid(row=1, column=0)
root.mainloop()
The problem in your case is that you can't return values from the __init__ method. This is why you have you save the result to a variable and retrieve it later

Tkinter GUI program issue. Entry.get() problem

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

How do you close a tkinter window in another function?

I want a button in my window to open a new window and close the previous one. Is it possible to have one button do both of these? I've tried in the following code, but it hasn't worked, just told me that window is not defined:
import tkinter
def window1():
window = tkinter.Tk()
tkinter.Button(window, text = "Next", command = window2).pack()
window.mainloop()
def window2():
window.destroy() #This is where the error is
menu = tkinter.Tk()
etc, etc, etc
window1()
First, you need to return the window object from the first function:
def window1():
window = tkinter.Tk()
tkinter.Button(window, text = "Next", command = lambda: window2(window)).pack()
window.mainloop()
return window
Then, you need to pass the window as an argument to your function:
def window2(window):
window.destroy()
menu = tkinter.Tk()
And then call window1 with:
window = window1()
and click the button to destroy it and do the rest
This is an example using Toplevels, which is usually a better choice than creating, destroying, re-creating Tk() instances. The unique Toplevel ID is passed to the close_it function using partial(). You would, of course, combine them or have the close function call the open function.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
from functools import partial
class OpenToplevels():
""" open and close additional Toplevels with a button
"""
def __init__(self):
self.root = tk.Tk()
self.button_ctr=0
but=tk.Button(self.root, text="Open a Toplevel",
command=self.open_another)
but.grid(row=0, column=0)
tk.Button(self.root, text="Exit Tkinter", bg="red",
command=self.root.quit).grid(row=1, column=0, sticky="we")
self.root.mainloop()
def close_it(self, id):
id.destroy()
def open_another(self):
self.button_ctr += 1
id = tk.Toplevel(self.root)
id.title("Toplevel #%d" % (self.button_ctr))
tk.Button(id, text="Close Toplevel #%d" % (self.button_ctr),
command=partial(self.close_it, id),
bg="orange", width=20).grid(row=1, column=0)
Ot=OpenToplevels()
Yes. Is possible. But you'll need to def that:
def window1:
blablabla
blablabla
def window2:
window2.destroy() <-- Here where the error was
How you noticed, put your name of window what you want Destroy and it will work!
using Python3
You could use a "global" such as:
root = Tk()
root.title('This is the root window')
def window_create():
global window_one
window_one = Tk()
window_one.title('This is window 1')
Then, from any function (or elsewhere) when you want to destroy window_one, do:
def window_destroyer():
window_one.destroy()
You could call your window_destroyer function from a button anywhere such as root which the example shows:
kill_window_btn = Button(root, text="Destroy", command=window_destroyer).pack()
Of course, follow your own naming conventions. :)
It seems to me, just 'global window_one' would solve it.

How do I bind the enter key to a function in tkinter?

I am a Python beginning self-learner, running on MacOS.
I'm making a program with a text parser GUI in tkinter, where you type a command in a Entry widget, and hit a Button widget, which triggers my parse() funct, ect, printing the results to a Text widget, text-adventure style.
> Circumvent the button
I can't let you do that, Dave.
I'm trying to find a way to get rid of the need to haul the mouse over to the Button every time the user issues a command, but this turned out harder than I thought.
I'm guessing the correct code looks like self.bind('<Return>', self.parse())? But I don't even know where to put it. root, __init__, parse(), and create_widgets() don't want it.
To be clear, the only reason anyone should hit enter in the prog is to trigger parse(), so it doesn't need to be espoused to the Entry widget specifically. Anywhere it works is fine.
In response to 7stud, the basic format:
from tkinter import *
import tkinter.font, random, re
class Application(Frame):
def __init__(self, master):
Frame.__init__(self, master, ...)
self.grid()
self.create_widgets()
self.start()
def parse(self):
...
def create_widgets(self):
...
self.submit = Button(self, text= "Submit Command.", command= self.parse, ...)
self.submit.grid(...)
root = Tk()
root.bind('<Return>', self.parse)
app = Application(root)
root.mainloop()
Try running the following program. You just have to be sure your window has the focus when you hit Return--to ensure that it does, first click the button a couple of times until you see some output, then without clicking anywhere else hit Return.
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
root.bind('<Return>', func)
def onclick():
print("You clicked the button")
button = tk.Button(root, text="click me", command=onclick)
button.pack()
root.mainloop()
Then you just have tweak things a little when making both the button click and hitting Return call the same function--because the command function needs to be a function that takes no arguments, whereas the bind function needs to be a function that takes one argument(the event object):
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
def onclick(event=None):
print("You clicked the button")
root.bind('<Return>', onclick)
button = tk.Button(root, text="click me", command=onclick)
button.pack()
root.mainloop()
Or, you can just forgo using the button's command argument and instead use bind() to attach the onclick function to the button, which means the function needs to take one argument--just like with Return:
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
def onclick(event):
print("You clicked the button")
root.bind('<Return>', onclick)
button = tk.Button(root, text="click me")
button.bind('<Button-1>', onclick)
button.pack()
root.mainloop()
Here it is in a class setting:
import tkinter as tk
class Application(tk.Frame):
def __init__(self):
self.root = tk.Tk()
self.root.geometry("300x200")
tk.Frame.__init__(self, self.root)
self.create_widgets()
def create_widgets(self):
self.root.bind('<Return>', self.parse)
self.grid()
self.submit = tk.Button(self, text="Submit")
self.submit.bind('<Button-1>', self.parse)
self.submit.grid()
def parse(self, event):
print("You clicked?")
def start(self):
self.root.mainloop()
Application().start()
Another alternative is to use a lambda:
ent.bind("<Return>", (lambda event: name_of_function()))
Full code:
from tkinter import *
from tkinter.messagebox import showinfo
def reply(name):
showinfo(title="Reply", message = "Hello %s!" % name)
top = Tk()
top.title("Echo")
Label(top, text="Enter your name:").pack(side=TOP)
ent = Entry(top)
ent.bind("<Return>", (lambda event: reply(ent.get())))
ent.pack(side=TOP)
btn = Button(top,text="Submit", command=(lambda: reply(ent.get())))
btn.pack(side=LEFT)
top.mainloop()
As you can see, creating a lambda function with an unused variable "event" solves the problem.
I found one good thing about using bind is that you get to know the trigger event: something like: "You clicked with event = [ButtonPress event state=Mod1 num=1 x=43 y=20]" due to the code below:
self.submit.bind('<Button-1>', self.parse)
def parse(self, trigger_event):
print("You clicked with event = {}".format(trigger_event))
Comparing the following two ways of coding a button click:
btn = Button(root, text="Click me to submit", command=(lambda: reply(ent.get())))
btn = Button(root, text="Click me to submit")
btn.bind('<Button-1>', (lambda event: reply(ent.get(), e=event)))
def reply(name, e = None):
messagebox.showinfo(title="Reply", message = "Hello {0}!\nevent = {1}".format(name, e))
The first one is using the command function which doesn't take an argument, so no event pass-in is possible.
The second one is a bind function which can take an event pass-in and print something like "Hello Charles! event = [ButtonPress event state=Mod1 num=1 x=68 y=12]"
We can left click, middle click or right click a mouse which corresponds to the event number of 1, 2 and 3, respectively. Code:
btn = Button(root, text="Click me to submit")
buttonClicks = ["<Button-1>", "<Button-2>", "<Button-3>"]
for bc in buttonClicks:
btn.bind(bc, lambda e : print("Button clicked with event = {}".format(e.num)))
Output:
Button clicked with event = 1
Button clicked with event = 2
Button clicked with event = 3
This worked for me while assigning buttons to functions.
Create a function to set buttons to other functions:
def enter(event=clr):
but_ch['command'] = clr
Then bind the function to the root and invoke:
root.bind('<Return>', lambda enter: but_ch.invoke())

Categories