Tkinter Open New Frame Without A Button Press - python

I have a multi-frame Tkinter program running and am in a situation where I need a new frame to open without a button press.
If I were to have a button it would be coded like this:
button = tk.Button(self, text="New Window",
command=lambda: controller.show_frame("NewWindow"))
Is there a way I can make a new window open after a time.sleep(60) command? This is what I have tried:
def on_button(self):
if LogIn in Data:
time.sleep(5)
print("Welcome")
root.after(6, controller.show_frame("HomePage"))
else:
print("please register")
Various Data and coding goes after the defining. If the condition is true it 'Logs In' This is when I want it to show the new frame
Root gives the error of: NameError: name 'root' is not defined
Controller.after(etc) gives : NameError: name 'controller' is not defined
Despite controller being used frequently without issue throughout the rest of the program

I would recommend the after callback See: Alarm handlers and other non-event callbacks
after(delay_ms, callback=None, *args) [#]
Registers an alarm callback that is called after a given time.
root.after(60000, new_window_func, args)

Related

Tkinter not waiting for user input inside functions

I am trying to make a program that when conditions are met, goes back to the beginning and waits. But instead of waiting for the user to push a button, it continues through the code.
I am using python 3.7.4 and Windows 10.
I assume this problem occurs because tkinter doesn't wait for user input in this situation, and instead continues through the code.
My code:
from tkinter import *
from tkinter.ttk import *
def start():
print("Start")
# Removes all widgets without destroying root
for widget in root.winfo_children():
widget.destroy()
button_1 = Button(root, text="Begin", command=begin).pack()
button_2 = Button(root, text="Do something else", command=something).pack()
# I want the program to wait here for the user to click a button
def begin():
print("\nDoing stuff")
if True:
start()
print("This should not be printed")
def something():
pass
root = Tk()
root.geometry("300x300")
btn1 = Button(root, text = "Start", command = start)
btn1.pack()
root.mainloop()
This outputs:
Start
Doing stuff
Start
This should not be printed
I want this to output:
Start
Doing stuff
Start
And then wait for the user to select a button.
If you want a function to wait for a user action, you need to explicitly tell it to wait.
Tkinter has three functions for that. One is wait_window, which will wait for a window to be destroyed. One is wait_visibility, which will wait for the visibility of a window to change. The third one is wait_variable, which waits for a specific tkinter variable to be set.
While tkinter is waiting, it's able to service other events.
In your case, the solution might look something like this:
var = BooleanVar(value=False)
def do_something():
something()
var.set(True)
button_2 = Button(root, text="Do something else", command=do_something).pack()
print("waiting...")
root.wait_variable(var)
print("done waiting.")
When you modify your code to include the above snippet, you'll notice that "waiting..." will be printed on stdout, and then nothing else will be printed until you click on the "Do something else" button and something returns, allowing the variable to be modified.

Tkinter TopLevel Destroy not being detected

So I've given myself a little project and I'm trying to make a little tool to connect to the OKEX exchange. Now I'm currently working on the GUI and I've decided to use Tkinter. After lots of research and what-not, I've come up with the following, but now I've become a bit stuck.
I've got 2 classes, one for the main window and one for the login window. However, some of the functions of the main window rely on what happens after I've submitted the login details. Now I know that Toplevel is for creating additional windows in Tkinter, and you normally close these windows with .destroy() , and if I want to pick up on this event in the main window class then I need to use the Toplevel.protocol("WM_DELETE_WINDOW", function_name) call ...but this isn't working for me.
It works as expected if I close using the cross in the top right, but not if I close with my function that calls .destroy() , can anyone explain to me why this isn't working as intended? Perhaps I have missed something ?
I want to change the text in the first frame to "Logged In" after the user (me) enters in their details, but I need to login first and pass through a user object to contain those details.
Anyways, here's the code, please help me out!
The code in question is the myquit function in the LoginBox class , and the goToLogin function in the MainBox class :)
from tkinter import *
from tkinter.ttk import *
class LoginBox:
def __init__(self, master): # Master is the frame that is passed in.
# Create Frame
self.master = master
self.master.title('~ Please Login ~')
def login_function(self):
user = "xxx"
secret_key = "yyy"
print("User - API Key: " + user)
print("User - Secret Key: " + secret_key)
# Perhaps check the login ...
# if it's a success then quit the function
self.myquit()
def myquit(self):
self.master.destroy()
class MainBox:
def __init__(self, master):
# Set the root window
self.master = master
self.master.geometry("500x500")
self.master.title("OkBot v0.1")
self.master.resizable(False, False)
# Initialize the frames
self.uiFrame1 = Frame(self.master) # The Top Layer -- Login Text + Login Button
# uiFrame1 Initialize --Login Text + Login Button
self.ui1_button = Button(self.uiFrame1, text="Login", command=self.goToLogin).grid(row=0, column=3, sticky=E, padx=1)
# Create Topview for popup , pass in User Object to LoginBox
def goToLogin(self):
loginMaster = Toplevel(self.master)
loginMaster.protocol("WM_DELETE_WINDOW", self.checkLogin) # This is if they close via X
loginGUI = LoginBox(loginMaster)
def checkLogin(self):
print("This function was called -- The Protocol for destroyed windows works")
# Initialize the objects and start the program
mainWindow = Tk()
myProgram = MainBox(mainWindow)
mainWindow.mainloop()
It works as expected if I close using the cross in the top right, but not if I close with my function that calls .destroy() , can anyone explain to me why this isn't working as intended? Perhaps I have missed something ?
loginMaster.protocol("WM_DELETE_WINDOW", self.checkLogin) only tells tkinter what to do when the window manager destroys the window. When you call some function which calls destroy(), that doesn't involve the window manager and therefore your callback is not called.
If you want something to happen when the window is destroyed, you should bind to the <Destroy> event.

