Why my button will not repeat command with tkinter GUI on python - python

I'm new to GUI and classes and I'm a just a bit confused, when I use a button in tkinter for python it's suppose to repeat it's command when pressed. but in my program it doesn't do that. is there something wrong with me codes that might counter it? I'm trying to make a simple program that echos whatever is typed.
-Thanks
from Tkinter import *
from PIL import Image, ImageTk
import tkMessageBox
class appsMain(Frame):
def __init__(self,parent):
Frame.__init__(self,parent)
self.parent=parent
self.initUI()
def initUI(self):
self.parent.title("OrganizedWindows")
self.send=Text(self,bg="white",height=3,width=35)
self.send.place(x=17,y=235)
self.msg=Text(self,width=35,height=12,state="disable")
self.msg.place(x=17,y=20)
sendbtn=Button(self,text=" Listen ",command=self.accept)
sendbtn.place(x=305,y=240)
self.pack(fill=BOTH, expand=1)
def accept(self,msg):
self.msg.configure(state="normal")
self.msg.insert(INSERT,msg+"\n")
self.msg.insert(INSERT,"BYE")
self.msg.configure(state="disable")
root=Tk()
root.geometry("350x300+300+300")
app=appsMain(root)
root.mainloop()

Your code has a few problems. The first is solved easily:
sendbtn=Button(self,text=" Listen ",command=self.accept)
doesn't work because when the button is clicked, self.accept is called with no additional arguments (accept expects 2 arguments, [self and msg], but it is only getting 1 [self]).
You can work around this with lambda:
sendbtn=Button(self,text=" Listen ",command=lambda : self.accept("some message here"))
(This is equivalent to):
def func:
self.accept("some message here")
sendbtn=Button(self,text=" Listen ",command=func)
But, I don't know if you want to constantly add different messages ... or where they come from, so it is difficult to give a general solution at this point.
Tkinter applications happily continue to run even after exceptions are raised. It is a good idea to watch the terminal for exceptions when you're developing a Tkinter application (In this case, it pointed me right to the source of the problem).

This is to better answer your Lambda comment question. Lambda is a quick, one-liner way to write a function. The variable you set it to is the same as the name of your function for def myFunction. Then you say the keyword lambda and the letter(s)/word(s) you put after the keyword lambda are just the parameters of your function. Next you put a colon (just like you would for a normal function-> def myFunction:). After that you write whatever you want the function to return. So if you wanted a function to square a given number, n, then you could write it normally like:
def square_num(n):
return n**2
OR as a cool Lambda:
square_num = lambda n: n**2
You can also have as many parameters as you wish, just like in a normal function, so for a given number raised to the x power you could write:
raise_num = lambda n, x: n**x

Related

Python: How do you obtain variables from another script which are constantly being updated

