docs:pyqt_basics

بدهيات البرمجة الرسومية على بايثون و Qt

هذه المقالة تفترض دراية ولو بسيطة في لغة البرمجة بايثون. إن كانت هذه المقالة أول عهدك بالبرمجة ننصح بقراءة أي من المقالات الأخرى

هذه المقالة تناقش البدهيات فقط. للمزيد يمكنك قراءة

ملاحظة:

  • هذه المقالة لم تكتمل بعد

مقدمة

المزايا

الأدوات

سنستعمل في هذه المقالة

  • بايثون 2.6
  • المصمم designer-qt4 (في فيدورا/أعجوبة تجده في حزمة qt-devel)
  • PyQt4 (في فيدورا/أعجوبة الحزمة لها نفس الاسم PyQt4)
  • بعض البرامج قد تحتاج pyuic4 (في فيدورا/أعجوبة تجدها في حزمة PyQt4-devel)

أدوات بديلة

لا يجوز استعمال PyQt4 من riverbankcomputing.co.uk إلا في برمجيات رخصتها متوافقة مع GPL. لذا إن كانت الرخصة غير متطابقة مع GPL عليك إما أن تشتري رخصة مملوكة من الشركة سابقة الذكر أو استعمال مشروع pyside وقد كان يعيب pyside أنه لم يصدر نسخة لنظام ويندوز إلا أن هذا قد انتهى الآن هناك نسخة متوفرة منه للعديد من الأنظمة بما فيها ويندوز.

برامج بسيطة

مرحبا يا فلان

تعريف بالبرنامج

مثال مرحبا يا فلان

هذا مثال يسأل عن اسم المستخدم ثم يرحب به ذاكرا اسمه.

كود البرنامج

استخدم أي محرر نصوص لكتابته ثم احفظه باسم hello1.py مثلا

#! /usr/bin/python
# -*- coding: UTF-8 -*-
import sys
from PyQt4 import QtGui
 
app = QtGui.QApplication(sys.argv)
n, ok = QtGui.QInputDialog.getText(None, u"مرحبا يا عالم!", u"فضلا اكتب اسمك:")
if ok and n:
    QtGui.QMessageBox.information(None, u"أهلا بك في Qt", u"مرحبا بك يا "+n+u"!")
else:
    QtGui.QMessageBox.information(None, u"أهلا بك في Qt", u"أنت لم تكتب اسما")

لتشغيل البرنامج اكتب

python hello1.py

أو امنح البرنامج صلاحيات التنفيذ ثم انقر عليه نقرا مزدوجا.

شرح سطور البرنامج

أول سطرين في البرنامج

#! /usr/bin/python
# -*- coding: UTF-8 -*-

يتكرران في كل البرامج. الأول يحدد مسار مفسر اللغة إن كنت تجهله أو كان مختلفا اكتب

#! /usr/bin/env python

أما الثاني فيحدد الترميز التلقائي المستخدم في حفظ الملف من أجل سلاسل يونيكود النصية في كل الأمثلة سنستعمل UTF-8.

السطرين اللاحقين يقومان باستيراد الحزم والوحدات الإضافية (المكتبات) في هذا المثال هما sys و QtGui

import sys
from PyQt4 import QtGui

ونلاحظ أن هناك صيغتان لتلك العملية. الطريقة الأولى هي استيراد حزمة إلى في فضاء التسمية الخاص بها أي أنه لو كان هناك متغير اسمه argv موجود في الحزمة sys سيكون اسمه sys.argv. أما الصيغة الثانية فتجلب شيء من الحزمة إلى فضاء التسمية الحالي فإن كان هناك وحدة اسمها QtGui في حزمة PyQt4 فإنها لن تكون PyQt4.QtGui بل QtGui.

الوحدة sys تحتوي على الكثير من الأمور المتعلقة بالبيئة التي يعمل فيها البرنامج ومنها argv التي تحتوي المعاملات التي نمررها للبرنامج من سطر الأوامر. وهي تلزمنا حتى نمررها كما هي ل Qt.

الوحدة الثانية هي QtGui حيث تحتوي على الفئات classes المتعلقة بالواجهة الرسومية في Qt

كل برنامج يستعمل Qt يجب أن ينشئ كائن من نوع QApplication ويكون ذلك هكذا على سبيل المثال

app = QtGui.QApplication(sys.argv)

