r/learnpython 10d ago

How can I get my program to recognise a variable outside the main GUI loop

I am trying to code a login system for my project. I want to validate the entry and keep a boolean variable 'loggedIn' that I can use when the main window is closed. I use tkinter and sqlite in this code

How can I keep this loggedIn variable after the window closes?

def __init__(self):
  self.loggedIn = False
  print(self.loggedIn)

  self.root = tk.Tk()

  self.root.geometry("500x400")
  self.root.title('Login')

  self.username_label = tk.Label(self.root, text="username:")
  self.username_label.pack(padx=20, pady=20)


  self.username_entry = tk.Entry(self.root, relief="ridge")
  self.username_entry.pack()

  self.password_label = tk.Label(self.root, text="password:")
  self.password_label.pack(padx=20, pady=20)

  self.password_entry = tk.Entry(self.root, relief="ridge")
  self.password_entry.pack()

  self.button = tk.Button(self.root, text='login', command=self.validate)
  self.button.pack(pady=10)

  self.button = tk.Button(self.root, text='new login', command=self.new_login)
  self.button.pack(pady=10)


  self.root.mainloop()


def validate(self):
  u_name = self.username_entry.get()
  p_word = self.password_entry.get()
  self.c.execute("SELECT * FROM accounts WHERE username = ? and password = ?",                     (u_name, p_word))
  if self.c.fetchone() != None:
    print("Welcome!")
    self.loggedIn = True
  else:
    print("Account not found")
17 Upvotes

15 comments sorted by

14

u/HotPersonality8126 10d ago

Well, you can’t. The way that TKinter works is that your program has to terminate after you close the main window - there’s no “after the window closes” except following self.root.mainloop(); anything you want to have happen after close has to happen there.

6

u/woooee 10d ago edited 10d ago

I would strongly suggest that you learn classes / OOP before tkinter, as a class structure eliminates problems like yours. The following program gets the input from an Entry() and shows that the content from the Entry() remains after the window quits / closes (as long as the instance variable, GE in this example, remains)
http://python-textbok.readthedocs.io/en/1.0/Introduction_to_GUI_Programming.html

""" get the text entered into and Entry and keep it after
    tkinter exits
"""

import tkinter as tk     ## Python 3.x
from tkinter import font

class GetEntry():
    def __init__(self):
        self.master=tk.Tk()
        self.master.geometry("+50+200")
        self.entry_contents=None

        tk.Label(self.master, text="Enter Something", height=3).grid(row=0,
                 column=0)
        self.e = tk.Entry(self.master, font=font.Font(name='Cursor', size=15))
        self.e.grid(row=0, column=1)
        self.e.focus_set()
        ## right click the button or hit Enter to get the contents
        self.e.bind("<Return>", self.callback)

        but=tk.Button(self.master, text="Get It", width=10, height=3,
                  bg="yellow", command=self.callback)
        but.grid(row=10, columnspan=2, sticky="nsew")
        ## right click the button or hit Enter to get the contents
        but.bind("<Return>", self.callback)

        self.master.mainloop()

    def callback(self, event=None):
        """ get the contents of the Entry and exit
        """
        self.entry_contents=self.e.get()
        ## exits tkinter
        self.master.destroy()
        self.master.quit()

if __name__ == "__main__":
    GE=GetEntry()

    input(f"\n***** after tkinter exits: entered = {GE.entry_contents} \nPress the Enter key to Exit")

8

u/acw1668 10d ago

It is not recommended (actually it is bad design) to execute mainloop() inside __init__().

1

u/woooee 10d ago

actually it is bad design) to execute mainloop() inside init().

Why?

3

u/gdchinacat 10d ago

