Python 警告用户未保存的更改将丢失

Python 警告用户未保存的更改将丢失,python,tkinter,frame,Python,Tkinter,Frame,我的应用程序由一个大框架(cp2)组成,其中包含两个较小的框架:顶部框架包含允许用户在应用程序的不同屏幕之间切换的按钮,底部框架包含屏幕本身(也包括框架)。我在开始时初始化所有屏幕,然后使用提升方法在它们之间切换 问题是,如果用户单击一个按钮转到另一个屏幕,在当前屏幕上所做的未保存更改将丢失。我想显示一个带有确认消息的警告,但我只看到触发警告的两种方式,我似乎不知道如何实现这两种方式 选项1将向exibirTela方法添加警告:如果用户单击调用此方法的按钮,该方法将在提升另一帧之前将屏幕上的值与

我的应用程序由一个大框架(cp2)组成,其中包含两个较小的框架:顶部框架包含允许用户在应用程序的不同屏幕之间切换的按钮,底部框架包含屏幕本身(也包括框架)。我在开始时初始化所有屏幕,然后使用提升方法在它们之间切换

问题是,如果用户单击一个按钮转到另一个屏幕,在当前屏幕上所做的未保存更改将丢失。我想显示一个带有确认消息的警告,但我只看到触发警告的两种方式,我似乎不知道如何实现这两种方式

选项1将向exibirTela方法添加警告:如果用户单击调用此方法的按钮,该方法将在提升另一帧之前将屏幕上的值与数据库进行比较。问题是,该方法知道哪一帧将被提升(t),但它不知道哪一帧现在位于顶部(用户将要离开的屏幕)

选项2是通过一个单独的方法发出警告,由框架失去顶部位置的事件触发。这个选项的问题是,似乎不存在这样的事件来将方法绑定到它

任何一个都适合我。当然,除非我有理由不使用它们,在这种情况下,请随时告诉我。当然,我们也欢迎任何其他建议

代码如下。对不起,葡萄牙语的名字。我用英语补充了一些评论:希望这有帮助

from tkinter import *
from tkinter.messagebox import *
import sqlite3

class cp2(Frame):
    def __init__(self, *args, **kwargs):
        Frame.__init__(self, *args, **kwargs)
        root.title(string="Controle de Produtos e Pedidos")
        self.columnconfigure(0, weight=1)
        self.rowconfigure(1, weight=1)

# Here I create two frames: one for the buttons, one for the screens

        barraOpcoes = Frame(self, bd=10, bg="yellow")
        barraOpcoes.grid(row=0, column=0, sticky=EW)
        barraOpcoes.columnconfigure(2, weight=1)
        areaPrincipal = Frame(self, bd=10)
        areaPrincipal.grid(row=1, column=0, sticky=NSEW)
        areaPrincipal.columnconfigure(0, weight=1)

# Here I create a set (telasAplicacao) to which I'll add all the screens (frames)

        self.telasAplicacao = {}

        for tela in (telaInicial,
                     telaCrudFabricante, telaCrudRevista,
                     telaInclusaoFabricante, telaInclusaoRevista,
                     telaAlteracaoFabricante, telaAlteracaoRevista):
            novaTela = tela(areaPrincipal, self)
            self.telasAplicacao[tela] = novaTela
            novaTela.grid(row=0, column=0, sticky=NSEW)

# Here I add the buttons that switch between frames

        btInicio = Button(barraOpcoes, text="Início", command=self.exibirTelaInicial)
        btInicio.grid(row=0, column=0, sticky=W)
        btFabricantes = Button(barraOpcoes, text="Fabricantes", command=self.exibirTelaCrudFabricante)
        btFabricantes.grid(row=0, column=1, sticky=W)
        btRevistas = Button(barraOpcoes, text="Revistas", command=self.exibirTelaCrudRevista)
        btRevistas.grid(row=0, column=2, sticky=W)
        btSair = Button(barraOpcoes, text="SAIR", command=lambda: sairCP2(self))
        btSair.grid(row=0, column=3, sticky=E)

# Always start with telaInicial on top: this is the welcome screen

        self.exibirTelaInicial()

    def exibirTela(self, t):
        minhaTela = self.telasAplicacao[t]
        minhaTela.exibirDados(codigo=kwargs["codigo"])
        minhaTela.lift()

    def exibirTelaInicial(self):
        self.exibirTela(telaInicial)

    def exibirTelaCrudFabricante(self):
        self.exibirTela(telaCrudFabricante)

    def exibirTelaCrudRevista(self):
        self.exibirTela(telaCrudRevista)

class telaAlteracaoFabricante(Frame):
    def __init__(self, parent, controller):
# lots of widgets on this frame
# also, all the other methods of this class

# and so on to all the other frames in telasAplicacao...

我想我为你的选择一找到了部分解决方案。有一个名为的方法,根据文档,它是:

返回包含此小部件所有子部件的小部件实例的列表。窗口按从下到上的堆叠顺序返回

现在,我创建了一个示例,展示了这种机制是如何工作的。该示例基于。我添加了一个
Text
小部件,它显示
self.container
的堆叠顺序。为此,我使用了上面提到的方法,它以堆叠顺序返回
self.container
的小部件。堆栈顶部(可见小部件)是这个列表的最后一个元素(如上所述),但我已经使用函数将其显示为第一个元素

import tkinter as tk
from tkinter import ttk


LARGE_FONT= ("Verdana", 12)


class MainWindowController(tk.Tk):

    def __init__(self, *args, **kwargs):        
        tk.Tk.__init__(self, *args, **kwargs)

        self.container = tk.Frame(self)
        self.container.grid_rowconfigure(0, weight=1)
        self.container.grid_columnconfigure(0, weight=1)
        self.container.pack(side="top", fill="both", expand=True)

        self.frames = {}

        for F in (StartPage, PageOne, PageTwo):
            frame = F(self.container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.so = tk.Text(self.container, state="disabled", font=("Arial", 14),
                          width=70, height=12, bd=1, relief="groove")
        self.so.grid(row=1, column=0, sticky="nsew", pady=(60, 0))
        self.update_stack_order()

        self.show_frame(StartPage)

    def update_stack_order(self):
        self.so.config(state="normal")
        self.so.delete("1.0", "end")
        self.so.insert("end", "Stack order of the widgets of self.container (0 means top)\n\n\n")
        for i, child in enumerate(reversed(self.container.winfo_children())):
            self.so.insert("end", str(i) + "\t" + repr(child) + "\n\n")
        self.so.config(state="disabled")

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()
        self.update_stack_order()


class StartPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label = ttk.Label(self, text="START PAGE", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        button = ttk.Button(self, text="Visit Page 1",
                            command=lambda: controller.show_frame(PageOne))
        button.pack()

        button2 = ttk.Button(self, text="Visit Page 2",
                            command=lambda: controller.show_frame(PageTwo))
        button2.pack()


class PageOne(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label = ttk.Label(self, text="PAGE ONE", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        button1 = ttk.Button(self, text="Back to Home",
                            command=lambda: controller.show_frame(StartPage))
        button1.pack()

        button2 = ttk.Button(self, text="Page Two",
                            command=lambda: controller.show_frame(PageTwo))
        button2.pack()


class PageTwo(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label = ttk.Label(self, text="PAGE TWO", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        button1 = ttk.Button(self, text="Back to Home",
                            command=lambda: controller.show_frame(StartPage))
        button1.pack()

        button2 = ttk.Button(self, text="Page One",
                            command=lambda: controller.show_frame(PageOne))
        button2.pack()


app = MainWindowController()
app.mainloop()
在更改帧时,您肯定会注意到
文本
小部件的输出改变了堆栈顺序。(请注意,
文本
小部件从不在顶部)。现在,您知道哪个框架位于容器顶部,您可以继续第一个选项的想法

如果您想了解更多关于tkinter中“提升”和“降低”的工作原理,请查阅Tk的官方文档,其中包含一节专门介绍此主题:


代码不是用Python编写的,因为Tk是一个Tcl库,但这些概念也应用于tkinter。

通过询问用户的所有退出条件!这里的方法:谢谢你的回复,但我的问题不是“X”按钮:我已经用WM_DELETE_窗口协议覆盖了这个按钮,正如你提供的链接所建议的那样。我需要跟踪的事件是帧的提升/降低:当用户通过exibirTela方法从一个帧切换到另一个帧时,我需要在提升下一个帧之前检查当前帧上是否进行了更改。为什么你说切换页面时页面上的所有数据都将丢失?如果不销毁小部件,数据应该仍然存在。我在你的代码中没有看到任何会破坏数据的东西。是的,从技术上讲,数据仍然存在,但是如果用户试图返回到该页面单击“保存”按钮,exibirDados方法将用数据库中当前的值替换它,因此当框架被提升时,任何未保存的更改都将丢失。谢谢!这就是我所需要的:现在我知道哪个帧将要失去“当前屏幕”状态,我可以检查是否有未保存的更改并警告用户。