Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I get the error _tkinter.TclError: bad window path name ".!button" and i'm not sure why

I decided to try using classes to recreate a game so that I do not have to use global variables but when I try to run the game I'm getting an error.

Traceback (most recent call last):  File "D:\Users\James\Desktop\botmod OOP\index.py", line 203, in <module>    Game.New_Game(Root)  File "D:\Users\James\Desktop\botmod OOP\index.py", line 18, in New_Game    Play_Button = self.Start_Canvas.create_window(300, 325, window = Play_Button)  File "C:\Users\James\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py">, line 2501, in create_window    return self._create('window', args, kw)  File "C:\Users\James\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py">, line 2474, in _create    *(args + self._options(cnf, kw))))
_tkinter.TclError: bad window path name ".!button"

I have looked through this to the best of my ability and cannot workout alone what the error is. Below is my code I can provide all the code if needed just comment. It should create a Tkinter button then apply it to the canvas but it gives me the error above.

def New_Game(self, Root):        
    self.Start_Canvas = Canvas(Root, width = 600, height = 500, bd=0, highlightthickness=0, bg = '#ffffff')
    self.Start_Canvas.pack()

    Title = self.Start_Canvas.create_text(300, 163, text = "BOTMOD", font = ('Cinema Gothic BTN Shadow', 75, 'normal'))

    Gen_PButton = Button(text = "PLAY", width = 10, font=('Arial', 15), command = self.Play_Game)
    Play_Button = self.Start_Canvas.create_window(300, 325, window = Gen_PButton)

    Gen_EButton = Button(text = "EXIT", width = 10, font=('Arial', 15), command = lambda: self.Game_End("EXIT"))
    Exit_Button = self.Start_Canvas.create_window(300, 375, window = Gen_EButton)

Before I used oop, this worked using Global variables and Functions so I cannot find what is causing the issue as the button is defined.

The Example Code is Below

from tkinter import *
from random import choice, shuffle

class Game:
    def __init__(self):
        self.Playing_Game = True
        self.Game_Paused = False
        self.Restart_Game = False
        self.Already_Played = False

    def New_Game(self, Root):        
        self.Start_Canvas = Canvas(Root, width = 600, height = 500, bd=0, highlightthickness=0, bg = '#ffffff')
        self.Start_Canvas.pack()

        Title = self.Start_Canvas.create_text(300, 163, text = "BOTMOD", font = ('Cinema Gothic BTN Shadow', 75, 'normal'))

        Gen_PButton = Button(text = "PLAY", width = 10, font=('Arial', 15), command = self.Play_Game)
        Play_Button = self.Start_Canvas.create_window(300, 325, window = Gen_PButton)

        Gen_EButton = Button(text = "EXIT", width = 10, font=('Arial', 15), command = lambda: self.Game_End("EXIT"))
        Exit_Button = self.Start_Canvas.create_window(300, 375, window = Gen_EButton)

    def Play_Game(self):
        if self.Already_Played == False:
            self.Start_Canvas.destroy()
        self.Menu_Canvas = Canvas(Root, width = 600, height = 500, bd=0, highlightthickness=0, bg = '#C0C0C0')
        self.Menu_Canvas.pack()

    def Game_End(self):
        if self.End_Option == "EXIT":
            self.Playing_Game = False
            Root.destroy()
        else:
            self.Game_Finished = True
            self.Game_Canvas.create_rectangle(120, 120, 480, 300, fill = "#ffffff")
            self.Game_Canvas.create_text(300, 210, text = End_Option, font = ('Abadi', 35, "bold"))
            #Continue_Button = Button(Root, text = 'Continue', command = self.Game_Restart)
            Exit_Button = Button(Root, text = 'Exit', command = lambda: self.Game_End('EXIT'))
            #Continue_Button.pack()
            Exit_Button.pack()

Root = Tk()
Game = Game()

while True:
    while Game.Restart_Game == False:
        if Game.Playing_Game == False:
            break
        else:
            Game_Finished = False
            Root = Tk()
            if Game.Already_Played == True:
                Game.Play_Game()
                Root.mainloop()
            elif Game.Already_Played == False:
                Game.New_Game(Root)
                Root.mainloop()
    break
like image 454
James Malkin Avatar asked Apr 13 '18 22:04

James Malkin


1 Answers

The immediate problem is that you haven't given the buttons a parent. The tl;dr is that you just need to add Root (or some other appropriate parent window) to the constructors for Gen_Pbutton and Gen_EButton, as you do for all of your other widgets.


The following two lines have an important difference:

Gen_PButton = Button(Root, text = "PLAY", width = 10, font=('Arial', 15), command = self.Play_Game)
Gen_PButton = Button(text = "PLAY", width = 10, font=('Arial', 15), command = self.Play_Game)

Both versions create a new name for a button widget (.!button in this case), and create a new tkinter.Button object associated with that name—but the first one also asks Root to create the actual widget named .!button, while the second one doesn't ask anyone to create the widget. So you end up with a Button object that's attached to a non-existent widget, and whenever you try to use that Button object, you get an error like this:

_tkinter.TclError: bad window path name ".!button"

The usual reason for an error like this is that you've destroyed the underlying button widget but keep trying to use the Button. But in this case, you never created the widget in the first place, which obviously leads to the same problem.


To understand exactly what's going on under the covers, you have to understand how Tkinter works—the actual GUI widgets and windows are managed by code in a completely different language, Tcl/Tk, and tkinter is association Python objects with Tcl objects by name and proxying every one of your method calls to those Tcl objects.


You may wonder why tkinter lets you get away with this construction in the first place, instead of giving you a more comprehensible error, one line earlier, something like this:

_tkinter.TclError: trying to create ".!button" with null parent

Well, technically, it's perfectly legal. You might create that Tcl widget later through some lower-level method, or you might have already created that Tcl widget and now just want to wrap a tkinter controller around it. Both are very rare cases, but they're not nonsensical, so tkinter allows for them.

And, more to the point: would you really understand the "better" error message any more easily? Neither one makes sense when you're first learning tkinter, and both are things you can learn to understand and deal with.

like image 181
abarnert Avatar answered Oct 20 '22 12:10

abarnert