【问题标题】:Understanding when to use classes vs methods in OOP - switching frames with tkinter了解何时在 OOP 中使用类与方法 - 使用 tkinter 切换帧
【发布时间】:2018-02-08 20:10:18
【问题描述】:

我正在尝试从这里的答案/问题中复制我自己的答案:Switch between two frames in tkinter

正如你从上面的答案中看到的那样,它实例化了 Frame 小部件的多个子类,当我们想要切换页面时,我们单击一个按钮,该类是我们基类中的一个方法。

但是,在“页面”类下创建多个“页面”方法不是更干净且有意义吗?我不确定该相信什么,我很想澄清一下我应该如何处理这个项目,以及为什么使用类或实例化方法会更好?

我已将我的 cmets 添加到下面的代码中,用于我不太理解的行,我希望我能从 StackOverflow 的世界中获得一些知识。

import Tkinter as tk

LARGE_FONT = ("Verdana", 12)

class Main(tk.Tk):

    def __init__(self, *args, **kwargs):
    ########### are we calling the Tk class from tkinter and passing in our 'Main' class?
        tk.Tk.__init__(self, *args, **kwargs)
    # why not self.container?
        container = tk.Frame(self)
    # again, should I be using self.page here instead?
        page = Pages(parent=container, controller=self)

        container.pack(side="top", fill="both", expand=True)

        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        for F in ('page.page_one()', 'page.page_two()'):
        # what exactly does __name__ do? And how can I replicate this with my derived classes instanced methods?
            page_name = F#F.__name__
            frame = page#(parent=container, controller=self)
            self.frames[page_name] = frame
            frame.grid(row=0, column=0, sticky='nsew')

        self.show_frame("page.page_one()")

    def show_frame(self, page_name):
        frame = self.frames[page_name]
        frame.tkraise()

class Pages(tk.Frame):

    # i could just use *args, **kwargs here couldn't I?
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.page_one(controller)

    # here I have my instance methods inside my derived 'Pages' class
    # isn't this much cleaner, having multiple instance methods inside a class?
    # or I should be separating each page into it's own instanced class?
    def page_one(self, controller):
        label = tk.Label(self, text='show_firmware_page', font=LARGE_FONT)
        label.pack(pady=10, padx=10)

        # how can I switch pages using the next button?
        next_btn = tk.Button(self, text='Next', command=lambda: controller.show_frame(self.page_two(controller)))
        quit_btn = tk.Button(self, text='Quit', command=lambda: controller.show_frame())

        quit_btn.pack()
        next_btn.pack()

    def page_two(self, controller):
        label = tk.Label(self, text='second_page', font=LARGE_FONT)
        label.pack(pady=10, padx=10)

        # how can I switch pages using the next button?
        next_btn = tk.Button(self, text='Next', command=lambda: Pages.show_frame("page_one"))
        quit_btn = tk.Button(self, text='Quit', command=lambda: Pages.show_frame())

        quit_btn.pack()
        next_btn.pack()


app = Main()
app.mainloop()

基本上,我目前的工作是尝试在我的类中使用方法来定义我的页面并在它们之间切换。我目前遇到了一些麻烦,看了其他人的答案后,很多人都认为每个类都实例化一个 Frame,我们在其中调用一个方法来在这些实例之间切换。

让我知道你对这个过程的想法,让我快速了解我应该如何处理这个项目。

非常感谢您的帮助 - 我真的很想把这些面向对象的东西记下来。

【问题讨论】:

  • IMO 试图通过使用 tkinter 来学习 OOP 可能不是一个好方法,因为其中的对象经常不像常规的 Python 类和它们的实例那样表现。这是因为它只不过是 Tk GUI 工具包的一个接口,虽然它有类和实例,但它们的行为通常与用纯 Python(或 C++、Java 等)编写时的行为方式不同.
  • @martineau 谢谢。我想我应该对这个问题做的就是把它吸起来,采用多类方法,一旦我完全掌握了 OOP 并尝试实现页面方法而不是类,稍后再重新审视这个项目。我只是不确定哪个是更“pythonic”的方式,如果你愿意的话,可以为这个项目实现类而不是方法,反之亦然。

标签: python python-2.7 class oop methods


【解决方案1】:

理论上,您可以让单个类中的方法按照您想要的方式重新配置 UI,但这可能并不容易。

您当前的代码无法工作,因为您的一种方法没有简单的方法来清理由另一种先前方法完成的工作(例如,通过删除它创建的标签和按钮)。

您链接到的原始答案通过在程序开始时创建所有小部件(不仅在它们即将显示时)来避免该问题。但是,一次只显示一页,因为它们都是已配置为显示在同一位置的框架。顶部的框架隐藏了其他框架并阻止它们的小部件做任何事情。通过在顶部移动不同的框架,您可以在页面之间切换。

如果我们忽略这些小部件显示问题,您可以让您的主类调用您编写的方法:

class Main(tk.Tk):

    def __init__(self, *args, **kwargs):
        container = tk.Frame(self) # I'm not sure a separate container is necessary
        container.pack(side="top", fill="both", expand=True)  # since we're only putting 
        container.grid_rowconfigure(0, weight=1)              # one other frame inside it
        container.grid_columnconfigure(0, weight=1)

        page = Pages(parent=container, controller=self)
        page.grid(row=0, column=0, sticky='nsew') # moved up from below

        self.frames = {}
        for F in (page.page_one, page.page_two): # no quotes or parentheses
            page_name = F.__name__ # the names are the strings "page_one" and "page_two"
            self.frames[page_name] = F

        self.show_frame("page_one")

    def show_frame(self, page_name):
        method = self.frames[page_name]
        method() # call the method we are switching to

class Pages(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

    def page_one(self):
        # do something to clean up previous pages here?
        label = tk.Label(self, text='show_firmware_page', font=LARGE_FONT)
        label.pack(pady=10, padx=10)

        next_btn = tk.Button(self, text='Next',
                             command=lambda: self.controller.show_frame("page_two")
        next_btn.pack()

    def page_two(self):
        label = tk.Label(self, text='second_page', font=LARGE_FONT)
        label.pack(pady=10, padx=10)

        next_btn = tk.Button(self, text='Next',
                             command=lambda: self.controller.show_frame("page_one"))
        next_btn.pack()

这将起作用(对于“工作”的某些定义)。我删除了退出按钮,因为我不确定处理它们的最佳方法是什么(您可能不希望它们调用show_frame)。

请注意,我绝不是 TKinter 的专家,所以当您转到下一个页面时,完全有可能有一些简单的方法可以从上一页中删除小部件。我只是不知道怎么做。

【讨论】:

  • 谢谢你,这很有帮助。我在切换框架/页面时的主要想法是让它们堆叠在一起。我的代码当前的工作方式使其将每个页面相互附加并创建一个更大的窗口,垂直附加页面。我需要将它们堆叠在一起,所以我不需要清理任何小部件。我如何从我链接的原始答案中获取想法并使其成为一个页面高于另一个页面?
  • 嗯,在这个版本中没有什么可堆叠的,因为只有一个 Pages 实例。如果你想堆叠帧,你可能需要几个不同类的实例,就像其他代码一样。
  • 是的,这就是我从一开始就试图实现的目标。我不确定是否应该使用类的实例或实例化方法。我试图用我的方法建立一个框架,但我在这样做时遇到了麻烦。是否有可能使用Pages 类下的方法将帧堆叠在一起,就像我目前已经布置的那样,或者这不是 Pythonic 的方式,而是我应该做其他答案的事情多个班级?
  • 问题是,你要堆叠哪些帧?如果您只创建一个实例,那么堆叠或切换的实例不会超过一个。我完全不清楚为什么要使用方法而不是类。
  • 我为什么要使用方法的原因是我应该在Pages 类下创建不同的对象是有道理的,基本上每个方法都代表一个页面。我是否能够使用这些方法创建框架的多个实例,或者我使用的方法和类是否错误?这就是我坚持使用 OOP 的地方。
猜你喜欢
  • 1970-01-01
  • 2016-03-21
  • 1970-01-01
  • 2016-06-01
  • 1970-01-01
  • 2020-11-22
  • 2020-09-20
  • 1970-01-01
  • 2020-05-10
相关资源
最近更新 更多