joluga
joluga

Reputation: 35

tkinter opens a second Window when openening a Dialog or creating a window

I have the following problem. I created a gui with Tkinter and when I run it in my IDE (Spyder) everything works perfectly fine, but when I save the file as and want to start it by just executing the .py, everytime a window is created or a dialog opens, a second Tkinter window is poping up. Same problem appears when I save the code as .pyw . I posted a short example that lasts in the same Problem.

import tkinter as tk
from tkinter import messagebox


class test_GUI(tk.Frame):
    def __init__(self,master=None):
        super().__init__(master)
        self._initializeWindow()
        self._window.protocol("WM_DELETE_WINDOW", self.__on_closing)
        self._window.mainloop()

    def _initializeWindow(self):
        self._window=tk.Tk()
        self._window.title("The window I initzialized")

    def __on_closing(self):
        if(messagebox.askokcancel("Quit", "Quit program?")):
            self._window.destroy()
            self._window.quit()        

app=test_GUI()

Upvotes: 3

Views: 3125

Answers (3)

Chris Collett
Chris Collett

Reputation: 1279

For those attempting to unit test your GUI and trying to insert the root tk dependency via dataclasses, you can fix the multi window problem by putting tk initialization in the __post_init__ method:

from dataclasses import dataclass
import tkinter as tk


@dataclass
class App():
    tk_root: tk.Tk = None

    def __post_init__(self):
        if self.tk_root is None:
            self.tk_root = tk.Tk()
# ...

Then, if you utilize tkinter classes (like StringVars) that need a root Tk to be initialized, you'll need to patch tk in your pytest fixture:

import pytest
from unittest.mock import patch, MagicMock

from GUI import App


@pytest.fixture
def app():
    with patch('GUI.tk'):
        return GUI(tk_root=MagicMock())

Upvotes: 0

fhdrsdg
fhdrsdg

Reputation: 10532

You define your class as

class test_GUI(tk.Frame):

so your class inherits from tk.Frame, which means that your class basically is a Frame with extra features.
When you do

super().__init__(master)

You initialize the class from which you are inheriting, which is tk.Frame. At this time, there is no tk.Tk object (and master=None). Because a Frame (or any other tkinter widget) cannot exist without an instance of tk.Tk, tkinter silently makes one for you. This is your first window.
After that you call

self._window = tk.Tk()

to make a tk.Tk instance yourself. This is your second window. Besides that you don't want two windows, you should never have more than one instance of tk.Tk (or more accurately the associated Tcl interpreter) running at the same time because this leads to unexpected behavior.

So how can you fix this?
You basically have two options: remove inheritance or initiate tk.Tk before initiating your class.

Without inheritance your app can be structured like

import tkinter as tk

class test_GUI():
    def __init__(self):
        self._window=tk.Tk()
        self._window.title("The window I initzialized")

        self.button = tk.Button(self._window, text='Test button')
        self.button.pack()

        ...
        self._window.mainloop()

With inheritance you can do it like this

import tkinter as tk

class test_GUI(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)

        self.master = master
        self.master.title("The window I initzialized")

        self.button = tk.Button(self, text='Test button')
        self.button.pack()


root = tk.Tk()
app=test_GUI(root)
app.pack(fill='both', expand=True)
root.mainloop()

Both ways work fine. I personally like the version with inheritance. Also check out Bryan Oakley's post on structuring a tkinter application here.

Upvotes: 3

Negi Babu
Negi Babu

Reputation: 527

def _initializeWindow(self):
        self._window=tk.Tk()
        self._window.title("The window I initzialized")
        self._window.withdraw()

self._window.withdraw() will remove the second window.

super().__init__(master) is actually responsible for the first window. Comment it out. In that case you won't need to withdraw the window you created in _initializeWindow.

import tkinter as tk
from tkinter import messagebox


class test_GUI(tk.Frame):
    def __init__(self,master=None):
        #super().__init__(master)
        self._initializeWindow()
        self._window.protocol("WM_DELETE_WINDOW", self.__on_closing)
        self._window.mainloop()

    def _initializeWindow(self):
        self._window=tk.Tk()
        self._window.title("The window I initzialized")
        #self._window.withdraw()

    def __on_closing(self):
        if(messagebox.askokcancel("Quit", "Quit program?")):
            self._window.destroy()
            self._window.quit()        

app=test_GUI()

Upvotes: 1

Related Questions