How to save events in tkinter? - python

I made an app in Tkinter and I am running into a few problems. So when I click a button a function should perform every 1 millisecond. However, once I exit the program and open it again, the computer forgets that I already clicked the button before and does not run the function automatically. I have to click the button again for the function to work. Is there a way for the computer to remember that I already clicked the button?
Here is my code so far:
from tkinter import *
root = Tk()
def declareFunction():
print("Hello World")
def runFunction():
declareFunction()
root.after(1, runFunction)
button = Button(root, text="Example", command=runFunction)
root.mainloop()
Is there a way for the computer to remember that I clicked the button, and run the function forever without me clicking on it again?

Well, the only way round this is to save that information (that the button has been clicked) to a file or database because the computer forgets everything about the program once you exit out of it. To make the data persistent there needs to be some external file or DB. Now there are so many options on storing data.
A few of them are pickle , Shelve , Plain text files, Databases like sqlite3 or better.
I will demonstrate what you want to do with plain text file.(must read docs to know about functionalities that these modules provide)
Using Plain Text files
The easiest of them.
Create a file, store False in there (to indicate button has not been clicked). When the button is clicked the first time, change the file contents from False to True and call the function. When the program loads up, read the file, see the contents.
If it says True, just run the function (and disable the button to prevent clicking as it's already been clicked. ). If it says False, just write your usual program.
NOTE you might want to disable the button to prevent clicking again as the function will just keep on running. Else up to your needs.
Example
import tkinter as tk
def declareFunction():
print('LoL')
def runFunction():
with open('test.txt', 'w') as f:
f.write('True')
# you might wanna disable the button here as well.
declareFunction()
root.after(1, runFunction)
if __name__ == '__main__':
with open('test.txt', 'r+') as f:
d = f.read()
if d == '': # write False to file as it is empty
f.seek(0)
f.write('False')
f.truncate()
root = tk.Tk()
tk.Button(root, text='Run Function', command=runFunction).pack()
root.mainloop()
elif d == 'False': # if file says False - button was not clicked so create one
root = tk.Tk()
tk.Button(root, text='Run Function', command=runFunction).pack()
root.mainloop()
elif d == 'True': # if file says True - button has been clicked
root = tk.Tk()
runFunction() # run the function already as button was clicked
# disable the button to prevent clicking again
tk.Button(root, text='run function', state=tk.DISABLED, command=runFunction).pack()
root.mainloop()

Related

Threading in Tkinter with Button Command

I'm building a tkinter app and as a part of it the user has to upload a file, write a message and then press a Button whose command runs in another thread.
self.sidebar_button = Button(self.sidebar_frame, text="send your message",
command=threading.Thread(target=send_msg).start)
If the Button is pressed in the right condition then everything is fine.
However, if the user doesn't upload the file and write the message before pressing the Button then i show the user an error message. The problem here is that since the first Button press has started the thread, it can't start again.
Can you think of a workaround for this problem?
Is it possible to disable the Button before the right conditions are met?
Look at this:
import tkinter as tk
# This is called each time there is a change in the entry
def check_conditions(*args):
message_text = message_var.get()
# Here we check the conditions:
if message_text == "":
button.config(state="disabled")
else:
button.config(state="normal")
def send():
message_text = message_var.get()
print(f"Send: {message_text!r}")
root = tk.Tk()
message_var = tk.StringVar(root)
# When the value of the `message_var` has changed, call `check_conditions`
message_var.trace("w", check_conditions)
# Create the entry and attach `message_var`
message = tk.Entry(root, textvariable=message_var)
message.pack()
button = tk.Button(root, text="Send", command=send, state="disabled")
button.pack()
root.mainloop()
It uses <tk.StringVar>.trace("w", <function>) to call check_conditions each time the user changes the entry. For more info, read this.
tkinter doesn't always like being called from other threads, so avoid using threading when using tkinter.

How to properly destroy a Toplevel widget in tkinter

I'm somewhat new to python and I'm trying to write my first GUI. I'm having problems when destroying a Toplevel widget. somehow my script gets stuck in a loop.
Basically, I have a button (in this case Hours left) when I press it, it should take me to a function that executes some code. If one condition is met (no credentials found) then it needs to open a Toplevel window where the user enters their credentials. Once they click on Log in the Toplevel window must be destroyed and the code should carry on with the rest of the commands in my function.
The problem is that when I destroy the Toplevel window my script enters a loop and it doesn't execute anything else. Here is a simplified version of my code
from tkinter import *
def loginSequence():
print("loginSequence")
def logoutSequence():
print("logoutSequence")
def hoursLeft():
print("hoursLeft")
input("Do something and press enter")
credentialsWindow()
print("Why is this message not printing?")
"""Rest of the code goes here"""
def credentialsWindow():
global credentials
credentials = Toplevel()
Label(credentials, text='Opss... I Could not find your credentials.\nPlease, log back in', font=40).pack()
Button(credentials, text='Log in', command=doSomethingandClose).pack()
Button(credentials, text='Cancel', command=destroyNewWindow).pack()
credentials.mainloop()
def doSomethingandClose():
input("Do something and press enter 2 destroy")
destroyNewWindow()
def destroyNewWindow():
credentials.destroy()
if __name__ == "__main__":
# Define the main screen for Ultipro Logger
logger_Root = Tk()
header = Label(logger_Root, text="Welcome to UltiPro Logger", font=40).pack()
# Login button
login_PB = Button(logger_Root, text=" Clock in ", font=40, bg="gray", command=loginSequence).pack()
# Logout button
logout_PB = Button(logger_Root, text=" Clock out ", font=40, bg="gray", command=logoutSequence).pack()
# Hours left alarm button
ResetAlarm_PB = Button(logger_Root, text=" Hours left ", font=40, bg="gray", command=hoursLeft).pack()
# Exit button
exit_PB = Button(logger_Root, text=" Exit ", anchor='se', justify='right', bg="gray", command=logger_Root.quit).pack()
logger_Root.mainloop()
If you run it and click "hours left" and then either cancel or log in it doesn't execute
print("Why is this message not printing?")
"""Rest of the code goes here"""
What's even weirder is that if you go back to the main menu and click exit, that piece of code will get executed. if you click exit again then the main window will close.
I have no clue why this is happening. I've been stuck for 3 days trying to figure it out but all the tkinter tutorials I've seen are pretty simple.

Is there a way to make a Button perform multiple commands

I'm doing a school project. I designed a welcome page for it on tkinter and put a button 'ok' which when pressed moves the code forward but the welcome page doesnt close itself once pressed.
i have tried defining another function to close it but that does not work.
welcome = Tk()
okbutton = Button(welcome, text='ok', command=R)
okbutton.pack()
welcome.mainloop()
and the code moves forward but welcome page remains open...Is there a method to resolve this?
Window never closes automatically when you create new window. You have to use welcome.destroy() for this. You can run it in function which creates new window.
import tkinter as tk
def welcome_page():
global welcome
welcome = tk.Tk()
tk.Label(welcome, text='Welcome').pack()
button = tk.Button(welcome, text='OK', command=other_page)
button.pack()
welcome.mainloop()
def other_page():
global welcome
global other
welcome.destroy() # close previous window
other = tk.Tk()
tk.Label(other, text='Other').pack()
button = tk.Button(other, text='OK', command=end)
button.pack()
welcome.mainloop()
def end():
global other
other.destroy() # close previous window
welcome_page()
To perform the two commands call one command inside the other (sounds like it should be towards the end) and assign the first command to the button.
A button can only call a single function, but that single function can do anything you want.
def do_ok():
print("hello!")
welcome.destroy()
welcome = Tk()
okbutton = Button(welcome, text='ok', command=do_ok)
okbutton.pack()
welcome.mainloop()

How can I reprogram the 'Ok' button in the Tkinter Messagebox module

I'm working on my very first python GUI, and I want to close all the previous windows from the code after clicking on the 'OK' button of the Message
messagebox.showinfo('Access Granted', 'Your data has been retrieved.')
The tkinter dialogs return a string representing what the user clicked on, so it's just a matter of saving that value and checking it afterwards. However, since showinfo only gives the user one option it's always going to return "ok", so there's no need to check the value. Just call your function after the dialog has been displayed:
def some_function():
messagebox.showinfo('Access Granted', 'Your data has been retrieved.')
root.destroy()
...
button = tk.Button(root, text="Quit", command=some_function)
So, say if your window was called root you would want to first define a function to 'destroy' the window
def closeWindow():
root.destroy()
Then you'd want to add that command to the button -
btn = tkinter.Button(text="Click Me!" command=closeWindow)
If you get any more errors, let me know!

Python 3 Tkinter Menu widget's callback seems out of sequence

I'm having trouble with the Tkinter Menu widget (no menu button), whereby the callback seems to run out of sequence. Here is a very minimal example:
# Python 3.6.5. Windows 7 x64.
from tkinter import *
root = Tk()
popup = Menu(root, tearoff=0)
popup.add_command(label="test", command=lambda: print("clicked 'test'"))
print("Before post")
popup.post(200,200) # Expecting print output from this (when clicked)
print("After post")
root.mainloop()
print("end of program")
Expected output:
Before post
clicked 'test'
After post
end of program
Actual output:
Before post
After post
clicked 'test' <--- Shouldn't this appear BEFORE previous line?
end of program
I've tried numerous things, without success, such as: popup.wait_window(), popup.update_idletasks(), popup.grab_release(), popup.unpost(), popup.destroy(), tk_popup (instead of Menu), etc.
Any advice would be appreciated.
clicked 'test' <--- Shouldn't this appear BEFORE previous line?
No, it shouldn't. The post only makes the menu appear, it will not wait for the user to select something from the menu. That's just not how tkinter menus are designed to work.
If you need your code to pause until the user makes a selection, you probably need to wait on a variable, and then make sure that all of the menu items set that variable.
I don't get that result on Linux, although apparently the command is supposed to execute. From the docs:
If a -command option is specified for a cascade entry then it is evaluated as a Tcl command whenever the entry is invoked.
My advice is don't try to trigger an event using another trigger. Instead, point both the menu command and whatever you are trying to do programmatically to the same target.
from tkinter import *
def func():
print("clicked 'test'")
root = Tk()
popup = Menu(root, tearoff=0)
popup.add_command(label="test", command=func)
root['menu'] = popup
print("Before post")
func()
print("After post")
root.mainloop()
print("end of program")

Categories