Python Tkinter refresh canvas - python

Hello I have a tuple in python with colours that are related to squares that are drawn in the canvas by the following dictionary:
colour_mapping = {0: "red", 1: "green", 2: "blue" , 3:"purple"}
To be more specific for example a node at the tuple is:
((2, 3), (3, 3))
This means that 4 squares should be drawn this way:
blue square purple square
purple square purple square
and then their colours should be changed accordingly to the next node in my tuple
To do this I iterate the tuple and for each element I draw a new rectangle at the canvas and then I call the time.sleep() function in order to give time to the user to see the differences to the previous state.
My problem is that only the last node is rendered correctly while all the others aren't shown. Can you help me?
Here is my code so far:
self.parent.title("AlienTiles")
self.style = Style()
self.style.theme_use("default")
self.frame = Frame(self, relief=RAISED, borderwidth=1)
self.frame.pack(fill=BOTH, expand=1)
self.canvas = Canvas(self.frame)
self.canvas.pack(fill=BOTH, expand=1)
self.pack(fill=BOTH, expand=1)
for i in range(len(path)) : #the tuple is path
state = path[i].state
print state
time.sleep(1)
y_offset=10
for x in state:
start_x=40
start_y=10
i=1
x_offset=0
for y in x:
x0=(start_x*i)+x_offset
y0=(start_y*i)+y_offset
x1=x0+size
y1=y0+size
colour=colour_mapping[y]
print colour
self.canvas.create_rectangle(x0, y0, x1, y1, fill=colour)
x_offset=x_offset+size+10
y_offset=y_offset+size+10
All in all, I try to make an animation described above. Is there anything I don't think correctly or something to refresh the canvas at each loop?

The only way for the canvas to refresh is for the event loop to service "redraw" events. In your loop you're never giving the event loop a chance to update, so you don't see any changes.
The quick fix is to call self.canvas.update_idletasks, but that's just a hack and not a proper solution.
The proper way to do animation is to use the event loop to do the iterations. You do this by placing work to be done on a queue -- in this case, the idle event queue. You can place things on this queue with the after command.
What you should do is write a function that does one iteration of your animation. Essentially, take everything in your while loop and move it to a function. Then, arrange for that function to be continually be called as long as there is work to do. You can either place the call to after in that function, or have a separate function controlling the animation.
Roughly speaking, the solution looks like this:
def do_one_frame(self, ...):
# do whatever you need to draw one frame
if (there_is_more_work_to_be_done):
self.after(10, do_one_frame)
This will draw one frame of your animation, check to see if there are any new frames to be drawn, and then arranges for the next frame to be drawn in 10ms. Of course, you can set that value to whatever you want in order to control the speed of the animation.
There are working examples of this technique on this website. For example, see https://stackoverflow.com/a/25431690/7432

First, I'm a bit confused about why ((2,3)(3,3)) would get you green and blue squares. Your color coding seems to indicate they would be blue and purple, would it not?
Second, I'm not fully sure I understand the statement "and then their colours should be changed accordingly to the next node in my tuple". Does that mean at one point you are going to pass in ((2,3)(3,3)) and expect to get 4 squares, then the next time pass in ((2,3)(3,3)(1,2)) and you would expect 6 squares to be drawn in blue?
Third, what is the output of your program? It seems as though you have enough print statements where you should be able to figure out where the problem lies.
Taking a guess without fully understanding the program, I would guess the problem is with one of your for loops not iterating over the proper value, which is causing not all your squares to be drawn. My guess is the first one:
for i in range(len(path)) :
But that is really a guess since like I said, I don't fully understand what is happening. I'll do my best to help if you can answer some of my questions. Sorry I'm not more help.

Related

How to get the exact coordinate of where a Tkinter widget begins and ends

