pythonmultithreadingtkinterclient

How do I pass 2 variables through each other?


I am making a client-server system using tkinter for the GUI, and I need to pass the app variable through the thread which communicates with the server. Problem is, I also need to pass the thread through the app.

The following is what I've tried so far, but I simply have no idea what to do in this situation. If my way of doing this is just blatantly incorrect, please correct me. I'm not a pro so I may be looking at it in the wrong way completely.

import tkinter as tk
import socket
import sys
from tkinter.simpledialog import askstring
from tkinter import *
from tkinter import messagebox
import time
import threading
import protocol


IP = "127.0.0.1"
PORT = 1234
BIG_BUFFER = 256
stop_event = threading.Event()

logged_in = False


def packed(cmd):
    return cmd.encode()


def receive_data_from_server(client, app):
    while not stop_event.isSet():
        try:
            server_cmd = client.recv(BIG_BUFFER).decode()
            if server_cmd:
                print(server_cmd)
                if protocol.check_cmd(server_cmd):
                    app.handle_server_response(server_cmd)
                else:
                    print("invalid cmd: " + server_cmd)
        except Exception as err:
            print(err)


def handle_server_response(self, response):
    if response == "log_in_acc":
        self.controller.login_successful()
    elif response == "log_in_err":
        self.error_label.config(text="Invalid username or password")
    else:
        print("Unknown response from server:", response)



class LogInWindow(tk.Tk):
    def __init__(self, client, data_thread, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.client = client
        self.data_thread = data_thread

        self.title("Log In")
        self.geometry("500x400")
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(1, weight=1)

        self.frames = {}
        for F in (LoginPage, RegisterPage):
            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=1, sticky="nsew")

        # Initially show LoginPage
        self.show_frame(LoginPage)

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

    def login_successful(self):
        # Destroy the current main application
        self.destroy()
        # Create and start a new application
        global logged_in
        logged_in = True

    def on_closing(self):
        if messagebox.askokcancel("Quit", "Do you want to close the application?"):
            self.client.send(packed("exit"))
            stop_event.set()
            self.destroy()
            self.data_thread.join()

    def send(self, msg):
        self.client.send(packed(msg))


class LoginPage(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)

        self.controller = controller

        label = tk.Label(self, text="Login Page", font=("Helvetica", 18))
        label.pack(pady=10, padx=10)

        username_label = tk.Label(self, text="Username:")
        username_label.pack()
        self.username_entry = tk.Entry(self)
        self.username_entry.pack()

        password_label = tk.Label(self, text="Password:")
        password_label.pack()
        self.password_entry = tk.Entry(self, show="*")
        self.password_entry.pack()

        login_button = tk.Button(self, text="Login", command=self.login)
        login_button.pack(pady=5)

        register_button = tk.Button(self, text="Register", command=lambda: controller.show_frame(RegisterPage))
        register_button.pack(pady=5)

        self.error_label = tk.Label(self, text="", fg="red")
        self.error_label.pack(pady=5)

    def login(self):
        username = self.username_entry.get()
        password = self.password_entry.get()

        self.controller.send("log_in-" + username + "-" + password)
        if username == "user" and password == "password":  # todo send log in request and receive
            self.controller.login_successful()
        else:
            self.error_label.config(text="Invalid username or password")


def main():
    try:
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client.connect((IP, PORT))  # connect to server

    except Exception as error:
        print(error)  # could be an error connecting to server or establishing the socket

    data_thread = threading.Thread(target=receive_data_from_server,
                                   args=(client, app))
    data_thread.daemon = True
    data_thread.start()

    app = LogInWindow(client, data_thread)
    data_thread.app = app
    if not stop_event.isSet():
        app.mainloop()


    time.sleep(0.1)
    client.close()


if __name__ == "__main__":
    main()


