Dialog box is not opening/application freezes while calling a API in tkinter

Viewed 145

I want to get a loading dialog box while I call a API in my tkinter application I have implement a custom dialog box in it but the dialog box is not coming basically the whole application freezes and loading dialog box also does not come.

This is a sample function which run when I click on submit button and then this function runs the api call and and open the dialog box while api is making call and also changes frame/navigate to other frame.

def login_button_fuction():
    top = tk.Toplevel(self)
    top.title("loading")
    top.geometry("200x100")
    top.resizable(0, 0)
    bar = ttk.Progressbar(top, orient="horizontal",
                          length=100, mode="indeterminate")
    bar.pack(pady=25)
    bar.start()
    url = "XXXXXXXXXXXXXXXXXXX"
    payload = json.dumps(
        {"username": username_entry.get(), "password": password_entry.get()})
    headers = {'Content-Type': 'application/json'}
    response = requests.post(url+"/users/getuser", data=payload, headers=headers)

    if response.json()["resultCode"] == 100:
        print("success")
        data = {
            "realname": response.json()["data"]["realname"],
            "phone": response.json()["data"]["phone"]}
        with open("user_data.dat", "wb") as f:
            pickle.dump(data, f)
        controller.show_frame(dashboardPage.dashboard_page)

    else:
        print("error")
    bar.grid_forget()
    top.destroy()

Also if any one can tell me how can I remove the minimize and close buttons from tkinter Dialog Box it will be very helpful. The loading Dialog Box Should come while making a API call and should automatically close when the Api call has been made.

I tried this but it hangs whole application and crashes it.


class login_page(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        #functions for buttons

        def try_login():
            url = "XXXXXXXXXXXXXXX"

            payload = json.dumps(
                {"username": username_entry.get(), "password": password_entry.get()})
            headers = {'Content-Type': 'application/json'}
            response = requests.post(
                url+"/users/getuser", data=payload, headers=headers)
            return response

        def login_button_fuction():
            top = tk.Toplevel(self)
            top.title("loading")
            top.geometry("200x100")
            top.resizable(0, 0)
            bar = ttk.Progressbar(top, orient="horizontal",
                                  length=100, mode="indeterminate")
            bar.pack(pady=25)
            bar.start()

            response = threading.Thread(target=try_login)
            response.start()
            response.join()

            print(response)
            if response.json()["resultCode"] == 100:
                print("success")
                data = {
                    "realname": response.json()["data"]["realname"],
                    "phone": response.json()["data"]["phone"],
                    "email": response.json()["data"]["email"]}

                with open("user_data.dat", "wb") as f:
                    pickle.dump(data, f)
                controller.show_frame(dashboardPage.dashboard_page)
            else:
                print("error")

            bar.grid_forget()
            top.destroy()
1 Answers

you need to do any work in a different thread, however all gui updates should happen in the main thread or things can get a bit wonky

something like the following untested code should work (maybe with minor alterations)

def do_login(username,password,callback):
    # this should run in a thread so it does not block the gui
    # since it is run in a thread it SHOULD NOT update the gui
    payload = json.dumps({"username": username, "password": password})
    headers = {'Content-Type': 'application/json'}
    callback(requests.post(url+"/users/getuser", data=payload, headers=headers))

import threading
def login_button_fuction():
    # this is called by the gui when the button is clicked
    top = tk.Toplevel(self)
    top.title("loading")
    top.geometry("200x100")
    top.resizable(0, 0)
    bar = ttk.Progressbar(top, orient="horizontal",
                                  length=100, mode="indeterminate")
    bar.pack(pady=25)
    def on_response(response):
        # after the call completes it will call back
        # to this function, once again in the main thread
        # since this is run in the main thread you CAN update the gui            
        print("Got response... hide bar...or something",data)
        if response.json()["resultCode"] == 100:
            print("success")
            data = {
        "realname": response.json()["data"]["realname"],
        "phone": response.json()["data"]["phone"]
            }
            with open("user_data.dat", "wb") as f:
                pickle.dump(data, f)
            
            controller.show_frame(dashboardPage.dashboard_page)

        else:
            print("error")
        bar.grid_forget()
        top.destroy()
        # End callback function

    bar.start()
    # callback needs to run in the main thread
    # root.after does not accept args so it needs to be another lambda
    # that calls the main thread ... there may be a more elegant way to do this
    callback = lambda data:root.after(1,lambda:on_response(data))
    # trigger a new thread to actually do the fetch
    threading.Thread(target=do_login,args=[user_entry.get(),pass_entry.get(),callback]).start()

See additional information about root.after

some additional answer specifically about root.after triggering main thread

below you can find a 100% verified working example(you can just copy/paste) with some extra prints to demonstrate "thready-ness"

import functools
import threading
import time
import requests
import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.geometry('400x150')
root.title('Tkinter Login Form - pythonexamples.org')

# username label and text entry box
usernameLabel = ttk.Label(root, text="User Name").grid(row=0, column=0)
username = tk.StringVar()
usernameEntry = ttk.Entry(root, textvariable=username).grid(row=0, column=1)

# password label and password entry box
passwordLabel = ttk.Label(root, text="Password").grid(row=1, column=0)
password = tk.StringVar()
passwordEntry = ttk.Entry(root, textvariable=password, show='*').grid(row=1, column=1)

labelVar = tk.StringVar(value="Waiting For Event")
label = ttk.Label(root, textvariable=labelVar).grid(row=10,column=0)
label = ttk.Label(root, text="Valid:'eve.holt@reqres.in'").grid(row=0,column=2)
def do_actual_login(username,password,callback):
    print("current thread(doing login request):", threading.current_thread())
    url="https://reqres.in/api/login"
    data={"username":username,"password":password}
    # fake delay to show not blocking...(request is pretty fast)
    time.sleep(5)
    print("ok really do request in thread")
    return callback(requests.post(url,data))

def validateLogin(username, password):
    print("current thread(on click handler):", threading.current_thread())
    bar = ttk.Progressbar(root, orient="horizontal",
                          length=100, mode="indeterminate")
    bar.grid(row=10,column=0)
    bar.start()

    def login_complete(response):
        print("current thread(login_complete):",threading.current_thread())
        bar.grid_remove()
        if response.status_code == 200:
           print("LOGIN SUCCESS!!!")
           labelVar.set("Login Success")
        else:
           print("LOGIN FAILED:",response,response.json())
           labelVar.set(response.json().get("error","Unknown Error Occurred"))
    # setup our thread and callback
    callback = lambda data:root.after(1,lambda:login_complete(data))
    threading.Thread(
        target = do_actual_login,
        args = [username.get(),password.get(),callback]
    ).start()



validateLogin = functools.partial(validateLogin, username, password)

# login button
loginButton = ttk.Button(root, text="Login", command=validateLogin).grid(row=4, column=0)

root.mainloop()

you can test a successfull login by using the username eve.holt@reqres.in with any password you want (I think) anyother username should fail as should omitting the password

Related