繁体   English   中英

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

[英]Tkinter.TopLevel; passing a function from another class?

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

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类窗口。 有没有办法做到这一点? 对于顶层,总是有self.withdraw,我打算在需要时按下按钮后用来删除主菜单。

我在想(请告诉我这听起来是否合理)是让主菜单类具有抽象功能,而“容器”类充当中间人并在其中具有创建和销毁class1对象的方法(主应用程序)调用时。 这样,我便会将此方法附加到主菜单中。

我可以获取有关如何解决此问题的帮助/反馈吗?

简短答案:

构造tkinter应用程序的最佳方法

长答案:

序幕:

如果我对您的程序逻辑的理解是正确的-您具有“游戏”设计,其中class1充当“ 主菜单” ,而class2模仿“游戏” ,则在我们完成“主菜单”之后开始。 您使用第三类的想法是合理的,但是我发现这种方法有点麻烦。 无论如何,让我们先坚持您想要的布局,然后再尝试类似的方法。 另请注意,由于您将class2视为“主应用程序”,因此我会将您的class2视为tk.Tk()

选项:

  • 3类:顶级-主菜单,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()

如您所见-这有点奏效。 但是总的来说,这种布局是薄弱的,以我的拙见,应该避免使用代表“变量桶”的类。 假设现在我们的“雇主”希望主应用程序中的另一个按钮可以再次显示主菜单窗口。 我们可以在Class2内创建Class1实例,也可以向Class3添加反向引用。 在第一种情况下,我们的时间布局变得不合逻辑,而在第二种情况下,我们的中间人不再是容器,而是控制器。

当然,所有这些只是我的主观意见,如果您对此方法足够了解,没有人可以责怪您。

  • 3类:顶级-主菜单,Tk-主应用程序,中间人-控制器:

嘿,这种布局是您真正想要的,因为我们的“中间人”内部有一个可以创建和销毁的方法 (如您所述)。

完整的代码实现与上面的代码非常相似(因此我决定不显示完整的代码,而是“抽象的片段”)。 此处的区别在于对Class3的反向引用(现在是控制器),负责Windows交互的所有代码的重定位到Class3中,实际上,现在没有理由将抽象函数的引用传递给Class1 ,因为它对我们的中间人Class3具有后向引用(我们可以直接从类中提取此函数)。

#   ...
#   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 / Platform / Network相关的内容时,应该保留此布局以用于更复杂的事情。 所以这是最后一个(针对此答案)选项...

  • 2类:顶级-主菜单,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类时,我们试图处理两个单独的窗口,但是让我们自己研究一下)如果您问我一个比第一个更合法的选择。

结论:

您的问题既是客观的(当您询问如何将某项内容传递给班级时)又是主观的(当您询问如何通过当前的结构将某项内容传递给班级时;当您要求反馈时),因此我的回答主要是:基于 (这通常不是答案,因为最终的决定权取决于您)。 您可以在此处找到基于意见的答案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM