我正在写一个小程序,我想首先打开一个主菜单,这个主菜单上的按钮打开一个单独的窗口。为了将这个主菜单和单独的窗口放在一起(它们都是类),我已经制作了第三个容器类,如下所示:
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)。这个方法将是我附加到主菜单的方法。
我可以获得一些关于如何处理此问题的帮助/反馈吗?
答案 0 :(得分:1)
Best way to structure a tkinter application
如果我对你的程序逻辑的理解是正确的 - 你有一个"游戏" 设计,其中class1
就像一个" 主菜单" 以及class2
模仿"游戏" ,这是在我们完成&#34之后开始的;主菜单" 。你使用第三类的想法是合理的,但我发现这种方法有点麻烦。无论如何,让我们首先坚持你想要的布局,然后我们可以尝试类似的东西。另请注意,我将class2
视为tk.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
添加反向引用。在第一种情况下,我们的布局随着时间的推移变得不合逻辑,在第二种情况下,我们的中间人不再是一个容器,而是一个控制器。
当然,所有这些只是我的主观意见,如果你对这种方法足够 - 没有人可以责备你。
实现此代码的完整代码与上面的代码非常相似(所以我决定不显示完整的代码,但是"抽象的部分")。这里的差异反映在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 /平台/网络相关的东西时,这个布局应该留给更复杂的东西。所以这是最后一个(对于这个答案)选项...
相同的逻辑,非常相似的布局,但只有两个类。没有更多话要说,尝试使用代码片段并找到这个和第一个例子之间的差异:
# 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。