Animated Tkinter - python

I am trying to write a basic tkinter example that will show a box stretching across a frame. At this point the code below will only print the final result, and not show the box moving. How do I fix the code so that it will work over time with out using something like move, so that I can modify the shape over time later?
from tkinter import Tk, Canvas, Frame, BOTH
from time import sleep
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.title("Board")
self.pack(fill=BOTH, expand=1)
self.canvas = Canvas(self)
self.ctr = 10
self.initUI()
def initUI(self):
print(self.ctr)
#The first four parameters are the x,y coordinates of the two bounding points.
#The top-left and the bottom-right.
r = self.canvas.create_rectangle((self.ctr * 10), 0, (self.ctr * 10 + 50), 50,
outline="#fb0", fill="#fb0")
'''
canvas.create_rectangle(50, 0, 100, 50,
outline="#f50", fill="#f50")
canvas.create_rectangle(100, 0, 150, 50,
outline="#05f", fill="#05f")
'''
self.canvas.pack(fill=BOTH, expand=1)
if self.ctr > 0:
self.updateUI()
def updateUI(self):
self.ctr -= 1
sleep(1)
self.initUI()
def main():
root = Tk()
root.geometry("400x100+300+300")
ex = Example(root)
root.mainloop()
if __name__ == '__main__':
main()

This should get you partway there (you'll need to fix the indenting, the offsets are not correct and the counter doesn't get reset). In future, make sure you don't call sleep when you are using the event loop of a GUI for example. Most GUI's have a method to hook something into their event loops (the root.after call in this case). All I've done is make your code work partially - this should not be seen as indicative of idiomatic python.
from tkinter import Tk, Canvas, Frame, BOTH
from time import sleep
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.title("Board")
self.pack(fill=BOTH, expand=1)
self.canvas = Canvas(self)
self.ctr = 10
def initUI(self):
print(self.ctr)
#The first four parameters are the x,y coordinates of the two bounding points.
#The top-left and the bottom-right.
r = self.canvas.create_rectangle((self.ctr * 10), 0, (self.ctr * 10 + 50), 50,
outline="#fb0", fill="#fb0")
'''
canvas.create_rectangle(50, 0, 100, 50,
outline="#f50", fill="#f50")
canvas.create_rectangle(100, 0, 150, 50,
outline="#05f", fill="#05f")
'''
self.canvas.pack(fill=BOTH, expand=1)
self.ctr += 1
if self.ctr > 0:
self.parent.after(1000, self.initUI)
def main():
root = Tk()
ex = Example(root)
root.geometry("400x100+300+300")
root.after(1000, ex.initUI)
root.mainloop()
if __name__ == '__main__':
main()

Related

Python tkinter Canvas Not Doing Anything

