pythontkinterfieldtkinter-entrypython-3.13

How to properly fill entry fields, and how to make the root appear just when is asked for


So, I am creating a database, on Python (Tkinter). Everything was going well, untill I realized that when I try to autofill the entry field by selecting a product, these are not well populated. The problem is that, the entry fields are populated by what is on the treeview, I don't know how to make it so that the info is gathered from the JOSN file instead (The costs on the treeview are not shown at the same time, because there's 2 different, depending the one you select is the one shown and the application will calculate the gain and margin). The other thing is that I have a menu window, and I can't make the root not appear until the log in is complete, so you have 2 windows open and is not nice (this is more for the looks but still).I use python 3.13.0

For the populated entry fields, I really don't know what to do, I am lost and have been scratching my head for the last few days.

For the menu I tried using root.withdraw() at the end of the code, it works, but then when I complete the log in, the "database" (the main app) does not open.

(If you see something I can change even if not related to the question I will be thankfull, Iam not a programming pro)

import tkinter as tk
from tkinter import messagebox, ttk
import json
import os

class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def to_dict(self):
        return {
            "username": self.username,
            "password": self.password
        }

    @classmethod
    def from_dict(cls, data):
        return cls(data['username'], data['password'])

    @staticmethod
    def save_users(users):
        with open('users.json', 'w') as f:
            json.dump([user.to_dict() for user in users], f)

    @staticmethod
    def load_users():
        if os.path.exists('users.json'):
            with open('users.json', 'r') as f:
                return [User.from_dict(item) for item in json.load(f)]
        return []

class Product:
    def __init__(self, name, price, cost_lb, cost_pm, weight, store_prices=None):
        self.name = name
        self.price = price
        self.cost_lb = cost_lb
        self.cost_pm = cost_pm
        self.weight = weight
        self.store_prices = store_prices if store_prices is not None else {}

    def gain(self, use_cost_lb=True):
        return self.price - (self.cost_lb if use_cost_lb else self.cost_pm)

    def margin(self, use_cost_lb=True):
        gain = self.gain(use_cost_lb)
        return (gain / self.price) * 100 if self.price > 0 else 0

    def to_dict(self):
        return {
            "name": self.name,
            "price": self.price,
            "cost_lb": self.cost_lb,
            "cost_pm": self.cost_pm,
            "weight": self.weight,
            "store_prices": self.store_prices
        }

    @classmethod
    def from_dict(cls, data):
        return cls(data['name'], data['price'], data['cost_lb'], data['cost_pm'], data['weight'], data.get('store_prices', {}))

class Store:
    def __init__(self):
        self.products = []
        self.load_products()

    def add_product(self, name, price, cost_lb, cost_pm, weight):
        product = Product(name, price, cost_lb, cost_pm, weight)
        self.products.append(product)
        self.save_products()

    def delete_product(self, name):
        self.products = [product for product in self.products if product.name != name]
        self.save_products()

    def update_product(self, name, price=None, cost_lb=None, cost_pm=None, weight=None):
        for product in self.products:
            if product.name == name:
                if price is not None:
                    product.price = price
                if cost_lb is not None:
                    product.cost_lb = cost_lb
                if cost_pm is not None:
                    product.cost_pm = cost_pm
                if weight is not None:
                    product.weight = weight
                break
        self.save_products()

    def save_products(self):
        with open('products.json', 'w') as f:
            json.dump([product.to_dict() for product in self.products], f)

    def load_products(self):
        if os.path.exists('products.json'):
            with open('products.json', 'r') as f:
                self.products = [Product.from_dict(item) for item in json.load(f)]
       
