所以,我正在 Python (Tkinter) 上创建一个数据库。一切都很顺利,直到我意识到当我尝试通过选择产品自动填充输入字段时,这些字段填充得不好。问题是,输入字段由树视图上的内容填充,我不知道如何制作,以便从 JOSN 文件中收集信息(树视图上的成本不会同时显示) ,因为有 2 个不同的选项,具体取决于您选择的选项是显示的选项,并且应用程序将计算增益和裕度)。另一件事是我有一个菜单窗口,在登录完成之前我无法让根不出现,所以你打开了两个窗口,这不太好(这更适合外观,但仍然如此)。我使用Python 3.13.0
对于填充的输入字段,我真的不知道该怎么办,我迷失了方向,这几天一直在抓耳挠腮。
对于我尝试在代码末尾使用
root.withdraw()
的菜单,它可以工作,但是当我完成登录时,“数据库”(主应用程序)不会打开。
(如果您看到我可以更改的内容,即使与问题无关,我也会很感激,我不是编程专业人士)
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()
双窗口问题是由库架构引起的。库会自动创建根窗口以及在其之上构建的所有内容。如果删除它,关闭它,所有 gui 应用程序都可能会出现意外的行为。因此,您可以在根窗口内创建一个框架,并用登录屏幕元素填充它。用户登录后,删除或清除框架并通过第二阶段。
您的其他问题的解决方案就在这里。
此函数从给定产品的 json 文件中获取数据。
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
你的 on_product_select 函数应该像这样完善。
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"])
因此,您可以从文件夹中获取数据并单独分配它们。