Это очень распространенная проблема, с которой мы сталкиваемся, борясь с «таинствами» JavaScript.
Давайте начнем с простой функции JavaScript:
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
Это простой синхронный вызов функции (где каждая строка кода выполняется одна за другой в последовательность), и результат будет таким же, как ожидалось.
Теперь добавим немного завихрения, введя небольшую задержку в нашей функции, чтобы все строки кода не выполнялись последовательно. Таким образом, он будет эмулировать асинхронное поведение функции:
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
Итак, вы идете, эта задержка просто сломала функциональность, которую мы ожидали! Но что именно произошло? Ну, на самом деле это довольно логично, если вы посмотрите на код. функция foo()
после выполнения ничего не возвращает (таким образом, возвращаемое значение равно undefined
), но оно запускает таймер, который выполняет функцию после 1s, чтобы вернуть «wohoo». Но, как вы можете видеть, значение, присвоенное бару, является немедленно возвращенным материалом из foo (), а не что-либо еще, что приходит позже.
Итак, как мы решаем эту проблему?
Давайте попросим нашу функцию для ОБЕЩАНИЯ. Обещание действительно о том, что это означает: это означает, что функция гарантирует, что вы предоставите любой результат, который он получит в будущем. поэтому давайте посмотрим на это в нашей маленькой проблеме выше:
function foo(){
return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
setTimeout ( function(){
// promise is RESOLVED , when exececution reaches this line of code
resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
}, 1000 )
})
}
let bar ;
foo().then( res => {
bar = res;
console.log(bar) // will print 'wohoo'
});
Таким образом, резюме - для решения асинхронных функций, таких как вызовы на основе ajax и т. д., вы можете использовать обещание resolve
значение (которое вы намерены вернуть). Таким образом, короче говоря, вы разрешаете значение вместо возврата в асинхронных функциях.
Один способ состоит в том, чтобы складывать кадры друг на друга, тогда вы можете просто поднять один над другим в порядке укладки. Тот, что сверху, будет тем, который виден. Это лучше всего работает, если все кадры имеют одинаковый размер, но с небольшой работой вы можете заставить его работать с кадрами любого размера.
Примечание: для этого все виджеты для страницы должны есть эта страница (т.е.: self
) или потомок как родитель (или мастер, в зависимости от выбранной вами терминологии).
Вот немного надуманного примера, чтобы показать вам общую концепцию:
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
#import Tkinter as tk # python 2
#import tkFont as tkfont # python 2
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(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 (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Если вы обнаружите, что концепция создания экземпляра в классе запутанна или разные страницы нуждаются в разных аргументах во время построения, вы можете явно вызвать каждый класс отдельно. Цикл служит в основном для иллюстрации того, что каждый класс идентичен.
Например, чтобы создать классы по отдельности, вы можете удалить цикл (for F in (StartPage, ...)
с помощью этого:
self.frames["StartPage"] = StartPage(parent=container, controller=self)
self.frames["PageOne"] = PageOne(parent=container, controller=self)
self.frames["PageTwo"] = PageTwo(parent=container, controller=self)
self.frames["StartPage"].grid(row=0, column=0, sticky="nsew")
self.frames["PageOne"].grid(row=0, column=0, sticky="nsew")
self.frames["PageTwo"].grid(row=0, column=0, sticky="nsew")
С течением времени люди задавали другие вопросы, используя этот код (или онлайн-учебник, который скопировал этот код). Вы можете прочитать ответы на эти вопросы:
Вот еще один простой ответ, но без использования классов.
from tkinter import *
def raise_frame(frame):
frame.tkraise()
root = Tk()
f1 = Frame(root)
f2 = Frame(root)
f3 = Frame(root)
f4 = Frame(root)
for frame in (f1, f2, f3, f4):
frame.grid(row=0, column=0, sticky='news')
Button(f1, text='Go to frame 2', command=lambda:raise_frame(f2)).pack()
Label(f1, text='FRAME 1').pack()
Label(f2, text='FRAME 2').pack()
Button(f2, text='Go to frame 3', command=lambda:raise_frame(f3)).pack()
Label(f3, text='FRAME 3').pack(side='left')
Button(f3, text='Go to frame 4', command=lambda:raise_frame(f4)).pack(side='left')
Label(f4, text='FRAME 4').pack()
Button(f4, text='Goto to frame 1', command=lambda:raise_frame(f1)).pack()
raise_frame(f1)
root.mainloop()
Чтобы переключить кадры в tkinter
, уничтожьте старый кадр, а затем замените его новым фреймом.
Пока Укладка фрейма Брайана Окли - это умное решение, оно сохраняет все Рам активен сразу. У этого есть непреднамеренный побочный эффект, который позволяет пользователям выбирать виджеты из других кадров, нажав Tab.
. Я изменил ответ Брайана, чтобы заменить show_frame()
на switch_frame()
, который уничтожает старый кадр, прежде чем заменять его. Это устраняет необходимость в объекте container
и позволяет использовать любой общий класс Frame
.
# Multi-frame tkinter application v2.2
import tkinter as tk
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = None
self.switch_frame(StartPage)
def switch_frame(self, frame_class):
"""Destroys current frame and replaces it with a new one."""
new_frame = frame_class(self)
if self._frame is not None:
self._frame.destroy()
self._frame = new_frame
self._frame.pack()
class StartPage(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
start_label = tk.Label(self, text="This is the start page")
page_1_button = tk.Button(self, text="Open page one",
command=lambda: master.switch_frame(PageOne))
page_2_button = tk.Button(self, text="Open page two",
command=lambda: master.switch_frame(PageTwo))
start_label.pack(side="top", fill="x", pady=10)
page_1_button.pack()
page_2_button.pack()
class PageOne(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
page_1_label = tk.Label(self, text="This is page one")
start_button = tk.Button(self, text="Return to start page",
command=lambda: master.switch_frame(StartPage))
page_1_label.pack(side="top", fill="x", pady=10)
start_button.pack()
class PageTwo(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
page_2_label = tk.Label(self, text="This is page two")
start_button = tk.Button(self, text="Return to start page",
command=lambda: master.switch_frame(StartPage))
page_2_label.pack(side="top", fill="x", pady=10)
start_button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
switch_frame()
работает, принимая любой объект класса, который реализует Frame
. Затем функция создает новый кадр для замены старого.
_frame
, если он существует, а затем заменяет его новым фреймом. .pack()
, например menubars, не будет изменяться. tkinter.Frame
. v2.2
- Initialize `_frame` as `None`.
- Check if `_frame` is `None` before calling `.destroy()`.
v2.1.1
- Remove type-hinting for backwards compatibility with Python 3.4.
v2.1
- Add type-hinting for `frame_class`.
v2.0
- Remove extraneous `container` frame.
- Application now works with any generic `tkinter.frame` instance.
- Remove `controller` argument from frame classes.
- Frame switching is now done with `master.switch_frame()`.
v1.6
- Check if frame attribute exists before destroying it.
- Use `switch_frame()` to set first frame.
v1.5
- Revert 'Initialize new `_frame` after old `_frame` is destroyed'.
- Initializing the frame before calling `.destroy()` results
in a smoother visual transition.
v1.4
- Pack frames in `switch_frame()`.
- Initialize new `_frame` after old `_frame` is destroyed.
- Remove `new_frame` variable.
v1.3
- Rename `parent` to `master` for consistency with base `Frame` class.
v1.2
- Remove `main()` function.
v1.1
- Rename `frame` to `_frame`.
- Naming implies variable should be private.
- Create new frame before destroying old frame.
v1.0
- Initial version.