للحصول على مدخلات من المستخدم يمكننا استخدام

  • QInputDialog.getText - تسأل المستخدم أن يدخل نصا
  • QInputDialog.getInt - تسأل عن عدد صحيح (يمكنك تحديد القيمة الدنيا والعليا والقفزة)
  • QInputDialog.getDouble - تسأل المستخدم أن يدخل عددا نسبيا (يجوز أن يكون فيه كسور ويجوز تحديد القيمة الدنيا والعليا وعدد المنازل العشرية)
  • QInputDialog.getItem - تسأل المستخدم أن يختار عنصرا من قائمة معدة مسبقا ويجوز أن نسمح بأن يدخل عنصرا من خارج القائمة أيضا إن شاء (كما نستطيع تحديد الاختيار التلقائي).

تعيد هذه الدوال الساكنة قيمتين هما القيمة التي اختارها المستخدم ثم نحدد هل أتم المستخدم الإدخال أم نقر على إلغاء Cancel

وفي هذا المثال نحن استخدمنا QInputDialog.getText للسؤال عن اسم المستخدم.

n, ok = QtGui.QInputDialog.getText(None, u"مرحبا يا عالم!", u"فضلا اكتب اسمك:")

حيث وضعنا اسم المستخدم في المتغيّر n وبيان إتمام العملية في المتغيّر ok. أما المعاملات التي مررناها فهي على الترتيب

  • النافذة التي يتبع لها هذا الصندوق - في مثالنا None أي لا يوجد.
  • عنوان الصندوق - وفي مثالنا هي “مرحبا يا عالم!” وحرف u الذي يسبق السلسلة النصية يبيّن لمفسر بايثون أنها كائن يونيكود (أي تحتوي على حروف عربية).
  • الرسالة في متن الصندوق - وهي في مثالنا “فضلا اكتب اسمك:”

ثم قمنا بفحص النتيجة بالأداة الشرطية if هكذا (انتبه لوجود : في نهاية الجملة)

if ok and n:

وهنا عملنا علاقة “و” المنطقية بين شرطين هما:

  • أن يكون المستخدم قد أتم الإدخال ولم يضغط إلغاء (حيث فحصنا قيمة ok هل هي صواب True أم لا)
  • أن يكون قد أدخل اسما غير فارغ (لأن النص الفارغ “” في بايثون يكافئ False)

فإن تطابق الشرطان نعرض نافذة معلومات. توفر لنا Qt عدة دوال لهذه الغاية تختلف فيما بينها بالأيقونة الموجودة على جانبها وهي

  • QtGui.QMessageBox.information - لعرض معلومة
  • QtGui.QMessageBox.warning - عرض تحذير
  • QtGui.QMessageBox.critical - لعرض الرسائل الحرجة (التي تبين خطأ فادح قد يتسبب في انهيار التطبيق)
  • QtGui.QMessageBox.question - لطرح سؤال يكون جوابه عبر ضغط زر مثل نعم أو لا.

ونحن في مثالنا استخدمنا QtGui.QMessageBox.information لعرض الترحيب بالمستخدم باسمه الذي أدخله سابقا

    QtGui.QMessageBox.information(None, u"أهلا بك في Qt", u"مرحبا بك يا "+n+u"!")

أما المعاملات التي مررناها فهي على الترتيب :

  • النافذة التي يتبع لها هذا الصندوق - في مثالنا None أي لا يوجد.
  • عنوان الصندوق - وفي مثالنا هي “أهلا بك في Qt”
  • الرسالة في متن الصندوق - وهي في مثالنا “مرحبا بك يا ” ثم نضيف لها قيمة المتغير n الذي يحتوي الاسم الذي أدخله المستخدم.

تدريبات

  • استخدم QInputDialog.getDouble للسؤال عن مقدار المال ثم QtGui.QMessageBox.information لعرض “مقدار الزكاة في كذا هو كذا إن حال عليها الحول وتجاوزت النصاب” علما أن مقدار الزكاة هو 2.5% أي تضرب مقدار المال ب 0.025
  • استخدم QInputDialog.getItem كي يختار المستخدم لونا من الألوان ثم نخبره عن الألوان التي تليق به.

مساحة المستطيل

التعريف بالبرنامج

مثال مساحة المستطيل

في هذا البرنامج سنعمل حلقة تظل تدور تسأل عن طول المستطيل وعرضه ثم تعرض مساحته ثم تعيد الكرة إن رغب المستخدم في ذلك.

كود البرنامج

#! /usr/bin/python
# -*- coding: UTF-8 -*-
import sys
from PyQt4 import QtGui
 
