Вопрос-Ответ

How to access variables from different classes in tkinter?

Как получить доступ к переменным из разных классов в tkinter?

Я много искал и до сих пор не знаю, как получить доступ к переменным из разных классов в python. В этом случае я хочу получить доступ к переменной self.v из PageOne класса в PageTwo класс.

Вот мой код.

import tkinter as tk
import smtplib

TITLE_FONT = ("Helvetica", 18, "bold")

class SampleApp(tk.Tk):

def __init__(self):
tk.Tk.__init__(self)

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):
frame = F(container, self)
self.frames[F] = frame

frame.grid(row=0, column=0, sticky="nsew")

self.show_frame(StartPage)

def show_frame(self, c):
frame = self.frames[c]
frame.tkraise()

class StartPage(tk.Frame):

def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="PyMail",foreground = "Red", font=("Courier", 30, "bold"))
label.pack(side="top")
sublabel = tk.Label(self, text="Bringing you the\n the easiest way of communication",
font=("Courier", 15))
sublabel.pack()

wallpaper = tk.PhotoImage(file='Python-logo-notext.gif')
img = tk.Label(self, image=wallpaper)
img.image = wallpaper
img.pack(side="top", expand = True)

button1 = tk.Button(self, text="Click Here to Login to your account",fg="red",
command=lambda: controller.show_frame(PageOne))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame(PageTwo))
button2.pack(side="bottom")
button1.pack(side="bottom")

class PageOne(tk.Frame):

def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
label = tk.Label(self, text="Personal Information", font=TITLE_FONT, foreground="blue")
label.pack(side="top", fill="x", pady=10)
global optionv
self.optionv = tk.StringVar()
self.optionv.set("---Select One---")
optionm = tk.OptionMenu(self, self.optionv, "---Select One---", "@gmail.com", "@yahoo.com", "@hotmail.com")

t1 = tk.Label(self, text="Email Account: ")
self.v = tk.StringVar()
self.v.set("")
entry1 = tk.Entry(self, textvariable=self.v)
t2 = tk.Label(self,text="\nPassword: ")
self.pwd = tk.StringVar()
self.pwd.set("")
entry2 = tk.Entry(self, textvariable=self.pwd)
entry2.config(show="*")
lgbutton=tk.Button(self, text="Log In", command=self.login)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))
#final = tk.Label(self, textvariable=self.v)
#finalpwd = tk.Label(self, textvariable=self.pwd)

t1.pack()
entry1.pack()
optionm.pack()
t2.pack()
entry2.pack()
#final.pack()
#finalpwd.pack()
lgbutton.pack()
button.pack(side="bottom")

def login(self):
value = tk.Label(self, text="Invalid username / password", font=("Courier", 15, "bold"), foreground="red")
success = tk.Label(self, text="Login was Successful \n (Click ""Continue"" to compose email)", font=("Courier", 15, "bold"), foreground="blue")
cbutton = tk.Button(self, text="Continue", command=lambda: self.controller.show_frame(PageTwo))
status = tk.Label(self, text="Please select your email domain", foreground="red")
if self.optionv.get() == "@gmail.com":
try:
global server
server = smtplib.SMTP("smtp.gmail.com", 587)
server.ehlo()
server.starttls()
server.login(self.v.get()+self.optionv.get(), self.pwd.get())
success.pack()
cbutton.pack(side="bottom")
except:
value.pack()
elif self.optionv.get() == "@yahoo.com":
try:
server = smtplib.SMTP("smtp.yahoo.com", 587)
server.ehlo()
server.starttls()
server.login(self.v.get()+self.optionv.get(), self.pwd.get())
success.pack()
cbutton.pack(side="bottom")
except:
value.pack()

elif self.optionv.get() == "@hotmail.com":
try:
server = smtplib.SMTP("smtp.live.com", 587)
server.ehlo()
server.starttls()
server.login(self.v.get()+self.optionv.get(), self.pwd.get())
success.pack()
cbutton.pack(side="bottom")
except:
value.pack()
else:
status.pack()

class PageTwo(tk.Frame):

def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Compose Mail", font=TITLE_FONT, foreground="green")
label.pack(side="top", fill="x", pady=10)

self.reciever = tk.StringVar()
self.reciever.set("")
senderl = tk.Label(self, text="Send to: ")
rmail = tk.Entry(self, textvariable=self.reciever)

self.senderoption = tk.StringVar()
self.senderoption.set("---Select One---")
senderdomain = tk.OptionMenu(self, self.senderoption, "---Select One---", "@gmail.com", "@hotmail.com", "@yahoo.com")

self.mail = tk.StringVar()
self.mail.set("")
self.textw = tk.Entry(self, textvariable=self.mail)

button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))

sendbutton = tk.Button(self, text = "Send Mail", command=self.sendmail)

