Why tkinter canvas doesn't update when used in a class - python

I am writing a small program, where I want to draw something on canvas. This code works for me;
import tkinter as tk
from PIL import Image, ImageTk
from l_systems import Lindenmayer
if __name__ == "__main__":
root = tk.Tk()
root.title("Draw Shapes with L-Equations")
cv = tk.Canvas(width=600, height=600, bg='white')
cv.pack()
image1 = Image.new("RGB", (600, 600), (255,255,255))
koch = Lindenmayer(image1)
koch.init(
iterations = 6,
angle = 25,
axiom = "---X",
rules = {"X":"2F-[1[X]+3X]4+F[3+FX]-X", "F":"FF"},
constants = "X") # This creates a drawing on PIL image
# Canvas.create_image expects a PhotoImage
photo = ImageTk.PhotoImage(image1)
cv.create_image((300,300), image=photo)
root.mainloop()
However, I want to organize my tkinter application as a class, therefore I have tried this code,
class main(tk.Frame):
w = 600
h = 600
def __init__(self,parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.cv = tk.Canvas(width=self.w, height=self.h, bg='white')
self.cv.pack()
self.render_image()
def render_image(self):
image1 = Image.new("RGB", (self.w, self.h), (255,255,255))
koch = Lindenmayer(image1)
koch.init(
iterations = 6,
angle = 25,
axiom = "---X",
rules = {"X":"2F-[1[X]+3X]4+F[3+FX]-X", "F":"FF"},
constants = "X"
)
photo = ImageTk.PhotoImage(image1)
self.cv.create_image((self.w/2,self.h/2), image=photo)
if __name__ == "__main__":
root = tk.Tk()
root.title("Draw Shapes with L-Equations")
app = main(root).pack()
root.mainloop()
In this second case, I don't see any drawing on canvas. It is just a white background. How can I fix this?

PhotoImage can have problem in classes and functions. Garbage collector can remove it from memory.
EDIT:
I could check this (because I have to Lindenmayer module)
but your class could look this:
Almost everything is in class.
Class names should normally use the CapWords convention. - see PEP 8 -- Style Guide for Python Code. Event SO use that rule to recognize classes in code and use light blue color.
import tkinter as tk
from PIL import Image, ImageTk
from l_systems import Lindenmayer
class Main(tk.Frame):
def __init__(self,parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.w = 600
self.h = 600
self.parent = parent
self.parent.title("Draw Shapes with L-Equations")
self.cv = tk.Canvas(width=self.w, height=self.h, bg='white')
self.cv.pack()
self.render_image()
self.parent.pack()
def render_image(self):
image1 = Image.new("RGB", (self.w, self.h), (255,255,255))
koch = Lindenmayer(image1)
koch.init(
iterations = 6,
angle = 25,
axiom = "---X",
rules = {"X":"2F-[1[X]+3X]4+F[3+FX]-X", "F":"FF"},
constants = "X"
)
self.photo = ImageTk.PhotoImage(image1)
self.cv.create_image((self.w/2,self.h/2), image=self.photo)
def run(self):
self.parent.mainloop()
if __name__ == "__main__":
Main(tk.Tk()).run()

Related

Create an image in a canvas inside a class [duplicate]

This question already has answers here:
Why does Tkinter image not show up if created in a function?
(5 answers)
Closed 1 year ago.
I wanted to create a chess program using OOP. So I made a superclass Pieces, a subclass Bishop, and a UI class GameUI. I created a canvas in the class GameUI. I wanted, that when I instantiate an object bishop in the class GameUI, it shows an Image from a bishop, on the canvas.
The problem is, when I instantiate the Bishop, I don't see any image. So I tried to do the same with a text : instead of using the method create_image from the class Canvas, I used the method create_text, and it worked : I saw a text on the canvas. That means, the problem comes from the method create_image, and I don't understand it.
If I create an Image directly in the class GameUi, it works! but that's not what I want...
So I don't have any error message. I see the canvas (with a blue background), but no image on it.
Here's the code :
from tkinter import PhotoImage, Tk, Canvas
class Pieces:
def __init__(self, can, color, x_position, y_position):
self.color = color
self.x_position = x_position
self.y_position = y_position
class Bishop(Pieces):
def __init__(self, can, color, x_position, y_position):
super().__init__(can, color, x_position, y_position)
if color == "black":
icon_path = 'black_bishop.png'
elif color == "white":
icon_path = 'white_bishop.png'
icon = PhotoImage(file=icon_path) # doesn't see the image
can.create_image(x, y, image=icon)
class GameUI:
def __init__(self):
self.windows = Tk()
self.windows.title("My chess game")
self.windows.geometry("1080x720")
self.windows.minsize(300, 420)
self.can = Canvas(self.windows, width=1000, height=600, bg='skyblue')
icon = PhotoImage(file=icon_path) # here I create the image in this class, and
can.create_image(x, y, image=icon) # we can see it very well
self.bishop = Bishop(self.can, "black", 50, 50)
self.can.pack()
self.windows.mainloop()
app = GameUI()
To make your code work, I decided to sort of rewrite it based on this answer. It works now, but really the only thing that you needed to add was self.icon instead of icon. icon gets garbage collected since there is no further reference to it, while self.icon remains. Also, it's not entirely the same as yours was, so it probably needs a bit of rewriting too.
from tkinter import *
from random import randint
class Piece:
def __init__(self, canvas, x1, y1):
self.x1 = x1
self.y1 = y1
self.canvas = canvas
class Bishop(Piece):
def __init__(self, canvas, x1, y1, color):
super().__init__(canvas, x1, y1)
if color == "black":
icon_path = 'black_bishop.png'
elif color == "white":
icon_path = 'white_bishop.png'
self.icon = PhotoImage(file=icon_path)
self.ball = canvas.create_image(self.x1, self.y1, image=self.icon)
def move_piece(self):
deltax = randint(0,5)
deltay = randint(0,5)
self.canvas.move(self.ball, deltax, deltay)
self.canvas.after(50, self.move_piece)
class GameUI:
def __init__(self):
# initialize root Window and canvas
root = Tk()
root.title("Chess")
root.resizable(False,False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()
# create two ball objects and animate them
bishop1 = Bishop(canvas, 10, 10, 'white')
bishop2 = Bishop(canvas, 60, 60, 'black')
bishop1.move_piece()
bishop2.move_piece()
root.mainloop()
app = GameUI()

Tkinter Canvas Image Not Displaying Even with Reference saved

So I have this code:
def addTux(self, h,w,g):
global root
cx = h/(2*g)
cy = w/(2*g)
img = Image.open("sprites/tux.png")
img.thumbnail((g,g))
print "The size of the Image is: "
print(img.format, img.size, img.mode)
# img.show()
self.p_sprite = ImageTk.PhotoImage(img)
root.p_sprite = self.p_sprite
self.tux = self.canvas.create_image(cx*g, cy*g, image=self.p_sprite)
c = self.canvas.coords(self.tux)
print c
print c prints coords...
img.show() opens my penguiny thumbnail (when it's not commented out)...
but Tkinter doesn't show squat.
self.canvas.pack() is called later, and I'm saving two references to the PhotoImage just to be double sure it's not garbage collected.
All I get is a white screen.
I'm sure it's a short in the wetware... just not sure where. Any ideas?
All Code below:
app.py:
from Tkinter import *
from PIL import Image, ImageTk
import constants
class Application(Frame):
def addTux(self, h,w,g):
global root
cx = h/(2*g)
cy = w/(2*g)
img = Image.open("sprites/tux.png")
root.img = img
img.thumbnail((g,g))
print "The size of the Image is: "
print(img.format, img.size, img.mode)
img.show()
self.p_sprite = ImageTk.PhotoImage(img)
root.p_sprite = self.p_sprite
self.tux = self.canvas.create_image(cx*g, cy*g, image=self.p_sprite)
self.canvas.pack()
c = self.canvas.coords(self.tux)
print c
def createTiles(self):
h = constants.bounds["y"][1]
w = constants.bounds["x"][1]
g = constants.grid_size
self.grid = []
# for i in range(0,(h/g)):
# # self.grid.append([])
# # print self.grid[i]
# for j in range(0,(w/g)):
# s = self.canvas.create_rectangle(j * g, i*g, (j+1)*g, (i+1)*g, fill="green")
# # self.grid[i].append(s)
self.addTux(h,w,g)
self.canvas.pack()
def createWidgets(self):
global root
self.frame = Frame(root)
self.frame.pack(fill=BOTH, expand=1)
self.canvas=Canvas(self.frame, height=constants.bounds["y"][1]+10, width=constants.bounds["x"][1]+10, background="red")
self.canvas.pack(fill=BOTH, expand=1)
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.createTiles()
print self.p_sprite
root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()
constants.py:
grid_size=40
bounds = {
"x": [0,1000],
"y": [0,720]
}

Tkinter - Images is not display

I am new on Tkinter. I was trying to display two images on my canvas but I couldn't. I tried to achieve this by creating two different files. One will contain all logic behind and the other one will handle the gui. Here is my code so far:
file1.py
from file2 import *
import tkinter as tk
import random
# global variables
w = 'initial'
class start_gui(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self,parent, *args, **kwargs)
# create canvas
self.canvas = tk.Canvas(parent, width=800, height=800, background="green")
self.canvas.pack()
c = Display(self.canvas)
c.current_play(w)
if __name__ == "__main__":
# create main window
root = tk.Tk()
root.geometry("800x800")
start_gui(root)
root.mainloop()
file2.py
import tkinter as tk
from functools import partial
from PIL import ImageTk
from PIL import Image
class Display:
def __init__(self, canv):
self.canvas = canv
def current_play(self, option):
if (option == 'initial'):
self.initial_display()
elif (option == 'n' or option == 's'):
self.ns_display()
def initial_display(self):
# display cat image
self.im = Image.open("cat.gif")
self.photo_image = ImageTk.PhotoImage(self.im)
self.demo = self.canvas.create_image(400, 400, image=self.photo_image, anchor='center')
self.canvas.create_rectangle(50, 25, 150, 75, fill="blue")
self.temp_image = tk.PhotoImage(file="cat.gif")
self.demo2 = self. canvas.create_image(600, 600, image = self.temp_image, anchor='center')
The problem here is that the two image items I created do not show up on the canvas but only the rectangle. Can someone help me with this?
PS: I am using python v 3.4
Another solution: We can make Display inherit from class tk.Canvas
import tkinter as tk
from PIL import ImageTk
from PIL import Image
import random
# global variables
w = 'initial'
class Display(tk.Canvas):
def __init__(self, parent, *args, **kwargs):
tk.Canvas.__init__(self, parent, *args, **kwargs)
def current_play(self, option):
if option == 'initial':
self.initial_display()
elif option == 'n' or option == 's':
self.ns_display()
def initial_display(self):
# display cat image
self.im = Image.open("cat.gif")
self.photo_image = ImageTk.PhotoImage(self.im)
self.demo = self.create_image(400, 400, image=self.photo_image, anchor='center')
self.create_rectangle(50, 25, 150, 75, fill="blue")
self.temp_image = tk.PhotoImage(file="cat.gif")
self.demo2 = self.create_image(600, 600, image = self.temp_image, anchor='center')
class start_gui(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self,parent, *args, **kwargs)
# create canvas
self.canvas = Display(parent, width=800, height=800, background="green")
self.canvas.pack()
self.canvas.current_play(w)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("800x800")
start_gui(root)
root.mainloop()
The problem is one of garbage collection. Your Display object is stored in a local variable inside start_gui.__init__. Once start_gui is constructed, this object is thrown away. The image is an attribute of that object, so it gets garbage-collected. When an image object gets garbage-collected, tkinter is unable to display it.
The simple solution is to keep a permanent reference to Display:
self.display = Display(canvas)
self.display.current_play(w)

Debugging my code

I am new to python and am trying to create an application that displays different information like a clock, current news, notice board etc.
I got the clock to work however I am encountering a few bugs. One is that a smaller window launches when the application does. I thought this was something to do with the self.root = tk.Tk() in the initialisation however doing anything to this line produces errors.
The other bug is that while the background image (0.png) used to fill up the entire screen as it is the same size as my monitor, when I added the clock to the application, the image is shifted to the bottom right of the screen, leaving a small white line to the top and left of the screen. I have tried to fix this by messing with the panel.pack changing it to grid and place however both of this did nothing to the lines. I feel like something is overwriting this line.
None of these bugs are showing up in the console and I don't know what to do. Here is the code I am running:
from tkinter import *
from PIL import ImageTk, Image
import os
import time
import tkinter as tk
class App(Tk):
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="",font=('comic',50,'bold'),bg = '#464545',fg = '#1681BE')
self.label.place(height = 206,width = 487, x = 1384, y = 824)
self.update_clock()
self.root.mainloop()
def update_clock(self):
now = time.strftime('%H:%M:%S')
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
root = Tk()
img = ImageTk.PhotoImage(Image.open("0.png"))
panel = Label(root, image = img)
panel.pack()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
app = App()
root.geometry("%dx%d+0+0" % (w, h))
root.mainloop()
I hope someone can find what's wrong with it because I certainly can't!
Since your App class inherit from Tk, you don't need to create another root window. So I gathered all your code inside the App class. When I use an image the side of my screen, I don't see any line at the top or at the left of the screen, so I hope it will work for you as well.
from PIL import ImageTk, Image
import os
import time
import tkinter as tk
class App(tk.Tk):
def __init__(self):
# call the __init__ method of Tk class to create the main window
tk.Tk.__init__(self)
# background image
img = ImageTk.PhotoImage(Image.open("0.png"))
panel = Label(self, image=img)
panel.pack()
# clock
self.label = tk.Label(self, text="", font=('comic',50,'bold'),
bg='#464545', fg='#1681BE')
self.label.place(height=206, width=487, x=1384, y=824)
self.update_clock()
# window geometry
w, h = self.winfo_screenwidth(), self.winfo_screenheight()
self.geometry("%dx%d+0+0" % (w, h))
self.overrideredirect(True)
self.mainloop()
def update_clock(self):
now = time.strftime('%H:%M:%S')
self.label.configure(text=now)
self.after(1000, self.update_clock)
app = App()

Tkinter canvas doesn't show entire photo image

I have created a short script to display a photo in a Tkinter canvas element. I size the canvas element to have the same size as the photo, but when I launch the window, I generally only see a small portion of the image, no matter how much I expand the window.
Also when I print out the size of the image, which I am using to set the size of the canvas, this size says (922, 614) whereas when I record mouse clicks on the canvas, the lower-right hand corner is at something like (500, 300). My code is below. What should I change so that the canvas is the same size as the image and fully shows the image?
class AppWindow(Frame):
def __init__(self, parent, list_of_files, write_file):
Frame.__init__(self, parent)
self.parent = parent
...
self.image = None
self.canvas = None
self.index = -1
self.loadImage()
self.initUI()
self.resetCanvas()
def initUI(self):
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
...
self.canvas = Tkinter.Canvas(self, width = self.image.width(), height = self.image.height())
def loadImage(self):
self.index += 1
img = cv2.imread(self.list_of_files[self.index])
img = cv2.resize(img, (0,0), fx = IMAGE_RESIZE_FACTOR, fy = IMAGE_RESIZE_FACTOR)
b, g, r = cv2.split(img)
img = cv2.merge((r,g,b))
im = Image.fromarray(img)
self.image = ImageTk.PhotoImage(image=im)
def resetCanvas(self):
self.canvas.create_image(0, 0, image=self.image)
self.canvas.place(x = 0, y = 0, height = self.image.height(), width = self.image.width())
Here is a screenshot showing a photo and how it is presented in the Tkinter canvas:
Here's what I have tried so far:
Not resizing the image or changing the resizing amount - this doesn't do anything
Making the canvas double the size of the image rather than equal to the image, likes so self.canvas = Tkinter.Canvas(self, width = self.image.width()*2, height = self.image.height()*2) This does make the canvas larger (I can tell because I have some drawing functions on the canvas), but the image stays the same small size, not showing the entire image
When I display the cv2 format image with cv2.imshow() I see the full image, so it's not cv2 that is cutting off portions of the image.
I realized what I have above is not a complete working example. I've pared the script down, and now running this I still see the same problem:
import Tkinter
import Image, ImageTk
from Tkinter import Tk, BOTH
from ttk import Frame, Button, Style
import cv2
import os
import time
import itertools
IMAGE_RESIZE_FACTOR = .3
class AppWindow(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.loadImage()
self.initUI()
def loadImage(self):
img = cv2.imread("w_4131.jpg")
img = cv2.resize(img, (0,0), fx = IMAGE_RESIZE_FACTOR, fy = IMAGE_RESIZE_FACTOR)
b, g, r = cv2.split(img)
img = cv2.merge((r,g,b))
im = Image.fromarray(img)
self.image = ImageTk.PhotoImage(image=im)
def initUI(self):
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
print "width and height of image should be ", self.image.width(), self.image.height()
self.canvas = Tkinter.Canvas(self, width = self.image.width(), height = self.image.height())
self.canvas.pack()
self.canvas.create_image(0, 0, image=self.image)
def main():
root = Tk()
root.geometry("250x150+300+300")
app = AppWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
When you place an image on a canvas at 0,0, that specifies the location of the center of the image, not the upper-left corner. The image is all there, it's just that you're only seeing the bottom-right corner.
Add anchor="nw" when creating the image for the coordinates to represent the upper-left corner of the image:
self.canvas.create_image(0, 0, image=self.image, anchor="nw")
As for the canvas not being the same size of the image, I'm not seeing that. The image seems to fit the canvas perfectly.

Categories

Resources