tkraise和Python GUI(tkinter)中的一些面向对象编程问题。

huangapple go评论59阅读模式
英文:

tkraise and some OOP issues in python GUI(tkinter)

问题

我是一个对Python基础知识了解甚少的初学者,对Python的基础知识和一些面向对象编程的基础知识只有很少的培训。我试图制作一个可以重定向到多个页面的GUI,我制作了以下GUI:

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as mx
from PIL import Image, ImageTk
import tkinter.scrolledtext as tkst
  
# 初始化平面代码更改功能框架
class PGcodefixer(ttk.Frame):
    # ...(代码已省略)

# 初始化非平面代码更改功能框架(正在进行中)
class NPGcodefixer(ttk.Frame):
    # ...(代码已省略)

# 初始化主页框架
class Homepage(ttk.Frame):
    # ...(代码已省略)

# 初始化App作为一个类
class App(tk.Tk):
    # ...(代码已省略)

    def showframe(self, frame):
        # ...(代码已省略)

# 启动App
if __name__ == "__main__":
    # ...(代码已省略)

对于我困惑的三个主要问题,我会非常感谢你的帮助。

  1. super().__init__(container)这种方式是如何工作的?特别是对于顶部初始化的第一个框架 - 容器是从哪个类继承的?

  2. 为什么代码中的controller和self未被识别(即controller.function根本没有被执行(用print语句检查过))?

  3. 与第2点相关 - 我考虑在最顶部初始化一个混合类来存储tkraise方法(以及将来的其他方法)和框架的字典,但我发现这样做相当奇怪(即它会提升一些框架(特别是它们的背景颜色和按钮颜色),但不会提升它们的按钮或其他小部件,这些小部件与前一个框架的相同)。这是否是实现框架tkraise方法的可能解决方案,我的当前解决方案(就方法论而言)是一个好方法吗?

    再次感谢您的帮助。网上有很多关于使用tkraise方法的解决方案,但我还没有找到一个使用ttk.frame的解决方案,所以我感到很迷茫。我在编程GUI方面是一个初学者(甚至对编程一般也是如此 - 我正在通过试错学习),这是我在tkinter上的第一个重要步骤,所以即使您对我目前正在做的避免错误的代码结构/不良编程实践(例如,任何O(n)错误吗?等等)有任何建议,我也将不胜感激!

编辑:包括几行丢失的代码,显示了tkraise的实现,并将controller分配给controller类。

编辑2:将控制器函数分配给self作为它们是方法。

编辑3:我还意识到在进一步测试时,str(classname)不等于“classname”,因此我更改了标准。然而,在从按钮调用showframe方法时,它根本不被执行,这真的让我困惑不已。为什么会这样?我想使用类型更改方法(如果str(x)在字符串列表中,以避免不必要地创建多个重复类的实例)。我现在对是否我的方法甚至是正确的方法来实现tkraise感到非常迷茫。请帮帮我!!

编辑4:考虑到Roberts先生的建议,将框架的实例添加到列表中。但现在出现了一个AttributeError,指出self.frames不是App的属性,尽管它在第一次初始化App的实例中作为字典进行了初始化。我该怎么做?

编辑5:修复了tkraise的错误实例,self.frames[frame].tkraise(self),并在导入语句中修复了另一个拼写错误。

英文:

I am a beginner at python with very minimal training on the basics of python and a bit of OOP basics. I was trying to make a GUI that can redirect to multiple pages and I made my GUI as follows:

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as mx
from PIL import Image, ImageTk
import tkinter.scrolledtext as tkst
  
# Initialize The Planar code Changing Functionality frame 
class PGcodefixer(ttk.Frame):
    def __init__(self, container, controller):
        super().__init__(container)
        self.config(width = 950, height = 587)
        self.controller = controller
        self.pack_propagate(False)
        self.grid_propagate(False)
        self.errflag = 0
        
        # Initialize Labels
        self.img3 = Image.open(r"C:\some\path\here") 
        self.img32 = self.img3.resize((250,250), Image.Resampling.LANCZOS)
        self.img3final = ImageTk.PhotoImage(self.img32)

        # Initialize styling of ttk widgets
        self.style = ttk.Style(self)
        self.style.theme_use("alt") 
        self.style.configure("Heading.TLabel", font = ("Comic Sans MS", "20", "bold"), justify = "center", compound  = "top", background = "#568BFC", foreground = "white")
        self.style.configure("TLabel", font = ("Comic Sans MS", "15", "bold"), justify = "center", compound  = "top", background = "#568BFC", foreground = "white") 
        self.style.configure("TButton", font = ("Comic Sans MS", "11", "bold"), justify = "center", Expand = True, background = "#FCC756", foreground = "white")
        self.style.configure("TEntry", justify = "center", Expand = True, background = "#FCC756", foreground = "Black")
        self.style.configure("TFrame", background = "#568BFC") # Background color in Hexcode

