【问题标题】:Python - Tkinter and OOPPython - Tkinter 和 OOP
【发布时间】:2021-06-12 18:03:33
【问题描述】:

我正在尝试将我的过程式编程 Python 项目转换为面向对象的编程。 在我的项目中,我使用的是 Tkinter。

程序版本运行良好,但在 OOP 中我得到了

第 2493 行,在 grid_configure 中 self.tk.call( _tkinter.TclError:无法调用“网格”命令:应用程序已被销毁

当我尝试对第一个标签进行网格化时出错。

我的代码:

    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()

错误消息根本没有帮助,所以希望这篇文章也能帮助其他人。

非常感谢您事先的帮助。

【问题讨论】:

  • .mainloop() 方法调用应该在你的类定义之外和你的主代码中
  • 代码的缩进需要修复。

标签: python oop tkinter grid


【解决方案1】:

关于你的代码有很多事情需要考虑,我将一一解决。

  1. 您正在导入所有 tkinter,没有任何别名
  2. 调用你的root“doc1”意味着你认为你会在那个类之外制作一个“doc2”、“doc3”等,这是行不通的。您不能有很多 root 实例。充其量应该是Toplevel,但前提是您打算为每个新文档打开一个新窗口。
  3. 你根本不应该在课堂上调用mainloop。您应该使用模块约定。这样,您可以将该脚本的元素导入另一个脚本,而不必担心该脚本会自动运行。至少,为mainloop 创建一个包装器是愚蠢的。您已经可以调用mainloop,将其粘贴在另一个方法中的目的是什么? (doc1.root.mainloop())
  4. 创建一个方法来进行非常具体的网格放置是不可重复使用的。我认为mixin 使某些功能更可用,但保持它们动态将是更好的方法。没有必要为小部件创建自定义包装器,但是如果您要在所有内容上放置 gridding 方法,那么为什么不制作更好的 gridding 方法并将其自动“混合”到适用的所有内容中呢?只要您能做到这一点,您还可以加入一些其他便利。
  5. Document 之类的东西(即,您可能需要很多或可能经常更改的东西)可能不应该是root。它应该在root 中。它当然不应该将root 作为属性埋在其中。

下面给出了我上面所说的许多例子。这些例子可以而且应该被修饰。例如,BaseWidget 可以包含xrootxyrooty 等的属性... ParentWidget 的设计方式,您可以使用range 作为@ 的第一个参数987654343@ 和 colcfg 在一次通话中进行“批量配置”。它们都返回self,所以它们可以内联使用。

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()

【讨论】:

  • 非常感谢你,我正在学习,这对我帮助很大。
【解决方案2】:

你快到了:你应该在 __init__ 方法中构建类,并且只在设置结束时调用 mainloop

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()

正如预期的那样,这将创建一个带有两个标签的窗口。

干杯!

【讨论】:

  • 这不是一个好的解决方案。 mainloop 应该在主代码中的类之外调用。即使不是这样,start 方法的意义何在?您只是将一个方法包装在另一个方法中。 doc1.root.mainloop() 更有意义,但至少可以说,将 root 埋在某个任意类中并不理想。
  • 当我使用这种模式时,Document实际上是顶级App,而不是任意的类,我同意这里的名称具有误导性。包装mainloop 主要是为了便于阅读:您创建一个应用程序并启动它。尽管如此,我意识到将根保留在应用程序之外可能会更好,正如 Oakley 先生 himself 所证实的那样。考虑到我对 tkinter 的了解 99% 是通过阅读他在 SO 上的答案,我应该阅读更多内容:D
  • @Pietro - 你肯定选择了一个知道他们的东西的人来学习,但他不是唯一一个知道tkinter 里里外外的人。我可以说出很多其他人的名字,但对于那些让我忘记或最近才出现的人来说,这并不公平。我也从@BryanOakley 那里学到了一些东西,但可以说我从阅读文档和个人调查中学到了更多。我与 Bryan 发生了分歧,我们都没有“错”,而且我们说的也不一样。不要将你所知道的一切都与一个人的解释挂钩。
  • 啊,好吧,99% 有点夸张,我还阅读了大量的 effbot.org(当它启动时)和 NMT 文档,这些都是极好的资源。然而,对于 OOP 部分,我发现它们非常缺乏,实际上 github 上的 SO 和随机项目通常是我从中获得灵感的,而且他确实在那里回答了很多。
猜你喜欢
  • 2021-08-17
  • 2020-09-21
  • 2018-06-20
  • 2020-09-22
  • 1970-01-01
  • 2011-02-22
  • 2021-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多