Tkinter 应用类从不调用其析构函数

Tkinter app class never calls its destructor

提问人:Arolan 提问时间:6/9/2023 最后编辑:Trenton McKinneyArolan 更新时间:6/9/2023 访问量:73

问:

我在 Python 中制作 Tkinter 类时遇到问题。 我有以下类,由于我添加了图形(initStatistics 方法),我的 Tkinter 应用程序不会停止。

问题是我需要它停止才能“持久化”数据库并保存数据帧。

import tkinter as tk
from tkinter import ttk

import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt
import seaborn as sns
from pandastable import Table

import database as db

sns.set_theme()


class GUI():
    def __init__(self): # Constructor
        print("TRACE : Constructor")
        self.categories = { # true = entrant
            'Choisir' : None,
            'Bourses' : True,
            'Remboursements' : True,
            'Stage' : True,
            'Virement parents' : True,
            'Blablacar' : True,
            'Cours particuliers' : True,
            'Transports' : False,
            'Loisirs' : False,
            'Loyer' : False,
            'Cadeaux' : False,
            'Sorties' : False,
            'Abonnements' : False,
            'Autre' : False,
            'Courses' : False,
        }

        self.root = tk.Tk()
        self.root.title("Tableur de l'espace")
        self.root.state('zoomed') # Pour maximiser à l'ouverture

        # Tabs
        self.tabControl = ttk.Notebook(self.root)
        self.tabHome = ttk.Frame(self.tabControl)
        self.tabControl.add(self.tabHome, text="Home")

        self.tabTable = ttk.Frame(self.tabControl)
        self.tabControl.add(self.tabTable, text="Historique")

        self.tabTransaction = ttk.Frame(self.tabControl)
        self.tabControl.add(self.tabTransaction, text="Nouvelle transaction")
        self.tabControl.pack(expand=1, fill="both")

        # Init Home
        self.th_lab_balance = tk.Label(self.tabHome, text="Solde actuel : ")
        self.th_lab_balance.grid(row=0, column=1, sticky="ew")
        self.th_lab_balance.config(font = ("Courier", 14))

        # Init table of transactions
        self.dfTransactions = db.load().sort_values('date', ascending=False)
        self.table = Table(self.tabTable, dataframe=self.dfTransactions, showtoolbar=True, showstatusbar=True)
        self.table.show()


        self.initStatistics()

        # Launch
        print("TRACE : mainloop")
        self.root.mainloop()
    
    
    def initStatistics(self):
        categoriesEntrant = {k:v for (k,v) in self.categories.items() if v}
        categoriesSortant = {k:v for (k,v) in self.categories.items() if not v}

        dfEntrant = self.dfTransactions.loc[(self.dfTransactions["categorie"].isin(categoriesEntrant) | self.dfTransactions["categorie"].isin(['SoldeInitial']))==True]
        dfSortant = self.dfTransactions.loc[self.dfTransactions["categorie"].isin(categoriesSortant)==True]

        # Current balance
        balance = str(round(dfEntrant['entrant'].sum() - dfSortant['sortant'].sum(), 2)) + "€"
        self.th_lab_balance_nb = tk.Label(self.tabHome, text=balance)
        self.th_lab_balance_nb.grid(row=0, column=2, sticky="ew")
        self.th_lab_balance_nb.config(font = ("Courier", 14))

        # Plots
        dfEntrant = dfEntrant.loc[dfEntrant["categorie"].isin(['SoldeInitial'])==False] # remove soldeInitial
        dfEntrantSommeParCategorie = dfEntrant.groupby('categorie')['entrant'].sum()
        dfSortantSommeParCategorie = dfSortant.groupby('categorie')['sortant'].sum()
        lf = ttk.Labelframe(self.tabHome, text='Plot Area')
        lf.grid(row=0, column=0, sticky="nsew")

        fig, axes = plt.subplots(nrows=2, ncols=1)

        fig1 = dfEntrantSommeParCategorie.plot.pie(y="Entrant", ax=axes[0])
        fig2 = dfSortantSommeParCategorie.plot.pie(y="Sortant", ax=axes[1])
        
        fig1.axis("off")
        fig2.axis("off")

        fig1.set_title("Recettes par catégories")
        fig2.set_title("Dépenses par catégories")
        
        plt.tight_layout()
        self.plot = FigureCanvasTkAgg(fig, master=lf)
        self.plot.get_tk_widget().pack()
    
    def __del__(self) : # Destructor
        print("TRACE : Destructor")
        db.save(self.dfTransactions)

display = GUI()
del display

我试图从我的代码中删除上述方法,然后调用析构函数。不过,我无法弄清楚问题出在哪里。

蟒蛇 熊猫 matplotlib tkinter

评论

0赞 Thingamabobs 6/9/2023
Mainloop 此时会停止您的脚本。用self.root.protocol('WM_DELETE_WINDOW', self.destructor_method)
0赞 acw1668 6/9/2023
关闭窗口时,将执行析构函数方法。我想知道你的问题是什么。请注意,不建议(即使不是一个好的设计)调用内部。mainloop()__init__()

答:

1赞 LiiVion 6/9/2023 #1

您可以使用以下代码示例。正如@Thingamabobs已经建议的那样,您可以在杀死 gui 之前使用 run aWM_DELETE_WINDOWfunction

from tkinter import *


def exit():
    root.destroy()
    # sys.exit()        <-- if you want to stop your whole script, not just the gui

root = Tk()
root.geometry('800x600')
root.protocol('WM_DELETE_WINDOW', exit) # calling the exit function on closing


root.mainloop()

我建议使用 tkinter 类,因为它使处理变得容易得多。下面是一个示例,如何将 Tkinter 与类一起使用。inherited

from tkinter import *
import sys


class GUI(Tk): # inherited from the Tk Class > used as root window
    def __init__(self):
        super().__init__() # Init from Tk Class
        self.geometry('800x600')

        self.destroy_button = Button(self, text='Click to Destroy root', command=self.destroy_button_event)
        self.destroy_button.pack(pady=50)

        self.custom_frame = CustomFrame(self, width=400, height=200, background='black')
        self.custom_frame.pack(expand=True, fill=BOTH, pady=200)

    def destroy_button_event(self):
        self.destroy()
        # sys.exit()        <-- if you want to stop your whole script, not just the gui


class CustomFrame(Frame):
    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)

        self.destroy_frame_button = Button(self, text='Click to Destroy frame', command=self.destroy_frame_button_event)
        self.destroy_frame_button.pack()

    def destroy_frame_button_event(self):
        self.destroy()


if __name__ == '__main__':
    gui = GUI()
    gui.mainloop()

评论

0赞 Arolan 6/10/2023
多谢。这似乎奏效了。我会听从你关于继承:)的建议