Python Learningاز صفر تا نوشتن پروژه از حفظ
درس ۱۴ · ساخت پروژه

ساخت پروژه ۲: پنجره، دکمه‌ها و جدول

حدود ۱۰۰ دقیقه۱ تمرین۶ سؤال آزمون
بعد از این درس می‌توانی:
  • کلاس قدم قبل را در یک فایل واقعی بگذاری
  • فرم، توابع دکمه‌ها، جدول و دکمه‌ها را به ترتیب اضافه کنی
  • بعد از هر قدم برنامه را اجرا و چک کنی
  • با یک چک‌لیست همه‌ی حالت‌ها را تست کنی
حواست باشد

این درس در VS Code انجام می‌شود، چون tkinter داخل مرورگر اجرا نمی‌شود. هر قدم را اول خودت بنویس، اجرا کن، و فقط بعد کد آماده را باز کن و مقایسه کن.

قدم ۰: فایل

در پوشه‌ی python-learning فایل shop_manager.py بساز. اول سه خط import و خط FILE را بنویس، بعد کلاس Shop که در درس قبل از حفظ نوشتی را زیرش بگذار.

shop_manager.py — شروع
import json
import os
import tkinter as tk
from tkinter import ttk, messagebox

FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'shop_data.json')

# class Shop: ... (از درس قبل)

قدم ۱: پنجره و فرم

زیر کلاس: یک شیء shop بساز، پنجره با عنوان «مدیریت فروشگاه - Python» و اندازه‌ی 760x560، و چهار ردیف برچسب + جعبه با حلقه که در fields ذخیره شوند. فعلاً آخر فایل root.mainloop() بگذار و اجرا کن.

کد آماده‌ی قدم ۱ (بعد از نوشتن خودت)
Python
shop = Shop()
root = tk.Tk()
root.title('مدیریت فروشگاه - Python')
root.geometry('760x560')

fields = {}
for row, (key, label) in enumerate([('name', 'نام کالا'), ('buy', 'قیمت خرید'), ('sell', 'قیمت فروش'), ('qty', 'تعداد')]):
    ttk.Label(root, text=label).grid(row=row, column=0, padx=8, pady=5, sticky='e')
    entry = ttk.Entry(root, width=28)
    entry.grid(row=row, column=1, padx=8, pady=5)
    fields[key] = entry

قدم ۲: خواندن فرم و تابع run

values() چهار جعبه را بخواند و (name, buy, sell, qty) برگرداند (نام با strip، قیمت‌ها float، تعداد int). run(action) تابع را اجرا کند، اگر موفق بود جدول را refresh کند (به‌جز برای جست‌وجو)، و ValueError/TypeError و OSError را با messagebox.showerror نشان دهد.

تمرین

منطق values (در مرورگر)

به‌جای جعبه‌ها، دیکشنری form از متن‌ها داری. تابع values(form) بنویس که (name, buy, sell, qty) را با نوع درست برگرداند.

Ctrl + Enter
دیدن راه‌حل (اول خودت تلاش کن)
def values(form):
    name = form['name'].strip()
    buy = float(form['buy'])
    sell = float(form['sell'])
    qty = int(form['qty'])
    return name, buy, sell, qty
کد آماده‌ی قدم ۲
Python
def values():
    name = fields['name'].get().strip()
    buy = float(fields['buy'].get())
    sell = float(fields['sell'].get())
    qty = int(fields['qty'].get())
    return name, buy, sell, qty

def run(action):
    try:
        action()
        if action is not search: refresh()
    except (ValueError, TypeError) as exc:
        messagebox.showerror('خطا', str(exc))
    except OSError as exc:
        messagebox.showerror('خطای فایل', str(exc))

قدم ۳: توابع دکمه‌ها

شش تابع کوتاه: add، edit، delete، sell، search و report. هر کدام فقط فرم را می‌خواند و متد مناسب shop را صدا می‌زند. report نتیجه را با f-string و {income:,.0f} در messagebox.showinfo نشان می‌دهد.

