Tkinter.TopLevel;从另一个类传递函数?

时间:2017-06-03 02:03:27

标签: python class user-interface tkinter

我正在写一个小程序,我想首先打开一个主菜单,这个主菜单上的按钮打开一个单独的窗口。为了将这个主菜单和单独的窗口放在一起(它们都是类),我已经制作了第三个容器类,如下所示:

class1(tk.TopLevel):
    def __init__(self, arg1):
           #make button with arg1 function as the attached command
class2:
    ....
    def arg1(self):
        #initialize main app 

 class3:
      def __init__(self):
          class2()
          class1(arg1)

我面临的问题是我不希望在按下按钮之前打开窗口2;有没有办法做到这一点?随着toplevel theres总是self.withdraw,我打算用来在按下按钮后删除主菜单。

我在想什么(请告诉我这听起来是否合理)是让主菜单类采用抽象函数,而“容器”类充当中间人并且在其中有一个创建和销毁它的方法调用class1对象(主app)。这个方法将是我附加到主菜单的方法。

我可以获得一些关于如何处理此问题的帮助/反馈吗?

1 个答案:

答案 0 :(得分:1)

简答:

Best way to structure a tkinter application

长答案:

前奏:

如果我对你的程序逻辑的理解是正确的 - 你有一个"游戏" 设计,其中class1就像一个" 主菜单" 以及class2模仿"游戏" ,这是在我们完成&#34之后开始的;主菜单" 。你使用第三类的想法是合理的,但我发现这种方法有点麻烦。无论如何,让我们首先坚持你想要的布局,然后我们可以尝试类似的东西。另请注意,我将class2视为tk.Tk(),因为您将其评论为"主应用程序"。

选项:

  • 3个课程:Toplevel - 主菜单,Tk - 主应用程序,中间人 - 容器

这是你想要的布局。让我们尝试编写简单的代码然后再谈谈它:

#   imports
try:
    import tkinter as tk
    import tkinter.simpledialog as sd
    import tkinter.messagebox as msg
except ImportError:
    import Tkinter as tk
    import tkSimpleDialog as sd
    import tkMessageBox as msg

import random as rnd

#   classes
class Class1(tk.Toplevel):
    def __init__(self, master, arg_function):
        tk.Toplevel.__init__(self, master)
        self.minsize(350, 200)

        #   make button with arg_function as the attached command
        #   lets hold our function first
        self.function_to_execute = arg_function

        #   define widgets
        self.main_frame = tk.Frame(self)
        self.function_label = tk.Label(self, text='Function is %s\n Let\'s try to call it?' % arg_function.__name__)
        self.execute_button = tk.Button(self, text='Execute %s and Quit' % arg_function.__name__,
                                        command=self.execute_and_quit)

        #   pack stuff
        self.main_frame.pack(fill='both', expand=True)
        self.function_label.pack(fill='both', expand=True)
        self.execute_button.pack(fill='x')

        # handle closing
        self.protocol('WM_DELETE_WINDOW', lambda: self.execute_and_quit(False))

    def execute_and_quit(self, execute=True):
        info = None
        if callable(self.function_to_execute) and execute:
            info = self.function_to_execute()
        self.destroy()
        self.master.renew(info)


