Reputation: 1
I'm creating a custom tkinter Frame class for use in a larger project. I've got it in a usable state now, but it's buggy. When called, the class should create a widget that creates checkbuttons above and to the left of a canvas object with an image item on it. 2 issues I'm having with this:
1) When first executed, the code creates the entire widget almost as wanted, except the image on the canvas doesn't appear until a resize event occurs, say I grab a side of the window and resize it.
2) Then ,when a resize event occurs, the image doesn't always resize exactly right. Very often, especially if the resize is done with a quick jerk of the cursor, the canvas can end up larger than the image, or the image can be cut off a bit by the edge of the window. In the bad resize image I linked below, the canvas is the black peaking from beneath the image. Why is the image not matching the canvas in size every time?
Of note, when I use the fullscreen button, the image doesn't resize at all. When I then use the windowed button, the image resizes to the fullscreen size, resulting in a huge image inside the small window. The resize event seems to be executing a step behind, leading me to think I'm misusing the event queue somehow, though I'm not sure that's the source of both my issues...
Any ideas?
import tkinter as tk
from PIL import Image, ImageTk
KATA_CHART = "../assets/kata_chart.png"
HIRA_CHART = "../assets/hira_chart.png"
class KanaChart(tk.Frame):
def __init__(self, master, kana_type, **kwargs):
super().__init__(master, **kwargs)
self.build_chart(kana_type)
def _resize_callback(self, *args):
width = self.canvas.winfo_width()
height = self.canvas.winfo_height()
self.img = self.original.resize((width, height))
self.img = ImageTk.PhotoImage(image=self.img)
self.canvas.itemconfig(self.canvas_img, image=self.img)
def build_chart(self, kana_type):
vowels = "AIUEO"
consonants = " KSTNHMYRWNGZDBP"
let_var_dict = {}
for letter in vowels + consonants:
var = tk.BooleanVar()
let_var_dict[letter] = var
for i in range(6):
self.grid_rowconfigure(i, weight=1)
for i in range(17):
self.grid_columnconfigure(i, weight=1)
# build the checkbuttons
for i in range(5):
letter = vowels[i]
row = i + 1
var = let_var_dict[letter]
b = tk.Checkbutton(self, text=letter, relief=tk.GROOVE, var=var,
command=self._checkb_wrapper("r", row, var))
b.grid(row=row, column=0, sticky="nsew")
for i in range(16):
letter = consonants[i]
column = i + 1
var = let_var_dict[letter]
b = tk.Checkbutton(self, text=letter, relief=tk.GROOVE, var=var,
command=self._checkb_wrapper("r", column, var))
b.grid(row=0, column=column, sticky="nsew")
# build the canvas with the chart on it
if kana_type == "hira":
self.original = Image.open(HIRA_CHART)
elif kana_type == "kata":
self.original = Image.open(KATA_CHART)
self.canvas = tk.Canvas(self, bg="black")
self.canvas.grid(row=1, column=1, rowspan=5, columnspan=16,
sticky="nsew")
self.img = ImageTk.PhotoImage(image=self.original)
self.canvas_img = self.canvas.create_image(0, 0, image=self.img,
anchor=tk.NW)
self._resize_callback(None)
self.bind("<Configure>", self._resize_callback)
root = tk.Tk()
chart = KanaChart(root, "kata")
chart.pack(fill=tk.BOTH, expand=1)
root.mainloop()
I've considered chopping up the image I'm using into smaller squares, so I'd end up with 80 small images that I could just match to each cell in my widget's grid, but I've assumed one large image is more efficient to resize than 80 small images. Don't think that would circumnavigate the issue I'm having now, anyway.
Upvotes: 0
Views: 1537
Reputation: 46688
Since <Configure>
event is bound on self
(the Frame), the canvas is not yet resized when the callback is executed. Adding self.canvas.update()
at the beginning of _resize_callback()
may solve the issue.
It is better to bind <Configure>
event on the canvas instead. Then using width and height attributes of the event object:
def _resize_callback(self, event):
width, height = event.width, event.height
self.img = self.original.resize((width, height))
self.img = ImageTk.PhotoImage(image=self.img)
self.canvas.itemconfig(self.canvas_img, image=self.img)
Update the binding as well:
def build_chart(self, kana_type):
...
#self._resize_callback(None) # no need to call here
self.canvas.bind("<Configure>", self._resize_callback)
BTW, there is issue in the following line of your code in the two for loops inside build_chart()
:
b = tk.Checkbutton(self, text=letter, relief=tk.GROOVE, var=var,
command=self._checkb_wrapper("r", row, var))
...
b = tk.Checkbutton(self, text=letter, relief=tk.GROOVE, var=var,
command=self._checkb_wrapper("r", column, var))
It should be:
b = tk.Checkbutton(self, text=letter, relief=tk.GROOVE, var=var,
command=lambda row=row, var=var: self._checkb_wrapper("r", row, var))
...
b = tk.Checkbutton(self, text=letter, relief=tk.GROOVE, var=var,
command=lambda column=column, var=var: self._checkb_wrapper("r", column, var))
Upvotes: 0