I've made a script that uses a while True loop to constantly update a series of variables based on UDP packets that I am constantly recieving. I want to ultimately create a GUI that displays that data and updates the screen constantly, which I plan to do with tkinter (using my_label.after in a function which then calls itself, not sure if this is a good plan).
Here is some testing scripts that I can't get to work properly:
GUI2.py (my test looping script)
import time
var = 0
while True:
var += 1
time.sleep(0.1)
GUI Testing.py (the script that would be accessing those variables)
from GUI2 import *
import time
print('never')
print(var)
time.sleep(1)
The second script never reaches the print('never') line, I think because it gets stuck in the other script's while True loop and never returns.
How should I go about this? I have one script that I want in a constant loop to update my variables to the correct values based on incoming packets, and then another script updating a tkinter window. I went this way as most examples I could find using Tkinter didn't use any sort of while True loops. Could I just put my packet recieving code inside the Tkinter mainloop, and would that effectively act as a while True?
EDIT (added Tkinter loop that I can't get working):
This opens a Tkinter window, but the label stays at 99, then reopens a window when I close it with the new x value (ie. 98, 97, etc). I want the label to update every second.
import tkinter as tk
import time
x = 99
while True:
root = tk.Tk()
label = tk.Label(root, text=x)
label.pack()
x -= 1
time.sleep(1)
root.mainloop()
Below is a sample script to show you how you can update the value in the label widget at a certain time interval. I have provided you the hyperlinks to help you understand tkinter's methods. Best regards.
Key points:
use the textvariable option of the tk.Label widget.
use tkinter's control variable. I have shown you how to set and get it's value.
you can use tkinter's widget method called .after() without having to explicitly use a while-statement and time.sleep() method. Tkinter has it's own event loop that you can use.
writing your tkinter GUI as a class makes it easier to implement what you need.
Example Script:
import tkinter as tk
class App(tk.Frame):
def __init__( self, master, *args, **kw ):
super().__init__( master )
self.master = master
self.create_label()
self.update_label()
def create_label( self ):
self.var = tk.IntVar() # Holds an int; default value 0
self.label = tk.Label(self, textvariable=self.var ) # Use textvariable not text
self.label.pack()
def update_label( self ):
value = self.get_value()
self.var.set( value ) # Set the label widget textvariable value.
self.after(1000, self.update_label) # Call this method after 1000 ms.
def get_value( self ):
'''To simulate calling a function to return a value'''
value = self.var.get() + 1
return value
if __name__ == "__main__":
root = tk.Tk()
root.geometry('100x100+0+24')
app = App( root )
app.pack()
root.mainloop() #This command activates tkinter's event loop
Edit:
As a clarification, this answer shows how to utilize the .after() and .mainloop() methods in GUI Testing.py, i.e. using tkinter event loop and not use two while-loops, to achieve what you wanted to do. This is a way to simplify your GUI script.
For more sophisticated algorithms, e.g. more than one while-loop is involved, you have to look into using threads(note it has its issues) or more recently I found a way of using python's Asyncio approach to do it. The learning curve for these two approaches is a lot steeper. To use the asyncio approach, you can explore modifying my answer to do what you want.
Best solution is to use threads however If you plan to do in simplest possible manner then implement the main loop inside your Tkinter GUI and once you read the packet simply update it on your GUI in same loop. Here is the Updated and working Code.
import tkinter as tk
import time
def setvalue(self, x):
self.label.config(text=x, )
root.update()
time.sleep(1)
def changevalues(self):
x = 99
self.label = tk.Label(root, text=x)
self.label.pack()
while x >0:
x -= 1
setvalue(root,x)
root = tk.Tk()
changevalues(root)
root.mainloop()

winsound in a tkinter variable

I have made a hangman game in Tkinter with buttons for each letter, each button is linked to a function that will process the letter and tell me if it is correct or not. How can I make a function a correct sound and an incorrect sound, I do not want to say where I am getting my wav file from, because it will move (it will always be in the same file as my programme).i do not want to download another module and the answers from another question do not work.
i tried this but it did not make a sound:
def no():
lambda: PlaySound('wrong.wav', SND_FILENAME)
def yes():
lambda: PlaySound('right.wav', SND_FILENAME)
You seem unclear what lambda does:
Python supports the creation of anonymous functions (i.e. functions that are not bound to a name) at runtime, using a construct called "lambda".
In the example you have given you have not created any anonymous function so therefore they are totally unnecessary. Just remove them and everything should work.
def no():
PlaySound('wrong.wav', SND_FILENAME)
def yes():
PlaySound('right.wav', SND_FILENAME)
Remember however winsound is Windows only (so no cross platform development) and to use these functions you need from winsound import *

Python StringVar().get() in Tkinter returns a blank value

The StringVar.get() method returns a blank value when the function c() is called. However, it works perfectly fine when I call only the new_db () function.
I really cannot understand the problem. Could somebody explain it to me?
#modules
import os
from Tkinter import *
chance=3
def cr():
print data.get()
#new_db
def new_db():
global data
m.destroy()
new=Tk()
data=StringVar()
Entry(new,font='BRITANIC 16',textvariable=data).grid(column=1,row=2)
Button(new,text='Create New Database',command=cr).place(x=175,y=75)
new.geometry('500x100+400+250')
new.mainloop()
def c():
global m
m=Tk()
Button(m,text='erferf',command=new_db).pack()
m.mainloop()
c()
Look at this answer When do I need to call mainloop in a Tkinter application?. It tells that the mainloop() must be called once and only once.
Also, the Tk object m should still exist when new_db() is executed on the click of the Button.
For what you try to accomplish, you should create the Tk() only once, and call mainloop() only once. Then you shoud place code to hide/show the appropriate widgets. Look at In Tkinter is there any way to make a widget not visible? to know how to show/hide widgets.

Tkinter only calls after_idle once

I am new to Tkinter, so I apologize if this is easy, but I have search for a couple of hours and can't figure it out. What I want to do is after the mainloop is idle, I always want to call the function checkForGroupUpdates(). When I run the code below, it only runs once. I can't figure out to have it run every time the mainloop is idle. I appreciate the help.
from Tkinter import *
import random
class Network(Frame):
""" Implements a stop watch frame widget. """
def __init__(self, parent=None, **kw):
Frame.__init__(self, parent, kw)
self.makeWidgets()
def makeWidgets(self):
""" Make the time label. """
self._canvas = Canvas(self, width=600, height=400)
self._canvas.pack()
def checkForGroupUpdates(self):
print "checking"
h=0
this=10
while this>.0001:
this=random.random()
print h
h=h+1
print "checked"
def main():
root = Tk()
nw = Network(root)
nw.pack(side=TOP)
root.after_idle(nw.checkForGroupUpdates)
root.mainloop()
if __name__ == '__main__':
main()
#user1763510, notice that in Bryan Oakley's answer, he has checkForGroupUpdates call self.after again. This is because self.after only does a single call, so getting repeated calls requires having it call itself within the function that gets called by the first call. This way, it keeps repeatedly calling itself.
The same goes for the after_idle() function. You have to have checkForGroupUpdates call after_idle() again at the bottom.
Here is the documentation for after, after_idle, etc. There is even a little example in the after description, which makes it all clear.
Documentation: http://effbot.org/tkinterbook/widget.htm
Example from link above, under the afterdescription:
#Method 1
class App:
def __init__(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.after(100, self.poll)
To use after_idle instead, it would look like this:
#Method 2
class App:
def __init__(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.update_idletasks()
self.master.after_idle(self.poll)
Notice the addition of the self.master.update_idletasks() line. This draws the GUI and handles button presses and things. Otherwise, after_idle() will suck up all resources and not let the GUI self-update properly in the mainloop().
An alternative to using
self.master.update_idletasks()
self.master.after_idle(self.poll)
is to use:
#Method 3
self.master.update_idletasks()
self.master.after(0, self.poll)
Using self.master.after(0, self.poll) is my preferred technique, as it allows me to easily change the 0 to something else if I decide I don't need to run self.poll constantly. By increasing the delay time to at least 1 ms, you no longer need to call self.master.update_idletasks() at all. So, this works too:
#Method 4
self.master.after(1, self.poll)
Also notice that for all examples above, calling self.poll() in the __init__ function is what kicks it all off, and storing master into self.master is necessary simply so that inside poll you can call the after or after_idle function via self.master.after_idle, for example.
Q: Is this stable/does it work?
A: I ran a test code using Method 3 just above for ~21 hrs, and it ran stably the whole time, allowing the GUI to be usable and all.
Q: What is the speed comparison for each method above?
A:
Method 1: (I didn't speed test it)
Method 2: ~0.44 ms/iteration
Method 3: ~0.44 ms/iteration
Method 4: ~1.61 ms/iteration
Q: Which is my preferred method?
A: Method 3 or 4.
Instead of calling the function all the time when the app is idle, you should just call it once every fraction of a second. For example, if you want to check 10 times every second you would do something like this:
def checkForGroupUpdates(self):
<do whatever you want>
self.after(100, self.checkForGroupUpdates)
Once you call that function once, it will arrange for itself to be called again in 100ms. This will continue until the program exits. If the program goes "non-idle" (ie: while responding to a button click), this function will pause since tkinter is single-threaded. Once the program goes idle again, the check will continue.

Tkinter Keyboard Binds

I'm working on an interface using Tkinter and the canvas widget, and so far have found answers to issues I have had from others questions and the answers posted, but I am stumped on this one.
I have several keyboard binds in the class where my GUI elements are created, and they all work fine when the program is started. The binds looks something like this:
self.canvas.get_tk_widget().bind("<Control-o>",self.flash_open)
and are within the __init__ function of the class. As of yesterday, I initialized this class
to start the program, then waited for the user to select open from a menu, which then opened (among other things) a tkmessagebox
self.specfilename =askopenfilename(filetypes=[("spec", "")],initialdir= self.pathname)
With this filename I am able to retrieve my required variable names from a certain filetype (inconsequential to the problem). Today I modified the __init__ function to call the open function when the program starts. Since nothing else can be done until this file is opened, it would make sense to open it first thing. Once the file is selected and the Tkmessagebox is closed, the root window is active, but none of the keyboard binds work. My functions still work using the menu/buttons assigned to them, just not the binds. I have tried binding the shortcuts to the root, with the same result, and am now thinking it may be an issue with the order I am calling them
def __init__(self):
...
self.openfile() #calls the tkmessagebox
self.root.mainloop() #starts gui
I had actually run into this issue before, where a toplevel() instance was closed/destroyed and disabled the binds of the parent window. There isn't any error message to speak of, the binds just don't do anything. I should also mention I have tried to focus on the root window again using
self.openfile()
self.root.mainloop()
self.root.focus_set()
I got around it before by using the wm_withdraw() and wm_deiconify() functions to simply hide the child window, then close it after the program is complete. This fix is a little more difficult to apply in this case however. If anyone can shed some light on the cause of the problem I'd appreciate it.
Edit:
I've written up a runable code segment to show exactly what my issue is.
import os
from tkFileDialog import askopenfilename
from Tkinter import *
class Start:
def __init__(self):
self.root = Tk()
self.root.title('Binding Troubles')
menubar = Menu(self.root)
#add items and their commands to the menubar
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Do work", command=self.do_work)
filemenu.add_command(label="Open File",command=self.openfile)
menubar.add_cascade(label="File", menu=filemenu)
#bind control-o to perform the do work function
self.root.bind("<Control-o>",self.flash_do_work)
self.root.bind("<Control-O>",self.flash_do_work)
#add the menubar to the GUI
self.root.config(menu=menubar)
#initially open a tkdialog to open a file
self.openfile()#comment out this line to make the bind work
self.root.focus()#also tried self.root.focus_set()
self.root.mainloop()
def flash_do_work(self,event):
#indirect tie to the do_work() function, I'm don't know a
#proper way to make functions handle calls from both events and non-events
self.do_work()
def openfile(self):
#gets current path
self.pathname = os.getcwd()
#Requests filename using a tkdialog
self.filename =askopenfilename(initialdir= self.pathname)
print self.filename
def do_work(self):
#placeholder for actual function; shows whether the bind is working or not
print "work"
Start()
The bind will work if self.openfile() is removed from __init__, and used only from the menu
Another Edit: I've updated the example again, giving a menu option to run the openfile() function. I noticed that if openfile() is called in __init__, the bind will not work. But if next the openfile function is called again, this time manually from the menu, the bind will start working again. Not exactly sure what to take from this. Also, my apologies for the post getting so long.
Change
self.openfile()
to
self.root.after(1, self.openfile)
This moves the call to askopenfilename into the main event loop. Having it outside the main event loop is somehow clobbering your event bindings.
I had this kind of problem a couple of times and it took quite a while until I found a solution I was comfortable with. As #Steven Rumbalski suggests I tried with delaying the application, which works but seems shaky.
Then I found the functions for waiting until something is complete, in this case wait_visibility(widget). This will delay execution until the widget is visible, which seems to be the thing to be waiting for. Try this:
self.root.wait_visibility(self.root) # Wait for root to be displayed
self.openfile()
Now; I'm not sure why this is so, and it seems that there may be differences depending on platform: Tkinter window event . This has nevertheless worked for me on Windows10 and Python 3.10.5.

Categories

Resources