کد آماده‌ی قدم ۳
Python
def add():
    shop.add(*values())

def edit():
    shop.edit(*values())

def delete():
    shop.delete(fields['name'].get().strip())

def sell():
    shop.sell(fields['name'].get().strip(), int(fields['qty'].get()))

def search():
    name = fields['name'].get().strip().lower()
    refresh(name)

def report():
    count, income = shop.report()
    messagebox.showinfo('گزارش فروش', f'تعداد فروخته‌شده: {count}\nدرآمد کل فروش: {income:,.0f}')

قدم ۴: refresh و choose

refresh(query='') جدول را خالی و از نو پر کند. choose(event) ردیف انتخاب‌شده را در جعبه‌ها بنویسد. (هنوز table نساختی؛ اشکالی ندارد، چون تابع تا صدا زده نشود اجرا نمی‌شود.)

کد آماده‌ی قدم ۴
Python
def refresh(query=''):
    for item in table.get_children():
        table.delete(item)
    for name, p in shop.products.items():
        if query in name.lower():
            table.insert('', 'end', values=(name, p['buy'], p['sell'], p['qty']))

def choose(event):
    selection = table.selection()
    if not selection:
        return
    name, buy, sell_price, qty = table.item(selection[0], 'values')
    for key, value in [('name', name), ('buy', buy), ('sell', sell_price), ('qty', qty)]:
        fields[key].delete(0, tk.END)
        fields[key].insert(0, value)

قدم ۵: دکمه‌ها، جدول و اجرا

لیست buttons، قاب و حلقه‌ی ساخت دکمه‌ها با lambda a=action: run(a)؛ جدول با چهار ستون؛ bind به choose؛ وزن ردیف ۵ و ستون ۳؛ refresh() و در آخر root.mainloop().

کد آماده‌ی قدم ۵
Python
buttons = [('افزودن کالا', add), ('ویرایش کالا', edit), ('حذف کالا', delete),
           ('ثبت فروش', sell), ('جست‌وجو', search), ('نمایش همه', refresh), ('گزارش فروش', report)]
frame = ttk.Frame(root)
frame.grid(row=4, column=0, columnspan=4, padx=10, pady=12)
for i, (label, action) in enumerate(buttons):
    ttk.Button(frame, text=label, command=lambda a=action: run(a)).grid(row=i // 4, column=i % 4, padx=4, pady=4)

columns = ('name', 'buy', 'sell', 'qty')
table = ttk.Treeview(root, columns=columns, show='headings', height=13)
for key, label in [('name', 'نام کالا'), ('buy', 'قیمت خرید'), ('sell', 'قیمت فروش'), ('qty', 'موجودی')]:
    table.heading(key, text=label)
    table.column(key, width=150, anchor='center')
table.grid(row=5, column=0, columnspan=4, padx=10, pady=10, sticky='nsew')
table.bind('<<TreeviewSelect>>', choose)
root.grid_rowconfigure(5, weight=1)
root.grid_columnconfigure(3, weight=1)
refresh()
root.mainloop()

چک‌لیست تست برنامه‌ی کامل

برنامه را اجرا کن و همه‌ی این‌ها را امتحان کن. هر کدام درست بود تیک بزن:

در پروژه‌ی فروشگاه

اگر چیزی کار نکرد، کدت را با مستندات خط به خط پروژه مقایسه کن و درس باگ‌گیری را ببین.

آزمون این درس

برای گذراندن این درس حداقل ۸۰٪ جواب درست لازم است. هر چند بار خواستی می‌توانی دوباره امتحان بدهی.

۱. چرا refresh می‌تواند قبل از ساختن table تعریف شود؟
۲. چرا run برای search جدول را refresh نمی‌کند؟
۳. shop.add(*values()) یعنی
۴. کدام خط باعث می‌شود با کلیک روی ردیف فرم پر شود؟
۵. بعد از کلیک روی ردیف، جعبه‌ی تعداد چه دارد؟
۶. اگر root.mainloop() را جا بیندازی؟