I want to find the exact coordinates of the following parts of widgets as shown in the image in blue circles. I cannot figure out how to do this I have tried the following:
self.winfo_x()
self.winfo_y()
self.winfo_xy()
All of these will not give the answer and I do not know any other ways after any of my research. I am making a custom way to drag widgets and I am creating a containment system and need the beginning and end points.
Image(I could not show, I don't have the reputation needed):https://i.stack.imgur.com/ID3kX.png
EDIT:
Here is the picture of my code, I implemented the answer below by using hypothetical button called s:
https://i.stack.imgur.com/mnIaf.png
You can use the method winfo_x to get the x coordinate of the window relative to its parent. You can use winfo.parent to get the name of the parent window, and the method nametowidget to convert that to a widget. The solution is to combine those to recursively get the x or y coordinate of the widget and every parent.
It might look something like this:
def absolute_x(widget):
if widget == widget.winfo_toplevel():
# top of the widget hierarchy for this window
return 0
return widget.winfo_x() + absolute_x(widget.nametowidget(widget.winfo_parent()))

Draw a table with tkinter where cells need to be colored on click and retrieve by code the choices

I'm new to tkiinter and i didn't manage to find a solution to my problem on internet.
First thing I need to draw a sort of table with triangles similar to the one in this picture (https://www.tilelook.com/system/tile_picture/resource/4973584/d3d_default_RE04MC017.png).
Then the user can choose a color somewhere (from a list or something similar) and change the color of a triangle by clicking on it. The most important thing is that i need to retrieve these information in the code (for each trinagle I need to know which color the user choosed).
Edit:
I still didn't write any code, but i know how to draw the table with Canvas and more or less how to handle the coloring part. The hard part for me is how to retrieve the informations in the code, I think it's like considering each element of the table like an independent object or something like that, but i have no idea how to do it.
I won't give you a complete solution but you may find the below a helpful starting point
import tkinter as tk
from random import choice
def getRandomColor():
return choice(['red','blue','green','yellow','white','goldenrod'])
def click(event):
print(vars(event))
item = event.widget.find_withtag('current')
event.widget.itemconfig(item,fill=getRandomColor())
root = tk.Tk()
root.grid()
c = tk.Canvas(root,width=300,height=300,bg='black')
c.grid()
c.create_polygon(0,0,100,0,50,100,fill='blue',tag='tri_1')
c.create_polygon(100,0,50,100,150,100,fill='yellow',tag='tri_2')
c.bind('<Button-1>',click)
root.mainloop()
This will create two triangles. If you click on a triangle it will change to a random color (from a small list of colors).
This will give you some ideas about
a. drawing polygons on a tkinter canvas
b. binding a function to a click
c. changing the properties of a canvas item
Hope you find this helpful

What range does mainloop() have over code?

I would like to make a python tkinter window with custom-moving widgets on a canvas to simulate motion. For now, I have one canvas, and one not-moving oval widget. I am having problems at the base level; mainloop(). I understand that it runs in wait for the user to do something, but I am having a hard time seeing:
How to control/see exactly what code mainloop() is reiterating (where, and only tkinter?);
How to properly interrupt it and return to it from another function, if it doesn't do it itself;
What code should be reiterated? All tkinter objects, or only updating changing ones? Use some kind of update operation instead? Finally;
What is the functionality difference between tkinter.mainloop() and window.mainloop()? Perhaps the previous questions will answer.
I have minor experience with Swift, and started learning the very similar Python yesterday evening. I've tried probably hundred of mutations to my code, which currently is in the test stage. I have moved everything in and out of the apparent range of the mainloop, and even got several hundred tiny Python windows all over the screen. Everything does one of two things: it does nothing, or gives me an error. Since I don't know what is even running, or if it is running, I can't diagnose anything. My goal is simply to move a circle one hundred pixels repeatedly. I've scanned around for sources, but—it may be me—a clear one is scarce. I have my code here all marked up. This page is closest to what I am looking for: Move a ball inside Tkinter Canvas Widget (simple Arkanoid game). Everything appears to be under mainloop. So, everything is redrawn every pass? Here, unfortunately, is my whole script; I can't only show pieces. It, for some reason, only brings up a small window, not a full-screen one. (Edit: I seem to have lost the screen size code)
import tkinter
import time
# Initial values for circle's corners and start idicator ('b'):
x1 = 10
y1 = 10
x2 = 210
y2 = 210
b = 0
# Window ('window')
window = tkinter.Tk()
# Canvas ('area')
area = tkinter.Canvas(window, width=1368, height=650)
area.place(x=0, y=0)
# Ovals to be placed on 'area'
oval1 = area.create_oval(x1,y1,x2,y2,fill='#42befe')
oval2 = area.create_oval(100,10,300,210,fill='#d00000')
# Turns b to 1 to start shifting when 'butt' is pressed:
def startFunc():
b = 1
print('b = 1')
# My button to activate 'startFunc'
butt = tkinter.Button(window, text='Start movement', command=startFunc)
butt.pack()
# Adjusts the x and y coordinates when they are fed in:
def Shift(A, B, C, D):
print('Shift activated.')
window.after(1000)
print('Edit and return:')
A += 100
B += 100
C += 100
D += 100
return(A, B, C, D)
# Problems start about here: my Mainloop section;
# I have little idea how this is supposed to be.
while True:
if b == 1:
# Takes adjusted tuple
n = Shift(x1, y1, x2, y2)
print('Returned edited tuple.')
# Changes coordinates
x1 = n[0]
y1 = n[1]
x2 = n[2]
y2 = n[3]
print(f'{x1}, {y1}, {x2}, and {y2}')
# Reiterate moving oval
oval1 = area.create_oval(x1,y1,x2,y2,fill='#42befe')
#Does this re-run 'window' relations outside here, or only within the 'while'?
window.mainloop()
It ought to show a 1368 by 650 window, not a tiny one. The button does nothing but print, which means the final 'while' is not running, despite the mainloop. It want it to loop inside the 'while' line, which should adjust coordinates and move my blue circle. The iteration may NOT touch the initial values, or else it would reset them.
In effect, calling mainloop is the same as if you added this to your code instead of calling mainloop():
while the_program_is_running():
event = wait_for_event()
process_the_event(event)
As a rule of thumb, mainloop() should be called exactly once after the UI has initialized and you are ready for the user to start interacting with your program. When it exits, you typically won't have any code after it, and your program will exit.
How to control/see exactly what code mainloop() is reiterating (where, and only tkinter?);
I don't know what you mean by "reiterating". It doesn't run any code except it's own internal code. It simply waits for events, and then dispatches them to handlers.
How to properly interrupt it and return to it from another function, if it doesn't do it itself;
It's exceedingly rare to do this in a running program. Typically, calling mainloop is the last thing your program does before the user starts interacting with it, and as soon as it exits your program quits.
However, to answer the specific answer of how to interrupt it, you can call the quit method of the root window. That will cause the most recent call to mainloop() to return.
What code should be reiterated? All tkinter objects, or only updating changing ones? Use some kind of update operation instead?
That question is hard to answer because it doens't make much sense. When you call mainloop(), it will watch for all events on all tkinter objects.
What is the functionality difference between tkinter.mainloop() and window.mainloop()
They have exactly the same effect and behavior. Tkinter oddly chose to make mainloop available from any widget. The most common way to call it is from either the tkinter module itself, or from the root window.
My goal is simply to move a circle one hundred pixels repeatedly.
The normal way to do that is to create a function that moves it one hundred pixels. Then, that function (-- or a function that calls it -- can put itself on an event queue to be run in the future.
For example, the following code will move a canvas object 100 pixels every second until the program exits:
def move_object():
the_canvas.move(item_id, 100, 0)
the_canvas.after(1000, move_object)
When it is called, it will move the item 100 pixels to the right. Then, it will place a new call to itself on the event queue to be picked up and handled in approximately 1000 milliseconds.
There are many working examples of using after on this site, including the question you linked to in your question.
Everything appears to be under mainloop. So, everything is redrawn every pass?
No, not exactly. The only objects that are redrawn are things that need to be redrawn. Moving objects on a canvas, resizing a window, dragging another window over your window, etc, all place an event on the event queue that tells tkinter "this object needs to be redrawn". The processing of that event happens automatically by mainloop. If nothing is happening in your application, nothing gets redrawn by mainloop.
It ought to show a 1368 by 650 window, not a tiny one
That is because you haven't given the main window a size. You've given the canvas a size, but you're using place which won't cause the containing window to grow or shrink to fit. As a beginner, you should completely avoid place and instead use pack or grid, because pack and grid will both automatically size your window to fit everything inside.
While it's tempting to use place for its perceived simplicity, in reality it usually requires you to do a lot more work than if you used one of the other geometry managers, and it results in a GUI that isn't particularly responsive to change.
while True:
You should almost never do this in tkinter. Tkinter -- and almost all event based programs -- rely on a steady flow of events. When you have an infinite loop, it cannot process those events. You can put an explicit call to update the screen inside your loop, but that is inefficient and should be avoided. If you need to do something periodically, create a function that encapsulates the body of your loop, then use after to get mainloop to run it while it is processing events.
window.after(1000)
You should almost never use after this way without a second argument. This usage is functionally no different than calling time.sleep(1) in that it prevents mainloop from processing events. You should structure your code to allow for a steady stream of events to be processed by mainloop.
while True: ... window.mainloop()
You definitely need to avoid calling mainloop inside a loop. A well behaved tkinter program should call mainloop() exactly once.

Can you change the attributes of a Canvas object after creation?

I'm trying to simulate an American traffic light, with 3 circles on a rectangle, all drawn on a set Canvas. The simulation is supposed to mirror "animation" by changing which light is displayed every 2 seconds in the following order: green > yellow > red > green, etc forever.
The only way I can think of to do this is by using a canvas.move(), canvas.after(), canvas.update() pattern to move a filled oval object to superimpose one unfilled circle at a time. I've gotten the logic down to move a circle at the proper speed and in the correct order. The thing is, I just instantiate a circle filled with "green", but I can't change it to be "yellow" or "red" using this method. It seems silly to have to canvas.delete("filled") and redraw it in a new place with a different fill every 2 seconds, because that's a lot to do for such a simple program.
Question 1: Is there a way I can just alter the fill option for my filled Canvas object at will, using some method or other means?
Question 2: Am I approaching this scenario incorrectly? Is there a better way to simulate this?
Yes you should be able to change settings of the canvas with config().
Likewise, use itemconfig() to change items on the canvas. This does require that you save a handle to the item or tag them.
Example based on tkinterbook:
item = canvas.create_line(xy, fill="red")
canvas.coords(item, new_xy) # change coordinates
canvas.itemconfig(item, fill="blue") # change color

Clearing graph before replotting matplotlib

I have a little app that allows me to change an input value with a tKinter scale widget and see how a graph reacts to different changes in inputs. Every time I move the scale, it's bound to an event that redoes the calculations for a list and replots. It's kind of slow.
Now, I'm replotting the entire thing, but it's stacking one axis on top of the other, hundreds after a few minutes of use.
deltaPlot = Figure(figsize=(4,3.5), dpi=75, frameon=False)
c = deltaPlot.add_subplot(111)
c.set_title('Delta')
deltaDataPlot = FigureCanvasTkAgg(deltaPlot, master=master)
deltaDataPlot.get_tk_widget().grid(row=0,rowspan=2)
and the main loop runs
c.cla()
c.plot(timeSpread,tdeltas,'g-')
deltaDataPlot.show()
It's clearing the initial plot, but like I said the axes are stacking (because it's redrawing one each time, corresponding to the slightly altered data points). Anyone know a fix?
To improve speed there are a couple of things you could do:
Either Run the remove method on the line produced by plot:
# inside the loop
line, = c.plot(timeSpread,tdeltas,'g-')
deltaDataPlot.show()
...
line.remove()
Or Re-use the line, updating its coordinates appropriately:
# outside the loop
line, = c.plot(timeSpread,tdeltas,'g-')
# inside the loop
deltaDataPlot.show()
line.set_data(timeSpread,tdeltas)
The documentation of Line2d can be found here.
You might also like to read the cookbook article on animation.
HTH

Categories

Resources