# Initialize The Nonplanar code Changing Functionality frame (Work in progress)
class NPGcodefixer(ttk.Frame):
    def __init__(self,container, controller):
        super().__init__(container)
        self.controller = controller
        self.config(width = 950, height = 587)
        self.pack_propagate(False)
        self.grid_propagate(False)

        # Initialize Labels
        self.img2 = Image.open(r"C:\some\path\here") 
        self.img22 = self.img2.resize((200,200), Image.Resampling.LANCZOS)
        self.img2final = ImageTk.PhotoImage(self.img22) 

        # Initialize the styling of ttk widgets
        self.style = ttk.Style(self)
        self.style.theme_use("alt") 
        self.style.configure("TLabel", font = ("Comic Sans MS", "16", "bold"), justify = "center", compound  = "top", background = "#DDE805", foreground = "white") 
        self.style.configure("TButton", font = ("Comic Sans MS", "11", "bold"), justify = "center", Expand = True, background = "#F57FF1", foreground = "white")
        self.style.configure("TFrame", background = "#DDE805") 

# Initialize The Homepage Frame
class Homepage(ttk.Frame):
    def __init__(self, container, controller):
        super().__init__(container)
        self.controller = controller
        self.config(width = 950, height = 587)
        self.pack_propagate(False)
        self.grid_propagate(False)
            
        # Initialize Labels (The first two lines correspond to how you are supposed to put an image into tkinter using Pillow)
        self.img1 = Image.open(r"C:\some\path\here")
        self.img12 = self.img1.resize((200,200), Image.Resampling.LANCZOS) 
        self.img1final = ImageTk.PhotoImage(self.img12) 
        self.labelh1 = ttk.Label(self, image = self.img1final, text = "Welcome to the Timesaver!\n Select the function you wish to use").place(relx = 0.315, rely = 0.15)
    
        # Initialize Button
        self.buttonh1 = ttk.Button(self, text = "Planar Gcode Compiler", command = controller.showframe(self = App, frame = "PGcodefixer")).place(relx = 0.2255, rely = 0.65)
        self.buttonh2 = ttk.Button(self, text = "Nonplanar Gcode Compiler", command = controller.showframe(self = App, frame = "NPGcodefixer")).place(relx = 0.55, rely = 0.65)
        self.buttonh3 = ttk.Button(self, text = "About", command = self.AboutMsg).place(relx = 0.85, rely = 0.9)

        # Initialize the styling of ttk Widgets
        self.style = ttk.Style(self)
        self.style.theme_use("alt") # To enable the button color to be changed, standard ttk will not allow it.
        self.style.configure("TLabel", font = ("Comic Sans MS", "16", "bold"), justify = "center", compound  = "top", background = "#FFB6C1", foreground = "white") # Compound method for relative positioning of text and Image
        self.style.configure("TButton", font = ("Comic Sans MS", "11", "bold"), justify = "center", Expand = True, background = "#67EB67", foreground = "white")
        self.style.configure("TFrame", background = "#FFB6C1") # Background color in Hexcode

    # Initialize a popup about Window for those interested to know more
    def AboutMsg(self):
        mx.showinfo(title = 'About', message =
                'Multi-Purpose Timesaver ver1.0\n'
                'Designed by X')   

# 5. Initialize App as a class
class App(tk.Tk):
    def __init__(self):
        super().__init__()

        # Initializing Window
        self.title("IMPT - Internship Multi-Purpose Timesaver") # Window Title
        self.geometry("950x587") # Using Golden Ratio, haha. Just the Window Size
        self.resizable(False, False) 
        H = Homepage(self, App).pack()
        P = PGcodefixer(self,App).pack()
        N = NPGcodefixer(self,App).pack()
        self.frames = {"Homepage": H, "PGCodefixer": P, "NPGcodefixer": N}

    def showframe(self, frame):
        if frame in self.frames.keys():
            print("yes")
            self.frames[frame].tkraise(self) #The self was asked for by vscode as positional argument is missing without it.
        else:
            print("no")
            pass

# 6. Bootstrapping the App
if __name__ == "__main__":
    app = App()
    App.showframe("Homepage")
    app.mainloop() 

