I'm trying to spawn a thread, which periodically changes the color of my oval canvas, but somehow this codes does nothing:
from Tkinter import *
class Application(Frame):
def laufer(self):
self.canvas1.configure(fill='black')
def bt_start(self):
t = Thread(target=self.laufer)
t.start()
def createWidgets(self):
self.canvas1 = Canvas(self, width=50, height=50)
self.canvas1.create_oval(10, 10, 50, 50, outline="black",
fill="green", width=1)
self.canvas1.grid(row=3, column=0)
[bt_start...]
Which is the correct way to change the fill color?
To answer the question about how to change the color of the item, you must use the itemconfigure method, giving it the id of the object you want to configure, or a tag that represents zero or more objects to configure.
For example:
self.id = self.canvas1.create_oval(..., fill="green")
...
self.canvas1.itemconfigure(self.id, fill="black")
There are bigger problems, however. You cannot interact with the canvas from any thread other than the main thread. It might work, but there's a good chance your app will eventually crash.
There are two solutions: the preferred one is, don't use threads. They add complexity, and a lot of times you just don't need them. In the type of program used in the question, threads are completely unnecessary.
If, however, you really do need threads for your program, the generally accepted solution is to create a thread-safe queue, have your worker threads post some kind of data to the queue, and then have your main thread poll the queue on a regular schedule to act upon the data.
Related
I have a question about how to properly address widget attributes from outside the GUI main loop. Here is the gist of my project: I have an HMI, and it runs in its own thread. Outside of the HMI, there will be another thread that is collecting data and writing to hardware. I stripped out a bunch of stuff to create a basic example so you can see what I mean. It consists of a main window, a frame where the operator can select manual or auto, and a notebook that will display temperature probe data. In the real world, there will be varying numbers of probes and additional attributes, which is why I instantiate the probes at the top of the script. In real life, this would come from a SQL database and the GUI would be built dynamically based on database properties.
from tkinter import *
import tkinter as tk
from tkinter import ttk
from threading import Thread
from time import sleep
import random
wProbes= {1: {'Title': 'Indoors','Temperature':0,'MainObject':'','TempLabel':''},2: {'Title': 'Outdoors','Temperature':0,'Object':''}}
controlTemplate={'State':0,'On':0, 'Off':0}
systemControl = {'Auto':controlTemplate,'Manual':controlTemplate}
def gui():
class SystemOperation(tk.LabelFrame):
def __init__(self,parent):
super().__init__(parent)
self.config(text='System Operation',labelanchor='n',bd=2,highlightbackground='black',relief= 'solid',font=("arial", 20))
self.place(width=500, height=900,x=150,y=100)
def autoModeClick():
self.bAutoMode.configure(background='green')
self.bManualMode.configure(background='white')
systemControl['Auto']['State'] = 1
systemControl['Manual']['State'] = 0
def manualModeClick():
self.bManualMode.configure(background='green')
self.bAutoMode.configure(background='white')
systemControl['Auto']['State'] = 0
systemControl['Manual']['State'] = 1
self.bAutoMode = Button(self, text='Auto', background='green', command=autoModeClick)
self.bAutoMode.place(x=100, y=100, width=70)
self.bManualMode = Button(self, text='Manual', command=manualModeClick)
self.bManualMode.place(x=100, y=150, width=70)
class TemperatureProbe(tk.LabelFrame):
def __init__(self,parent,inst):
super().__init__(parent)
self.config(text=wProbes[inst]['Title'],labelanchor='n',bd=2,highlightbackground='black',relief= 'solid',font=("arial", 20))
self.place(width=500, height=300)
self.currentTemp=0
self.actTemp=Label(self,text='Temperature= %d F'%self.currentTemp,width=20)
self.actTemp.place(relx=.5, y=20, anchor='center')
self.pack_propagate(0)
wProbes[inst]['TempLabel']=self.actTemp
class probeNotebook(ttk.Notebook):
def __init__(self,parent):
super().__init__(parent)
self.place(x=900,y=100,width=800,height=700)
for t in sorted(wProbes.keys()):
tabName = wProbes[t]['Title']
self.tabFrame = tk.Frame(self, width=700, height=900, bg='white', borderwidth=2, relief='solid')
self.add(self.tabFrame, text=' %s '%tabName)
self.pFB=TemperatureProbe(self.tabFrame,t)
self.pFB.place(x=100, y=20)
wProbes[t]['MainObject']=self.pFB
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.geometry("800x800")
self.title('Test Window')
SystemOperation(self)
probeNotebook(self)
if __name__=="__main__":
app=MainWindow()
app.mainloop()
threadGUI = Thread(target=gui)
threadGUI.start()
while 1:
#A bunch of stuff will be happening here that interfaces with external hardware
if wProbes[1]['MainObject']!='':
randInt=random.randint(10,200)
wProbes[1]['TempLabel'].config(text='Temperature= %d F'%randInt)
wProbes[2]['TempLabel'].config(text='Temperature= %d F' % (randInt+200))
sleep(.5)
So, you can see that a notebook is created, and the tabs are then created based on the number of probes in the probe dictionary. Also in the probe dictionary, I set a reference to the probe container, and then the probe temperature label. If you run the code, you can see the temperatures change correctly based on the probe instances with a test random int.
But what I was really hoping to do is just use the probe MainObject reference and address its child widgets that way, i.e. wProbes[1]['MainObject'].actTemp, because in reality the probe container will have more widgets such as limits and setpoints and warnings. But if creating individual references is the correct way, I'm good with that too. Am I on the right track?
Another thing I wondered about as I go forward is how I can separate the GUI from the process script completely. Imagine the GUI as a standalone app, and a second app that acts like a server and does the heavy lifting and communicates with the GUI. Any basic hints on doing this? I'll do the work in figuring it out, but if you push me in the right direction I'd appreciate it.
Also, if there are criticisms of my project approach in general, I'm all ears. I've done quite a bit of python programming, but this is my first tkinter attempt. Thanks!
(Note: I hope it formatted correctly. I just copied and pasted into my ide, and it was fine. Worst case would be that the indentation is wrong in a couple of places when copied, but give it a try.)
global window
window = Tk()
window.geometry('300x200')
window.minsize(300,200)
window.maxsize(300,200)
window.configure(bg='black')
window.title("testupdate")
global outputtext
outputtext = tk.StringVar()
outputtext.set("starting window...")
my_label = tk.Label(window, textvariable = outputtext, bg = 'black', fg = 'white', font = 'terminal')
my_label.pack()
class ChangeLabel():
def __init__(self, text):
outputtext = tk.StringVar()
outputtext.set(text)
my_label.config(textvariable = outputtext)
Here's the main code: https://replit.com/#YourPetFinch/textadventure#main.py
I've been trying to make a text adventure game with Tkinter so I can package it all nicely as an app. I created a function that I can call from another file to update a label as the game output so I can keep the GUI setup simple, but I've been having a problem where the window won't show up until the code has finished running. I'm new to tkinter (and honestly not very good at Python in general) so this is probably a stupid question.
That's not how global is used. The global statement is only used inside a function, to state that you're going to modify a global. You can't actually make a global that crosses files, but your from gui import * will handle that.
The issue here is understanding event-driven programming. When you create a window, nothing gets drawn. All that does is send a number of messages to signal the core. The messages will not get fetched and dispatched until it gets into the .mainloop(). The main loop processes all the messages and does the drawing, which might queue up more messages.
So, you cannot use time.sleep(2) like that. As you saw, that will interrupt the process and prevent any drawing from being done. Instead, you will have to use window.after to request a callback after some number of seconds. The .mainloop monitors the timers, and will give you a call when time runs out. You cannot use time.sleep inside an event handler either, for the same reason; your GUI will freeze until your event handler returns.
The goal is to achieve different "screens" in TkInter and change between them. The easiest to imagine this is to think of a mobile app, where one clicks on the icon, for example "Add new", and new screen opens. The application has total 7 screens and it should be able to change screens according to user actions.
Setup is on Raspberry Pi with LCD+touchscreen attached. I am using tkinter in Python3. Canvas is used to show elements on the screen.
Since I am coming from embedded hardware world and have very little experience in Python, and generally high-level languages, I approached this with switch-case logic. In Python this is if-elif-elif...
I have tried various things:
Making global canvas object. Having a variable programState which determines which screen is currently shown. This obviously didn't work because it would just run once and get stuck at the mainloop below.
from tkinter import *
import time
root = Tk()
programState = 0
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)
if(programState == 0):
backgroundImage = PhotoImage(file="image.gif")
canvas.create_image(0,0, image=backgroundImage, anchor=NW);
time.sleep(2)
canvas.delete(ALL) #delete all objects from canvas
programState = 1
elif(programState == 1):
....
....
....
root.mainloop()
Using root.after function but this failed and wouldn't show anything on the screen, it would only create canvas. I probably didn't use it at the right place.
Trying making another thread for changing screens, just to test threading option. It gets stuck at first image and never moves to second one.
from tkinter import *
from threading import Thread
from time import sleep
def threadFun():
while True:
backgroundImage = PhotoImage(file="image1.gif")
backgroundImage2 = PhotoImage(file="image2.gif")
canvas.create_image(0,0,image=backgroundImage, anchor=NW)
sleep(2)
canvas.delete(ALL)
canvas.create_image(0,0,image=backgroundImage2, anchor=NW)
root = Tk()
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)
# daemon=True kills the thread when you close the GUI, otherwise it would continue to run and raise an error.
Thread(target=threadFun, daemon=True).start()
root.mainloop()
I expect this app could change screens using a special thread which would call a function which redraws elements on the canvas, but this has been failing so far. As much as I understand now, threads might be the best option. They are closest to my way of thinking with infinite loop (while True) and closest to my logic.
What are options here? How deleting whole screen and redrawing it (what I call making a new "screen") can be achieved?
Tkinter, like most GUI toolkits, is event driven. You simply need to create a function that deletes the old screen and creates the new, and then does this in response to an event (button click, timer, whatever).
Using your first canvas example
In your first example you want to automatically switch pages after two seconds. That can be done by using after to schedule a function to run after the timeout. Then it's just a matter of moving your redraw logic into a function.
For example:
def set_programState(new_state):
global programState
programState = new_state
refresh()
def refresh():
canvas.delete("all")
if(programState == 0):
backgroundImage = PhotoImage(file="image.gif")
canvas.create_image(0,0, image=backgroundImage, anchor=NW);
canvas.after(2000, set_programState, 1)
elif(programState == 1):
...
Using python objects
Arguably a better solution is to make each page be a class based off of a widget. Doing so makes it easy to add or remove everything at once by adding or removing that one widget (because destroying a widget also destroys all of its children)
Then it's just a matter of deleting the old object and instantiating the new. You can create a mapping of state number to class name if you like the state-driven concept, and use that mapping to determine which class to instantiate.
For example:
class ThisPage(tk.Frame):
def __init__(self):
<code to create everything for this page>
class ThatPage(tk.Frame):
def __init__(self):
<code to create everything for this page>
page_map = {0: ThisPage, 1: ThatPage}
current_page = None
...
def refresh():
global current_page
if current_page:
current_page.destroy()
new_page_class = page_map[programstate]
current_page = new_page_class()
current_page.pack(fill="both", expand=True)
The above code is somewhat ham-fisted, but hopefully it illustrates the basic technique.
Just like with the first example, you can call update() from any sort of event: a button click, a timer, or any other sort of event supported by tkinter. For example, to bind the escape key to always take you to the initial state you could do something like this:
def reset_state(event):
global programState
programState = 0
refresh()
root.bind("<Escape>", reset_state)
I have this really simple and short few lines of code:
import Tkinter as tk
master = tk.Tk()
w = tk.Canvas(master, width=800, height=600)
w.pack(side="top", fill="both", expand=True)
master.mainloop()
For readibility, extended functionality, portability and other reasons I want to put the code into a class, like this:
class Example(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.create_window()
def create_window(self):
self.canvas = tk.Canvas(self, width=800, height=600)
self.canvas.pack(side="top", fill="both", expand=True)
if __name__ == "__main__":
app = Example()
app.mainloop()
The code runs fine, exactly like the one above, but when I stop the program I get a "Python has stopped working" message. It doesn't affect the program itself but I want to know what's causing it and why it runs perfectly outside a class.
Tkinter's Tk and other classes have hundreds of methods and attributes. I know that if one read ancient GUI programing books, from the times of Windows 3.x, the recommendation for creating your own Windows would be to subclass the underlying library widget and wrtite yoru code. That is what you are trying to do above, by subclassing Tk.
Well, do not do that. Really, don't. That way you can't create any attribute on your class without triple-checking if it is not colliding with some other attribute.
Use inhretance in OO to create your own class hierarchy - and that is a relevant and ordered thing that will make things easier to you. To inherit from the GUI code' Window, and attach members to it like "name_text_entry" and "name_variable" (and repeat that for each input control in your program) is a thing that is easily perceived as mixing very different things (Window control attributes and methods) with the internal workings of your own program. Here is a more extensive consideration on why this pattern does not work (not in Python, but I just picked it from the first Google results)
So, just create your own program, and leave Tkinter's classes alone - unless you are creating a new custom Widget (to be used in another part of the code).
Your code should be more like:
class Example(object):
def __init__(self, *args, **kw):
self.tk = tk.Tk(*args, **kw)
self.create_window()
def create_window(self):
self.canvas = tk.Canvas(self, width=800, height=600)
self.canvas.pack(side="top", fill="both", expand=True)
And just work.
The error message, as seen in the comments, is not deterministic, and can be yielded by lots of different factors - from tkinter.Tk internally depending on not being subclassed (even if that is a bug), to it having an internal "canvas" attribute, and so on.
Just use composition and be happy.
Till now, I used to end my Tkinter programs with: tk.mainloop(), or nothing would show up! See example:
from Tkinter import *
import random
import time
tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
pass
ball = Ball(canvas, "red")
tk.mainloop()
However, when tried the next step in this program (making the ball move by time), the book am reading from, says to do the following. So I changed the draw function to:
def draw(self):
self.canvas.move(self.id, 0, -1)
and add the following code to my program:
while 1:
ball.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
But I noticed that adding this block of code, made the use of tk.mainloop() useless, since everything would show up even without it!!!
At this moment I should mention that my book never talks about tk.mainloop() (maybe because it uses Python 3) but I learned about it searching the web since my programs didn't work by copying book's code!
So I tried doing the following that would not work!!!
while 1:
ball.draw()
tk.mainloop()
time.sleep(0.01)
What's going on? What does tk.mainloop()? What does tk.update_idletasks() and tk.update() do and how that differs from tk.mainloop()? Should I use the above loop?tk.mainloop()? or both in my programs?
tk.mainloop() blocks. It means that execution of your Python commands halts there. You can see that by writing:
while 1:
ball.draw()
tk.mainloop()
print("hello") #NEW CODE
time.sleep(0.01)
You will never see the output from the print statement. Because there is no loop, the ball doesn't move.
On the other hand, the methods update_idletasks() and update() here:
while True:
ball.draw()
tk.update_idletasks()
tk.update()
...do not block; after those methods finish, execution will continue, so the while loop will execute over and over, which makes the ball move.
An infinite loop containing the method calls update_idletasks() and update() can act as a substitute for calling tk.mainloop(). Note that the whole while loop can be said to block just like tk.mainloop() because nothing after the while loop will execute.
However, tk.mainloop() is not a substitute for just the lines:
tk.update_idletasks()
tk.update()
Rather, tk.mainloop() is a substitute for the whole while loop:
while True:
tk.update_idletasks()
tk.update()
Response to comment:
Here is what the tcl docs say:
Update idletasks
This subcommand of update flushes all currently-scheduled idle events
from Tcl's event queue. Idle events are used to postpone processing
until “there is nothing else to do”, with the typical use case for
them being Tk's redrawing and geometry recalculations. By postponing
these until Tk is idle, expensive redraw operations are not done until
everything from a cluster of events (e.g., button release, change of
current window, etc.) are processed at the script level. This makes Tk
seem much faster, but if you're in the middle of doing some long
running processing, it can also mean that no idle events are processed
for a long time. By calling update idletasks, redraws due to internal
changes of state are processed immediately. (Redraws due to system
events, e.g., being deiconified by the user, need a full update to be
processed.)
APN As described in Update considered harmful, use of update to handle
redraws not handled by update idletasks has many issues. Joe English
in a comp.lang.tcl posting describes an alternative:
So update_idletasks() causes some subset of events to be processed that update() causes to be processed.
From the update docs:
update ?idletasks?
The update command is used to bring the application “up to date” by
entering the Tcl event loop repeatedly until all pending events
(including idle callbacks) have been processed.
If the idletasks keyword is specified as an argument to the command,
then no new events or errors are processed; only idle callbacks are
invoked. This causes operations that are normally deferred, such as
display updates and window layout calculations, to be performed
immediately.
KBK (12 February 2000) -- My personal opinion is that the [update]
command is not one of the best practices, and a programmer is well
advised to avoid it. I have seldom if ever seen a use of [update] that
could not be more effectively programmed by another means, generally
appropriate use of event callbacks. By the way, this caution applies
to all the Tcl commands (vwait and tkwait are the other common
culprits) that enter the event loop recursively, with the exception of
using a single [vwait] at global level to launch the event loop inside
a shell that doesn't launch it automatically.
The commonest purposes for which I've seen [update] recommended are:
Keeping the GUI alive while some long-running calculation is
executing. See Countdown program for an alternative. 2) Waiting for a window to be configured before doing things like
geometry management on it. The alternative is to bind on events such
as that notify the process of a window's geometry. See
Centering a window for an alternative.
What's wrong with update? There are several answers. First, it tends
to complicate the code of the surrounding GUI. If you work the
exercises in the Countdown program, you'll get a feel for how much
easier it can be when each event is processed on its own callback.
Second, it's a source of insidious bugs. The general problem is that
executing [update] has nearly unconstrained side effects; on return
from [update], a script can easily discover that the rug has been
pulled out from under it. There's further discussion of this
phenomenon over at Update considered harmful.
.....
Is there any chance I can make my program work without the while loop?
Yes, but things get a little tricky. You might think something like the following would work:
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
while True:
self.canvas.move(self.id, 0, -1)
ball = Ball(canvas, "red")
ball.draw()
tk.mainloop()
The problem is that ball.draw() will cause execution to enter an infinite loop in the draw() method, so tk.mainloop() will never execute, and your widgets will never display. In gui programming, infinite loops have to be avoided at all costs in order to keep the widgets responsive to user input, e.g. mouse clicks.
So, the question is: how do you execute something over and over again without actually creating an infinite loop? Tkinter has an answer for that problem: a widget's after() method:
from Tkinter import *
import random
import time
tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
self.canvas.move(self.id, 0, -1)
self.canvas.after(1, self.draw) #(time_delay, method_to_execute)
ball = Ball(canvas, "red")
ball.draw() #Changed per Bryan Oakley's comment
tk.mainloop()
The after() method doesn't block (it actually creates another thread of execution), so execution continues on in your python program after after() is called, which means tk.mainloop() executes next, so your widgets get configured and displayed. The after() method also allows your widgets to remain responsive to other user input. Try running the following program, and then click your mouse on different spots on the canvas:
from Tkinter import *
import random
import time
root = Tk()
root.title = "Game"
root.resizable(0,0)
root.wm_attributes("-topmost", 1)
canvas = Canvas(root, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
self.canvas.bind("<Button-1>", self.canvas_onclick)
self.text_id = self.canvas.create_text(300, 200, anchor='se')
self.canvas.itemconfig(self.text_id, text='hello')
def canvas_onclick(self, event):
self.canvas.itemconfig(
self.text_id,
text="You clicked at ({}, {})".format(event.x, event.y)
)
def draw(self):
self.canvas.move(self.id, 0, -1)
self.canvas.after(50, self.draw)
ball = Ball(canvas, "red")
ball.draw() #Changed per Bryan Oakley's comment.
root.mainloop()
while 1:
root.update()
... is (very!) roughly similar to:
root.mainloop()
The difference is, mainloop is the correct way to code and the infinite loop is subtly incorrect. I suspect, though, that the vast majority of the time, either will work. It's just that mainloop is a much cleaner solution. After all, calling mainloop is essentially this under the covers:
while the_window_has_not_been_destroyed():
wait_until_the_event_queue_is_not_empty()
event = event_queue.pop()
event.handle()
... which, as you can see, isn't much different than your own while loop. So, why create your own infinite loop when tkinter already has one you can use?
Put in the simplest terms possible: always call mainloop as the last logical line of code in your program. That's how Tkinter was designed to be used.
I'm using an MVC / MVA design pattern, with multiple types of "views". One type is a "GuiView", which is a Tk window. I pass a view reference to my window object which does things like link buttons back to view functions (which the adapter / controller class also calls).
In order to do that, the view object constructor needed to be completed prior to creating the window object. After creating and displaying the window, I wanted to do some initial tasks with the view automatically. At first I tried doing them post mainloop(), but that didn't work because mainloop() blocked!
As such, I created the window object and used tk.update() to draw it. Then, I kicked off my initial tasks, and finally started the mainloop.
import Tkinter as tk
class Window(tk.Frame):
def __init__(self, master=None, view=None ):
tk.Frame.__init__( self, master )
self.view_ = view
""" Setup window linking it to the view... """
class GuiView( MyViewSuperClass ):
def open( self ):
self.tkRoot_ = tk.Tk()
self.window_ = Window( master=None, view=self )
self.window_.pack()
self.refresh()
self.onOpen()
self.tkRoot_.mainloop()
def onOpen( self ):
""" Do some initial tasks... """
def refresh( self ):
self.tkRoot_.update()