I am trying to text in a textbox every seconds. I have found several explanations on how to do this using the Tk().after() method. like this example
root = Tk()
def foo():
print(repeat)
root.after(5000, foo())
foo()
root.mainloop()
However, when trying this solution the main window never appears. It also does not exit with an exception. The only thing I can think of is that I am entering an infinite loop before reaching the mainloop call.
This is a condensed version of my code
def vp_start_gui():
global val, w, root
root = Tk()
top = MainWindow (root)
init(root, top)
root.mainloop()
class MainWindow():
def __init__():
self.widgets
def init(top, gui, *args, **kwargs):
global w, top_level, root
w = gui
top_level = top
root = top
root.after(15000,updateLoans(w, root))
def updateLoans(w, rt):
w.LoanOfferView.insert(END, viewModel.loanOffers())
w.LoanDemandView.insert(END, viewModel.loanDemands())
rt.after(15000,updateLoans(rt))
vp_start_gui()
The viewModel is a third module that pulls a very small amount of data from an API. `LoanDemands is a scrolledText widget.
Does anyone have any idea what might be going wrong?
python3.4
using 'page' gui designer to develop tkinter UI
You need something for the program to wait for before entering infinite loop. Try this:
Button(root, text='start', command=foo).pack()
Too replace your line:
foo()
Also, you should not put parentheses when passing functions to other functions:
root.after(5000, foo)
So I found the answer to this problem myself.
code example that was not working
root = Tk()
def foo():
print(repeat)
root.after(5000, foo())
foo()
root.mainloop()
code that does work for me
root = Tk()
def foo():
print(repeat)
root.after(5000, foo)
foo()
root.mainloop()
After looking closer at this SO answer, this rasperri pi forum answer and tkinter documentation on effbot that I quoted at the bottom. I realized that the name of the function was being referenced but the function itself is not actually called inside of the after() method
Registers an alarm callback that is called after a given time.
This method registers a callback function that will be called after a
given number of milliseconds. Tkinter only guarantees that the
callback will not be called earlier than that; if the system is busy,
the actual delay may be much longer.
The callback is only called once for each call to this method. To keep
calling the callback, you need to reregister the callback inside
itself:
class App:
def init(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.after(100, self.poll)
Related
I am creating a basic application using tkinter and I want to split the code for my gui and my code for functions and stuff into different files, but this function:
from tkinter import *
from functions import * # this is my file I want to put my functions in
# FUNCTIONS that needs root and event
def callback(event): # my question is about this function
root.after(50, select_all, event.widget)
# Setup
root = Tk()
(bind)
edtLocation1 = Entry(root)
edtLocation1.bind('<Control-a>', callback)
Needs to take in an event and root at the same time if I move it to a new file, but I cannot do this inside another file:
def callback(event, root): # gives error because I have to pass in an event
root.after(50, select_all, event.widget)
Since I don't know how to pass in an event...
Long question short, how could I go about passing an event and root into the callback?
You can use lambda:
def callback(event, root):
root.after(50, select_all, event.widget)
...
edtLocation1.bind('<Control-a>', lambda e: callback(e, root))
You don't need to pass in root. You can call the after method from any widget, and the event object that is passed in has a reference to the widget that caught the event.
def callback(event):
event.widget.after(50, select_all)
Even though there is a mainloop being called my tk window will not appear. The code used to work but as soon as I coded in the second function in the nums class there is no tk window. I would like for someone to point out the mistake instead of simply handing out the answer.
Can someone please help me fix this problem?
I use Python IDLE 3.8
Image: [1]: https://i.stack.imgur.com/o65WI.png
Code:
from tkinter import *
from random import randint
import time
#number assignments
class nums:
def __init__(self):
self.value=randint(1,100)
def assignnewnums(oldnum1,oldnum2,lbltxt,lbl,answer):
getans = answer.get()
if(getans==str((oldnum1.value+oldnum2.value))):
del(oldnum1)
del(oldnum2)
oldnum1=nums()
oldnum2=nums()
lbltxt="Correct!"
lbl.config(text=lbltxt)
time.sleep(5)
lbltxt="What is {} + {}".format(oldnum2.value,oldnum1.value)
lbl.config(text=lbltxt)
else:
lbltxt="Wrong! Try Again!"
lbl.config(text=lbltxt)
time.sleep(3)
lbltxt="What is {} + {}".format(oldnum2.value,oldnum1.value)
lbl.config(text=lbltxt)
a = nums()
b = nums()
#GUI startup
root = Tk()
#Label
title = Label(root, text="AddPrac", fg="dark blue")
title.pack()
#Question
questxt = "What is {} + {}".format(a.value,b.value)
ques = Label(root,text=questxt,fg="red")
ques.pack()
#UserAnswer
ans = Entry(root)
ans.pack()
#SubmitButton
enter = Button(root,text="Submit Answer!",fg="yellow",command=nums.assignnewnums(a,b,questxt,ques,ans))
enter.pack()
#GUI continued startup
root.mainloop()
I tried your code and the window does appear if you wait a few seconds.
This is due to the following offending code snippet:
command=nums.assignnewnums(a,b,questxt,ques,ans)
This doesn't do what you think it does. You were thinking of:
command=lambda: nums.assignnewnums(a, b, questxt, ques, ans)
The way your code is written now, it does not bind a callback to the button, but rather, calls- and executes the function (since you are invoking it explicitly), and attempts to bind the return value as a callback, which makes no sense. As a side effect of calling the function, the main thread sleeps (since assignnewnums uses time.sleep) for a bit before you reach root.mainloop.
Anytime you are binding a callback to a button, you want to provide a callable object - either just a function object, or if arguments are critical, a lambda or functools.partial.
I'm working on functions within a class, and one of the issues I'm running into is adding a button that terminates the program. Here is the current code:
class ClassName():
def __init__(self, root):
self.root = root
def close_root(self, root):
root.destroy()
root.quit()
def addExitButton(self, root):
Button(root, text = 'Exit', width = 10, command = self.close_root).grid(row = 5,
column = 0)
Within the button arguments, I have tried command = self.close_root(root) But this gives me an error because you can't call a function if you want the button to do something (I forget the reason as to why this is). I have also tried
def close_root(self):
self.destroy()
self.quit()
def addExitButton(self, root):
Button(..., command = self.close_root,...)
And this does not work either as the class doesn't have the attribute destroy. I'm not sure how to approach this after trying a few different ways.
You need to actually access the root's functions. So using self.root.destory() or self.root.quit() will work because your root object has those methods but your class does not.
You should also only use one of them, in this case, destroy is the best option I think. And you can probably just use that when creating the button. So replace the button callback (command) with self.root.destory.
More here on how to close a Tkinter window here.
The idea was show in label where my cursor(line.column) is. Which work with .index(INSERT) well but if i bind the right mouse button with text it returns the previous cursor position not the current.
It seems that callback is executed after event?
from tkinter import Tk, Text, Frame, Label, StringVar, constants, END, INSERT
EXPL_TEXT = "I know that dress is karma. Perfume regret\nYou got me thinking bout"
class App(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.pack()
self.var = StringVar()
self.init_widgets()
def init_widgets(self):
self.text = Text(self)
self.text.bind('<Button-1>',self.callback_index)
self.text.pack()
self.text.insert(END,EXPL_TEXT)
self.label = Label(self, textvariable=self.var)
self.label.pack()
def callback_index(self,event):
x = self.text.index(INSERT)
self.var.set(x)
if __name__ == '__main__':
root = Tk()
app = App(root)
root.mainloop()
The issue I believe you are seeing is that when you click your mouse down the event fires to read INSERT. The problem is before you lift your mouse the location will still have the value of the previous INSERT. So in order for you to get the update after the event has completed we can use after() to wait for the event to finish and then set the value for self.var.
Change your callback_index method to:
def callback_index(self,event):
root.after(0, lambda: self.var.set(self.text.index(INSERT)))
What we are doing is telling python to schedule something to happen after a set time. I believe (Correct me if I am wrong) Because an event is in progress it waits until that event finishes to perform the action in the after() method.
We use lambda to create an anonymous function to update your self.var variable and all should work as intended.
When my program executes the python GUI freezes. Here is my main code. Can I get some help in doing threading? So the execution happens in the background and I can still be able to use the "x" button in the GUI if I want to end the execution? Currently I just ask the user to close the cmd to end the program.
if __name__ == "__main__":
root = Tk()
root.title('Log')
root.geometry("400x220")
font1=('times', 15)
font2=('times', 10)
#Label inside root
Label(root, relief=GROOVE, font=font2, text="level").pack()
variable = StringVar(root)
variable.set("INFO") # default value
w = OptionMenu(root, variable, "CRITICAL", "DEBUG")
w.pack()
Button(root, font=font1, background= "yellow", text='START',command=main).pack()
Label(root, text="To end just close the CMD window").pack()
root.mainloop()
UPDATE: Turns out the Button callback was autorunning launch because the function object wasn't being set as the callback, the called function itself was. The fix is to replace the callback lambda: spawnthread(fcn) so that a function object is set as the callback instead. The answer has been updated to reflect this. Sorry for missing that.
The GUI mainloop will freeze when you try to run some other function, and has no way to restart itself (because it's frozen.)
Let's say the command you'd like to run alongside the GUI mainloop is myfunction.
Imports:
import time
import threading
import Queue
You need to set up a ThreadedClient class:
class ThreadedClient(threading.Thread):
def __init__(self, queue, fcn):
threading.Thread.__init__(self)
self.queue = queue
self.fcn = fcn
def run(self)
time.sleep(1)
self.queue.put(self.fcn())
def spawnthread(fcn):
thread = ThreadedClient(queue, fcn)
thread.start()
periodiccall(thread)
def periodiccall(thread):
if(thread.is_alive()):
root.After(100, lambda: periodiccall(thread))
You then want the widget calling the function to instead call a spawnthread function:
queue = Queue.Queue()
Button(root, text='START',command=lambda: spawnthread(myfunction)).pack() #<---- HERE
N.B. I'm adapting this from a multithreaded tkinter GUI I have; I have all my frames wrapped up in classes so this might have some bugs since I've had to tweak it a bit.