chrisvp chrisvp - 3 months ago 14
Python Question

issue using Singleton pattern with tkinter Photoimage/Tk in python

Wrote the following code to display a chessboard using Tkinter in Python:

import tkinter as tk

class Gui(tk.Tk):
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
cls._instance.__initialized = False
return cls._instance

def __init__(self):
if self.__initialized:
return
self.__initialized = True
super().__init__()

class Color(object):
white =(0xff,0xff,0xff)
black =(0x00,0x00,0x00)

class Tile(tk.PhotoImage):
@staticmethod
def putTile(image, color, width, height, coordinates):
pix = "#%02x%02x%02x" % color
coor = (coordinates[0]*width, coordinates[1]*height)
line = "{" + " ".join([pix]*width) + "}"
image.put(" ".join([line]*height),coor)

class Grid(tk.PhotoImage):
def __init__(self,grid):
super().__init__(width=10*len(grid), height=10*len(grid[0]))
for x in range(len(grid)):
for y in range(len(grid[x])):
Tile.putTile(self,Color.white if grid[x][y]==1 else
Color.black,10,10,(x,y))

class ChessBoard(Grid):
chessboard = 4 * ([4 * [0,1]] + [4 * [1,0]])
def __init__(self):
super().__init__(self.chessboard)


So
Gui()
is implemented as a singleton pattern. Also
tk.Tk.__init__()
is made to be called only once, otherwice I got a window everytime
Gui()
was called.

I would expect the following to display a window with a chessboard:

Case 1:

label = tk.Label(Gui(), image=ChessBoard())
label.pack()
Gui().mainloop()


This creates an empty window without errors or warnings. A print statement shows that method
tilePut
is indeed called.

Only when I add an additional
Gui()
statement in my program, as shown below, everyting works perfectly and a chessboard is printed.

Case 2:

Gui()
label = tk.Label(Gui(), image=ChessBoard())
label.pack()
Gui().mainloop()


So I guess the
image.put
call requires a
Gui()
to exist. Though if I try the following code:

Case 3:

board = ChessBoard()
label = tk.Label(Gui(), image=board)
label.pack()
Gui().mainloop()


I get an error about calling
image.put
too soon. Considering I do not get this same error in case 1, I am surprised case 1 does not work. Can anyone explain why?

Answer

The answer to your questions boils down to two factors:

  1. The root window must exist before you can create an image
  2. You must keep a reference to the image or tkinter will destroy the image data when it does garbage collection.

The proper way to use this code would be to first create an instance of Gui, then create Chessboard and save what is returned in a variable. You can then use these references in the rest of your code.

This is the common way to accomplish that:

root = Gui()
chessboard = ChessBoard()
label = tk.Label(root, image=chessboard)
label.pack()
root.mainloop()

Since you are defining Gui as a singleton, the following will also work. However, I think the use of a singleton adds complexity and makes the code less clear since it looks like you are creating three instances of Gui:

Gui()
chessboard = ChessBoard()
label = tk.Label(Gui(), image=chessboard)
label.pack()
Gui().mainloop()