from Tkinter import *
root=Tk()
def Secondwindow():
firstframe.destroy()
secondframe = Frame(root)
secondframe.pack()
secondcontent = Label(secondframe, text = 'second window content').pack()
def Thirdwindow():
secondframe.destroy()
thirdframe = Frame(root)
thirdframe.pack()
thirdcontent = Label(thirdframe, text = 'third window content').pack()
def Fourthwindow():
thirdframe.destroy()
fourthframe = Frame(root)
fourthframe.pack()
fourthcontent = Label(fourthframe, text = 'fourth window content').pack()
thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
firstframe = Frame(root)
firstframe.pack()
firstcontent = Label(firstframe, text = 'first window content').pack()
firstbutton = Button(firstframe, text = 'Next ->', command = Secondwindow).pack()
root.mainloop()
现在,这完美无缺,但随着我的程序越来越大,越来越复杂,我开始意识到这既不优雅也不易维护。我想简单地在(或多或少)序列中编写每个函数,但是当程序读取对尚未定义的函数的引用时会导致名称错误(似乎程序不应该担心它直到它必须运行该函数,到那时它已经看到了函数定义,但是很好)。
有什么最简单的方法来实现这个功能(从函数中调用函数)而不必在第一个函数定义的中间粘贴下一个函数定义?提前谢谢!
答案 0 :(得分:2)
我取消嵌套函数以查看错误是什么。您遇到的问题是函数尝试访问另一个函数范围内定义的变量。那不行。你要么必须嵌套函数,以便它们的范围重叠,就像你做的那样 - 这很尴尬 - 或者你必须使用全局变量 - 这不是那么尴尬,但仍然很尴尬 - 或者你必须从函数中传递变量名发挥作用。
但是,因为你在这里使用回调 - 这是非常先进的! - 执行第三个选项更复杂。如果你真的想让这个工作,我建议采用面向对象的方法。但坦率地说,我建议从一个比初学程序员更简单的东西开始。
最重要的是你习惯了范围规则。至少,我可以用你的代码来解释。这是对你得到的NameErrors的解释。
def Secondwindow():
firstframe.destroy()
secondframe = Frame(root)
secondframe.pack()
secondcontent = Label(secondframe, text = 'second window content').pack()
secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
def Thirdwindow():
secondframe.destroy()
thirdframe = Frame(root)
thirdframe.pack()
thirdcontent = Label(thirdframe, text = 'third window content').pack()
thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
这两个功能看起来几乎完全相同。但他们没有!原因如下:
def Secondwindow():
firstframe.destroy()
这一行是指firstframe
,它是在全局范围内定义的(即在程序的“最低级别”。这意味着它可以从任何地方访问。所以你在这里可以。
secondframe = Frame(root)
secondframe.pack()
secondcontent = Label(secondframe, text = 'second window content').pack()
secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
这些变量都在Secondwindow
的范围内定义。这意味着他们仅在Secondwindow
内存在。离开Secondwindow
后,它们就不复存在了。这有很好的理由!
def Thirdwindow():
secondframe.destroy()
现在你遇到了问题。这会尝试访问secondframe
,但secondframe
仅在Secondwindow
内定义。所以你得到NameError
。
thirdframe = Frame(root)
thirdframe.pack()
thirdcontent = Label(thirdframe, text = 'third window content').pack()
thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
同样,这些都只在ThirdWindow
范围内定义。
现在,我无法解释为使这项工作需要了解的所有内容,但这是一个基本的提示。您可以通过说
在函数的命名空间内创建一个全局变量global secondframe
secondframe = Frame(root)
通常python假定函数中定义的变量是局部变量,所以你必须告诉它。这就是global secondframe
的作用。现在你真的不应该经常这样做,因为随着全球范围填充越来越多的变量,使用它们变得越来越难。函数创建较小的范围(或在某些上下文中调用的“命名空间”),这样您就不必跟踪所有名称(以确保您不在两个地方使用相同的名称,或者制作其他更为灾难性的错误。)
通常,为了避免创建全局变量,您可以通过调用return secondframe
让每个函数返回它定义的框架。然后,您可以为包含前一帧的每个函数添加一个函数参数,如def Thirdwindow(secondframe)
中所示。但是因为你使用回调来调用Secondwindow
等,这种方法很棘手。下面是一些使用lambda
语句解决问题的代码。
from Tkinter import *
root=Tk()
def Secondwindow(firstframe):
firstframe.destroy()
secondframe = Frame(root)
secondframe.pack()
secondcontent = Label(secondframe, text = 'second window content').pack()
secondbutton = Button(secondframe, text = 'Next ->', command = lambda: Thirdwindow(secondframe)).pack()
def Thirdwindow(secondframe):
secondframe.destroy()
thirdframe = Frame(root)
thirdframe.pack()
thirdcontent = Label(thirdframe, text = 'third window content').pack()
thirdbutton = Button(thirdframe, text = 'Next ->', command = lambda: Fourthwindow(thirdframe)).pack()
def Fourthwindow(thirdframe):
thirdframe.destroy()
fourthframe = Frame(root)
fourthframe.pack()
fourthcontent = Label(fourthframe, text = 'fourth window content').pack()
firstframe = Frame(root)
firstframe.pack()
firstcontent = Label(firstframe, text = 'first window content').pack()
firstbutton = Button(firstframe, text = 'Next ->', command = lambda: Secondwindow(firstframe)).pack()
root.mainloop()
但解决这个问题的最佳方法是使用面向对象的代码。不幸的是,这个主题太复杂了。它只会为已经很长的帖子添加更多的措辞。老实说,我认为你应该花一些时间习惯功能和首先确定范围。
那就是说,我找到了一个摆弄面向对象变化的时刻。这是:
from Tkinter import *
root=Tk()
class FrameRepeater(object):
def __init__(self, start=0, end=4):
self.frame = None
self.number = start
self.end = end
def new_frame(self):
if self.frame:
self.frame.destroy()
self.frame = Frame(root)
self.frame.pack()
self.content = Label(self.frame, text = 'window ' + str(self.number) + ' content')
self.content.pack()
self.button = Button(self.frame, text = 'Next ->', command = self.replace)
self.button.pack()
self.number += 1
def replace(self):
if self.number < self.end:
self.new_frame()
elif self.number >= self.end:
self.content.config(text='Press button again to quit')
self.button.config(command=self.quit)
def quit(self):
self.frame.destroy()
root.destroy()
exit()
FrameRepeater().new_frame()
root.mainloop()
有几点需要注意。首先,在那些读起来像这样的行中,有一个微妙的错误:
thirdcontent = Label(thirdframe, text = 'third window content').pack()
您将None
存储在thirdcontent
中,因为pack()
方法没有返回值。如果您想保留对Label
的引用,则必须先保存引用,然后单独保存pack()
,就像我在上面new_frame
中所做的那样。
其次,正如您从我的replace
方法中看到的那样,您实际上不必销毁框架来更改标签或按钮命令的文本!上面仍然会破坏前三帧,以显示它是如何工作的。
希望这能让你开始!祝你好运。
答案 1 :(得分:1)
您可以为每个函数添加parent
变量,因为这或多或少是递归的唯一动态部分:
def RecursiveWindow(parent):
parent.destroy()
frame = Frame(root)
frame.pack()
framContent = Label(frame, text = 'second window content').pack()
if foo: # This won't go on forever, will it?
RecursiveWindow(self)
看起来您正在使用框架和转发按钮编写应用程序,例如Windows安装程序或幻灯片。
为什么不只有一个主框架对象和文本分开,而不是有多个框架,每个框架只有它们包含的文本不同?我没有使用Tk作为我的GUI,但这就是我的意思(可能有用):
from Tkinter import *
slides = ['Text one', 'Text two', 'Text three', 'cow']
number = 0
root = Tk()
frame = Frame(root).pack()
button = Button(frame, text = 'Next ->', command = NextFrame).pack()
def NextFrame(number):
frameContent = Label(frame, text = slides[number]).pack()
number += 1
答案 2 :(得分:1)
如果您可以复制和粘贴代码,则可以将其分解:
from Tkinter import *
root=Tk()
messages = ['first window content', 'second window content', 'third window content', 'fourth window content' ]
def nextframe(current, messages):
# what happens when you click the button
def command():
current.destroy()
makeframe(messages)
return command
def makeframe(messages):
frame = Frame(root)
frame.pack()
# take the first message
next_content = Label(frame, text=messages.pop(0)).pack()
if messages: # if there are more make the button
next_button = Button(frame, text = 'Next ->', command = nextframe(frame, messages)).pack()
makeframe(messages)
root.mainloop()