class Class2(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.minsize(350, 200)

        #   define widgets
        self.main_frame = tk.Frame(self)
        self.user_info_label = tk.Label(self)
        self.quit_button = tk.Button(self, text='Quit', command=self.destroy)

        #   pack stuff
        self.main_frame.pack(fill='both', expand=True)
        self.user_info_label.pack(fill='both', expand=True)
        self.quit_button.pack(fill='x')

        #   let's hide our app on initialisation
        self.withdraw()

    def renew(self, info_to_renew=None):
        self.deiconify()

        if info_to_renew is None or info_to_renew == '':
            self.user_info_label['text'] = 'Your person is unknown'
        else:
            if type(info_to_renew) is str:
                self.user_info_label['text'] = 'Hello, %s! Although there\'s nothing more about you...' % info_to_renew
            elif info_to_renew:
                self.user_info_label['text'] = 'Gosh! You\'re filthy drunkard!'
            else:
                self.user_info_label['text'] = 'Are you building your drink refusal skills?'


class Class3:
    def __init__(self):
        #   hold functions
        self.arg_functions_to_choose_from = [self.ask_for_name, self.ask_for_drunkenness]

        #   hold our windows
        self.main_app = Class2()
        self.main_menu = Class1(self.main_app, self.pick_random_function())
        self.main_app.mainloop()

    def pick_random_function(self):
        return rnd.choice(self.arg_functions_to_choose_from)

    def ask_for_name(self):
        return sd.askstring(title='Please, introduce yourself', prompt='What is your name?', parent=self.main_menu)

    def ask_for_drunkenness(self):
        return msg.askyesno(title='Driving under the influence of alcohol is prohibited in this state',
                            message='Are you drunk?', icon='question', parent=self.main_menu)

#   entry point
entry_point = Class3()

正如你所看到的 - 它有点有效。但总的来说,这种布局很弱,而且在我看来,这是一个代表变量桶的类。应该避免。假设现在我们的雇主"想要主应用中的另一个按钮,它可以再次显示主菜单窗口。我们可以在Class1内创建Class2的实例,也可以向Class3添加反向引用。在第一种情况下,我们的布局随着时间的推移变得不合逻辑,在第二种情况下,我们的中间人不再是一个容器,而是一个控制器。

当然,所有这些只是我的主观意见,如果你对这种方法足够 - 没有人可以责备你。

  • 3个课程:Toplevel - 主菜单,Tk - 主应用程序,中间人 - 控制器:
嘿,这个布局是你真正想要的,因为我们的"中间人" 在其中有一个创建和销毁的方法(正如您所提到的)。

实现此代码的完整代码与上面的代码非常相似(所以我决定不显示完整的代码,但是"抽象的部分")。这里的差异反映在Class3(现在是控制器)的反向引用中,将负责窗口交互的所有代码重新定位到Class3,事实上,现在有了&# 39;没有理由将抽象函数的引用传递给Class1,因为它对我们的中间人有反向引用 - Class3(我们可以直接从类中提取这个函数)。 / p>

#   ...
#   classes
class Class1(tk.Toplevel):
    def __init__(self, master, controller):
        tk.Toplevel.__init__(self, master)
        #   let's keep reference to our middleman
        self.controller = controller
        #   define and pack widgets
        #   ...
    #   all other methods interacts with children/widgets of this window and with controller
    #   ...


class Class2(tk.Tk):
    def __init__(self, controller):
        tk.Tk.__init__(self)
        #   let's keep reference to our middleman
        self.controller = controller
        #   define and pack widgets
        #   ...
    #   all other methods interacts with children/widgets of this window and with controller
    #   ...


class Class3:
    def __init__(self):
        #   hold functions
        self.arg_functions_to_choose_from = [self.ask_for_name, self.ask_for_drunkenness]

        #   hold our windows
        self.main_app = Class2(self)
        self.main_menu = Class1(self.main_app, self)

    #   all other methods interacts with windows, starts mainloop, handles events, etc...
    #   ...

#  ...

这种布局的弱点在于不必要的复杂性和功能的重复。 Tk()课程已经成为任何Tk相关儿童的控制者,并且可以有效地管理自己。我相信当我们尝试控制Tk相关的东西以及(例如)来自一个类的一些Python /平台/网络相关的东西时,这个布局应该留给更复杂的东西。所以这是最后一个(对于这个答案)选项...

  • 2个类:Toplevel - 主菜单,Tk - 主应用程序,容器, 控制器:

相同的逻辑,非常相似的布局,但只有两个类。没有更多话要说,尝试使用代码片段并找到这个和第一个例子之间的差异:

#   imports
#...
#   classes
class Class1(tk.Toplevel):
    def __init__(self, master):
        tk.Toplevel.__init__(self, master)
        self.minsize(350, 200)
        #   make button with arg_function as the attached command
        #   lets hold our function first

        self.function_to_execute = self.master.pick_random_function()

        #   define widgets
        self.main_frame = tk.Frame(self)
        self.function_label = tk.Label(self, text='Function is %s\n Let\'s try to call it?' % self.function_to_execute.__name__)
        self.execute_button = tk.Button(self, text='Execute %s and Quit' % self.function_to_execute.__name__,
                                        command=self.execute_and_quit)

        #   pack stuff
        self.main_frame.pack(fill='both', expand=True)
        self.function_label.pack(fill='both', expand=True)
        self.execute_button.pack(fill='x')

        # handle closing
        self.protocol('WM_DELETE_WINDOW', lambda: self.execute_and_quit(False))

    def execute_and_quit(self, execute=True):
        info = None
        if callable(self.function_to_execute) and execute:
            info = self.function_to_execute()
        self.destroy()
        self.master.renew(info)


class Class2(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.minsize(350, 200)
        self.arg_functions_to_choose_from = [ask_for_name, ask_for_drunkenness]
        #   define widgets
        self.main_frame = tk.Frame(self)
        self.user_info_label = tk.Label(self)
        self.show_menu_button = tk.Button(self, text='Repeat', command=self.show_menu)
        self.quit_button = tk.Button(self, text='Quit', command=self.destroy)

        #   pack stuff
        self.main_frame.pack(fill='both', expand=True)
        self.user_info_label.pack(fill='both', expand=True)
        self.show_menu_button.pack(fill='x')
        self.quit_button.pack(fill='x')

        self.show_menu()

    def renew(self, info_to_renew=None):
        self.deiconify()

        if info_to_renew is None or info_to_renew == '':
            self.user_info_label['text'] = 'Your person is unknown'
        else:
            if type(info_to_renew) is str:
                self.user_info_label['text'] = 'Hello, %s! Although there\'s nothing more about you...' % info_to_renew
            elif info_to_renew:
                self.user_info_label['text'] = 'Gosh! You\'re filthy drunkard!'
            else:
                self.user_info_label['text'] = 'Are you building your drink refusal skills?'

    def show_menu(self):
        self.withdraw()
        menu = Class1(self)

    def pick_random_function(self):
        return rnd.choice(self.arg_functions_to_choose_from)


#   functions
def ask_for_name():
    return sd.askstring(title='Please, introduce yourself', prompt='What is your name?')


def ask_for_drunkenness():
    return msg.askyesno(title='Driving under the influence of alcohol is prohibited in this state',
                        message='Are you drunk?', icon='question')

#   entry point
main_app = Class2()
main_app.mainloop()

它也不是一个理想的解决方案,因为,再次,不必要的复杂性(我们尝试处理两个单独的窗口,当我们的主菜单主应用程序既可以继承tk.Frame课程,又可以留下自我研究的内容,但是,如果你问我,它是比第一个更合法的选择。

结论:

你的问题既客观(当你问如何将某些东西传递给一个班级时)和主观问题(当你问到如何将某些东西传递给一个班级,当前的结构;当你要求反馈时),因此我的回答是基于意见的反对(它甚至不是一般的答案,因为最终的决定是你的)。你可以找到一个不那么基于意见的答案here