app = QtGui.QApplication(sys.argv)
 
reply=QtGui.QMessageBox.Yes
while(reply == QtGui.QMessageBox.Yes):
    w, ok = QtGui.QInputDialog.getDouble(None, u"مساحة المستطيل!", u"فضلا ادخل طول المستطيل:")
    if ok:
      h, ok = QtGui.QInputDialog.getDouble(None, u"مساحة المستطيل!", u"فضلا ادخل عرض المستطيل:")
 
    if ok:
        a=w*h
        QtGui.QMessageBox.information(None, u"مساحة المستطيل",
            u"مساحة المستطيل الذي أدخلته تساوي "+str(a))
 
    reply=QtGui.QMessageBox.question(None, u"مساحة المستطيل",
            u"هل تريد المحاولة من جديد؟",
            QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)

شرح سطور البرنامج

أغلب الكود يشبه الكود السابق فلا داع لتكرار شرحه.

قمنا في السطر التالي باستهلال قيمة للمتغير reply لتكون QtGui.QMessageBox.Yes

reply=QtGui.QMessageBox.Yes

الهدف من هذا هو أن نلزم البرنامج دخول الحلقة التكرارية while والتي تعني “طالما” حيث أننا اشترطنا فيها أن تكون تلك القيمة التي تجعل الحلقة تظل تدور.

while(reply == QtGui.QMessageBox.Yes):

لاحظ استخدام فحص المساواة بعملية == وليس المساواة المفردة = فهذه الأخيرة تعني الإحلال assignment أي وضع قيمة في متغير.

القيمة QtGui.QMessageBox.Yes تأتي من رد المستخدم على سؤال “هل تريد المحاولة من جديد؟”

    reply=QtGui.QMessageBox.question(None, u"مساحة المستطيل",
            u"هل تريد المحاولة من جديد؟",
            QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)

فإن أجاب المستخدم بالضغط على “لا” فإن قيمة reply ستكون QtGui.QMessageBox.No عندها ستخرج الحلقة التكرارية while لأن المساواة مع QtGui.QMessageBox.Yes لم تتحقق.

أما الجزء المسؤول عن عرض النتيجة فلا يكاد يختلف عن المثال السابق

        a=w*h
        QtGui.QMessageBox.information(None, u"مساحة المستطيل",
            u"مساحة المستطيل الذي أدخلته تساوي "+str(a))

لاحظ أننا قمنا بحساب المساحة بضرب الطول في العرض ثم حفظها في المتغير a الذي سيحتوي الجواب عددا نسبيا Float ولعرض الرقم قمنا بتحويله إلى نص عبر str ثم أضفناه إلى نهاية النص “مساحة المستطيل الذي أدخلته تساوي ”

تدريبات

  • اعمل برنامج يسأل عن قيمة عددية بشكل متكرر فإن ضغط المستخدم على إلغاء cancel يقوم بعرض المجموع.
  • اعمل برنامج يطلب إدخال عدد العلامات ثم يطلب إدخالها واحدة بعد الاخرى ثم يعرض المجموع والمعدل
  • اعمل برنامج يظل يسأل عن العلامات واحدة بعد الأخرى حتى يتم الضغط على زر الإلغاء Cancel ثم يحسب المعدل والمجموع لما تم إدخاله دون احتساب التي تم الضغط على إلغاء فيها.

استعمال برنامج المصمم

التعرف على البرنامج

عند تشغيل Qt4 Designer تحصل على صندوق حوار كما في الشكل صندوق حوار إنشاء صندوق مشروع جديد

انقر على نافذة رئيسية MainWindow ثم إنشاء Create فتحصل على نافذة للتصميم كما في الشكل التالي:

شاشة التصميم

  1. النافذة الرئيسية التي نعمل على تصميمها
  2. العناصر التي نستطيع استخدامها وتسمى الودجات Widgets أي Window Gadget
  3. هرمية توزيع الودجات
  4. خصائص الكائن وتكون موزعة بحسب هرمية الاشتقاق للكائن (يمكن اختيار توزيع آخر)
  5. متصف الموارد resources (مثل الصور التي تنضدد داخل البرنامج وليس خارجه)
  6. اسم الكائن

لاختبار التصميم وتشغيله اضغط على زر CTRL+R

بعد الانتهاء من التصميم يتم حفظه في ملف ينتهي ب .ui مثلا test.ui.

تعريف الودجات الأساسية

يتوفر في Qt عدد من ودجات الأزرار كما في الصورة ومنها:

الأزرار

وتوفر عدد من ودجات الإدخال مثل

  • Line Edit التي تستخدم لإدخال نص من سطر واحد

ودجات الإدخال

وهناك ودجات مخصصة للعرض دون التحرير مثل

  • Labels وهي ملصقات عنونة توضع أمام الأزرار مثلا

ودجات العرض

وهناك ودجات تعرض عناصر متعددة مثل

  • Tree الشجرة

ودجات العناصر المتعددة

وهناك عدد من الحاويات وهي ودجات تستخدم لتجميع عدد من الودجات معا مثل

  • Frame الإطار

الحاويات

عمل صندوق حوار بسيط

اعمل مشروع جديد ونافذة رئيسية جديد ثم اختر النافذة الرئيسية بالنقر على مكان خال فيها ثم اذهب إلى صندوق محرر الخصائص Property Editor ثم حدد عنوان النافذة windowTitle ليكون “محول وحدات القياس” وحيث أن قائمة الخصائص طويلة يمكنك ترتيبها هجائيا بالنقر على الترتيب Sort الموجود على أيقونة إعداد محرر الخصائص في الزاوية. أو يمكنك البحث عن جزء من الخاصية التي تريد تعديلها مثل كلمة title كما في الصورة

البحث في الخصائص

قم بإضافة ملصق عنوان ثم تغيير نصه عبر Text أو عبر النقر المزدوج

إضافة ملصق عنونة

أكمل تصميم واجهة البرنامج بإضافة بقية اللصقات ثم أضف صندوق Combo box ثم إضافة الوحدات المطلوب التحويل بينها عبر النقر بالزر الأيمن ثم Edit Items ثم أضف Double Spin Box ثم أضف الزر Button لتبديل الوحدتين.

شاشة برنامج التحويل

قم بتجربة التصميم عبر CTRL+R أو عبر Form ثم Preview أو Preview in ثم اختر السمة Style التي تريد.

طريقة صف الودجات

عيوب صف الودجات مباشرة دون تخطيط

  • عدم الدقة في تعيين الأماكن بسبب طبيعة حركة الفأرة
  • عند التكبير تظل الودجات (خصوصا صناديق الإدخال) بنفس حجمها الأول
  • عند استخدام خط أكبر أو ما شابه قد يفيض النص خارج الودجة لضيق مساحتها أو قد تصبح المسافات بينها غير منطقية.
  • عند استعمال البرنامج في بيئة عربية لن تنقلب أماكن توزيعها.

ودجات التخطيط

العناصر المستخدمة في عمل تخطيط layout للتصميم هي

عناصر التخطيط

المخططات الأفقية والعمودية

احفظ التصميم السابق باسم جديد ثم انقر بالزر الأيمن على مكان خال في النافذة الرئيسية ثم اختر Layout Vertically حتى تحصل على ما يشبه الصورة المرفقة ثم قم بتكبير أو تضغير النافذة ولاحظ الفرق بنفسك (أيضا اضغط على CTRL+R لتجريب التصميم)

تكديس العناصر فوق بعضها

وبهذا يصبح البرنامج مكون من صفوف منتظمة مكدسة فوق بعضها. وحيث أن أغلب البرامج لا تكون مجرد صفوف يمكن أن يكون كل صف فيها عبارة عن تكديس أفقي للعناصر وذلك بوضع Horizontal layout كما في الخطوات التالية

تكديس العناصر أفقيا

الجداول grid

إن من أجمل المخططات هي التي تتم عبر Grid Layout حيث هناك شبكة تشبه الجدول تتوزع العناصر فيها بين نقاطها مع إمكانية عمل توسع span على أكثر من صف أو أكثر من عمود.

لتحويل التصميم الأولي إلى مخطط الجدول حمل الملف الأول ثم احفظه باسم جديد ثم انقر بالزر الأيمن على مكان خال في النافذة الرئيسية للتصميم ثم اختر من القائمة Layout ثم Layout in grid

قم بإعادة تحريك الودجات مرة أخرى حتى تتأكد من أنها موضوعة في مكانها الصحيح (عندما تجرها إلى خلية في صف موجود مسبقا تضيء باللون الأحمر وعندما تعمل صف أو عمود جديد يظهر خط ممتد باللون الأزرق)

إذا أردت أن يمتد زر “تبديل الوحدتين” على قدر كل الأعمدة قم بجر طرفه الأيمن لتوسيعه وستجد أنه يتوسع فقط بقدر الأعمدة (لا يوجد عمود ونصف)