Python tkinter raise exceptions and wait for button press

I'm new in Tkinter and I'm currently trying to create a small game. When something happens in the game I want to create a pop-up window which will inform the user about any changes. This is the code that I have written for it.
def message(self, text):
top = tkinter.Toplevel(width=50, height=25)
top.title("Message")
msg = tkinter.Message(top, text=text)
msg.pack()
ok = tkinter.Button(top, text="OK", command=top.destroy)
ok.pack()
My two questions are:
Can I replace this by an exception that will create an "Error Message" window? If it isn't necessary then can I use it to be raised by the exception?
I want the user to be forced to see and read the message, so how can I freeze the main window(the user can't click on anything else) until he presses the OK button on the pop-up window?
Use:
top.grab_set()
To show Error Message you can use tkmessagebox.showerror().

Python Tkinter: How to combine keyboard prompts and clickable buttons adequately?

Hello Stack community,
As I tried writing a simple google searchbar GUI app, I seem to have created a trade-off between either a clickable GUI button or a working keyboard command.
This depends on passing 'self' into the function called 'google'. Without 'self' the GUI Submit button will work, and the Enter key will raise an error in the console. With 'self' passed into google, the Enter key will work, but the GUI Submit button raises the opposite error. It has to do with the amount of arguments passed into this function 'google'.
Is there a way to make both the submit button and the Enter key work?
In this example the GUI Submit button works, the Enter key will give an error:
#!/usr/bin/env python3
from tkinter import ttk
from tkinter import *
import webbrowser
def google():
url = "https://www.google.nl/#q=" + search.get()
webbrowser.open_new_tab(url)
#GUI
root = Tk()
search = StringVar()
ttk.Entry(root, textvariable=search).grid()
submit = ttk.Button(root, text="Search", command=google).grid()
root.bind("<Return>", google)
root.mainloop()
Add a default for the times that nothing is passed to the function. It doesn't matter what it is since you don't use it.
def google(event=None):
print("google function called")
## url = "https://www.google.nl/#q=" + search.get()
## webbrowser.open_new_tab(url)
#GUI
root = Tk()
search = StringVar()
ttk.Entry(root, textvariable=search).grid()
submit = ttk.Button(root, text="Search", command=google).grid()
root.bind("<Return>", google)
root.mainloop()
The reason it fails is that a Tkinter callback function passes an event argument. So any callback you pass has to have this argument. Adding an argument fixes it for the bind but then breaks it for the submit.
This is because for the submit no argument is passed and your function now requires an argument. So basically what it means is you can't use the same function for both purposes.
One simple way to get round this is to use a lambda in the bind call.
root.bind("<Return>", lambda e: google())

Python tkinter, how to disable background buttons from being clicked

When opening a new tkinter window, I only want the user to be able to click buttons on the new window. They should not be able to click on buttons from other windows that are part of the application. How would I accomplish this?
Here is a snip of my code:
def exportEFS(self):
self.exportGUI = Toplevel()
Button(self.exportGUI, text='Backup', command=self.backup).pack(padx=100,pady=5)
Button(self.exportGUI, text='Restore', command=self.restore).pack(padx=100,pady=5)
def backup(self):
self.backupWindow = Toplevel()
message = "Enter a name for your Backup."
Label(self.backupWindow, text=message).pack()
self.entry = Entry(self.backupWindow,text="enter your choice")
self.entry.pack(side=TOP,padx=10,pady=12)
self.button = Button(self.backupWindow, text="Backup",command=self.backupCallBack)
self.button.pack(side=BOTTOM,padx=10,pady=10)
In this snip, once the backupWindow is opened, the exportGUI remains open, but the user should not be able to click "Backup" or "Restore" while the backupWindow is opened.
Thanks!
You will want to call grab_set on the TopLevel window so that all keyboard and mouse events are sent to that.
def exportEFS(self):
self.exportGUI = Toplevel()
Button(self.exportGUI, text='Backup', command=self.backup).pack(padx=100,pady=5)
Button(self.exportGUI, text='Restore', command=self.restore).pack(padx=100,pady=5)
def backup(self):
self.backupWindow = Toplevel()
self.backupWindow.grab_set()
message = "Enter a name for your Backup."
Label(self.backupWindow, text=message).pack()
self.entry = Entry(self.backupWindow,text="enter your choice")
self.entry.pack(side=TOP,padx=10,pady=12)
self.button = Button(self.backupWindow, text="Backup",command=self.backupCallBack)
self.button.pack(side=BOTTOM,padx=10,pady=10)
What you can do is set the state to disabled. As so:
self.button.config(state="disabled")
And to enable it, you just use:
self.button.config(state="normal")
However, you must assign your buttons to variables first, like this:
self.backup=Button(self.exportGUI, text='Backup', command=self.backup)
self.backup.pack(padx=100,pady=5)
self.restore=Button(self.exportGUI, text='Restore', command=self.restore)
self.restore.pack(padx=100,pady=5)
so you would disable these using:
self.backup.config(state="disabled")
self.restore.config(state="disabled")
and re-enable using:
self.backup.config(state="normal")
self.restore.config(state="normal")
Please note however, that while the button is disabled, nothing can be changed to that button, both through the code, or through the user using it. So that means if you wanted to change the text of that button, you would have to change the state of the button to "normal" before changing it (if it already isn't in that state, which by default, all widgets are in that state when first created).
Cheers :)

Categories

Resources