Solution

  • Pass the app variable to the thread after it's created, rather than trying to pass it during thread creation, which won't work due to the order of operations.

    Use a lock to ensure that the app variable is safely accessed and modified by both the main thread and the data thread. Here's the modified code:

    import tkinter as tk
    import socket
    import sys
    from tkinter.simpledialog import askstring
    from tkinter import *
    from tkinter import messagebox
    import time
    import threading
    import protocol
    
    IP = "127.0.0.1"
    PORT = 1234
    BIG_BUFFER = 256
    stop_event = threading.Event()
    
    logged_in = False
    
    def packed(cmd):
        return cmd.encode()
    
    def receive_data_from_server(client, app):
        while not stop_event.isSet():
            try:
                server_cmd = client.recv(BIG_BUFFER).decode()
                if server_cmd:
                    print(server_cmd)
                    if protocol.check_cmd(server_cmd):
                        app.handle_server_response(server_cmd)
                    else:
                        print("invalid cmd: " + server_cmd)
            except Exception as err:
                print(err)
    
    class LogInWindow(tk.Tk):
        def __init__(self, client, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            self.lock = threading.Lock()  # Lock for accessing app variable
            self.client = client
    
            self.title("Log In")
            self.geometry("500x400")
            self.protocol("WM_DELETE_WINDOW", self.on_closing)
    
            container = tk.Frame(self)
            container.pack(side="top", fill="both", expand=True)
            container.grid_rowconfigure(0, weight=1)
            container.grid_columnconfigure(1, weight=1)
    
            self.frames = {}
            for F in (LoginPage, RegisterPage):
                frame = F(container, self)
                self.frames[F] = frame
                frame.grid(row=0, column=1, sticky="nsew")
    
            # Initially show LoginPage
            self.show_frame(LoginPage)
    
        def show_frame(self, cont):
            frame = self.frames[cont]
            frame.tkraise()
    
        def login_successful(self):
            # Destroy the current main application
            self.destroy()
            # Create and start a new application
            global logged_in
            logged_in = True
    
        def on_closing(self):
            if messagebox.askokcancel("Quit", "Do you want to close the application?"):
                self.client.send(packed("exit"))
                stop_event.set()
                self.destroy()
    
        def send(self, msg):
            self.client.send(packed(msg))
    
    
    class LoginPage(tk.Frame):
        def __init__(self, parent, controller):
            super().__init__(parent)
    
            self.controller = controller
    
            label = tk.Label(self, text="Login Page", font=("Helvetica", 18))
            label.pack(pady=10, padx=10)
    
            username_label = tk.Label(self, text="Username:")
            username_label.pack()
            self.username_entry = tk.Entry(self)
            self.username_entry.pack()
    
            password_label = tk.Label(self, text="Password:")
            password_label.pack()
            self.password_entry = tk.Entry(self, show="*")
            self.password_entry.pack()
    
            login_button = tk.Button(self, text="Login", command=self.login)
            login_button.pack(pady=5)
    
            register_button = tk.Button(self, text="Register", command=lambda: controller.show_frame(RegisterPage))
            register_button.pack(pady=5)
    
            self.error_label = tk.Label(self, text="", fg="red")
            self.error_label.pack(pady=5)
    
        def login(self):
            username = self.username_entry.get()
            password = self.password_entry.get()
    
            self.controller.send("log_in-" + username + "-" + password)
            if username == "user" and password == "password":  # todo send log in request and receive
                self.controller.login_successful()
            else:
                self.error_label.config(text="Invalid username or password")
    
    
    def main():
        try:
            client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            client.connect((IP, PORT))  # connect to server
    
        except Exception as error:
            print(error)  # could be an error connecting to server or establishing the socket
    
        app = LogInWindow(client)
        data_thread = threading.Thread(target=receive_data_from_server, args=(client, app))
        data_thread.daemon = True
        data_thread.start()
    
        app.mainloop()
    
        stop_event.set()  # Signal the data thread to stop
        data_thread.join()  # Wait for the data thread to finish
        client.close()
    
    if __name__ == "__main__":
        main()
    

    This way, the app variable is passed to the receive_data_from_server thread after the app object is fully initialized. Also, a lock is used to ensure safe access to the app variable from both the main thread and the data thread.