Почему мое приложение tkinter закрывается без сообщения об ошибке только при втором запуске команды обновления?

#python #matplotlib #tkinter

#python #matplotlib #tkinter

Вопрос:

Я работаю над графическим интерфейсом tkinter для отображения изображений из определенных каталогов. В графическом интерфейсе есть кнопка построения, которая обновляет изображение. В полнофункциональном графическом интерфейсе изображение может быть обновлено с различными изменениями, включая источник изображения, но приведенный ниже пример этого не включает и отображает только идентичное изображение. Функция plot использует фигуру matplotlib и tk Canvas для возврата объекта, который может быть привязан к сетке, а также исходную фигуру для сбора мусора.

При втором вызове функции plot графический интерфейс закрывается без какого-либо сообщения об ошибке. В приведенном ниже примере функция plot используется при инициализации, а затем снова при нажатии кнопки. Тем не менее, я пробовал это с помощью упрощенной функции plot при инициализации (показано ниже в примере), и графический интерфейс закрывается только при втором запуске функции real plot.

 import tkinter as tk
from PIL import Image, ImageTk
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.figure import Figure 
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
                                               NavigationToolbar2Tk)

def plot_image(img_path, root, ratio=2.0):
    '''
    Plots image using plt.imshow and tk.Canvas
    
    Input:
        -img_path: string, filepath to plt.imread-acceptable source
        -root: root Tk object for returned widget
        -ratio: float, aspect ratio (W:H)
    Output:
        -tuple: (tkinter Canvas of plt Figure, plt subplot)
    
    Note: plt subplot garbage collection must be handled on application end
    '''
    # Create image
    img = Image.open(img_path)
    img_arr = np.asarray(img)
    
    # Create plot
    fig = Figure()
    base_size = 2.5
    fig, plot1 = plt.subplots(1, subplot_kw={'aspect': 'auto'},
                    figsize=(ratio*base_size, base_size))
    xleft, xright = plot1.get_xlim()
    ybottom, ytop = plot1.get_ylim()
    plot1.set_aspect(abs((xright-xleft)/(ybottom-ytop))*ratio)
    plot1.imshow(img_arr, rasterized=True, aspect='auto')

    # Return tk.Canvas
    canvas = FigureCanvasTkAgg(fig, master=root)
    canvas.draw()
    canvas = canvas.get_tk_widget()
    return (canvas, fig)

class UI(tk.Frame):
    '''
    Frame for displaying a single image
    '''
    def __init__(self, master, **options):
        tk.Frame.__init__(self, master, **options)
        self.img_path = "image.png"
        self.wgt_img, self.img = plot_image(self.img_path, self)
        self.wgt_img.grid(row=0, column=0)
        btn_load = tk.Button(self, text="Load",
                             command=lambda: self.update_image())
        btn_load.grid(row=1, column=0)

    def update_image(self):
        self.wgt_img.grid_forget()
        plt.close(self.img)
        self.wgt_img, self.img = plot_image(self.img_path, self)
        self.wgt_img.grid(row=0, column=0)

def run():
    root = tk.Tk()
    gui = UI(root)
    gui.pack()
    root.mainloop()

run()
 
 def load_image(img_path, root):
    '''
    Plots image using ImageTk.PhotoImage
    
    Input:
        -img_path: string, filepath to plt.imread-acceptable source
        -root: root Tk object for returned widget

    Output:
        -tuple: (tkinter Label, tkinter PhotoImage)
    '''
    img = Image.open(img_path)
    img = ImageTk.PhotoImage(img)
    return (tk.Label(root, image=img), img)
 

Я пробовал добавлять операторы печати к функциям. Похоже, что он может выйти из функции, но сразу после выхода из нее происходит сбой. На данный момент я в некоторой растерянности. Я думаю, что это как-то связано с использованием Canvas вместо Label, но я не знаю, кроме этого. Есть какие-нибудь идеи?

Комментарии:

1. Просто предположение, но self.wgt_img.grid_forget() на самом деле не уничтожает виджет, а просто удаляет его из макета. Переназначение его на две строки вниз может привести к нечистому GC старого wgt_img .

2. Используйте self.wgt_img.destroy() вместо self.wgt_img.grid_forget()

3. @TorbenKlein это имеет смысл, но, похоже, это не исправило

4. как я уже сказал, просто догадываюсь. Если бы я был уверен, я бы отправил его в качестве ответа… в любом случае, проголосуйте за публикацию решения.

Ответ №1:

Обновить

У меня это работает. Проблема заключалась в том, что холст зависел от существующей фигуры, поэтому, как только фигура была закрыта, холсту нечего было отображать, и он закрылся сам. Я не уверен, почему для этого нет сообщения об ошибке, но, по крайней мере, оно работает сейчас. Отредактированный код:

 def update_image(self):
        self.wgt_img.destroy()
        old_img = self.img
        self.wgt_img, self.img = plot_image(self.img_path, self)
        plt.close(old_img)
        self.wgt_img.grid(row=0, column=0)