12

I have been searching a lot and I still don't know how you access variables from different classes in python. In this case I want to access the variable self.v from PageOne class to PageTwo class.

Here is my code.

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()
martineau
  • 119,623
  • 25
  • 170
  • 301
Supachai Abusali
  • 143
  • 1
  • 1
  • 6
  • Use `self.v` in `StartPage` and access `self.v` with `parent.v` from `PageOne` or `PageTwo`. – Kenly Nov 11 '15 at 08:41

2 Answers2

39

At its core, your question has a simple answer. "How do I get a value from object X?" The answer is the same for any object: you get it by asking object X. All you need in order to do that is get a reference to the object and then access the attribute directly.

Accessing data from other pages

In your case, the code in PageTwo needs a reference to PageOne so you can get the v variable.

So, how do you get a reference? The code (which you copied either from a tutorial, or from the stackoverflow answer that the tutorial copied from) was designed to make this easy. Each page is given a reference to a controller, and this controller has a reference to each page. You can therefore ask the controller to give you a reference to a page.

The first step is to save the reference to the controller in each class. Interestingly, you're already doing this in PageOne, but you should do it in all the pages. Make sure you add self.controller = controller in every __init__ method, like so:

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

Next, we need to add a method in the controller class that will return a reference to the page. Add the following function to SampleApp:

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

Now, from within any "page" you can get access to the object for any other "page". For example, in PageTwo you can access the v variable from PageOne like this:

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

Using shared data

An even better solution is for your SampleApp class to create a single set of variables that all of the pages share. You can create a dictionary in that class, and then use the controller to give every page access. For example:

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

Then, from within any class you can access the data like this:

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

The reason this is the better solution is that your pages don't have to know how the other pages are implemented. When a page relies on the exact implementation of another page this is called tight coupling. If the pages don't need to know how the other pages are implemented, this is called loose coupling.

Loose coupling gives you more flexibility. Instead of having every page tightly coupled to every other page, they are all tightly coupled to a single object: the controller. As long as every page knows only about the controller, each page is free to be changed at any time without affecting the rest of the program.

Of course, if you change the controller you have to change all of the pages, but if you do a good job designing the controller that's less likely to occur and easier to manage when it does occur.

Community
  • 1
  • 1
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thank you so much for your help, I have always wondered what the "controller" was and you did a great job in explaining it.. Thank You so much – Supachai Abusali Nov 11 '15 at 16:32
  • 2
    @SupachaiAbusali: if you find this answer, and answers to other questions that you've asked, please consider voting for them and/or selecting one answer. See http://stackoverflow.com/help/someone-answers – Bryan Oakley Nov 11 '15 at 19:31
  • 1
    Hi would you please answer this question regarding tkinter ? I have tried for hours but no luck. https://stackoverflow.com/questions/53942023/passing-variable-from-one-window-to-another-in-tkinter – Yunus Dec 27 '18 at 09:17
-1

This has to do with the global frame.

If you create a variable inside of a class, it will only exist inside of that function. If you want to 'transfer' a variable inside of a class (or function, for that matter) to the global frame, you use 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

To visualise the memory, you can use pythontutor, as it will show you step by step how the memory is created.

I hope I could help you!

EDIT

I think I should add that if you define a variable inside a class/function with the same name as a variable in the global frame, it will not remove the global variable. Instead, it will create a new one (with the same name) in its own frame. A class or function will always use the variable in its own frame if available.

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

# OUTPUT:
# 5
# 3
Martijn Luyckx
  • 402
  • 2
  • 12
  • 3
    The question as written has nothing to do with the global frame. Using globals is a _workaround_, but it's not a proper fix. – Bryan Oakley Nov 11 '15 at 12:28
  • @BryanOakley After reading your solution, I get what you're saying. I may have misinterpreted the question it seems. Thanks for your feedback! – Martijn Luyckx Nov 11 '15 at 12:34
  • @MartijnLuyckx thank you for your advice but I already tried that before posting the question and it didnt seem to work – Supachai Abusali Nov 11 '15 at 15:56
  • @SupachaiAbusali That is indeed quite weird! I'm sorry that I could not help. – Martijn Luyckx Nov 11 '15 at 15:58
  • @MartijnLuyckx no worriess... your contribution was very much appreciated – Supachai Abusali Nov 11 '15 at 16:25
  • @SupachaiAbusali That pleases me, I'm glad I could contribute :) – Martijn Luyckx Nov 11 '15 at 16:26
  • 1
    Martijn: Global variables are generally considered a poor programming practice (see [Why are global variables evil?](https://stackoverflow.com/questions/19158339/why-are-global-variables-evil) or [Why is Global State so Evil?](https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil)) and their use should be discouraged IMO. Don't they teach Computer Science students this at your university? – martineau Jan 03 '20 at 10:10
  • 1
    @martineau When I posted this answer (4 years ago), I had only been enrolled in university for two months. They do absolutely teach about programming practices, but I had to get the basics down first (which I was still doing back then). I was definitely not in a position to answer this question correctly and thoroughly, but I didn't know that yet back then due to a lack of knowing what I didn't know yet. – Martijn Luyckx Jan 04 '20 at 11:14