تخطيط الجدول

النموذج

المباعدات

عمل القوائم

ملف الموارد qrc

تضمين صورة

برمجة التصميم

تحويل ملف التصميم ui إلى ملف بايثون

كل ما عليك هو استعمال الأداة pyuic4 كما في هذا المثال

pyuic4 uitest.ui -o uitest.py

حيث:

  • uitest.ui هو اسم ملف التصميم
  • uitest.py هو اسم ملف بايثون الناتج

برنامج لتشغيل التصميم

كود البرنامج

كل ما علينا الآن هو عمل برنامج يستورد النافذة الرئيسية من الوحدة الناتجة عن ملف ui (وهي في مثالنا وحدة uitest) ثم يعمل فئة مشتقة منها ويعمل كائن من تلك الفئة.

import sys
from PyQt4 import QtGui
from uitest import Ui_MainWindow
 
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)
 
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

شرح كود البرنامج

السطر التالي يستورد النافذة الرئيسية Ui_MainWindow من التصميم. لاحظ أن اسمها يكون مسبوقا ب Ui_ ثم الاسم الذي حددته للكائن في برنامج التصميم

from uitest import Ui_MainWindow

ثم قمنا بعمل فئة مشتقة من فئة QtGui.QMainWindow الموجودة في PyQt4 و التي جاءت من التصميم Ui_MainWindow

class MainWindow(QtGui.QMainWindow, Ui_MainWindow):

تم نأتي لكود استهلال كائن من هذه الفئة

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)

وأول ما يقوم به هو استهلال الفئات التي تم اشتقاقه منها. ويجوز أن تأخذ دالة الاستهلال init الخاصة بهذه الفئة معاملات خاصة بها لا علاقة لها بأصليها QtGui.QMainWindow و Ui_MainWindow.

ثم نستدعي دالة setupUi التي تبني التصميم

        self.setupUi(self)

وخارج الفئة نعمل الكود الذي ينفذ عند تشغيل البرنامج وهو استهلال التطبيق QApplication ثم إنشاء كائن من فئة النافذة الرئيسية التي صممناها ثم إظهارها

app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()

ثم ننفذ التطبيق عبر الدالة app.exec_ ونرسل الكود الذي تعيده للنظام عبر sys.exit هكذا

sys.exit(app.exec_())

وثائق خارجة

معالجة الأحداث

الإشارات singals والأتلام slots

محول الوحدات

انقر بالزر الأيمن على مكان خال في النافذة الرئيسية للتطبيق ثم اختر Change Singals/Slots ثم انقر على زر الإضافة أسفل الأتلام slots وأضف سطر يحتوي

fromValueChanged(double)

انقر على صندوق إدخال المقدار الذي نريد تحويله وتأكد من أن اسم الكائن ObjectName هو fromValue ثم من محرر الإشارات والأتلام Signal/Slot Editor انقر على زر الإضافة ثم غيّر المرسل <sender> إلى fromValue ثم غيّر <signle> إلى valueChanged التي تأخذ double ثم غير المستلم <receiver> إلى النافذة الرئيسية MainWindow ثم غير التلم <slot> إلى التلم الذي أضفناه للنافذة الرئيسية في الخطوة السابقة وهو

fromValueChanged(double)

قم بحفظ ملف التصميم ui ثم أعد توليد ملف python بواسطة

pyuic4 converter.ui -o converter.py

اعمل ملف نصي جديد يحتوي الكود الذي يشغل التصميم ومحتوياته هي

#! /usr/bin/python
# -*- coding: UTF-8 -*-
 
import sys
from PyQt4 import QtGui
from converter import Ui_MainWindow
 
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)
 
    def fromValueChanged(self, v):
        self.toValue.setText(str(v*1.609344))
 
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

وبهذا أصبح برنامجنا يحول من الميل إلى الكيلومتر برنامج التحويل يعمل

تدريبات

  • أضف دعم أكثر من وحدة في البرنامج السابق عبر استخدام إشارة currentIndexChanged والتي تأخذ عدد صحيح int على ودجة combo Box.
  • استخدم إشارة clicked للتبيل بين الضرب والقسمة مع قلب عناوين الوحدتين بين من وإلى
  • صمم برنامج به صندوق لإدخال عدد يتم إضافته للعداد ويحتوي على زر تصفير.
آخر تعديل:: 23 نيسان 2015 الساعة 00:20 (تحرير خارجي)