简体   繁体   中英

Issues resizing a canvas image in tkinter

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.

No image on canvas

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?

Good resize

Bad resize

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.

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))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM