2

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()
Negi Babu
  • 507
  • 4
  • 11
joluga
  • 25
  • 6

3 Answers3

2

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.

fhdrsdg
  • 10,297
  • 2
  • 41
  • 62
0
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()
Negi Babu
  • 507
  • 4
  • 11
  • I already tried your answer, but it seems that `self._window.withdraw()` only removes the window I want instead of the extra window. – joluga Jun 13 '18 at 09:51
  • @joluga Please check the code now and I have made corrections. – Negi Babu Jun 13 '18 at 09:52
  • 1
    Your answer would be better if you explained *why* `super().__init__(master)` is responsible for the second (or actually first) window. Why was it there in the first place, what does it imply, why is `tk.Frame` behind the class name? Wouldn't it be better to keep the inheritance and declare `tk.Tk()` in another place? – fhdrsdg Jun 13 '18 at 11:26
  • @fhdrsdg Sorry. I don't know that. I'm a newbie. I tried commenting each line of code to determine which is responsible for the window and found it was `super().__init__(master)` . If you could explain it please do it for me. :) – Negi Babu Jun 13 '18 at 11:35
  • @fhdrsdg , could you please explain what you mean with, "Wouldn't it be better to keep the inheritance and declare tk.Tk() in another place?". It seems that I can't easily dismiss the inheritance, because it causes some other Problems in my program. – joluga Jun 13 '18 at 12:01
0

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())
Chris Collett
  • 1,074
  • 10
  • 15