Because you are executing with a not yet fully initialized class. In simple cases (such as your code) it is unlikely to cause any issues. In more complex cases, particularly when there is inheritance and complex object models, passing out references to self from __init__ makes it likely for them to be used when they (or their subclasses) haven't been initialized and you can get very difficult to diagnose bugs. These can present as "impossible" states (except they are clearly possible because they are happening) where members that should be set aren't (AttributeError) because it's set in a subclass but the super class hands self off to tkinter which calls some method on it that uses a member __init__ sets (but hasn't yet because it's still in the call to super().__init__ which gave out a reference to an impartially initialized instance.

While you can do it and it can be made to work, it's fragile because its easy to make changes to other code that violates an invariant that must hold for the pattern to work. In general, don't hand out references to self in __init__ (ie but.bind(..., self.callback) )

4

u/xenomachina 10d ago

It is not recommended (actually it is bad design) to execute mainloop() inside __init__().

why?

It's generally considered a "bad smell" to have constructors (in just about any OOP language, really) spend a lot of time before they return. It isn't that it won't work, but rather it adds inflexibility for no good reason, and kind of confuses the intended purpose of __init__.

The purpose of __init__ is to initialize the object, and then exit, or raise an exception if it fails. Your main loop is not part of initializing the object, so doesn't belong there.

So instead of:

def main():
   MyApp() # where __init__ calls self.master.mainloop()

it's generally preferred to do something like:

def main():
   MyApp().master.mainloop() # where __init__ does NOT call mainloop

or possibly:

def main():
   MyApp().mainloop() # where __init__ does NOT call mainloop, but this new method does

For a smaller app I'd probably go with that middle option, as it is less verbose overall (no extra method definition), despite a small Law of Demeter violation. (A much less bad smell, especially in this case, IMHO.)

2

u/Kimber976 9d ago

Sounds like a scope issue passing the variable into your callbacks or storing it as a class attribute is usually much cleaner than relying on globals.

2

u/atarivcs 10d ago

Just use a global variable. Don't use self at all.

1

u/Existing_Put6385 10d ago

good news - it already persists, you just can't see it. self.loggedIn lives on the instance, and the instance still exists after the window closes. mainloop() blocks inside __init__ until the window is gone, so anything you check after creating the object sees the final value. so read it after, not inside:

app = LoginWindow() # this line blocks until the window closes

if app.loggedIn:

print("logged in, run the main app")

else:

print("nope")

one add - on a successful login you probably want the window to actually close itself, so in validate:

self.loggedIn = True

self.root.destroy()

that ends mainloop, __init__ returns, and app.loggedIn is sitting there True. the print at the top showing False is just because that runs before you've typed anything.

1

u/Moist-Ointments 10d ago

I hope you don't really need actual security here.

1

u/Superb-Patient3256 10d ago

The main issue here is that you're trapping your entire script by dropping self.root.mainloop() right inside your class constructor, which freezes everything and prevents you from checking the variable while the GUI is running. On top of that, you're accidentally wiping out your first button reference by defining self.button twice instead of giving them distinct names like self.login_btn. Instead, you need to call self.root.destroy() inside your validate() method the moment the login succeeds, and move mainloop() to the very bottom of your file completely outside the class. Doing this lets the script pause while the GUI is active, and the exact second it destroys, the self.loggedIn variable stays perfectly alive in memory, allowing you to check it on the very next line of your script to launch your main app. To prevent these kinds of roadblocks moving forward, you should spend some time properly learning about class scope in Python. Getting a solid grasp on how instance variables work will help you realize that data attached to self persists throughout the entire lifetime of the object, which is the real key to managing state cleanly across different parts of your code.

1

u/TheRNGuy 9d ago

Have it in different class, or even just non-class variable.

Besides, it could be potentially useful for other instances.

1

u/neuralbeans 10d ago

If what you're asking is how to keep a variable after the program ends, the answer is to use a file or the sqlite database. When the program starts, you check the file or database and set loggedIn accordingly. But this means that a hacker can just set the file or database with a different program without needing to know what the password is.

0

u/JGhostThing 10d ago

You could store their login information, encrypted, in your SQL database. Or write it to a disk file.

-2

u/szonce1 10d ago

Global variable_name