في الدرس 29 من سلسلة PyQt سنتعلم عن QSpinBox و QDoubleSpinBox، وهي أدوات تسمح للمستخدم باختيار أرقام بسهولة مع إمكانية تحديد الحد الأدنى والأقصى وخطوة التغيير.
1. ما هي QSpinBox و QDoubleSpinBox؟
-
QSpinBox: أداة لاختيار أعداد صحيحة (Integers) باستخدام أسهم زيادة ونقصان أو إدخال يدوي.
-
QDoubleSpinBox: مشابهة ولكن للأعداد العشرية (Floats).
2. أهم الخصائص
الخاصية | الوصف |
---|---|
setMinimum(value) |
تعيين الحد الأدنى للقيمة. |
setMaximum(value) |
تعيين الحد الأقصى للقيمة. |
setRange(min, max) |
تعيين الحدين الأدنى والأقصى معًا. |
setSingleStep(step) |
تحديد مقدار التغيير عند النقر على الأسهم. |
value() |
الحصول على القيمة الحالية. |
setValue(value) |
تعيين قيمة مبدئية. |
valueChanged |
إشارة تُطلق عند تغيير القيمة. |
3. مثال عملي
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QSpinBox, QDoubleSpinBox
class SpinBoxExample(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("مثال QSpinBox و QDoubleSpinBox")
self.resize(300, 200)
layout = QVBoxLayout()
# QSpinBox للأعداد الصحيحة
self.int_spin = QSpinBox()
self.int_spin.setRange(0, 100) # من 0 إلى 100
self.int_spin.setSingleStep(5) # التغيير كل 5
self.int_spin.valueChanged.connect(self.show_values)
layout.addWidget(self.int_spin)
# QDoubleSpinBox للأعداد العشرية
self.double_spin = QDoubleSpinBox()
self.double_spin.setRange(0.0, 10.0) # من 0.0 إلى 10.0
self.double_spin.setSingleStep(0.5) # التغيير كل 0.5
self.double_spin.setDecimals(2) # عرض منزلتين عشريتين
self.double_spin.valueChanged.connect(self.show_values)
layout.addWidget(self.double_spin)
# النص لعرض القيم
self.label = QLabel("القيم المختارة ستظهر هنا")
layout.addWidget(self.label)
self.setLayout(layout)
def show_values(self):
int_value = self.int_spin.value()
double_value = self.double_spin.value()
self.label.setText(f"القيمة الصحيحة: {int_value} | القيمة العشرية: {double_value}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SpinBoxExample()
window.show()
sys.exit(app.exec_())
4. شرح الكود
-
QSpinBox
-
يسمح باختيار أعداد صحيحة من 0 إلى 100.
-
التغيير يتم بمقدار 5 عند الضغط على الأسهم.
-
-
QDoubleSpinBox
-
يسمح باختيار أعداد عشرية بين 0 و 10.
-
التغيير يتم بمقدار 0.5.
-
عرض رقم عشري حتى منزلتين.
-
-
عند تغيير أي قيمة، يتم عرضها في النص.
5. استخدامات عملية
-
اختيار الكمية في تطبيقات التسوق.
-
تحديد درجة الصوت أو السطوع.
-
اختيار القيم الرقمية في النماذج.
تمام يا تامر 👍
سأعمل لك تمرين على QSpinBox و QDoubleSpinBox بحيث:
-
تختار كمية المنتج باستخدام
QSpinBox
. -
تحدد سعر الوحدة باستخدام
QDoubleSpinBox
. -
يتم حساب السعر الإجمالي تلقائيًا عند أي تغيير.
التمرين – حساب السعر الإجمالي
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QSpinBox, QDoubleSpinBox, QHBoxLayout
class PriceCalculator(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("حساب السعر الإجمالي")
self.resize(300, 200)
layout = QVBoxLayout()
# إدخال الكمية
quantity_layout = QHBoxLayout()
quantity_layout.addWidget(QLabel("الكمية:"))
self.quantity_spin = QSpinBox()
self.quantity_spin.setRange(1, 100) # من 1 إلى 100
self.quantity_spin.setSingleStep(1)
self.quantity_spin.valueChanged.connect(self.calculate_total)
quantity_layout.addWidget(self.quantity_spin)
layout.addLayout(quantity_layout)
# إدخال سعر الوحدة
price_layout = QHBoxLayout()
price_layout.addWidget(QLabel("سعر الوحدة:"))
self.price_spin = QDoubleSpinBox()
self.price_spin.setRange(0.0, 1000.0)
self.price_spin.setSingleStep(0.5)
self.price_spin.setDecimals(2) # منزلتان عشريتان
self.price_spin.valueChanged.connect(self.calculate_total)
price_layout.addWidget(self.price_spin)
layout.addLayout(price_layout)
# عرض السعر الإجمالي
self.total_label = QLabel("السعر الإجمالي: 0.00")
layout.addWidget(self.total_label)
self.setLayout(layout)
def calculate_total(self):
quantity = self.quantity_spin.value()
price_per_unit = self.price_spin.value()
total = quantity * price_per_unit
self.total_label.setText(f"السعر الإجمالي: {total:.2f}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = PriceCalculator()
window.show()
sys.exit(app.exec_())
شرح التمرين
-
QSpinBox للكمية:
-
يحدد عدد الوحدات (من 1 إلى 100).
-
-
QDoubleSpinBox لسعر الوحدة:
-
يحدد السعر بدقة منزلتين عشريتين.
-
-
حساب السعر الإجمالي يتم مباشرة عند أي تغيير في الكمية أو السعر.
-
النتيجة تظهر في QLabel بشكل فوري.
سأطور لك التمرين بحيث:
-
تستطيع إضافة أكثر من منتج مع الكمية وسعر الوحدة.
-
كل منتج يتم حفظه في جدول QTableWidget.
-
يتم حساب المجموع الكلي لجميع المنتجات تلقائيًا.
النسخة المطورة – إدارة المنتجات مع المجموع الكلي
import sys
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QLabel, QSpinBox, QDoubleSpinBox,
QHBoxLayout, QPushButton, QLineEdit, QTableWidget, QTableWidgetItem
)
class ProductManager(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("إدارة المنتجات")
self.resize(500, 400)
layout = QVBoxLayout()
# إدخال اسم المنتج
product_layout = QHBoxLayout()
product_layout.addWidget(QLabel("اسم المنتج:"))
self.product_name = QLineEdit()
product_layout.addWidget(self.product_name)
layout.addLayout(product_layout)
# إدخال الكمية
quantity_layout = QHBoxLayout()
quantity_layout.addWidget(QLabel("الكمية:"))
self.quantity_spin = QSpinBox()
self.quantity_spin.setRange(1, 100)
quantity_layout.addWidget(self.quantity_spin)
layout.addLayout(quantity_layout)
# إدخال سعر الوحدة
price_layout = QHBoxLayout()
price_layout.addWidget(QLabel("سعر الوحدة:"))
self.price_spin = QDoubleSpinBox()
self.price_spin.setRange(0.0, 1000.0)
self.price_spin.setDecimals(2)
self.price_spin.setSingleStep(0.5)
price_layout.addWidget(self.price_spin)
layout.addLayout(price_layout)
# زر إضافة المنتج
self.add_button = QPushButton("إضافة المنتج")
self.add_button.clicked.connect(self.add_product)
layout.addWidget(self.add_button)
# جدول عرض المنتجات
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["اسم المنتج", "الكمية", "سعر الوحدة", "الإجمالي"])
layout.addWidget(self.table)
# عرض المجموع الكلي
self.total_label = QLabel("المجموع الكلي: 0.00")
layout.addWidget(self.total_label)
self.setLayout(layout)
def add_product(self):
name = self.product_name.text().strip()
quantity = self.quantity_spin.value()
price_per_unit = self.price_spin.value()
if name == "" or price_per_unit == 0:
return # تجاهل إذا البيانات ناقصة
total = quantity * price_per_unit
# إضافة الصف للجدول
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setItem(row, 0, QTableWidgetItem(name))
self.table.setItem(row, 1, QTableWidgetItem(str(quantity)))
self.table.setItem(row, 2, QTableWidgetItem(f"{price_per_unit:.2f}"))
self.table.setItem(row, 3, QTableWidgetItem(f"{total:.2f}"))
# تحديث المجموع الكلي
self.update_total()
# مسح المدخلات
self.product_name.clear()
self.quantity_spin.setValue(1)
self.price_spin.setValue(0.0)
def update_total(self):
total_sum = 0.0
for row in range(self.table.rowCount()):
total_sum += float(self.table.item(row, 3).text())
self.total_label.setText(f"المجموع الكلي: {total_sum:.2f}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ProductManager()
window.show()
sys.exit(app.exec_())
مميزات النسخة المطورة
✅ إدخال اسم المنتج + الكمية + سعر الوحدة.
✅ عرض المنتجات في جدول منظم.
✅ حساب المجموع الكلي تلقائيًا.
✅ إعادة تعيين الحقول بعد كل إضافة.
التمرين المطور بحيث:
-
كل منتج يضاف يتم حفظه مباشرة في ملف Excel باسم
products.xlsx
. -
عند تشغيل البرنامج يتم تحميل البيانات السابقة من الملف تلقائيًا إذا كان موجودًا.
النسخة النهائية – الحفظ التلقائي في Excel
import sys
import os
import pandas as pd
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QLabel, QSpinBox, QDoubleSpinBox,
QHBoxLayout, QPushButton, QLineEdit, QTableWidget, QTableWidgetItem
)
class ProductManager(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("إدارة المنتجات")
self.resize(500, 400)
layout = QVBoxLayout()
# إدخال اسم المنتج
product_layout = QHBoxLayout()
product_layout.addWidget(QLabel("اسم المنتج:"))
self.product_name = QLineEdit()
product_layout.addWidget(self.product_name)
layout.addLayout(product_layout)
# إدخال الكمية
quantity_layout = QHBoxLayout()
quantity_layout.addWidget(QLabel("الكمية:"))
self.quantity_spin = QSpinBox()
self.quantity_spin.setRange(1, 100)
quantity_layout.addWidget(self.quantity_spin)
layout.addLayout(quantity_layout)
# إدخال سعر الوحدة
price_layout = QHBoxLayout()
price_layout.addWidget(QLabel("سعر الوحدة:"))
self.price_spin = QDoubleSpinBox()
self.price_spin.setRange(0.0, 1000.0)
self.price_spin.setDecimals(2)
self.price_spin.setSingleStep(0.5)
price_layout.addWidget(self.price_spin)
layout.addLayout(price_layout)
# زر إضافة المنتج
self.add_button = QPushButton("إضافة المنتج")
self.add_button.clicked.connect(self.add_product)
layout.addWidget(self.add_button)
# جدول عرض المنتجات
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["اسم المنتج", "الكمية", "سعر الوحدة", "الإجمالي"])
layout.addWidget(self.table)
# عرض المجموع الكلي
self.total_label = QLabel("المجموع الكلي: 0.00")
layout.addWidget(self.total_label)
self.setLayout(layout)
# تحميل البيانات من الملف إذا كان موجود
self.load_from_excel()
def add_product(self):
name = self.product_name.text().strip()
quantity = self.quantity_spin.value()
price_per_unit = self.price_spin.value()
if name == "" or price_per_unit == 0:
return # تجاهل إذا البيانات ناقصة
total = quantity * price_per_unit
# إضافة الصف للجدول
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setItem(row, 0, QTableWidgetItem(name))
self.table.setItem(row, 1, QTableWidgetItem(str(quantity)))
self.table.setItem(row, 2, QTableWidgetItem(f"{price_per_unit:.2f}"))
self.table.setItem(row, 3, QTableWidgetItem(f"{total:.2f}"))
# تحديث المجموع الكلي
self.update_total()
# حفظ في ملف Excel
self.save_to_excel()
# مسح المدخلات
self.product_name.clear()
self.quantity_spin.setValue(1)
self.price_spin.setValue(0.0)
def update_total(self):
total_sum = 0.0
for row in range(self.table.rowCount()):
total_sum += float(self.table.item(row, 3).text())
self.total_label.setText(f"المجموع الكلي: {total_sum:.2f}")
def save_to_excel(self):
data = []
for row in range(self.table.rowCount()):
data.append([
self.table.item(row, 0).text(),
int(self.table.item(row, 1).text()),
float(self.table.item(row, 2).text()),
float(self.table.item(row, 3).text())
])
df = pd.DataFrame(data, columns=["اسم المنتج", "الكمية", "سعر الوحدة", "الإجمالي"])
df.to_excel("products.xlsx", index=False)
def load_from_excel(self):
if os.path.exists("products.xlsx"):
df = pd.read_excel("products.xlsx")
for _, row_data in df.iterrows():
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setItem(row, 0, QTableWidgetItem(str(row_data["اسم المنتج"])))
self.table.setItem(row, 1, QTableWidgetItem(str(row_data["الكمية"])))
self.table.setItem(row, 2, QTableWidgetItem(f"{row_data['سعر الوحدة']:.2f}"))
self.table.setItem(row, 3, QTableWidgetItem(f"{row_data['الإجمالي']:.2f}"))
self.update_total()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ProductManager()
window.show()
sys.exit(app.exec_())
مميزات هذه النسخة
✅ حفظ تلقائي لكل عملية إضافة منتج في ملف products.xlsx
.
✅ تحميل تلقائي للبيانات السابقة عند فتح البرنامج.
✅ عرض المجموع الكلي دائمًا محدث.