I'm trying to make a grid with a canvas using tkinter and after I made some change the lines stopped being drawn. I'm not sure what I did but it might have to do with how the Canvas object is nested in the Frame object, but it was working just fine like that at one point.
I also tried using shapes and couldn't get them to draw.
from tkinter import *
class Application(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.canvas = CanvasClass()
class CanvasClass(Canvas):
def __init__(self):
super().__init__()
self.CanvasHeight = 600
self.CanvasWidth = 800
self.c = Canvas(width=self.CanvasWidth, height=self.CanvasHeight)
self.CreateBackground(20)
self.c.pack(expand=True)
def CreateBackground(self, d):
print(self.CanvasHeight)
print(self.CanvasWidth)
for x in range(d, self.CanvasWidth, d):
print(x)
self.create_line(x, 0, x, self.CanvasHeight)
root = Tk()
root.title = "TESTING"
app = Application()
app.canvas.create_polygon(50,200,50,200)
root.mainloop()
from tkinter import *
class Application(Frame):
def __init__(self,p):
super().__init__(p)
self.initUI()
def initUI(self):
self.canvas = CanvasClass(self)
self.canvas.pack()#1
class CanvasClass(Canvas):
def __init__(self,p):#2
super().__init__(p)
self.CanvasHeight = 600
self.CanvasWidth = 800
self.c = Canvas(width=self.CanvasWidth, height=self.CanvasHeight)
self.CreateBackground(20)
self.c.pack(expand=True)
def CreateBackground(self, d):
print(self.CanvasHeight)
print(self.CanvasWidth)
for x in range(d, self.CanvasWidth, d):
print(x)
self.create_line(x, 0, x, self.CanvasHeight)
root = Tk()
root.title = "TESTING"
app = Application(root)
app.canvas.create_polygon(50,200,50,200)
app.pack()#3
root.mainloop()
you forgot to add the parent and insert it into the window I have used pack() you can change if you want. and I got grids of lines in the window at the bottom when I execute after packing

Move objects automatically

The objective of my exercise is to create a game in which a falling ball needs to be caught by a bar at the bottom of the screen. Below code does not make the ball to fall automatically. I referred to below posts, but could not find a solution:
Tkinter bind to arc, Automatically Moving Shape? Python 3.5 Tkinter
import tkinter as tk
class Game(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.can = tk.Canvas(self, width=400, height=400)
self.can.pack(fill="both", expand=True)
self.ball = self.can.create_oval(40, 40, 60, 60, fill="red", tag="ball")
self.player = self.can.create_rectangle(300,345,350,360, fill="red")
self.bind("<Key>", self.move_player)
self.can.tag_bind("ball",self.move_b)
self.mainloop()
def move_b(self,event=None):
self.can.move(self.ball, 1, 0)
print(self.ball)
# move again after 25ms (0.025s)
self.can.after(25, self.move_b)
def move_player(self, event):
key = event.keysym
if key == "Left":
self.can.move(self.player, -20, 0)
elif key == "Right":
self.can.move(self.player, 20, 0)
if __name__ == '__main__':
Game()
2nd positional argument to tag_bind is an event, whereas in your code it's passed as the actual callback, self.move_b. First, add an event:
self.can.tag_bind("ball", "<ButtonRelease-1>", self.move_b)
If you don't want it to have event, simply pass None:
self.can.tag_bind("ball", None, self.move_b)
or don't use tag_bind at all, and simply call:
self.move_b()
Whenever you want the animation to start.

TK Image Not Appearing

I am trying to put an image on a TKinter canvas with other buttons under the image. For some reason I can not get this picture to appear. I have not yet implemented the buttons. Here is my code thus far.
class GUI_Control:
def __init__(self, player):
self.player = player
self.map = Tk()
self.MAP_WIDTH = 600
self.MAP_HEIGHT = 375
#define map gui here
self.canvas = Canvas(self.map, width=self.MAP_WIDTH, height=self.MAP_HEIGHT)
self.map_picture = PhotoImage(file=r"images/archipelago.gif")
self.canvas.create_image(0, 0, image=self.map_picture)
#define level gui's here
def open(self):
self.map.mainloop()
def hide_map(self):
self.map.destroy()
#debugging
if __name__ == "__main__":
gui = GUI_Control(Player.Player())
gui.open()
You'll need to use one of Tk’s geometry-management mechanisms to tell it where to render the canvas within it's container.
the simplest way would be to add self.canvas.pack() like so:
#define map gui here
self.canvas = Canvas(self.map, width=self.MAP_WIDTH, height=self.MAP_HEIGHT)
self.canvas.pack()
self.map_picture = PhotoImage(file=r"images/archipelago.gif")
self.canvas.create_image(0, 0, image=self.map_picture)
#define level gui's here
You need to call the pack() (or grid()) method of widgets for them to be displayed:
class GUI_Control:
def __init__(self, player):
self.player = player
self.map = Tk()
self.MAP_WIDTH = 600
self.MAP_HEIGHT = 375
#define map gui here
self.canvas = Canvas(self.map, width=self.MAP_WIDTH, height=self.MAP_HEIGHT)
self.canvas.pack(expand=YES, fill=BOTH) # ADDED
self.map_picture = PhotoImage(file="images/archipelago.gif")
self.canvas.create_image(0, 0, image=self.map_picture, anchor='nw')
#define level gui's here
def open(self):
self.map.mainloop()
def hide_map(self):
self.map.destroy()
#debugging
if __name__ == "__main__":
gui = GUI_Control(Player.Player())
gui.open()

Using wx.EVT_PAINT and wx.PaintDC

You use wx.EVT_PAINT and wx.PaintDC to draw shapes, so that when window is resized (redrawn) shapes will not be lost. This works when the window is created. But, how will I preserve the shapes that I create after window is created?
Below, I present you a code, when the app first starts, a rectangle is drawn on the window. When user double clicks somewhere on the window, another rectangle is created. The initial rectangle is always preserved because it is bind to wx.EVT_PAINT event, so that it will be redrawn every time the window is redrawn.
But the second rectangle is not associated to the wx.EVT_PAINT, therefore it is lost when window is redrawn. How do I preserve the second rectangle as well?
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DCLICK, self.on_left_double_click)
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
dc = wx.ClientDC(self)
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test",style=wx.DEFAULT_FRAME_STYLE,size=wx.Size(400, 300))
self.main_panel = MyPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
There is no universal solution to this except handling every drawing operation in a wx.PaintDC. You would do something along the lines of the following:
def __init__(self, parent):
# ...
self.show_yellow_box = False
self.box_pos = None
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
if self.show_yellow_box:
x, y = self.box_pos
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
self.box_pos = (x, y)
self.show_yellow_box = True
self.Refresh() # important, to trigger EVT_PAINT on panel
If the operations in the paint event are more expensive, you probably will end up collecting the expensive drawing operations on the DC in a wx.MemoryDC and blit the bitmap content back onto the panel in the MyPanel.OnPaint.
There is a temporary DC (wx.Overlay/wx.OverlayDC), which is however only useful to apply temporary changes between paint events.

Python Tkinter: Canvas scrolling with MouseWheel

I have created a tree inside a Canvas, and I have also allowed MouseWheel to scroll up and down.
However, how do I prevent scrolling if tree content has not exceed canvas size? (Tree content can possibly exceed canvas size by expanding)
Please run the following code:
from Tkinter import Tk, Frame, BOTH, Canvas
from xml.dom.minidom import parseString
from idlelib.TreeWidget import TreeItem, TreeNode
class DomTreeItem(TreeItem):
def __init__(self, node):
self.node = node
def GetText(self):
node = self.node
if node.nodeType == node.ELEMENT_NODE:
return node.nodeName
elif node.nodeType == node.TEXT_NODE:
return node.nodeValue
def IsExpandable(self):
node = self.node
return node.hasChildNodes()
def GetSubList(self):
parent = self.node
children = parent.childNodes
prelist = [DomTreeItem(node) for node in children]
itemlist = [item for item in prelist if item.GetText().strip()]
return itemlist
data = '''
<top>
<b>
<c>d</c>
<c>e</c>
</b>
<b>
<c><c><c><c><c>f</c></c></c></c></c>
<c><c><c><c><c>f</c></c></c></c></c>
<c><c><c><c><c>f</c></c></c></c></c>
</b>
</top>
'''
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent, background = "white")
parent.configure(bg = "black")
self.pack(fill = BOTH, expand = True, padx = 20, pady = 20)
self.parent = parent
self.parent.geometry('%dx%d+%d+%d' % (700, 700, 0, 0))
self.canvas = Canvas(self, bg = "white", bd = 10, highlightbackground = "black")
self.canvas.grid(column = 0, row = 0, rowspan = 2)
dom = parseString(data)
item = DomTreeItem(dom.documentElement)
node = TreeNode(self.canvas, None, item)
node.update()
node.expand()
self.parent.bind("<MouseWheel>", self.mouse_wheel) # Windows mouse wheel event
self.parent.bind("<Button-4>", self.mouse_wheel) # Linux mouse wheel event (Up)
self.parent.bind("<Button-5>", self.mouse_wheel) # Linux mouse wheel event (Down)
def mouse_wheel(self, event):
""" Mouse wheel as scroll bar """
direction = 0
# respond to Linux or Windows wheel event
if event.num == 5 or event.delta == -120:
direction = 1
if event.num == 4 or event.delta == 120:
direction = -1
self.canvas.yview_scroll(direction, "units")
def main():
root = Tk()
Application(root)
root.mainloop()
if __name__ == '__main__':
main()
You can use the bbox method of canvas to retrieve the actual height of drawn items on canvas. bbox return a tuple defining a rectangle. You can compare it with the height of your canvas widget.
height = self.canvas.winfo_height()
_,_,_,items_height = self.canvas.bbox(Tkinter.ALL)
if (items_height < height):
direction = 0
I'm pretty unfamiliar with Trees, but it looks like it's just a bunch of Labels. You can measure their heights and compare them with the height of the Canvas to determine if you need to scroll. This is a little sloppy, but it worked for me:
if self.canvas.winfo_reqheight() < len(self.canvas.winfo_children()) * self.canvas.winfo_children()[0].winfo_reqheight():
self.canvas.yview_scroll(direction, "units")
else:
pass
EDIT: in case that's too messy, here's the pseudo code:
if CANVAS_HEIGHT < NUMBER_OF_LABELS * LABEL_HEIGHT:
scroll

Categories