class StoreApp:
    def __init__(self, root):
        self.root = root
        self.root.title("RenoCart")
        self.store = Store()
        self.users = User.load_users()  # Load existing users
        self.current_user = None  # Track the currently logged-in user

        self.show_menu()

    def center_window(self, window, width, height):
        screen_width = window.winfo_screenwidth()
        screen_height = window.winfo_screenheight()
        
        x = (screen_width // 2) - (width // 2)
        y = (screen_height // 2) - (height // 2)
        
        window.geometry(f"{width}x{height}+{x}+{y}")

    def show_menu(self):
        self.clear_frame()
        menu_window = tk.Toplevel(self.root)
        menu_window.title("Main Menu")
        self.center_window(menu_window, 210, 250)


        title_label = tk.Label(menu_window, text="RenoCart", font=("Helvetica", 16))
        title_label.pack(pady=10)

        # Login button
        login_button = tk.Button(menu_window, text="Login", command=self.open_login_window)  # Changed this line
        login_button.pack(pady=5)

        # Sign-up button
        signup_button = tk.Button(menu_window, text="Sign Up", command=self.signup_user)
        signup_button.pack(padx=7.5)

        # Quit Button
        quit_button = tk.Button(menu_window, text="Close", command=self.root.quit)
        quit_button.pack(pady=10)

    def signup_user(self):
        signup_window = tk.Toplevel(self.root)
        signup_window.title("Sign up")
        signup_window.geometry("300x200")  
        self.center_window(signup_window, 210, 220)

        # Username label and entry
        username_label = tk.Label(signup_window, text="Username:")
        username_label.pack()
        username_entry = tk.Entry(signup_window)
        username_entry.pack()

        # Password label and entry
        password_label = tk.Label(signup_window, text="Password:")
        password_label.pack()
        password_entry = tk.Entry(signup_window, show="*")
        password_entry.pack()

        # Code for signup
        code_label = tk.Label(signup_window, text="Sign Up Code:")
        code_label.pack()
        code_entry = tk.Entry(signup_window)
        code_entry.pack(pady=5)

        # Sign Up button
        signup_button = tk.Button(signup_window, text="Sign Up", command=lambda: self.signup(username_entry.get(), password_entry.get(), code_entry.get()))
        signup_button.pack(pady=10)

        # Back to menu button
        back_button = tk.Button(signup_window, text="Back", command=self.show_menu)
        back_button.pack()

    def signup (self, username, password, code):
        required_code = "1234"  # The required code for sign-up
        if code != required_code:
            tk.messagebox.showerror("Error", "Invalid sign-up code.")
            return

        if any(user.username == username for user in self.users):
            tk.messagebox.showerror("Error", "Username already exists.")
            return

        new_user = User(username, password)
        self.users.append(new_user)
        User.save_users(self.users)

        tk.messagebox.showinfo("Success", "User signed up successfully!")

    def clear_frame(self):
        # Remove all widgets from the frame
        for widget in self.root.winfo_children():
            widget.destroy()

    def open_login_window(self):
        login_window = tk.Toplevel(self.root)
        login_window.title("Login")
        login_window.geometry("300x200")     
        self.center_window(login_window, 210, 220)  

        # Username Entry
        username_label = tk.Label(login_window, text="Username:")
        username_label.pack(pady=5)
        username_entry = tk.Entry(login_window)
        username_entry.pack(pady=5)

        # Password Entry
        password_label = tk.Label(login_window, text="Password:")
        password_label.pack(pady=5)
        password_entry = tk.Entry(login_window, show="*")
        password_entry.pack(pady=5)

        # Login Button
        login_button = tk.Button(login_window, text="Login", command=lambda: self.login_user(username_entry.get(), password_entry.get(), login_window))
        login_button.pack(pady=10)

        # Cancel Button
        cancel_button = tk.Button(login_window, text="Cancel", command=login_window.destroy)
        cancel_button.pack(pady=5)

    def login_user(self, username, password, login_window):
        users = User.load_users()  # Load users from the JSON file
        for user in users:
            if user.username == username and user.password == password:
                tk.messagebox.showinfo("Success", "Login successful!")
                login_window.destroy()  # Close the login window
                self.access_app()  # Proceed to the next screen
                return
        tk.messagebox.showerror("Error", "Invalid username or password.")

    def access_app(self):
        self.clear_frame()  
        self.create_widgets()       

    def create_widgets(self):
        # Input
        self.name_var = tk.StringVar()
        self.price_var = tk.DoubleVar()
        self.cost_lb_var = tk.DoubleVar()
        self.cost_pm_var = tk.DoubleVar()
        self.weight_var = tk.DoubleVar()
        self.cost_type = tk.StringVar(value="L&B")
        self.search_var = tk.StringVar()
        self.weight_unit = tk.StringVar(value="lbs")

        tk.Label(self.root, text="Name:").grid(row=0, column=0)
        tk.Entry(self.root, textvariable=self.name_var).grid(row=0, column=1)
        tk.Button(self.root, text="Add", command=self.add_product).grid(row=0, column=2)

        tk.Label(self.root, text="Price:").grid(row=1, column=0)
        tk.Entry(self.root, textvariable=self.price_var).grid(row=1, column=1)
        tk.Button(self.root, text="Delete", command=self.delete_product).grid(row=1, column=2)

        tk.Label(self.root, text="L&B Cost:").grid(row=2, column=0)
        tk.Entry(self.root, textvariable=self.cost_lb_var).grid(row=2, column=1)
        tk.Button(self.root, text="Update", command=self.update_product).grid(row=2, column=2)

        tk.Label(self.root, text="Pont-Masson Cost:").grid(row=3, column=0)
        tk.Entry(self.root, textvariable=self.cost_pm_var).grid(row=3, column=1)

        tk.Label(self.root, text="Weight (lbs):").grid(row=4, column=0)
        tk.Entry(self.root, textvariable=self.weight_var).grid(row=4, column=1)

        # Weight unit selection
        tk.Label(self.root, text="Weight Unit:").grid(row=4, column=2)
        tk.OptionMenu(self.root, self.weight_unit, "lbs", "kg").grid(row=4, column=3)

        # Cost type selection
        tk.Label(self.root, text="Select Cost Type for Margin:").grid(row=5, column=0, columnspan=2)
        tk.OptionMenu(self.root, self.cost_type, "L&B", "Pont-Masson").grid(row=6, column=0, columnspan=2)

        # Search
        tk.Label(self.root, text="Search:").grid(row=7, column=0)
        tk.Entry(self.root, textvariable=self.search_var).grid(row=7, column=1)
        tk.Button(self.root, text="Search", command=self.search_products).grid(row=7, column=2)

        # Refresh button
        tk.Button(self.root, text="Refresh", command=self.show_products).grid(row=8, column=0, columnspan=3, sticky="w")

        # Clear button
        tk.Button(self.root, text="Clear", command=self.clear_selection).grid(row=3, column=2)

        # Compare Prices button
        tk.Button(self.root, text="Compare Prices", command=self.open_price_comparison).grid(row=8, column=3)

        # Treeview for displaying products
        self.tree = ttk.Treeview(self.root, columns=("Name", "Cost", "Price", "Gain", "Margin", "Weight"), show='headings')
        self.tree.heading("Name", text="Name")
        self.tree.heading("Cost", text="Cost")
        self.tree.heading("Price", text="Price")
        self.tree.heading("Gain", text="Gain")
        self.tree.heading("Margin", text="Margin (%)")
        self.tree.heading("Weight", text="Weight")

        # Column centering
        for col in ("Cost", "Price", "Gain", "Margin", "Weight"):
            self.tree.column(col, anchor='center')

        self.tree.grid(row=9, column=0, columnspan=5)

        self.tree.bind("<<TreeviewSelect>>", self.on_product_select)  # Bind selection event

        # Show products
        self.show_products()

    def add_product(self):
        name = self.name_var.get()
        try:
            price = self.price_var.get()
            cost_lb = self.cost_lb_var.get()
            cost_pm = self.cost_pm_var.get()
            weight = self.weight_var.get()

            if not name:
                raise ValueError("Product name cannot be empty.")
            if price < 0 or cost_lb < 0 or cost_pm < 0 or weight < 0:
                raise ValueError("Price and costs must be non-negative.")

            self.store.add_product(name, price, cost_lb, cost_pm, weight)
            self.clear_entries()
            self.show_products()
        except ValueError as e:
            messagebox.showerror("Error", str(e))

    def delete_product(self):
        selected_item = self.tree.selection()
        if not selected_item:
            messagebox.showwarning("Select Product", "Please select a product to delete.")
            return
        item_id = selected_item[0]
        product_name = self.tree.item(item_id, "values")[0]

        confirm_delete = messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete '{product_name}'?")
        if confirm_delete:
            self.store.delete_product(product_name)
            messagebox.showinfo("Success", f"Product '{product_name}' deleted.")
            self.clear_entries()
            self.show_products()

    def update_product(self):
        selected_item = self.tree.selection()
        if not selected_item:
            messagebox.showwarning("Select Product", "Please select a product to update.")
            return
        item_id = selected_item[0]
        product_name = self.tree.item(item_id, "values")[0]
        
        price = self.price_var.get() or None
        cost_lb = self.cost_lb_var.get() or None
        cost_pm = self.cost_pm_var.get() or None
        weight = self.weight_var.get() or None
        
        self.store.update_product(product_name, price, cost_lb, cost_pm, weight)
        messagebox.showinfo("Success", f"Product '{product_name}' updated.")
        self.clear_entries()
        self.show_products()

    def show_products(self):

        for item in self.tree.get_children():
            self.tree.delete(item)
        for product in self.store.products:
            use_cost_lb = self.cost_type.get() == "L&B"
            cost = product.cost_lb if use_cost_lb else product.cost_pm
            gain = product.gain(use_cost_lb)
            margin = product.margin(use_cost_lb)

            # Convert weight based on selection
            weight_display = product.weight
            if self.weight_unit.get() == "kg":
                weight_display = weight_display * 0.453592  # Convert lbs to kg

            self.tree.insert("", "end", values=(
                product.name,
                f"${cost:.2f}",
                f"${product.price:.2f}",
                f"${gain:.2f}",
                f"{margin:.2f}%",
                f"{weight_display:.2f} {self.weight_unit.get()}"
            ))

    def search_products(self):
        search_term = self.search_var.get().lower()
        for item in self.tree.get_children():
            self.tree.delete(item)
        for product in self.store.products:
            if search_term in product.name.lower():
                use_cost_lb = self.cost_type.get() == "L&B"
                cost = product.cost_lb if use_cost_lb else product.cost_pm
                gain = product.gain(use_cost_lb)
                margin = product.margin(use_cost_lb)

                weight_display = product.weight
                if self.weight_unit.get() == "kg":
                    weight_display = weight_display * 0.453592

                self.tree.insert("", "end", values=(
                    product.name,
                    f"${cost:.2f}",
                    f"${product.price:.2f}",
                    f"${gain:.2f}",
                    f"{margin:.2f}%",
                    f"{weight_display:.2f} {self.weight_unit.get()}"
                ))

    def clear_entries(self):
        self.name_var.set("")
        self.price_var.set(0)
        self.cost_lb_var.set(0)
        self.cost_pm_var.set(0)
        self.weight_var.set(0)

    def clear_selection(self):
        self.tree.selection_remove(self.tree.selection())  # Deselect any selected item
        self.clear_entries()

    def on_product_select(self, event):
        selected_item = self.tree.selection()
        if not selected_item:
            return

        item_id = selected_item[0]
        product_data = self.tree.item(item_id, "values")

        # Order of values in product_data is (Name, Cost, Price, Gain, Margin, Weight)
        self.name_var.set(product_data[0])  # Set product name
        self.cost_lb_var.set(float(product_data[1][1:]))  # L&B Cost (remove '$' sign)
        self.price_var.set(float(product_data[2][1:]))    # Price (remove '$' sign)
        self.cost_pm_var.set(float(product_data[1][1:]))  # Pont-Masson Cost (remove '$' sign)
        self.weight_var.set(float(product_data[5].split()[0]))  # Weight (extract value)

    def open_price_comparison(self):
        selected_item = self.tree.selection()
        if not selected_item:
            messagebox.showwarning("Select Product", "Please select a product to compare prices.")
            return
        item_id = selected_item[0]
        product_name = self.tree.item(item_id, "values")[0]
        product = next((p for p in self.store.products if p.name == product_name), None)
        if product:
            PriceComparisonWindow(self.root, product)

class PriceComparisonWindow:
    def __init__(self, parent, product):
        self.top = tk.Toplevel(parent)
        self.top.title(f"Price Comparison for {product.name}")
        self.product = product
        
        # Store labels and entry fields for each store
        self.store_vars = {
            "Rona": tk.DoubleVar(value=self.product.store_prices.get("Rona", 0)),
            "Home Depot": tk.DoubleVar(value=self.product.store_prices.get("Home Depot", 0)),
            "Patrick Morin": tk.DoubleVar(value=self.product.store_prices.get("Patrick Morin", 0)),
            "BMR": tk.DoubleVar(value=self.product.store_prices.get("BMR", 0)),
        }

        tk.Label(self.top, text="Store Prices").grid(row=0, column=0, columnspan=2)

        for idx, (store, var) in enumerate(self.store_vars.items()):
            tk.Label(self.top, text=store).grid(row=idx + 1, column=0)
            entry = tk.Entry(self.top, textvariable=var)
            entry.grid(row=idx + 1, column=1)
            entry.bind("<KeyRelease>", self.update_price_info)  # Update on key release

        tk.Button(self.top, text="Save", command=self.save_prices).grid(row=len(self.store_vars) + 1, columnspan=2)

        # Labels for comparison results
        self.comparison_labels = {}
        for idx, store in enumerate(self.store_vars.keys()):
            label = tk.Label(self.top, text="")
            label.grid(row=len(self.store_vars) + 2 + idx, columnspan=2)
            self.comparison_labels[store] = label

        # Label for cheapest store
        self.cheapest_store_label = tk.Label(self.top, text="", font=('Arial', 10, 'bold'))
        self.cheapest_store_label.grid(row=len(self.store_vars) + len(self.comparison_labels) + 2, columnspan=2)

        # Label for most expensive store
        self.most_expensive_store_label = tk.Label(self.top, text="", font=('Arial', 10, 'bold'))
        self.most_expensive_store_label.grid(row=len(self.store_vars) + len(self.comparison_labels) + 3, columnspan=2)

        self.update_price_info()

    def save_prices(self):
        for store, var in self.store_vars.items():
            self.product.store_prices[store] = var.get()
        messagebox.showinfo("Success", "Prices saved successfully.")
        self.update_price_info()

    def update_price_info(self, event=None):
        prices = {store: var.get() for store, var in self.store_vars.items()}

        # Update comparison labels
        for store, price in prices.items():
            if self.product.price < price:
                comparison_text = f"Your price is Lower than {store}"
            elif self.product.price > price:
                comparison_text = f"Your price is Higher than {store}"
            else:
                comparison_text = f"Your price is the Same as {store}"
            self.comparison_labels[store].config(text=comparison_text)

        # Calculate the cheapest store
        cheapest_store = min(prices, key=prices.get)
        cheapest_price = prices[cheapest_store]
        self.cheapest_store_label.config(text=f"Cheapest Store: {cheapest_store} - ${cheapest_price:.2f}")

        # Calculate the most expensive store
        most_expensive_store = max(prices, key=prices.get)
        most_expensive_price = prices[most_expensive_store]
        self.most_expensive_store_label.config(text=f"Most Expensive Store: {most_expensive_store} - ${most_expensive_price:.2f}")

if __name__ == "__main__":
    root = tk.Tk()
    app = StoreApp(root)
    root.mainloop()

Solution

  • Two window problem is caused by the library architecture. Library automatically creates root window and everything built top of it. If you remove it, close it all gui applications will be likely to make unexpected behaviours. So you can make a frame inside root window, populate it with the login screen elements. After user logged in, delete or clear frame and pass through second phase.

    Your other problem's solution is here.

    this function gets data from json file of given product.

    def get_product_data_db(self, product_name): 
        with open("products.json","r") as f:
            text = f.read()
            product_list = json.loads(text)
    
        for product in product_list:
            if(product["name"] == product_name):
                return product
    

    and your on_product_select function should be refined as this.

    def on_product_select(self, event):
        selected_item = self.tree.selection()
        if not selected_item:
            return
    
        item_id = selected_item[0]
        selected_product = self.tree.item(item_id, "values")
    
        product_data = self.get_product_data_db(selected_product[0])
        self.name_var.set(product_data["name"]) 
        self.cost_lb_var.set(product_data["cost_lb"])
        self.price_var.set(product_data["price"])
        self.cost_pm_var.set(product_data["cost_pm"])
        self.weight_var.set(product_data["weight"]) 
    

    So you get data from folder and assign them individually.