I am confused about 3 main things, and would greatly appreciate your help with them.

  1. How does super(init) container work? especially for the first frame initialized at the top - which class is container being inherited from?

  2. Why is the controller and self not recognized in the code(i.e. controller.function is just not executed at all(checked with print statements)

  3. related to 2. - I considered initializing a mixin class at the very top to store the tkraise method (among others in the future) and dictionary of frames, but found that to be rather weird(i.e. it would raise some frames(specifically their background colour and button colours), but not their buttons or other widgets which remained the same as that of the previous frame. Is this a possible solution to implement the frame tkraise method, and is my current solution(in terms of methodology) a good one?

    Once again, thank you for your help. A lot of solutions for the tkraise method online have used tk.frame but I haven't found one that uses ttk.frame and so I was quite lost. I am a beginner in terms of programming GUIs (and even to programming in general - I am trying to learn by trial and error) and this is my first major step on tkinter, and so even if you have any tips to provide in terms of structuring my code/bad programming practices(e.g. bad O(n) anywhere? etc.) to avoid that I am making, I would greatly appreciate them as well!

EDIT: Included a couple of missing lines of code showing the implementation of tkraise, and added assignment of controller to the controller class.

EDIT2: Added assignment of controller functions to self as they are methods.

EDIT3: I also realized upon further testing that str(classname) does not equal "classname" and so Ichanged the criteria. owever, upon calling the showframe method from the buttons, it is just not executed for some reason which really confused me.. why does this happen? I wanted to use the type change method (if str(x) in a list of strings to avoid making multiple duplicate instances of the class needlessly. I am now quite lost as to whether my approach is even the right one here to implement tkraise..Please help!!

EDIT4: Took into account Mr Roberts' suggestion to add the instances of frames into a list. But now an AttributeError pops up that self.frames is not an attribute of App, despite being a dictionary initialized within the instance of App in the first place. How do I proceed?

EDIT 5: Fixed the erroneous instance for tkraise as self.frames[frame].tkraise(self) and fixed another typo in import statement

答案1

得分: 0

  1. super(init) 是如何工作的?

嗯,那不是你的写法。你的写法是 super().__init__。你的第一个类 (PGcodefixer) 继承自 ttk.Frame,所以 super().__init__ 等同于 ttk.Frame.__init__。如果你的类要继承自多个父类,super() 的处理会更复杂,但如果只是从一个基类派生,这个概念相当简单。

  1. 为什么 controller 和 self 没被识别...

你将 controller 作为构造函数的参数,但你没有将这个参数保存在任何地方。如果以后需要使用它,你需要做类似 self.controller = controller 的操作,这样你以后可以引用它。

请记住,self 并没有任何神奇之处。它只是一个参数名字。按照约定,类方法的第一个参数被称为 self,但像所有函数参数一样,这仅在函数的生命周期内有效。与 C++ 中的 this 不同,你可以使用其他名字,但如果这样做,你可能会受到 Python 社区的永恒鄙视。

  1. 用于 tkraise 的 Mixin?

我在这段代码中没有看到 tkraise,所以我不太清楚你在说什么。Mixin 可以是减少重复代码输入的有用方式,但只有在代码真正是 "通用" 的情况下才有用。如果你在添加异常和特殊情况,那么也许这不是一个很好的主意。

英文:
  1. How does super(init) work?

Well, that's not what you have. What you have is super().__init__. Your first class (PGcodefixer) inherits from ttk.Frame, so super().__init__ is the same as typing ttk.Frame.__init__. If your class were to inherit from multiple parent classes, the super() processing is more complicated, but if you just derive from one base, the concept is pretty easy.

  1. Why is the controller and self not recognized...

You have controller as a parameter to your constructors, but you're not saving that parameter anywhere. If you need it later, you need to do something like self.controller = controller so you can refer to it later.

Remember that self is not magic in any way. It's just a parameter name. By convention, the first parameter in a class method is called self, but like all function parameters, that only lasts for the life of the function. Unlike this is C++, you CAN use another name, but you would earn eternal scorn from the Python community.

  1. Mixin for tkraise?

I don't see tkraise in this code, so I'm not sure what you're talking about. Mixins can be a useful way to reduce repetitive typing, but only if the code is truly "universal". If you're adding excepts and special cases, then maybe it isn't such a good idea.

答案2

得分: 0

以下是代码的翻译部分:

class Homepage(tk.Frame):
    def __init__(self, container, controller):
        ...
        # 初始化按钮
        ttk.Button(self, text="Planar Gcode Compiler", command=lambda: controller.showframe("PGcodefixer")).place(relx=0.2255, rely=0.65)
        ttk.Button(self, text="Nonplanar Gcode Compiler", command=lambda: controller.showframe("NPGcodefixer")).place(relx=0.55, rely=0.65)
        ttk.Button(self, text="About", command=self.AboutMsg).place(relx=0.85, rely=0.9)
        ...

    ...

class App(tk.Tk):
    def __init__(self):
        ...
        # 使小部件在(0, 0)处占用所有可用空间
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        H = Homepage(self, self)  # 使用self作为控制器参数
        P = PGcodefixer(self, self)
        N = NPGcodefixer(self, self)
        self.frames = {"Homepage": H, "PGcodefixer": P, "NPGcodefixer": N}

        # 使用grid()将框架叠加在彼此上面
        H.grid(row=0, column=0, sticky="nsew")
        P.grid(row=0, column=0, sticky="nsew")
        N.grid(row=0, column=0, sticky="nsew")

    def showframe(self, frame):
        if frame in self.frames:
            print("yes")
            self.frames[frame].tkraise()  # 移除传递给tkraise()的self参数
        else:
            print("no")

if __name__ == "__main__":
    app = App()
    # 使用实例"app"而不是类名"App"来调用.showframe(...)
    app.showframe("Homepage")
    app.mainloop()
英文:

There are few issues in your code:

class Homepage(tk.Frame):
    def __init__(self, container, controller):
        ...
        # Initialize Button
        self.buttonh1 = ttk.Button(self, text = "Planar Gcode Compiler", command = controller.showframe(self = App, frame = "PGcodefixer")).place(relx = 0.2255, rely = 0.65)
        self.buttonh2 = ttk.Button(self, text = "Nonplanar Gcode Compiler", command = controller.showframe(self = App, frame = "NPGcodefixer")).place(relx = 0.55, rely = 0.65)
        self.buttonh3 = ttk.Button(self, text = "About", command = self.AboutMsg).place(relx = 0.85, rely = 0.9)
        ...

In the above code:

  • command = controller.showframe(...) will execute controller(...) immediately and assign the result (which is None) to command option, so clicking the button later will do nothing. Use command = lambda: controller.showframe(...) instead
  • controller.showframe(self = App, frame = "...") should be controller.showframe("...") instead

class App(tk.Tk):
    def __init__(self):
        ...
        H = Homepage(self, App).pack()
        P = PGcodefixer(self,App).pack()
        N = NPGcodefixer(self,App).pack()
        self.frames = {"Homepage": H, "PGCodefixer": P, "NPGcodefixer": N}
   ...
  • H = Homepage(self, App).pack() will assign the result of .pack() to H, so H will be None. Separate the line into two lines: H = Homepage(...) and H.pack() (actually using .pack() here is not the correct layout function for your case). Same for P and N as well.
  • Using App as the controller is wrong, use self instead

    def showframe(self, frame):
        if frame in self.frames.keys():
            print("yes")
            self.frames[frame].tkraise(self) #The self was asked for by vscode as positional argument is missing without it.
        else:
            print("no")
            pass

In the above code, self.frames[frame].tkraise(self) should be self.frames[frame].tkraise() instead


So the updated code should be as below:

class Homepage(tk.Frame):
    def __init__(self, container, controller):
        ...
        # Initialize Button
        ### use lambda in command option and pass the class name only to showframe()
        ttk.Button(self, text = "Planar Gcode Compiler", command = lambda: controller.showframe("PGcodefixer")).place(relx = 0.2255, rely = 0.65)
        ttk.Button(self, text = "Nonplanar Gcode Compiler", command = lambda: controller.showframe("NPGcodefixer")).place(relx = 0.55, rely = 0.65)
        ttk.Button(self, text = "About", command = self.AboutMsg).place(relx = 0.85, rely = 0.9)
        ...

    ...

# 5. Initialize App as a class
class App(tk.Tk):
    def __init__(self):
        ...

        # make the widget put at (0, 0) occupy all the available space
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        H = Homepage(self, self)  # use self as the controller argument
        P = PGcodefixer(self, self)
        N = NPGcodefixer(self, self)
        self.frames = {"Homepage": H, "PGcodefixer": P, "NPGcodefixer": N}

        # use grid() to overlay frames on top of each other
        H.grid(row=0, column=0, sticky="nsew")
        P.grid(row=0, column=0, sticky="nsew")
        N.grid(row=0, column=0, sticky="nsew")

    def showframe(self, frame):
        if frame in self.frames:   # .keys() is not necessary
            print("yes")
            self.frames[frame].tkraise()  # removed self argument passed to tkraise()
        else:
            print("no")

# 6. Bootstrapping the App
if __name__ == "__main__":
    app = App()
    # use the instance "app" instead of class name "App" to call .showframe(...)
    app.showframe("Homepage")
    app.mainloop()

huangapple
  • 本文由 发表于 2023年6月19日 09:35:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76503163.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定