0

I am trying to transform my procedural-programming Python project to object oriented programming. In my project I am using Tkinter.

The procedural version worked just fine, but in OOP I get the

line 2493, in grid_configure self.tk.call( _tkinter.TclError: can't invoke "grid" command: application has been destroyed

error right when I try to grid the first labels.

My code:

    from tkinter import *
       class Document:
       root = Tk()
       root.geometry("1000x500")
    
       file_name = Label(text="File Name")
       document_title = Label(text="Document Title")

       def gridding(self):
          self.file_name.grid(row=1,column=2)
          self.document_title.grid(row=2,column=2)

       root.mainloop()

   doc1 = Document()
   doc1.gridding()

The error message is not helping at all, so hopefully this post will help others as well.

Many thanks for your help beforehand.

Marius
  • 45
  • 1
  • 7

3 Answers3

2

There are a lot of things to consider about your code, and I will address all of them.

  1. You are importing all of tkinter without any alias
  2. Calling your root "doc1" implies that you think you will be making a "doc2", "doc3", etc.. out of that class, and that is not going to work. You can't have many root instances. At best, that should be Toplevel, but only if you intend to open a new window for every new document.
  3. You shouldn't be calling mainloop inside the class, at all. You should be using module conventions. This way, you can import elements of this script into another script without worrying about this script running things automatically. At the very least, creating a wrapper for mainloop is silly. You can already call mainloop, what is the purpose of sticking it inside another method? (doc1.root.mainloop())
  4. Creating a method to do very specific grid placement isn't reusable. I would argue that a mixin that makes certain features more usable, but keeps them dynamic would be a better approach. It isn't necessary to create a custom wrapper for widgets, but if you are going to put a gridding method on everything then why not make a better gridding method and have it automatically "mixed in" to everything that applies? And as long as you go that far you might as well include some other conveniences, as well.
  5. Something like Document (ie, something where you may need many or may change often) likely shouldn't be the root. It should be IN the root. It certainly shouldn't have the root buried in it as a property.

Below gives numerous examples of the things I stated above. Those examples can and should be embellished. For instance, BaseWidget could include properties for x, rootx, y, rooty etc... The way ParentWidget is designed, you can use a range as the first argument of rowcfg and colcfg to do "mass configuring" in one call. They both return self so, they can be used inline.

import tkinter as tk
from typing import Iterable


class BaseWidget:
    @property
    def width(self) -> int:
        self.update_idletasks()
        return self.winfo_width()
        
    @property
    def height(self) -> int:
        self.update_idletasks()
        return self.winfo_height()
    
    def grid_(self, r=None, c=None, rs=1, cs=1, s='nswe', **kwargs):
        #this allows keyword shorthand, as well as original keywords
        #while also allowing you to rearrange the above arguments 
        #so you know the exact order they appear and don't have to use the keyword, at all
        self.grid(**{'row':r, 'column':c, 'rowspan':rs, 'columnspan':cs, 'sticky':s, **kwargs})
        #return self so this can be used inline
        return self
        
        
class ParentWidget:
    @property
    def descendants(self):
        return self.winfo_children()
    
    #inline and ranged grid_rowconfigure
    def rowcfg(self, index, **options):
        index = index if isinstance(index, Iterable) else [index]
        for i in index:
            self.grid_rowconfigure(i, **options)
        #so this can be used inline
        return self
        
    #inline and ranged grid_columnconfigure
    def colcfg(self, index, **options):
        index = index if isinstance(index, Iterable) else [index]
        for i in index:
            self.grid_columnconfigure(i, **options)
        #so this can be used inline
        return self


class Custom_Label(tk.Label, BaseWidget):
    @property
    def text(self) -> str:
        return self['text']
        
    @text.setter
    def text(self, value:str):
        self['text'] = value
    
    def __init__(self, master, **kwargs):
        tk.Label.__init__(self, master, **kwargs)
        

class Document(tk.Frame, ParentWidget, BaseWidget):
    def __init__(self, master, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)
        
        #the rest of this class is an example based on what little code you posted
        #the results are not meant to be ideal. It's a demo of usage ... a gist
        self.file_name = Custom_Label(self, text='File Name').grid_(1,2)
        self.doc_title = Custom_Label(self, text='Document Title').grid_(2,2)
        
        #possible
        #r = range(len(self.descendants))
        #self.colcfg(r, weight=1).rowcfg(r, weight=1)

        self.colcfg(2, weight=1).rowcfg([1,2], weight=1)
        

class Root(tk.Tk, ParentWidget):
   def __init__(self, title, width, height, x, y, **kwargs):
       tk.Tk.__init__(self)
       self.configure(**kwargs)
       self.title(title)
       self.geometry(f'{width}x{height}+{x}+{y}')
       
       self.rowcfg(0, weight=1).colcfg(0, weight=1)
       
       doc1 = Document(self).grid_()

if __name__ == '__main__':
    Root('MyApplication', 800, 600, 200, 200, bg='#000000').mainloop()
OneMadGypsy
  • 4,640
  • 3
  • 10
  • 26
1

You are almost there: you should build the class in the __init__ method, and only call mainloop at the end of the setup:

from tkinter import Tk
from tkinter import Label


class Document:
    def __init__(self):
        self.root = Tk()
        self.root.geometry("1000x500")

        self.file_name = Label(text="File Name")
        self.document_title = Label(text="Document Title")

        self.gridding()

    def gridding(self):
        self.file_name.grid(row=1, column=2)
        self.document_title.grid(row=2, column=2)

    def start(self):
        self.root.mainloop()


doc1 = Document()
doc1.start()

This creates a window with two labels, as expected.

Cheers!

Pietro
  • 1,090
  • 2
  • 9
  • 15
  • This is not a good solution. `mainloop` should be called outside of the class in the main code. Even if that wasn't the case, what is the point of the `start` method? You're just wrapping a method in another method. `doc1.root.mainloop()` makes more sense, but burying the `root` in some arbitrary class is not ideal, to say the least. – OneMadGypsy Mar 15 '21 at 17:39
  • When I use this pattern, `Document` is actually the top level `App`, not an arbitrary class, I agree that the name here is misleading. Wrapping `mainloop` is mostly for ease of reading: you create an app and you start it. Still, I realize that keeping the root outside the app is probably better, as confirmed by Mr Oakley [himself](https://stackoverflow.com/a/17470842/2237151). Considering that 99% of what I know of tkinter is by reading his answers on SO, I should have read more :D – Pietro Mar 15 '21 at 17:59
  • @Pietro - You definitely picked someone that knows their stuff to learn from, but he isn't the only one here that knows `tkinter` inside and out. I could name quite a few others, but it wouldn't the fair to the ones that have slipped my mind or only recently showed up. I have also learned a fair bit from @BryanOakley, but I have arguably learned substantially more from reading the docs and personal investigation. I've had disagreements with Bryan where neither of us were "wrong", and we weren't saying the same thing. Don't peg everything you know to one persons interpretation. – OneMadGypsy Mar 15 '21 at 18:26
  • Ah ok, 99% was a bit of an hyperbole, I have also read a good chunk of effbot.org (when it was up) and [NMT](https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/index.html) documentation, that are excellent resources. However for the OOP part I found them very lacking, and indeed SO and random projects on github was usually what I got inspiration from and well, he does answer a lot there. – Pietro Mar 15 '21 at 18:36
0

line 2493, in grid_configure self.tk.call( _tkinter.TclError: can't invoke "grid" command: application has been destroyed.

error right when I try to grid the first labels.

Move mainloop at bottom.

:
:
doc1 = Document()
doc1.gridding()
mainloop()

Screenshot:

enter image description here

toyota Supra
  • 3,181
  • 4
  • 15
  • 19