senderl.pack(side="top", anchor="w")
rmail.pack(side="top", anchor="nw")
senderdomain.pack(side="top", anchor="nw")
self.textw.pack(fill="both")
button.pack(side="bottom")
sendbutton.pack(side="bottom")

def sendmail(self):
sent = tk.Label(self, text="Email has been sent")
if self.senderoption.get() == "@gmail.com":
try:
server.sendmail(self.v.get()+self.optionv.get(), self.reciever.get()+self.senderoption.get(), "YES")
print("Success")
sent.pack()
except:
print("Unsuccesful")
print(PageOne.self.v.get())

if __name__ == "__main__":
app = SampleApp()
app.title("PyMail")
app.geometry("400x400")
app.mainloop()
Переведено автоматически
Ответ 1

По сути, на ваш вопрос есть простой ответ. "Как мне получить значение от объекта X?" Ответ одинаков для любого объекта: вы получаете его, запрашивая объект X. Все, что вам нужно для того, чтобы сделать это, это получить ссылку на объект, а затем получить прямой доступ к атрибуту.

Доступ к данным с других страниц

В вашем случае коду в PageTwo нужна ссылка на PageOne, чтобы вы могли получить v переменную.

Итак, как вы получаете ссылку? Код (который вы скопировали либо из руководства, либо из ответа stackoverflow, из которого скопирован учебник) был разработан, чтобы упростить это. Каждой странице дается ссылка на контроллер, и у этого контроллера есть ссылка на каждую страницу. Поэтому вы можете попросить контроллер предоставить вам ссылку на страницу.

Первый шаг - сохранить ссылку на контроллер в каждом классе. Интересно, что вы уже делаете это в PageOne, но вы должны делать это на всех страницах. Убедитесь, что вы добавляете self.controller = controller в каждый __init__ метод, вот так:

class PageTwo(tk.Frame):
def __init__(self, parent, controller):
...
self.controller=controller
...

Далее нам нужно добавить метод в класс контроллера, который будет возвращать ссылку на страницу. Добавьте следующую функцию в SampleApp:

class SampleApp(tk.Tk):
...
def get_page(self, page_class):
return self.frames[page_class]
...

Теперь изнутри любой "страницы" вы можете получить доступ к объекту для любой другой "страницы". Например, в PageTwo вы можете получить доступ к v переменной из PageOne вот так:

page1 = self.controller.get_page(PageOne)
page1.v.set("Hello, world")

Использование общих данных

Еще лучшим решением для вашего SampleApp класса является создание единого набора переменных, общего для всех страниц. Вы можете создать словарь в этом классе, а затем использовать контроллер для предоставления доступа к каждой странице. Например:

class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.shared_data = {
"username": tk.StringVar(),
"password": tk.StringVar(),
...
)

Затем из любого класса вы можете получить доступ к данным следующим образом:

entry1 = tk.Entry(self, textvariable=self.controller.shared_data["username"])
...
username = self.controller.shared_data["username"].get()

Причина, по которой это лучшее решение, заключается в том, что вашим страницам не обязательно знать, как реализованы другие страницы. Когда страница полагается на точную реализацию другой страницы, это называется тесная связь. Если страницам не нужно знать, как реализованы другие страницы, это называется слабой связью.

Слабая связь дает вам больше гибкости. Вместо того, чтобы каждая страница была тесно связана с любой другой страницей, все они тесно связаны с одним объектом: контроллером. Пока каждая страница знает только о контроллере, каждую страницу можно изменять в любое время, не затрагивая остальную часть программы.

Конечно, при смене контроллера вам придется менять все страницы, но если вы хорошо поработаете над дизайном контроллера, вероятность возникновения этого будет меньше, и управлять им будет проще, когда это произойдет.

Ответ 2

Это связано с глобальным фреймом.

Если вы создадите переменную внутри класса, она будет существовать только внутри этой функции. Если вы хотите "перенести" переменную внутри класса (или функции, если уж на то пошло) в глобальный фрейм, вы используете global.

class firstClass():
global my_var_first
my_var_first = "first variable"

print(my_var_first) # This will work, because the var is in the global frame

class secondClass():
my_var_second = "second variable"
print(my_var_first) # This will work, as the var is in the global frame and not defined in the class

print(my_var_second) # This won't work, because there is no my_var_second in the global frame

Для визуализации памяти вы можете использовать pythontutor, поскольку он шаг за шагом покажет вам, как создается память.

Я надеюсь, что смог вам помочь!

Редактировать

Я думаю, мне следует добавить, что если вы определите переменную внутри класса / функции с тем же именем, что и у переменной в глобальном фрейме, это не удалит глобальную переменную. Вместо этого он создаст новую переменную (с тем же именем) в своем собственном фрейме. Класс или функция всегда будут использовать переменную в своем собственном фрейме, если она доступна.

x = 5
def print_variable():
x = 3
print(x)
print(x)
print_variable()

# OUTPUT:
# 5
# 3
python python-3.x tkinter