دورة البايثون المتقدمة لسُلَيْمان القسيمي

دورة البايثون المتقدمة:

بسم الله الرحمن الرحيم.
حياكم الله زوار هذه الصفحة وجعلكم من عباده الصالحين.
نُقدِم لكم هنا دورة البايثون المتقدمة التي بدأت في التاسع عشر من شهر ذي القِعدة لعام 1443، واستمرت لمدة عشرة أيام.
تم تقسيم الدروس إلى مقاطع صغيرة حسْب الموضوعات.
ستجد عزيزي الزائر صندوق للخيارات يمكنك اختيار المقطع الذي تريد الاستماع إليه،
ثم الضغط على زر التشغيل أو التحكم في المقطع من المشغل الموجود أسفل الصندوق.
كما يمكنك التنقل السريع بين بداية كل درس بالضغط على علامة النجمة *.
ستجد عنوان الدرس، وهذه هي مقدمته فقط،
وتحته العناوين الفرعية التي تم الحديث عنها.
كما نجد هنا أيضا ملخص كتابي لكل درس، ودليل للكلاسات والدوال والخصائص التي وردت في الدرس.
اختصارات التنقل السريع:
الرقم 1: للتنقل بين ملخصات الدروس.
الرقم 2: للتنقل بين دليل كلاسات ودوال وخصائص الدرس.
الرقم 3: للتنقل بين التمرينات.
حرف h: للتنقل بين جميع العناوين.

كما لا ننسى قبل البدء أن نشكر جميع القائمين على الدورة والمشاركين بالمجهودات المتنوعة.
الأستاذ علي العمري: التنسيق مع المعلم والمتعلمين، وتوفير المكان المناسب لتلقي العلم، وأيضا رفع الدروس.
الأستاذ سُليْمان القسيمي: الشرح والتطبيق ومتابعة المتدربين بالتمرينات ونحوه.
الإخوة محمد هادي بالطيب، ومصطفى محمود، وعبد الله حيدر:
تلخيص الدروس كتابيا وتقسيمها حسْب الموضوعات.
بارك الله في الجميع وجعله في موازين حسناتهم.





ملخص الدرس الأول:

ملخص الدرس الأول الذي بث مباشرة على منصة زوم يوم الأحد 19/06/2022 : السلام عليكم و رحمة الله و بركاته . بعد أن تقوموا بتنزيل البايثون من الروابط التي أرسلتها لكم سنقوم بتثبيته و تفاصيل التثبيت تجدونها في المحاضرة الصوتية . بعد ذلك نفتح موجه الأوامر cmd و نكتب هكذا : python فلو ظهرت لكم رسالة فيها إصدار البايثون الذي قمتم بتثبيته ثم هذا الرمز : >>> فهذا يعني أن كل شيء على ما يرام و مبروك عليكم البايثون و ننطلق في أولى خطوات دورتنا . البايثون يتضمن مدير تنزيل الحزم أو المكتبات و يعرف إختصارا ب pip . يمكنكم ، و هذا أمر إختياري ، تحديث pip بكتابة هذا الأمر بموجه الأوامر : python -m pip install --upgrade pip الآن سنقوم بتثبيت مكتبة wx هكذا : pip install wxpython و هذا الأمر هو الذي نقوم بواسطته بتنزيل كل المكتبات التي نحتاجها في برامجنا نقوم فقط بتغيير اسم المكتبة . بعد تثبيت المكتبة يصبح بإمكاننا إستخدامها و الإستفادة من المزايا التي توفرها لنا . لكتابة أكواد برامجنا يمكننا إستخدام عدد كبير من محررات الأكواد لكن أغلبها غير متوافق مع قارئات الشاشة و قد شرحت لكم محررا متميزا و هو visual studio code و تجدون الشرح التفصيلي لكيفية تثبيته و إستخدامه في المحاضرة الصوتية . بغض النظر عن محرر الأكواد الذي ستختارونه ، سكتب الآن أول كود بإستخدام wx . لإستخدام أي مكتبة لابد من إستدعائها و مكتبتنا سنستدعيها هكذا : import wx ثم سننشىء تطبيق جديد بواسطة الدالة App() فنعرّف متغيرا هكذا : app = wx.App() و ملاحظة هنا لابد من الإشارة إليها و هي في wx كل الكلاسات و الدوالي تبدأ بحرف كبير و إن كانت الدالة تتكون من كلمتين أو أكثر فإن كل كلمة حرفها الأول دوما يكون كبير . بعد إستدعاء المكتبة و إنشاء التطبيق يصبح بإمكاننا كتابة برنامجنا . أول ما سنكتبه هي رسالة ترحيبية بالمستخدم هكذا : wx.MessageBox("مرحبا بكم في دورة البايثون المتقدمة للمكفوفين ","ترحيب") الدالة MessageBox تستقبل عدة معامل سنكتفي في هذه المرحلة باثنين فقط و هما نص الرسالة و عنوانها . ثم سنكتب محاكاة لتسجيل الدخول لموقع أو تطبيق معين . سنحتاج لتعريف متغيرين يدخلهما المستخدم و لكن قبل ذلك سنحدد متغيرين تكون قيمتهما اسم المستخدم و كلمة المرور الصحيحتان ، هكذا : user ="python" password = "123" و هنا سيظهر البرنامج رسالة للمستخدم تطلب منه ادخال اسم المستخدم الخاص به هكذا : username = wx.GetTextFromUser("أدخل هنا اسم المستخدم","اسم المستخدم ") و نفس الشيء لكلمة المرور الخاصة به هكذا : pwd = wx.GetTextFromUser("أدخل هنا كلمة المرور ","كلمة المرور") و للتحقق من هوية هذا المستخدم سنضع شرطا هكذا : if username == user and pwd == password : اذا ادخل المستخدم بيانات صحيحة نظهر له الرسالة التالية : wx.MessageBox("لقد تم تسجيل دخولكم بنجاح","نجاح") else: أما إذا أدخل بيانات خاطئة فستظهر له هذه الرسالة : wx.MessageBox("لقد أدخلتم اسم مستخدم أو كلمة مرور غير صحيحة","خطأ")

تمرين على الدرس الأول:

الإخوة الكرام سلام الله عليكم. من خلال متابعتكم للدرس الأول من دورة البايثون المتقدمة, اكتب برنامجًا لحساب ضريبة القيمة المضافة وفقًا للمعايير الآتية: 1. استخدم دالة wx.GetTextFromUser لأخذ قيمة المبلغ من المستخدم, ودالة wx.MessageBox لعرض النتائج. 2. قم بعرض رسالة خطأ عن طريق دالة wx.MessageBox في حال لو قام المستخدم بالضغط على موافق دون أن يدخل المبلغ. 3. معادلة حساب ضريبة القيمة المضافة هي كالتالي: السعر الإجمالي يساوي المبلغ المدخل + المبلغ المدخل مضروبًا في نسبة الضريبة. مثال: لو كان المبلغ الذي أدخله المستخدم هو 10 وكانت نسبة الضريبة 5% فإن طريقة إيجاد المبلغ الإجمالي مع الضريبة سيكون هكذا المبلغ الإجمالي = 10 + 10 * (5/100) ابذلوا ما بوسعكم لترجمة هذه المسألة الرياضية إلى برنامج, ولا بأس بأن أذكركم أنه سبق وأن عملنا هذا التمرين في الدورة السابقة من دون استخدام الواجهات الرسومية.

ملخص الدرس الثاني - الكلاس والوراثة

مفهوم الكلاس والوراثة: فكر معي في واقع الحياة اليومية. لو دخلت إلى عالم التصنيع والإنتاج فستعلم أن جميع المصانع تتخذ لها قالبًا للمنتجات يحدد لك المعالم الأساسية لذلك المنتج. ينطبق الحال أيضًا على الفنان التشكيلي ... فهو يضع قالبًا من الجبس ربما ويقوم فيه بصب أعماله التشكيلية فيه للحصول على نسخ متشابهة من حيث الخصائص العامة. لو جئنا للبرمجة, فالحال هو الحال. فالكلاس هو مخطط برمجي عام يحتوي على متغيرات, ونسميها خصائص, ودوال, نسميها وظائف, تندرج كلها تحت غرض واحد. يمكن للمبرمج أن يأخذ ما يشاء من نسخ فريدة من هذا الكلاس للاستفادة من خصائصه ودواله العامة. النسخة التي يأخذها المبرمج من الكلاس تسمى كائنًا, ويمكن أن يتفرد كل كائن من الكائنات بقيم مختلفة للخصائص العامة الموجودة في الكلاس. كيف نترجم هذا الكلام بالكود؟ انظروا هذا المثال أريد تعريف كلاس بايثون لتمثيل خصائص السيارة, وأقصد هنا أية سيارة تخطر على بالك. قد يشتمل المخطط العام لمواصفات السيارات على هذه الخصائص الطراز model لون السيارة colour ناقل الحركة transmition محرك السيارة engine ... دعونا الآن نترجم هذا الكلام إلى كود بايثون. لتعريف كلاس في بايثون نستخدم الكلمة class متبوعة باسم الكلاس الذي نريد إنشاؤه بعدها نضع علامة النقطتين كدلالة على أن هذا الكلاس سيحوي أكوادًا خاصة به, بمعنى أخر سيترتب عليك وضع الأكواد التالية على مستوى إزاحة معين. تحت عنوان الكلاس سنبدأ بتعريف المتغيرات الأساسية التي تنتمي إلى الكلاس, أو بمعنى آخر, الخصائص الأساسية للكلاس هكذا: class Car: model = None colour = "black" transmition = "automatic" engine = "4 cilender" لننشئ الآن كائنًا من هذا الكلاس هكذا. avalon = Car() كما تلاحظون ... قمنا بتعريف متغير ليحمل قيمة الكائن, ثم أسندنا إليه عملية تنفيذ الكلاس, ولعلكم انتبهتم إلى كون أن عملية تنفيذ الكلاس مطابقة تمامًا لعملية تنفيذ الدوال. طيب دعونا الآن نقوم بتعديل خصائص الكائن avalon هكذا avalon.model = "toyota" avalon.colour = "wight" avalon.engine = "6 cilender" لنطبع الآن لون الكائن avalon print(avalon.colour) الناتج: wight ماذا لو أنشأنا كائنًا آخر من الكلاس Car ford = Car() ford.model = "navigator" ford.colour = "green" ford.engine = "6 cilender" طيب لو طبعنا الآن لون كلًا من الكائن avalon والكائن ford print(avalon.colour) الناتج: wight print(ford.colour) الناتج: green الملاحظ هنا أن هذين الكائنين يشتركان في طبيعة الخصائص العامة, إلا أن قيمة كل خاصية تختلف بين كائن وآخر حسب الحاجة. راجعوا التسجيل للوقوف على جميع التفاصيل الصغيرة. كتابة وظائف الكلاس. اتفقنا من البداية أن الدوال التي تنتمي إلى كلاس معين نسميها وظائف methods. لا تختلف طريقة تعريف الوظائف عن ما نفعله لتعريف الدوال القياسية. المختلف هنا هو أنه لا بد من وضع كلمة self كأول معامل للدالة أثناء الإنشاء. وببساطة: كلمة self تُستخدم كاسم مستعار للنسخة التي لم تؤخذ بعد من الكلاس. بمعنى آخر هي إشارة إلى الكلاس نفسه أثناء الإنشاء. خذوا معي هذا المثال على نفس كلاس السيارات, وارجعوا إلى التسجيل للمزيد من التفاصيل. class Car: model = None colour = "black" transmition = "automatic" engine = "4 cilender" def showInfo(self): print(f"طراز السيارة: {self.model}") print(f"لون السيارة: {self.colour}") print(f"نوع ناقل الحركة: {self.transmition}") print(f"نوع المحرك: {self.engine}") الآن: يمكن استخدام الوظيفة على أي نسخة من نسخ الكلاس بكتابة اسم الكائن متبوعًا بنقطة بعدها اسم الدالة مع قوسَي المناداة لنطبق ذلك على الكائن avalon avalon.showInfo() دالة البناء __init__ في الكلاسات: تستخدم دالة الإنشاء __init__ لإعداد خصائص الكائن أثناء عملية الإنشاء بحيث لا يضطر المبرمج إلى كتابة كل خاصية على حدة عند إنشاء أكثر من كائن لنفس الكلاس. وفي الحقيقة, حينما تقوم بتنفيذ الكلاس هكذا Car() فإنك بطبيعة الحال تقوم باستدعاء الدالة الإنشائية التي قامت بايثون بتعريفها لك من وراء الكواليس. في الحقيقة, دالة __init__ التي بايثون تقوم بتجهيزها لك لا تعدوو عن كونها دالة شكلية تنفذ لك اللاشيء. أما إن كنت تريد الاستفادة منها فعليًا فعليك بإعادة تعريفها أثناء بناء الكلاس. نعود إلى كلاس السيارات, ولنصنع له دالة بنائية class Car: def __init__(self, model, colour, transmition, engine): self.model = model self.colour = colour self.transmition = transmition self.engine = engine def showInfo(self): print(f"طراز السيارة: {self.model}") print(f"لون السيارة: {self.colour}") print(f"نوع ناقل الحركة: {self.transmition}") print(f"نوع المحرك: {self.engine}") انتبهو إلى التغيرات التي طرأت على الكلاس. 1. تخلصنا من كتابة خصائص الكلاس تحت جملة التعريف الخاصة به مباشرة وقمنا بنقلها إلى دالة البناء. 2. قمنا بتعريف دالة بناء تأخذ منك أربع معاملات, كل معامل يمثل إحدى خواص الكلاس. 3. داخل دالة البناء قمنا بالإشارة إلى اسم الكائن الذي لم يتم إنشاؤه بعد من خلال الاسم المستعار self وتم تعيين خواص هذا الكائن من خلال القيم الذي سيقوم المستخدم بتمريرها كمعاملات للدالة الإنشائية __init__ طيب لنعود الآن إلى تعريف الكائن avalon مع وجود الدالة البنائية. avalon = Car("toyota", "black", "automatic", "6 cilender") لنعرف الكائن ford أيضًا ford = Car("navigator", "green", "automatic", "6 cilender") هذا كل ما في الأمر. اختصرت علينا الدالة البنائية عملية تعيين قيم خصائص الكائن بتمرير قيمة كل خاصية في الخانة التابعة لها في الدالة. الوراثة في بايثون: يمكن أن نفهم الوراثة في البرمجة عمومًأ على أنها العملية التي يقوم فيها كلاس ص بأخذ خصائصه ووظائفه من الكلاس س ويقوم بدوره بالتعديل عليها وإضافة خصائص أخرى فريدة. كيف تتم عملية الوراثة إذًا؟ ببساطة: لو أراد المبرمج أن ينشئ كلاسًا جديدًا بحيث يرث كلاسه الجديد من كلاس آخر كلما عليه هو أن يكتب كلمة class ثم مسافة بعدها اسم الكلاس الجديد , ثم قوسين هلاليَين يضع بينهما اسم الكلاس الذي يريد أن يرث منه, ولا ينسى النقطتين نهاية السطر مثال: لدي كلاس اسمه Person وأردت أن أنشئ كلاسًا جديدًا باسم Teacher بحيث يرث خصائص ووظائف الكلاس Person الكود: class Teacher(Person): # هنا نكمل عملية بناء الكلاس الجديد نظام الإدارة المدرسية من خلال مفهومي الكلاس والوراثة: تم خلال الدرس الماضي بناء مشروع بسيط للإدارة المدرسية. احتوى هذا المشروع على كلاسَين اثنين الأول Person والذي يحتوي على المعلومات الأساسية لأفراد المدرسة الثاني Teacher: وهو كلاس يرث خواصه من الكلاس Person ويضيف إليها المعلومات التي تخص المعلمين. هناك كلاس ثالث لم يتسع الوقت إلى بنائه من المفترض أن يكون لتمثيل الطلاب, بحيث يرث بدوره خصائصه الأساسية من الكلاس Person> لكي لا أطيل عليكم, سأضع لكم ما أسعفنا به الوقت من أكواد المشروع وأترك لكم التفكير في الكود الخاص بكلاس الطالب من باب اختبار الفهم الكود: import wx app = wx.App() class Person: def __init__(self, name, address, school, role): self.name = name self.address = address self.school = school self.role = role def showInfo(self): wx.MessageBox(f"""الاسم: {self.name} المدرسة: {self.school} العنوان: {self.address} الوظيفة: {self.role}""", "المعلومات الأساسية") class Teacher(Person): def __init__(self, name, address, school, role, subject): super().__init__(name, address, school, role) # لا بأس أن أعلق على هذا السطر لأهميته, هو في الحقيقة سطر ضروري يقوم باستدعاء دالة البناء الخاصة بالكلاس الموروث منه داخل دالة بناء الكلاس الوارث, كل هذا بفضل دالة super() التي تسمح بالوصول إلى الدوال غير المعدلة من الكلاس الأب في الكلاس الابن. self.subject = subject def showSubject(self): wx.MessageBox(self.subject, "المادة") teachers = [] while True: option = input("1. إضافة معلم\n2.عرض المعلمين\n3. تفاصيل معلم") if option == "1": name = input("اكتب اسم المعلم") address = input("اكتب عنوان سكن المعلم") school = input("اكتب اسم المدرسة التي ينتمي إليها هذا المعلم") role = "معلم" subject = input("أدخل المادة التي يقوم هذا المعلم بتدريسها") teachers.append(Teacher(name, address, school, role, subject)) wx.MessageBox("تم إضافة المعلم بنجاح", "نجاح") elif option == "2": for teacher in teachers: print(teacher.name) elif option == "3": name = input("اكتب اسم المعلم الذي تريد معرفة معلوماته الأساسية") for teacher in teachers: if teacher.name == name: teacher.showInfo() break else: wx.MessageBox("لم يتم العثور على اسم المعلم في النظام")

تمرين على الدرس الثاني:

الإخوة الكرام مرتادي دورة البايثون المتقدمة للمكفوفين أسعد الله صباحكم. من خلال متابعتك لدرس الكلاس والوراثة قم بكتابة برنامج لإدارة محل إيجار المركبات إذا علمت أن هذا المحل يقوم بتأجير كلًا من السيارات الخصوصية وشاحنات النقل بسعر يومي. ينبغي أن يحتوي البرنامج على المواصفات الآتية: 1. كلاس عام يحتوي على البيانات المشتركة للسيارات الصغيرة والشاحنات يحتوي على خاصيتي الاسم وسعر الإيجار. 2. كلاس للسيارات الخصوصية يرث من الكلاس العام ويضيف إليه خاصية نوع السيارة هل هي سيارة دفع رباعي SUV أو سيارة صغيرة (سيدان). 3. كلاس خاص بالشاحنة يرث خصائصه من الكلاس العام ويضيف إليه مقدار الحمولة للشاحنة. 4. اعرض للمستخدم  خيارين للتفاعل مع البرنامج. الخيار الأول إضافة مركبة: ومن المفترض أن يسأله البرنامج ما إذا كان يريد إضافة سيارة خصوصية أم شاحنة. الخيار الثاني: عرض قائمة المركبات المتاحة. ولا بأس أن تصنع لي أي برنامج من أفكارك لعرض ما فهمته حول درس الكلاس والوراثة إن لم ترق لك فكرة هذا التمرين.

الدرس الثالث إنشاء النوافذ

السلام عليكم و رحمة الله و بركاته . اليوم سندرس النافذة في wx . النافذة هي عبارة عن كلاس من كلاسات مكتبة wx . و هذا الكلاس فيه صفات لكلاس لنافذة مجردة و فارغة لا تحتوي على أي عنصر . و على فكرة كل العناصر في wx هي عبارة عن كلاسات . لو أردنا أخذ نسخة من كلاس النافذة أو الإطار نكتب هكذا : import wx app = wx.App() و هذه الاسطر السابقة تعرضنا اليها في درسنا الأول و لا تحتاج لشرح إضافي . قلت لكم لو أردنا أخذ نسخة من كلاس النافذة و اسمه Frame نكتب الامر الموالي : window = wx.Frame(None , title = "برنامجي الأول") عرّفنا متغيرا window و يمكنكم تسميته بما تريدون و جعلنا قيمته نسخة من كلاس Frame من wx و يتطلب معاملا اجباريا و هنا هو None أي لاشيء لأن هذه النافذة ليس لها حاوي بل هي النافذة الرئيسية و حاويها "لاشيء" كما يمكن تمرير معامل اختيارية مثل عنوان النافذة و كما ترون اكتفينا ـ، في هذه المرحلة ، بمعامل اختياري واحد . ثم نظهر النافذة هكذا : window.Show() و لكي لا تختفي النافذة بمجرد ظهورها سنجعل التطبيق الذي عرفناه في البداية يشتغل بشكل مستمر حتى يتمكن المستخدم من التعامل مع النافذة و التفاعل معها . app.MainLoop() و عند تنفيذ هذا الكود ستظهر لنا نافذة برنامجنا الأول و ستبقى موجودة الى أن يقرر المستخدم اغلاقها . هذا الكود أظهر لنا النافذة صحيح لكن كتابته بهذه الطريقة باستخدام كلاس النافذة مباشرة لا يصح برمجيا بل يجب استخدام كلاس خاص ببرنامجنا يرث خصائصه و من كلاس الاطار في wx . اذن سنعيد كتابة كود برنامجنا بطريقة أكثر احترافية و تكون متناسقة و مفيدة . نبدأ طبعا بإستدعاء المكتبة و تعريف التطبيق . import wx app = wx.App() سننشىء كلاس خاص ببرنامجنا يرث كل خصائصه و وظائفه من كلاس الاطار فيwx الذي اذكركم باسمه Frame . class mainwindow(wx.Frame): السطران المواليان تم شرحهما و الإستفاضة في ذلك في المحاضرة الصوتية عودوا الى هذا الشرح لفهمهما بشكل جيد . def __init__(self): super().__init__(None,title= "برنامجي الأول") سنستفيد من الدالة Center لنجعل نافذتنا تتموقع وسط شاشة الحاسوب هكذا : self.Center() الآن سنشرع في تعبئة نافذتنا بالعناصر التي نريدها و التي نحتاجها لتشغيل برنامجنا . و لأن نافذتنا فارغة فقبل وضع العناصر عليها يجب"وضع " فراش أو السفرة كما نفعل مع طاولة الأكل قبل وضع الصحون و الأواني عليها " و هو العنصر " اللائحة" . اللائحة Panel هي كلاس من كلاسات مكتبتنا و ننشئها هكذا : p = wx.Panel ( self , -1 ) self هو أول معامل اجباري و هو الحاوي و هو يرمز الى كلاس النافذة . -1 هو id و يمكننا تحديده لكل عنصر كما نريد و لكن اعطائه القيمة -1 معناه أننا نترك لبايثون صلاحية تحديده حتى لا ننسى نحن و نعطي نفس id لأكثر من عنصر و بالتالي نتجنب تعارض بين id اعناصر من نافذتنا . سننشىء العناصر الضرورية لإدخال اسم المستخدم و لقبه العائلي للبرنامج . و سنستخدم لذلك مربعات التحرير . في نوافذ البرامج ( أو في مواقع الأنترنت) عندما نجد مربع تحرير نجد فوقه ( أو على يمينه أو شماله ) جملة أو كلمة مكتوبة يراها المبصرون و يقرأها الناطق للمكفوفين و هي جملة إرشادية تعلم المستخدم ما الذي عليه أن يكتبه في مربع التحرير . هذه الجملة أو الكلمة تسمى برمجيا في مكتبةwx ب StaticText . تابعوا معي الآن كيف سأنشىء مربع التحرير الأول . قبل المربع نفسه ننشىء جملته الارشادية هكذا : wx.StaticText(p , -1 , " الإسم الإول") كما ترون ممرنا لهذا العنصر المعامل الاجباري الاول و هو " الحاوي" و هنا حاوي الجملة الارشادية هو اللائحة و مررنا كذلك id الخاص بهذا العنصر و نص الجملة الإرشادية لنعرف ماذا سنكتب في مربع التحرير . و مربع التحرير فيwx يسمّى TextCtrl و ننشئه هكذا : سنجعل مربع التحرير الأول في متغير لأننا ربما نحتاج للتعامل معه في باقي البرنامج . self.first = wx.TextCtrl(p,-1) و أنتم الآن تعودتم على المعامل التي علينا تمريرها عند إنشاء أي عنصر و هي الحاوي و id و هي المعامل الاجبارية و توجد عدة معامل اختيارية ( و في كثير من الاحيان ضرورية) سنتعرض اليها في حينها خلال هذه الدورة . كان ممكنا أن نسمي هذا المربع first فقط و ليس self.first و لكني أسميته self.first لأننا ، ربما ، نحتاج للتعامل معه في دوالي داخل هذا الكلاس أي داخل هذه النافذة . و سنقوم بإنشاء مربع التحرير الثاني بنفس الطريقة هكذا : wx.StaticText(p , -1 , " اللقب العائلي ") self.last = wx.TextCtrl(p,-1) و سنضيف لنافذتنا زر يقوم بإغلاق البرنامج عندما نضغط عليه . سنعرف متغيرا ليرمز للزر هكذا : close = wx.Button(p,-1," إغلاق البرنامج") مررنا لهذا الزر الحاوي و id و النص الظاهر للزر . الزر عند الضغط عليه يقوم بتنفيذ أمر ما و هذا الأمر يسمى " حدث event " و لربط الزر بالحدث الذي سينفذه نكتب : close.Bind(wx.EVT_BUTTON , self.onclose ) الدالة Bind تطلب منا تحديد نوع الحدث الذي نريده و هنا نوع الحدث هو " الضغط على الزر و نعرفه برمجيا ب wx.EVT_BUTTON كل الحروف بعد النقطة هي حروف كبيرة كما تطلب هذه الدالة أن نحدد لها اسم الدالة التي سنقوم فيها ببرمجة الحدث الذي نريد تنفيذه بالضغط على الزر . ثم قبل الخروج من دالة البناء سنظهر النافذة هكذا : self.Show() الآن نحن خارج دالة البناء و لكننا دوما داخل الكلاس الرئيسي و سننشىء دوالي البرنامج و في مثال اليوم لدينا دالة واحدة و هي الدالة التي ستغلق البرنامج عندما نضغط على زر " إغلاق البرنامج " . def onclose(self, evt): يجب أن يكون اسم هذه الدالة هو نفسه الذي مررناه للدالة Bind. هذه الدالة هي دالة حدث و نمرر لها معاملين : -self بما أنها ستشتغل داخل هذا الكلاس. -المعامل الثاني لهذه الدالة يمرره الزر تلقائيا عندما نضغط عليه و نحن علينا أن نخصص له مكانا و نعطيه اسما. و نكتب في هذه الدالة الأمر الذي نريد تنفيذه عند الضغط على الزر و هنا نريد أن نغلق البرنامج بمجرد الضغط على الزر فنكتب الأمر هكذا : wx.Exit() و في الأخير ننادي على كلاسنا mainwindow() و نترك التطبيق يشتغل بإستمرار إلى حين يقرر المستخدم إغلاق البرنامج بالضغط على الزر الذي خصصناه لذلك أو بالضغط على alt + f4 app.MainLoop() و إليكم الآن الكود كاملا . 1-كود النافذة بدون كلاس خاص بها : import wx app = wx.App() window = wx.Frame(None , title = "برنامجي الأول") window.Show() app.MainLoop() 2-نافذة لها كلاسها الخاص و وضعنا عليها عدد من العناصر : import wx app = wx.App() class mainwindow(wx.Frame): def __init__(self): super().__init__(None, title= "برنامجي الأول") p = wx.Panel ( self , -1 ) self.Center() wx.StaticText(p , -1 , " الإسم الإول") self.first = wx.TextCtrl(p,-1) wx.StaticText(p , -1 , " اللقب العائلي ") self.last = wx.TextCtrl(p,-1) close = wx.Button(p,-1," إغلاق البرنامج") close.Bind(wx.EVT_BUTTON , self.onclose ) self.Show() def onclose(self, evt): wx.Exit() mainwindow() app.MainLoop()

دليل كلاسات ودوال وخصائص الدرس الثالث:

wx.Frame() : كلاس الإطار, والمعاملات الإجبارية هو الحاوي Parent, بعد ذالك تأتي المعاملات الإختيارية ومنها العنوان الخاص بالنافذة title. Center() : الدالة التي تجعل النافذة تتوسط الشاشة, طبعا نستخدمها بستخدام كلامة self لو من داخل الكلاس self.Center(). wx.Button() : الclass الخاص بالأزرار, المعاملات الإجبارية هي الحاوي parent, المعاملات الأخرى غير إجبارية ومنها, id, title. wx.TextCtrl() : الclass المختص بحقول التحرير, المعاملات الإجبارية هي الحاوي parent, غير الإجبارية هو الid. wx.Panel() : لإنشاء لائحة جديدة, تحتوي على معامل يكون الحاوي لهذه اللائحة وهو يكون الإطار فتكون القيمة self. Bind() : الدالة التي تختص بربط الأحداث, مثلا ربط زر بحدث, والمعاملات هي نوع الحدث, وتناول الأخ سليمان حدث ربط الزر وهو wx.EVT_BUTTON, ثم نقم بالفصل بفاصلة, ثم نقوم بكتابة اسم الدالة المراد ربط الزر بها, طبعا لإستخدامها نكتب اسم المتغير الذي تم إسناد الزر إليه ثم نقطة ثم اسمها والمعاملات. wx.Exit() : دالة تُستخدم لإغلاق البرنامج. Show() : الدالة المختصة في إظهار النافذة, طبعا لإستخدامها يجب كتابتها self.Show() في حال تم كتابتها عبر class.

تمرين على الدرس الثالث:

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

ملخصالدرس الرابع:

موجز في درسنا الرابع من الدورة, تم التطرق إلى النقاط التالية: - الوصول إلى محتويات ال TextCTRL. - تعديل محتوى المربعات النصية TextCtrl بشكل برمجي. - تغيير مكان تركيز المؤشر برمجيًا. - التعامل مع الأنماط المختلفة لمربعات التحرير. - بداية مع مشروع إدارة الدرجات. أولًا: الوصول إلى محتوى المربعات النصية وتعديلها في الدرس السابق, حينما قمنا بإنشاء المربعات النصية, قمنا بكتابة الإيعازات الخاصة بذلك في دالة الإنشاء الخاصة بالكلاس على النحو التالي: first = wx.TextCtrl(p, -1) حيث تم تعريف متغير محلي باسم first وتم فيه إسناد مربع التحرير الخاص بنا من خلال علامة المساواة. في الحقيقة, كون أن هذا المتغير هو متغير محلي, هذا يعني أن نطاق الوصول إليه لا يشمل سوى مجال الدالة التي أنشأنا المتغير بداخلها, أي دالة البناء __init__, ولا يمكن بالحالة الطبيعية الوصول إلى هذا المتغير لا من خارج الكلاس ولا حتى من الدوال الأخرى من الكلاس نفسه. الحل لهذه الإشكالية موجود بين أيدينا. ففي درس الكلاس والوراثة, تعلمنا أنه لكي نسمح لدوال الكلاس بالوصول إلى متغيرات الكلاس ينبغي أن نعرف تلك المتغيرات على أنها خواص لهذا الكلاس, وذلك من خلال كلمة self المشيرة إلى الكلاس. نعود إلى السطر الذي أنشأنا فيه مربع التحرير ونغير first إلى self.first ليصبح السطر كاملًا بهذه الطريقة: self.first = wx.TextCtrl(p, -1) بهذه الطريقة؛ سنتمكن من الوصول إلى الحقل من أي دالة أخرى في الكلاس بمجرد الإشارة إليه ب self.first. طيب ... ماذا لو أردت معرفة محتوى تلك المربعات النصية من غير دالة البناء؟ تذكروا معي النافذة التي كانت تحتوي على حقلَي الاسم الأول واسم العائلة مع زر التسجيل, سأكتب لكم الكود الخاص بها وسنغير ما يلزم. import wx app = wx.App() class MyWindow(wx.Frame): def __init__(self): super().__init__(None, title="برنامجي الأول") self.Center() p = wx.Panel(self) wx.StaticText(p, -1, "الاسم الأول: ") self.first = wx.TextCtrl(p, -1) wx.StaticText(p, -1, "اسم العائلة: ") self.last = wx.TextCtrl(p, -1) showMessage = wx.Button(p, -1, "تسجيل") self.Show() MyWindow() app.MainLoop() جميل ... نريد الآن ربط زر التسجيل بدالة تقوم بقراءة محتوى المربع النصي المحتوي على اسم المستخدم الأول والأخير, ثم ترحب بالمستخدم عبر MessageBox في البداية, سنقوم بربط الزر بدالة الحدذ كما فعلنا في الدرس الماضي من خلال الوظيفة Bind ضع سطرًا فارغًا قبل جملة self.Show() واكتب إيعاز الربط بهذه الطريقة showMessage.Bind(wx.EVT_BUTTON, self.onMessage) تَذَكَّر أن إيعاز ربط عناصر نوافذ wx بالأحداث يتكون من: الاسم البرمجي للعنصر ثم نقطة ثم الوظيفة Bind ثم قوس هلالي ثم اسم الحدث ثم فاصلة ثم اسم الدالة المنفذة التي ستعالج وقوع الحدث ثم إغلاق القوس. في سطر الربط الذي كتبناه كان الاسم البرمجي للكائن هو showMessage واسم الحدث هو wx.EVT_BUTTON واسم الدالة التي ستعالج الحدث هو onMessage وهي موجودة بداخل الكلاس بدليل وجود كلمة self. قبل اسم الدالة. لنعرف الآن دالة onMessage التي أخبرنا الوظيفة Bind بأن تبحث عنها لو ضغط المستخدم على الزر انتقل إلى نهاية دالة البناء واكتب الآتي def onMessage(self, event): # لنضع الآن ال MessageBox التي سترحب لنا بالمستخدم wx.MessageBox(f"أهلًا بك يا {self.first.Value} {self.last.Value}", "ترحيب") لاحظوا معي حينما أردنا التعويض عن محتوى المربع النصي في الرسالة الترحيبية لم نكتفي في الحقيقة بكتابة اسم المتغير المشير إلى مربع التحرير, بل استخدمنا خاصية Value الموجودة في كل كائنات ال TextCtrl. وخاصية Value هي تلك الخاصية التي تحفظ لنا المحتوى النصي لمربع التحرير من أجل إعادة استخدامها أو التعديل عليها. على ذكر التعديل, دعونا نحذف محتوى المربعين النصيَين self.first و self.last بعد الترحيب بالمستخدم. self.first.Value = "" self.last.Valu = "" كما ترون, هي عملية اسناد طبيعية للخاصية Value قمنا فيها بتغيير القيمة الحالية إلى قيمة نصية جديدة تساوي "" أي نصًا فارغًا. ماذا لو أردنا جعل مؤشر التنقل يعود إلى حقل كتابة الاسم الأول بعد الترحيب بالمستخدم؟ حقيقةً هناك وظيفة مربوطة بكل كلاسات wx تقريبًا باسم SetFocus تقوم بتغيير التركيز إلى ذلك العنصر. دعونا نجرب. self.first.SetValue() المفترض أن يقوم الكود السابق بإعادة التركيز إلى الحقل self.first وهو حقل إدخال الاسم الأول. لنشاهد الآن الكود كاملًا مع التعديل. import wx app = wx.App() class MyWindow(wx.Frame): def __init__(self): super().__init__(None, title="برنامجي الأول") self.Center() p = wx.Panel(self) wx.StaticText(p, -1, "الاسم الأول: ") self.first = wx.TextCtrl(p, -1) wx.StaticText(p, -1, "اسم العائلة: ") self.last = wx.TextCtrl(p, -1) showMessage = wx.Button(p, -1, "تسجيل") showMessage.Bind(wx.EVT_BUTTON, self.onMessage) self.Show() def onMessage(self, event): wx.MessageBox(f"أهلًا بك يا {self.first.Value} {self.last.Value}", "ترحيب") self.first.Value = "" self.last.Value = "" self.first.SetFocus() MyWindow() app.MainLoop() ثانيًا: الأنماط المختلفة لمربعات التحرير. إن مربعات النصوص تأتي بأشكال وأنماط متعددة حسب ما تقتضيه حاجة وجود ذلك الحقل النصي. فهناك حقول نصية أحادية السطر, أي أنها تستقبل مدخلات لا تتعدى السطر الواحد, والتي كثيرًا ما نجدها في حقول كتابة اسم المستخدم مثلًا. وهناك أيضًا حقول محمية أحادية السطر, وهي تلك الحقول التي تُستخدم لإدخال كلمات المرور. ولديك الحقول متعددة السطر, والحقول غير القابلة للتعديل وإلى آخر ذلك من الأنماط الكثيرة. كون أن wx هي مكتبة واجهات من الطراز الأول, فبالتأكيد أنها تدعم كل هذه الأنماط وأكثر من ذلك أيضًا. منذ أن بدأنا في إنشاء مربعات التحرير؛ لم نتطرق إلى مسألة الأنماط styles هذه ولم نعدل شيئًا منها. لكن في الحقيقة, wx لديها أنماط افتراضية للعناصر بحيث لا يُجبر المبرمج أن يعينها طالما لم تكن هناك حاجة لذلك. طيب ماذا لو أراد ذلكم المبرمج تخصيص تلكم الأنماط؟ حينما كنا ننشئ مربعات تحرير؛ لم نكن نمرر سوى حاوي العنصر ورقم المعرف الخاص به كهذا المثال: self.first = wx.TextCtrl(p, -1) إلا أن هناك معاملات اختيارية كثيرة لوظيفة الإنشاء الخاصة بمربعات التحرير نذكر منها: value لتعيين قيمة افتراضية للنص و pos لتعيين موضع العنصر و size لتعيين حجمه. من تلك المعاملات الاختيارية معامل اسمه style ويتم فيه تعيين النمط المطلوب لمربع التحرير الذي نحن في صدد إنشائه يوجد عدد لا بأس به من الأنماط الخاصة بمربعات التحرير في مكتبة wx منها على سبيل المثال wx.TE_MULTILINE لجعل النص متعدد الأسطر wx.TE_READONLY لمنع التحرير على محتويات المربع النصي wx.TE_PASSWORD لجعل المربع النصي محميًا لأغراض كتابة كلمات المرور. wx.HSCROLL لإضافة شريط سحب أفقي إلى المربع. ببساطة, يمكنك استخدام أيًا من هذه الأنماط بتعيينها كقيمة للمعامل style في دالة البناء الخاصة بمربع التحرير. خذ معي هذا المثال: لو أريد إنشاء مربع تحرير لإدخال كلمات المرور من خلال النمط wx.TE_PASSWORD فسأكتب الآتي password = wx.TextCtrl(p, -1, style=wx.TE_PASSWORD) جميل. ماذا لو أردت جمع أكثر من نمط في مربع واحد كأن أجعله متعدد الأسطر وللقراءة فقط؟ يمكنك فعل ذلك من خلال الجمع بين أسماء الأنماط بعلامة الجمع العادية (+) كما هو مع هذا المثال: content = wx.TextCtrl(p, -1, style=wx.TE_MULTILINE + wx.TE_READONLY) ملاحظة مهمة: تحصل بعض الإشكالات لقارئ الشاشة مع الحقول متعددة الأسطر بغض النظر عن كونها للقراءة فقط أو قابلة للتحرير. تتمثل هذه المشكلة في كون أن قارئ الشاشة يقوم بتجزئة الجمل في الحقل بغير الشكل التي من المفترض أن تأتي عليه الجملة. لحل ذلك؛ يمكن إضافة نمط wx.HSCROLL إلى الحقل إلى جانب نمط تعدد الأسطر كما هو في هذا المثال: content = wx.TextCtrl(p, -1, style=wx.TE_MULTILINE + wx.HSCROLL) أما الآن فإلى الكود الذي كتبناه في هذا الصدد. وهو عبارة عن نافذة تحتوي على حقل دون نمط, وحقل ذو تنسيق واحد, وحقل ثالث يحتوي على أكثر من نمط. import wx app = wx.App() class MyWindow(wx.Frame): def __init__(self): super().__init__(None, title="مربعات التحرير") self.Center() p = wx.Panel(self) wx.TextCtrl(p, -1) wx.TextCtrl(p, -1, style=wx.TE_PASSWORD) wx.TextCtrl(p, -1, style=wx.TE_MULTILINE + wx.HSCROLL) self.Show() MyWindow() app.MainLoop() ثالثًا: مشروع إدارة الدرجات. خلال الأيام التدريبية الأربعة, تطورنا كثيرًا في wx, وحان الوقت أن نرى ثمرة هذا التطور بإنشاء مشروع برمجي متكامل. بدأنا في وضع اللبنة الأساسية للمشروع منذ ليلة الدرس الرابع, وتعلمنا في هذه البداية بعض الفوائد الجديدة. قبل ذلك, دعونا نلخص الفكرة العامة من المشروع: نريد إنشاء برنامج متعدد النوافذ. تحتوي النافذة الرئيسية للبرنامج على زرَين, الأول لإضافة درجات طالب من الطلاب إلى النظام, والزر الآخر يقوم بعرض كشف لجميع الدرجات بطريقة مرتبة. برمجيًا, سنقوم بإنشاء 3 كلاسات, كل كلاس يمثل نافذة من النوافذ الكلاس الأول سيمثل النافذة الرئيسية, الكلاس الثاني لمحاورة تسجيل الدرجات, وأما الكلاس الثالث فهو لعرض الكشف. انتهينا بالأمس من وضع احتياجات الكلاس الأول وبعض احتياجات الكلاس الثاني, لكن لكي لا يطول هذا الملخص, سأكتفي بوضع الكود الذي توصلنا إليه ووضع تعليقات سريعة على بعض الأسطر البرمجية. وأذكركم دائمًا بالعودة إلى تسجيلات الدرس للتفاصيل الصغيرة. الكود: import wx app = wx.App() class MainWindow(wx.Frame): # كلاس النافذة الرئيسية def __init__(self): super().__init__(None, title="مدير الدرجات") self.Center() self.Maximize(True) # هذه الوظيفة تقوم بعرض الإطار على كامل الشاشة p = wx.Panel(self) add = wx.Button(p, -1, "إضافة تسجيل") # الزر الذي من المفترض أن يظهر محاورة الإضافة add.Bind(wx.EVT_BUTTON, self.onAdd) # ربط زر الإضافة بحدث النقر, وتعيين دالة onAdd التي بداخل الكلاس لتنفيذ الأوامر المنوطة بالزر report = wx.Button(p, -1, "عرض كشف الدرجات") # الزر الخاص بعرض التقرير, ولم يتم ربطه بشيء إلى هذه اللحظة self.Show() def onAdd(self, event): # دالة الحدث التي ربطناها مع زر الإضافة AddRecord(self) # هذا هو السطر الوحيد في الدالة, يقوم السطر ببساطة باستدعاء كلاس المحاورة الفرعية التي أنشأناها لتسجيل بيانات درجات الطالب, مع تمرير النافذة الحالية بمثابة حاوي للمحاورة class AddRecord(wx.Dialog): # هذا هو الكلاس الذي قامت دالة الحدث الخاصة بزر الإضافة باستدعاؤه, والكلاس هنا يرث من wx.Dialog وليس wx.Frame, بحكم أن wx.Dialog أكثر ملاأمة للمحاورات الفرعية def __init__(self, parent): # دالة البناء الخاصة بالنافذة الفرعية, وقد وضعنا فيها معاملًا لتمرير الحاوي super().__init__(parent, title="إضافة تسجيل") # استدعاء دالة البناء الخاصة ب wx.Dialog قبل التعديل وتم فيها تمرير الحاوي الذي تحصلنا عليه من دالة البناء الجديدة, بالإضافة إلى تعيين العنوان بالطبع self.Center() # توسيط النافذة p = wx.Panel(self) wx.StaticText(p, -1, "اسم الطالب: ") self.st_name = wx.TextCtrl(p, -1) wx.StaticText(p, -1, "معرف الطالب: ") self.st_id = wx.TextCtrl(p, -1) wx.StaticText(p, -1, "الدرجة: ") self.grade = wx.TextCtrl(p, -1) saveButton = wx.Button(p, -1, "حفظ") # الزر الذي من المفترض أن يقوم بحفظ الحقول إلى ملف البيانات, إلا أننا لم نقم بربط الزر بشيء إلى هذه اللحظة self.Show() MainWindow() # استدعاء كلاس النافذة الرئيسية المشغلة للبرنامج app.MainLoop() # جعل النافذة مستمرة طالما لم يقم المستخدم بإغلاقها. نهاية الموجز. سبحانك الله وبحمدك, أشهد أن لا إله إلا أنت, أستغفرك وأتوب إليك.

دليل كلاسات ودوال وخصائص الدرس الرابع

wx.Dialog() : لإنشاء محاورة تكون شبيهة بالإطار ولاكن بالغالب يتم إستخدامِها في داخل البرنامج أي فوق الإطار, ولاكن يمكننا إنشائها كنافذة أساسية, المُعامِلات: الحاوي, parent, مطلوب, يتم تمرير None في حال كانت نافذة أساسية, أو يمكننا تمرير لها الإطار الرئيسي أي الحاوي يكون self, كي تكون تابعة للإطار. Maximize() : لجعل نافذة البرنامج بِحجم الشاشة, أي تظهر على الشاشة كلها ولاكن تحتاج معامل واحد وهو قيمة العرض تكون صح أم خطأ فبالغالب تكون True لتكون النافذة كبيرة. Value : اسم متغير قيمة حقل التحرير, ويتم إستخدامه عن طريق المتغير الذي تم إسناد إليه حقل التحرير على سبيل المثال first=wx.TextCtrl(panel, -1) لو أحببنا تغيير القيمة فنكتب first.Value="مرحبا", وإذا أردنا أخذها فقط نكتب إسم المتغير بحاجة الإستخدام. style : كلمة مفتاحية تُستخدَم في أغلب كائنات wx لِتحديد الشكل المحدد لِهذا الكائن, واليكن التطبيق على حقول التحرير: هذه الستايلات التي ذُكِرت في الدرس الرابع: wx.TE_PASSWORD : لجعل حقل التحرير محمي, أي حقل كلمة مرور. wx.TE_READONLY : لجعل الحقل للقراءة فقط. wx.TE_MULTILINE : لجعل الحقل متعدد الأسطر. wx.HSCROLL : لضبط حقل التحرير ليكن متوائمًا مع قارئات الشاشة من ناحية تقسيم الجمل في الأسطر. يمكننا الجمع بأكثر من شكل بنفس الوقت عن طريق علامة الزائد, فعلى سبيل المثال لو أردت جعل حقل الكتابة متعدد الأسطر وللقراءة فقط يمكنني كتابة style = wx.TE_MULTILINE+wx.TE_READONLY

تمرين على الدرس الرابع:

الإخوة الكرام أرحب بكم من جديد مع هذا التمرين البرمجي الذي أرجو من الجميع أن يعملوا على حله. من خلال متابعتك للدرس الرابع, قم ببناء برنامج يسمح للمستخدم بكتابة بعض النصوص وحفظها إلى ملف نصي جديد. من المفترض أن تحتوي واجهة البرنامج على مربع تحرير متعدد الأسطر لكتابة النص المراد تخزينه, إلى جانب زر يقوم بقراءة محتوى المربع النصي وحفظه إلى ملف نصي جديد. بالنسبة إلى اسم الملف, يمكنكم استخدام دالة GetTextFromUser لطلب اسم الملف حينما يقوم المستخدم بالنقر على زر الحفظ أما فيما يتعلق بطريقة إنشاء ملف  نصي جديد فقد سبق وأن شرحنا ذلك في الدورة السابقة, لكن لا بأس بأن أذكركم بالطريقة بهذا المثال البسيط. file = open("ملف جديد.txt", "w") file.write("بسم الله الرحمن الرحيم") بالتوفيق للجميع.

ملخص الدرس الخامس:

تم خلال هذا الدرس إكمال العمل على مشروع إدارة الدرجات الذي بدأنا مرحلة التأسيس له خلال الدرس الفائت. لم يتطرق الدرس إلى معلومات أو أدوات جديدة في wx, وإنما كان مجرد تطبيق لحصيلة المعلومات التي تطرقنا إليها خلال الدروس الأربع السابقة مع التعريف ببعض الدوال البسيطة هنا وهناك. لذلك, هذا الملخص لن يحتوي على شرح تفصيلي لأي شيء جديد, وإنما سأضع فيه الكود المصدري لمشروع إدارة الدرجات كاملًأ مع بعض التعليقات لما يستحق التعليق لكي تتمعنوا في الكود جيدًا وتتتبعوا مسار عمله بطريقة مكتوبة. أما لمن أراد الاستزادة؛ فلا بأس إذًأ من العودة إلى التسجيل الخاص بالدرس الخامس على اليوتيوب كالعادة. والآن إلى الكود: import wx app = wx.App() class MainWindow(wx.Frame): # كلاس النافذة الرئيسية def __init__(self): super().__init__(None, title="مدير الدرجات") self.Center() self.Maximize(True) p = wx.Panel(self) add = wx.Button(p, -1, "إضافة تسجيل") add.Bind(wx.EVT_BUTTON, self.onAdd) # ربط زر الإضافة بدالة الحدث report = wx.Button(p, -1, "عرض كشف الدرجات") report.Bind(wx.EVT_BUTTON, self.onReport) # ربط زر عرض الكشف بدالة الحدث الخاصة بها self.Show() def onAdd(self, event): AddRecord(self) # هذا هو السطر الوحيد في الدالة, يقوم السطر ببساطة باستدعاء كلاس المحاورة الفرعية التي أنشأناها لتسجيل بيانات درجات الطالب, مع تمرير النافذة الحالية بمثابة حاوي للمحاورة def onReport(self, event): # دالة الحدث المرتبطة بزر عرض الكشف ReportDialog(self) # إظهار المحاورة التي ستعرض لنا كشف الدرجات باستدعاء كلاسها وتمرير النافذة الرئيسية كحاوي للمحاورة class AddRecord(wx.Dialog): # هذا هو الكلاس الذي قامت دالة الحدث الخاصة بزر الإضافة باستدعاؤه, والكلاس هنا يرث من wx.Dialog وليس wx.Frame, بحكم أن wx.Dialog أكثر ملاأمة للمحاورات الفرعية def __init__(self, parent): # دالة البناء الخاصة بالنافذة الفرعية, وقد وضعنا فيها معاملًا لتمرير الحاوي super().__init__(parent, title="إضافة تسجيل") # استدعاء دالة البناء الخاصة ب wx.Dialog قبل التعديل وتم فيها تمرير الحاوي الذي تحصلنا عليه من دالة البناء الجديدة, بالإضافة إلى تعيين العنوان بالطبع self.Center() p = wx.Panel(self) wx.StaticText(p, -1, "اسم الطالب: ") self.st_name = wx.TextCtrl(p, -1) wx.StaticText(p, -1, "معرف الطالب: ") self.st_id = wx.TextCtrl(p, -1) wx.StaticText(p, -1, "الدرجة: ") self.grade = wx.TextCtrl(p, -1) saveButton = wx.Button(p, -1, "حفظ") cancel = wx.Button(p, wx.ID_CANCEL, "إلغاء") # زر الإلغاء, وهنا نحن لسنا في حاجة إلى ربط هذا الزر بدالة الحدث كون أننا استخدمنا wx.ID_CANCEL كمعرف للزر. وهذا المعرف يتم رصده تلقائيًا وتنفيذ اللازم عن طريق المحاورة نفسها saveButton.Bind(wx.EVT_BUTTON, self.onSave) # ربط زر الحفظ بدالة الحدث saveButton.SetDefault() # تعيين زر الحفظ ليكون هو الزر الافتراضي بحيث يمكن تنشيطه من خلال الضغط على زر الإدخال من أي مكان في المحاورة دون الحاجة إلى تركيز المؤشر عليه self.Show() def onSave(self, event): # دالة الحدث المرتبطة بالزر file = open("data.txt", "a", encoding="utf-8") # فتح ملف البيانات للكتابة عليه, وقد استخدمنا هنا الوضعية append لكي نمنع بايثون من حذف المحتوى السابق للملف في حال وجود ملف جديد file.write(f"{self.st_name.Value};{self.st_id.Value};{self.grade.Value}\n") # كتابة محتوى المربعات النصية إلى الملف, مع إضافة سطر فارغ تلقائيًا نهاية الملف تجنبًا لتداخل البيانات file.close() # إغلاق الملف بعد الكتابة عليه self.Close() # إغلاق المحاورة والعودة إلى النافذة الرئيسية class ReportDialog(wx.Dialog): # الكلاس الخاص بعرض الكشف def __init__(self, parent): # دالة البناء الخاصة بمحاورة الكشف, وقد تم فيها وضع معامل لتمرير الحاوي super().__init__(parent, title="كشف الدرجات") self.Maximize(True) p = wx.Panel(self) self.content = wx.TextCtrl(p, -1, style=wx.TE_MULTILINE + wx.TE_READONLY + wx.HSCROLL) # الحقل الذي سيظهر فيه كشف الدرجات, وهو حقل متعدد الأسطر وللقراءة فقط close = wx.Button(p, -1, "إغلاق") # الزر الخاص بإغلاق المحاورة close.Bind(wx.EVT_BUTTON, self.onClose) # ربط زر الإغلاق بدالة الحدث self.Show() self.data_read() # استدعاء الوظيفة data_read التي تم تعريفها في الكلاس لغرض قراءة محتوى ملف البيانات وتوليد الكشف def convert_to_grades(self, marks): # وظيفة تحويل الدرجات من قيمة مئوية إلى تقديرات أبجدية grade = "" # المتغير الذي سنقوم فيه باختزان التقدير الأبجدي وفقًا لنطاق الدرجة المئوية if marks >= 90 and marks <= 100: # جملة شرظية متعددة الأطراف للتحقق من نطاق الدرجة المدخلة grade = "أ" elif marks >= 80 and marks < 89: grade = "ب" elif marks >= 70 and marks < 79: grade = "ج" elif marks >= 60 and marks < 69: grade = "د" else: grade = "ه" return grade # إرجاع التقدير الأبجدي المستخرج من جمل الشرط السابقة def data_read(self): # وظيفة قراءة الملف ومعالجته إلى تقرير مقروء ومرتب file = open("data.txt", encoding="utf-8") # فتح ملف البيانات for line in file: # الدوران على محتوى الملف سطرًأ بسطر عن طريق الحلقة for line = line.strip() # اقتطاع المسافات الزائدة في أطراف السطر إن وجدت line = line.split(";") # تقسيم السطر إلى 3 خلايا وفقًا لعلامة الفاصلة المنقوطة الواقعة بين كل خلية وأخرى try: # محاولة فعل شيء ما grade = self.convert_to_grades(int(line[2])) # تحويل خلية الدرجة إلى تقدير أبجدي لعرضه في الكشف self.content.write(f"""اسم الطالب: {line[0]} معرف الطالب: {line[1]} درجة الطالب: {line[2]} التقدير: {grade} """) # كتابة معلومات الطالب ودرجته إلى المربع النصي المخصص except IndexError: # استثناء الخطأ IndexError الذي قد يحدث لو كان هناك في الملف سطرًا فارغًا لم يتمكن بايثون من تقسيمه pass # نخبر بايثون أن لا يفعل شيئًا في حال لو حصل الخطأ file.close() # إغلاق ملف البيانات بعد معالجته self.content.InsertionPoint = 0 # وضع مؤشر الحركة على اول سطر في المربع النصي def onClose(self, event): # دالة الحدث المرتبطة بزر الإغلاق self.Close() # إغلاق المحاورة والعودة إلى النافذة الرئيسية MainWindow() # استدعاء كلاس النافذة الرئيسية المشغلة للبرنامج app.MainLoop() # جعل النافذة مستمرة طالما لم يقم المستخدم بإغلاقها.

دليل الدرس الخامس:

Exception : يمكنكم وضعها مع الtry / except تحديدًا مع ال except لتخزن لكم الخطأ بعدها تستطيعون طباعته, وتكوينها يكون هكذا . try: # الكود هنا except Exception as e: # قمنا بوضعها كe أي نرمز لها بحرف e> print(e) SetDefault() : دالة تجعل الزر إفتراضي, أي عندما نقم بالضغط على زر الإدخال في أي مكان مثل حقول التحرير سيقوم بتنفيذ الزر, طبعًا نقم بإستخدامها عن طريق الكائن على سبيل المثال لدي متغير اسمه btn وقيمته كائن زر فنكتب btn.SetDefault() self.Destroy() : تقم بإغلاق النافذة وحذفها مِن الذاكرة, تُستخدم مع الإِطارات والمحاورات. self.Close() : نفس الدالة السابقة, تقم بإغلاق النوافذ ولاكن لا تحذفها من الذاكرة. wx.ID_CANCEL : نوع ID نقم بإستخدامه مع الأزرار ليجعل لنا زر الإلغاء عند الضغط عليه يتم اغلاق النافذة ويعمل كذالك مع زر الهروب, نقم بتمريره مكان المعامل ID. write() : دالة للكتابة في حقل التحرير, أي تقم بالإضافة له ولا تغير القيمة بأكملها مثلا لدي في القيمة بسم الله لو قمنا بالكتابة عن طريق الدالة كلمة الرحمن ستصبح القيمة بسم الله الرحمن.

الدرس السادس:

بسم الله الرحمن الرحيم الملخص الكتابس السادس لدورة البايثون المتقدمة للمكفوفين في مجال تطوير تطبيقات الحاسوب. تم خلال الدرس السادس مناقشة المحاور الآتية. * التعريف بالقوائم وأنواعها * إنشاء القوائم وإضافة العناصر إليها * معرفة العنصر المحدد * تغيير العنصر المحدد. * حذف العنصر المحدد. * تغيير قيمة عنصر في القائمة * حدث التنقل داخل القوائم * مشروع توضيحي. أولًأ: ما هي القوائم؟ القوائم, وتسمى أحيانًأ صناديق الخيارات, هي كائنات رسومية تتيح للمستخدم اختيار عنصر, أو مجموعة عناصر, وذلك لغرض معين يحدده البرنامج. كثيرة هي النماذج التي نجد فيها عنصر القوائم حاضر في برامجنا المختلفة, فلدينا سطح المكتب, مستكشف الملفات, نماذج التسجيل, كلها أمثلة لتطبيقات تم فيها توظيف القوائم وصناديق الاختيار بشكل أو بآخر. في wx يمكن أن نجد صناديق الاختيار بثلاثة أشكال مختلفة من الناحية البصرية وبعض السلوكيات البرمجية, لكنها تتشابه إلى حد كبير من ناحية الكود. وقد تطرقنا إلى تلك الأشكال الثلاثة في الدرس وقلنا أن wx.ListBox هو الشكل الذي تظهر فيه جميع العناصر دفعة واحدة من الناحية البصرية ويمكن للمستخدم التنقل داخلها من خلال الأسهم أو النقر على أحد تلك العناصر بالفأرة. wx.ComboBox وهو صندوق خيارات مع مربع تحرير للكتابة, يفيد في مسألة البحث. wx.Choice: هو صندوق منسدل DropDownList يظهر العنصر المحدد فقط من الناحية البصرية, ويمكن التنقل بين عناصره من خلال الأسهم, أو بفتح القائمة المنسدلة واختيار العنصر بالفأرة. ربما تكونوا قد لاحظتم, أنه قد لا تظهر فروقات جوهرية بين الأشكال الثلاثة إذا ما تعلق الأمر مع قارئات الشاشة, إلى أنه من الجيد الإلمام بتلك الأشكال من باب المعرفة بالشيء أولًا, ومراعاة الجانب البصري من الناحية الأخرى. ثانيًا: إنشاء القوائم. لإنشاء صندوق خيارات في wx سواء كان ذلكم الصندوق ListBox أو ComboBox أو Choice فستكون الصورة العامة هي على النحو الآتي متغير لحفظ العنصر ثم علامة المساواة ثم اسم العنصر هل هو wx.ListBox أو ComboBox أو Choice ثم قوس هلالي نضع الحاوي في المعامل الأول ثم فاصلة ثم المعرف الذي اتفقنا دائمًأ أن يكون 0-1 ثم نغلق القوس مثال: 1. إنشاء ListBox self.computers = wx.ListBox(p, -1) 2. إنشاء ComboBox self.computers = wx.ComboBox(p, -1) 3. إنشاء Choice self.computers = wx.Choice(p, -1) طيب لو أردنا إنشاء أحد الصناديق السابقة مع وضع مجموعة من العناصر بداخلها أثناء الإنشاء سنكتب هكذا self.computers = wx.ListBox(p, -1, choices=["dell", "hp", "linovo"]) حيث أن choice هو معامل اختياري يستقبل قائمة تحتوي على مجموعة نصوص, كل قطعة نصية تمثل عنصر. ضع في اعتبارك أنك تستطيع استخدام المعامل choice في جميع الأشكال الثلاثة بنفس الطريقة. طيب الآن نريد أن نتعرف على الأدوات المتاحة للتحكم بصندوق الخيارات الذي أنشأناه. لتحقيق ذلك, سنقوم بإنشاء نافذة نضع فيها أحد الصناديق الثلاثة إلى جانب عدد من الأزرار, كل زر سيؤدي وظيفة معينة. كود النافذة التجريبية: import wx app = wx.App() class MyWindow(wx.Frame): def __init__(self): super().__init__(None, title="صناديق الاختيار") self.Center() p = wx.Panel(self) self.computers = wx.ListBox(p, -1, choices=["dell", "hp", "linovo"]) readSelection = wx.Button(p, -1, "قراءة ترتيب العنصر المحدد") readStringSelection = wx.Button(p, -1, "قراءة اسم العنصر المحدد") deleteSelection = wx.Button(p, -1, "حذف العنصر المحدد") renameSelection = wx.Button(p, -1, "إعادة تسمية العنصر المحدد") appendItem = wx.Button(p, -1, "إرفاق عنصر إلى نهاية القائمة") insertItem = wx.Button(p, -1, "إدراج عنصر جديد عند موضع التحديد الحالي") self.Show() MyWindow() app.MainLoop() جميل. دعونا نبدأ الآن بربط الزر الأول, أي الزر الخاص بقراءة ترتيب العنصر المحدد, أي رقمه ضمن العناصر هل هو العنصر الأول أو الثاني وهكذا. لنتذكر, ترتيب العناصر في بايثون والبرمجة عمومًا يبدأ من 0 للعنصر الأول. نعود. ضع سطرًا فارغًا قبل self.Show() وقم بربط الزر readSelection بدالة الحدث هكذا readSelection.Bind(wx.EVT_BUTTON, self.onRead) الآن انتقل إلى نهاية دالة البناء, أي بعد self.Show مباشرة وعرف دالة onRead التي ربطتها بالزر هكذا. def onRead(self, event): طيب الآن كيف يمكن معرفة رقم العنصر المحدد؟ ببساطة توجد خاصية في كلًا من wx.ListBox و wx.ComboBox و wx.Choice تسمى Selection هي المسؤولة عن معرفة ترتيب العنصر وتغييره أيضًا. إذًا دعونا نعرض قيمة هذه الخاصية على شكل MessageBox, ولنتذكر أن MessageBox تجبرنا على تحويل كل ما نريد طباعته إلى نص عن طريق الدالة str اكتب هذا السطر في متن الدالة onRead wx.MessageBox(str(self.computers.Selection)) شكل دالة onRead سيكون على النحو الآتي: def onRead(self, event): wx.MessageBox(str(self.computers.Selection)) الآن, دعونا نطبق نفس الطريقة على مسألة قراءة قيمة العنصر المحدد. قبل self.Show في دالة البناء ضع سطرًا جديدًا واربط الزر readStringSelection بدالة الحدث هكذا: readStringSelection.Bind(wx.EVT_BUTTON, self.onReadStringSelection) سريعًا نعرف دالة onReadStringSelection أسفل أكواد دالة onRead ونجعلها تطبع قيمة العنصر المحدد من خلال الخاصية StringSelection هكذا def onReadStringSelection(self, event): wx.MessageBox(self.computers.StringSelection) ملاحظة: عند تشغيل البرنامج والضغط على أحد الزرين لقراءة التحديد لنعلم أن wx لا تضع التحديد على العنصر الأول بشكل تلقائي, ولفعل ذلك يمكننا ضبط التحديد برمجيًا من خلال تغيير قيمة الخاصية Selection للقائمة مباشرة بعد إنشائها هكذا self.computers.Selection = 0 # *ضع هذا السطر مباشرة تحت سطر تعريف القائمة في دالة البناء. أيضًا يمكن ضبط التحديد من خلال اسم العنصر بهذه الطريقة: self.computers.StringSelection = "dell" نتابع إلى الزر التالي وهو الزر الذي من المفترض أن يحذف لنا العنصر المحدد. قبل self.Show() ضع سطرًا فارغًأ واكتب فيه جملة الربط هكذا deleteSelection.Bind(wx.EVT_BUTTON, self.onDelete) جميل. الآن عرف دالة الحدث أسفل دالتي الحدث السابقتين هكذا def onDelete(self, event): قلنا أننا نريد من هذا الزر أن يحذف لنا العنصر الذي حدده المستخدم من القائمة. وقد سبق بأن علمنا طريقة جلب العنصر المحدد بطريقتي ال Selection وال StringSelection الحقيقة نحتاج هنا إلى طريقة ال Selection التي تأتِ لنا برقم التحديد المحدد بحكم أن وظيفة الحذف في wx تتعامل مع ترتيب العنصر وليس اسمه ارفق لنكتب الكود إذًا selection = self.computers.Selection # إيجاد رقم العنصر المحدد ووضعه في متغير. self.computers.Delete(selection) # استخدام الوظيفة Delete من القائمة وتمرير رقم العنصر المراد حذفه إلى المعامل الأول والوحيد للدالة. شكل دالة الحدث سيكون هكذا. def onDelete(self, event): selection = self.computers.Selection self.computers.Delete(selection) نتابع إلى الزر التالي وهو الزر الخاص بإعادة التسمية, ضع سطرًا فارغًا قبل self.Show واربط الزر بالحدث كما فعلت مع إخوانه هكذا: renameSelection.Bind(wx.EVT_BUTTON, self.onRename) أسفل أكواد دوال الحدث السابقة عرف دالة الحدث onRename هكذا def onRename(self, event): # بما أننا نريد إعادة تسمية العنصر المحدد يجب أولًأ جلب ترتيب العنصر كما تعلمنا سابقًا selection = self.computers.Selection # لنظهر الآن رسالة تأخذ من المستخدم الاسم الجديد new_name = wx.GetTextFromUser("اكتب الاسم الجديد", "إعادة التسمية") # بقي الآن أن نستخدم الوظيفة SetString ونعطيها رقم العنصر المراد تغييره إلى جانب اسمه الجديد هكذا self.computers.SetString(selection, new_name) جميل. تبقى لدينا زرَين لنربطهما بالحدث, الأول لإرفاق عنصر جديد إلى نهاية القائمة والآخر لإدراج عنصر في موضع التحديد الحالي. لنعمل أولًأ على زر الإرفاق. كالعادة, ضع سطرًأ فارغًا قبل self.Show() واربط الزر بالحدث هكذا appendItem.Bind(wx.EVT_BUTTON, self.onAppend) بعدها انشئ دالة الحدث الخاصة بالزر أسفل الكلاس بهذه الطريقة. def onAppend(self, event): # لنأخذ الآن اسم العنصر الجديد من المستخدم new_computer = wx.GetTextFromUser("اكتب اسم ا الحاسوب الجديد", "حاسوب جديد") # الآن ما عليك سوى استخدام الوظيفة Append وتمرير العنصر الجديد إليها هكذا self.computers.Append(new_computer) أخيرًا, لنربط الزر الذي سيدرج لنا عنصرًأ جديدًا في مكان المؤشر الحالي, وسنبدأ كالعادة بربط الزر بدالة الحدث. ضع سطرًا فارغًا فوق self.Show() واكتب جملة الربط على هذا النحو: insertItem.Bind(wx.EVT_BUTTON, self.onInsert) أسفل الكلاس, عرف دالة onInsert كما فعلت مع الدوال السابقة def onInsert(self, event): # اطلب الآن اسم العنصر الجديد كما فعلت مع الوظيفة Append new_computer = wx.GetTextFromUser("اكتب اسم ا الحاسوب الجديد", "حاسوب جديد") # بما أننا نريد إدراج العنصر مكان التحديد الحالي لا بد بطبيعة الحال أن نقوم بجلب رقم العنصر المحدد كما تعلمنا سابقًا selection = self.computers.Selection # لم يتبقى الآن سوى استخدام وظيفة الإدراج Insert وتمرير اسم العنصر الجديد أولًا, ثم تمرير موضع إدراجه ثانيًا على هذا النحو: self.computers.Insert(new_computer, selection) لنشاهد الآن كود النافذة كاملًا, ولك أن تجرب الضغط على الأزرار المعرفة لتكتشف مفعول الأدوات المرتبطة بكل زر. import wx app = wx.App() class MyWindow(wx.Frame): def __init__(self): super().__init__(None, title="صناديق الاختيار") self.Center() p = wx.Panel(self) self.computers = wx.ListBox(p, -1, choices=["dell", "hp", "linovo"]) self.computers.Selection = 0 # readSelection = wx.Button(p, -1, "قراءة ترتيب العنصر المحدد") readStringSelection = wx.Button(p, -1, "قراءة اسم العنصر المحدد") deleteSelection = wx.Button(p, -1, "حذف العنصر المحدد") renameSelection = wx.Button(p, -1, "إعادة تسمية العنصر المحدد") appendItem = wx.Button(p, -1, "إرفاق عنصر إلى نهاية القائمة") insertItem = wx.Button(p, -1, "إدراج عنصر جديد عند موضع التحديد الحالي") readSelection.Bind(wx.EVT_BUTTON, self.onRead) readStringSelection.Bind(wx.EVT_BUTTON, self.onReadStringSelection) deleteSelection.Bind(wx.EVT_BUTTON, self.onDelete) renameSelection.Bind(wx.EVT_BUTTON, self.onRename) appendItem.Bind(wx.EVT_BUTTON, self.onAppend) insertItem.Bind(wx.EVT_BUTTON, self.onInsert) self.Show() def onRead(self, event): wx.MessageBox(str(self.computers.Selection)) def onReadStringSelection(self, event): wx.MessageBox(self.computers.StringSelection) def onDelete(self, event): selection = self.computers.Selection self.computers.Delete(selection) def onRename(self, event): selection = self.computers.Selection new_name = wx.GetTextFromUser("اكتب الاسم الجديد", "إعادة التسمية") self.computers.SetString(selection, new_name) def onAppend(self, event): new_computer = wx.GetTextFromUser("اكتب اسم ا الحاسوب الجديد", "حاسوب جديد") self.computers.Append(new_computer) def onInsert(self, event): new_computer = wx.GetTextFromUser("اكتب اسم ا الحاسوب الجديد", "حاسوب جديد") selection = self.computers.Selection self.computers.Insert(new_computer, selection) MyWindow() app.MainLoop() ثانيًا: حدث التنقل داخل القائمة. يحصل أحيانًا أنك تريد تنفيذ أمر ما متى ما قام المستخدم بالتحرك داخل صندوق الخيارات, كأن تقوم بإصدار صوت حركة كما يحصل مع الألعاب. في الحقيقة, هذا الأمر بسيط جدًا, وهو يتبع مفهوم الأحداث الذي استخدمناه مع الأزرار. فكما أن حدث النقر على الزر له اسم في wx, فإن حدث التنقل داخل القائمة أيضًأ له اسم يختلف باختلاف نوع القائمة. أما فيما يتعلق بطريقة الربط, فليس ثمة اختلاف لا من ناحية وظيفة الربط, ولا من ناحية المعاملات التي يجب تمريرها إلى الوظيفة. لو عدنا إلى قائمة self.computers فإن طريقة ربطها بحدث الحركة سيكون من خلال الحدث wx.EVT_LISTBOX على هذا النحو self.computers.Bind(wx.EVT_LISTBOX, self.onMove) لم يبقى الآن سوى تعريف الدالة onMove نهاية الكلاس وكتابة أي أمر تريده أن يتنفذ حينما يتحرك المستخدم بداخل النافذة. ملاحظة مهمة: لا يمكن استخدام الحدث EVT_LISTBOX إلا على القائمة التي من نوع wx.ListBox, أما لو أردت رصد الحركة في wx.ComboBox و wx.Choice فلك أن تستخدم أسماء الحدث الخاصة بكل واحد منهما كما في هذين المثالين # للعنصر wx.ComboBox self.computers.Bind(wx.EVT_COMBOBOX, self.onMove) # للعنصر wx.Choice: self.computers.Bind(wx.EVT_CHOICE, self.onMove) ثالثًا: متفرقات هنا وهناك. في حديثنا عن wx.ListBox على وجه التحديد ذكرنا أنه بالإمكان جعل هذا الكائن متعدد التحديد, وذلك بفضل النمط wx.LB_MULTIPLE حيث يمكن ببساطة إنشاء نسخة متعددة التحديد من wx.ListBox بمجرد تمرير هذا النمط إلى المعامل الاختياري style بهذا الشكل self.computers = wx.ListBox(p, -1, choices=["dell", "hp", "linovo"], style=wx.LB_MULTIPLE) طيب ماذا لو أردت معرفة العناصر التي قام المستخدم بتحديدها مع وجود هذا النمط. مع القائمة العادية كنا نستخدم الخاصية Selection لجلب ترتيب العنصر المحدد على شكل رقم integer. هنا باعتبار أن التحديد قد يحتمل عنصر أو أكثر فإن قيمة خاصية التحديد ستكون من نوع list مكونة من مجموعة أرقام, كل رقم يمثل ترتيب أحد العناصر المحددة. أضف إلى ذلك, اسم الخاصية المعنية بمعرفة التحديد ستكون Selections بدلًا من Selection. تحدثنا أيضًا عن شكل رابع من أشكال صناديق متعددة الخيارات, وهو صندوق الراديو wx.RadioBox وال RadioBox هو صندوق ذو تسمية معينة يضم بداخله عدد من أزرار الاختيار RadioButtons. في الحقيقة, كثيرًأ ما نجد هذا الحقل في نماذج التسجيل إلى مختلف المواقع الالكترونية, وبالتحديد في حقل تعيين النوع (ذكر أو أنثى). أما فيما يخص إنشاء صندوق راديو مع wx واستخدامه من خلال الكود, فلا يختلف الأمر عما تعلمناه مع الأشكال الثلاثة الأخرى إلى في شيء واحد فقط. ولكي يتضح لك الاختلاف انتبه معي إلى هذا المثال self.units = wx.RadioBox(p, -1, "طريقة التحويل", choices=["فهرنهايت إلى سيليزي", "سيليزي إلى فهرنهايت"]) لا بد وأنك قد لاحظت الفرق البسيط والمتمثل في وجود تسمية للعنصر من المفترض أنها تظهر كعنوان للصندوق دون الحاجة إلى استخدام wx.StaticText بخلاف الإشكال السابقة. بقية الأدوات, من حيث تغيير التحديد ومعرفة العنصر المحدد سيكون ذاته ما تعلمته في جزئية القوائم العادية. رابعًا: مشروع تحويل الوحدات الحرارية. كعادة معظم دروسنا في هذه الدورة التدريبية, ختمنا الدرس السابق بمشروع عملي حقيقي لنفهم من خلاله كيف يمكن جمع المفاهيم المجردة في مشروع ملموس. وباعتبارنا أشبعنا الأشكال الثلاثة الأولى من صناديق الاختيار على حساب ال RadioBox ارتأينا أن نعتمد الأخير في تنفيذ فكرتنا البسيطة. الغرض من المشروع هو بناء محول للوحدات الحرارية, السيليزي والفهرنهايت, بحيث نتيح للمستخدم تحديد طريقة التحويل من خلال صندوق خيارات من نوع RadioBox. يوجد لدينا أيضًا حقل لكتابة درجة الحرارة المراد التحويل منها, بالإضافة إلى الزر الخاص بالتحويل. عند الضغط على زر التحويل؛ سيقوم البرنامج بقراءة ترتيب العنصر المحدد بالإضافة إلى قيمة درجة الحرارة المعطاة. إذا كان التحديد يساوي صفرًا, معنى ذلك أن المستخدم يريد تحويل درجة الحرارة المعطاة من فهرنهايت إلى سيليزي, أما إن كان التحديد يساوي 1 فإن العكس هو الصحيح. على أساس تلك الشروط, سيتم تحويل درجة الحرارة وفقًا للمعادلة المخصصة لكل اختيار, بعدها يتم عرض الناتج على شكل MessageBox. أترككم مع الكود, ولكم مراجعة تسجيلات المحاضرة للمزيد من التفاصيل والشرح. import wx app = wx.App() class MyWindow(wx.Frame): def __init__(self): super().__init__(None, title="محول الوحدات الحرارية") self.Center() p = wx.Panel(self) self.units = wx.RadioBox(p, -1, "طريقة التحويل", choices=["فهرنهايت إلى سيليزي", "سيليزي إلى فهرنهايت"]) wx.StaticText(p, -1, "درجة الحرارة: ") self.temp = wx.TextCtrl(p, -1, value="0") convert = wx.Button(p, -1, "تحويل") convert.SetDefault() convert.Bind(wx.EVT_BUTTON, self.onConvert) self.Show() def onConvert(self, event): temp = self.temp.Value temp = float(temp) selection = self.units.Selection if selection == 0: result = (temp-32) / 1.8 wx.MessageBox(f"{temp} فهرنهايت يساوي {result} سيليزي", "الناتج") else: result = temp * 1.8 +32 wx.MessageBox(f"{temp} سيليزي يساوي {result} فهرنهايت", "الناتج") MyWindow() app.MainLoop()

دليل الدرس السادس:

wx.ComboBox() : الكلاس الخاص بصندوق الإختيارات combo box, يكون فيه الخيارات ويحتوي على حقل تحرير وهو الذي يكون بداخله الخيارات, نفسه يُستخدَم في حقول البحث مثل بحث جوجل. المُعاملات الخاصة بهذا الكلاس هي : parent: الحاوي. id: الآيدي. choices, هذا إختياري, يجب كتابة اسمه كَي تقوموا بإضافته, وهو معامل يقبل قائمة list بها الخيارات التي يحتوي عليها الصندوق, على سبيل المثال: choices=["1", "2", "3"] wx.Choice() : أيضًا صندوق الخيارات, ولاكنه هنا يختلف لأنه لا يحتوي على حقل للكتابة, فقط إختيار, بالنسبة لقارئات الشاشة, فيتم التنقل بالأسهم بلا مشاكل, أما المبصرين يحتاجوا إلى فتحهُ أولًا. المُعاملات الخاصة بهذا الكلاس هي : parent: الحاوي. id: الآيدي. choices, هذا إختياري, يجب كتابة اسمه كَي تقوموا بإضافته, وهو معامل يقبل قائمة list بها الخيارات التي يحتوي عليها الصندوق, على سبيل المثال: choices=["1", "2", "3"] wx.ListBox() : الكلاس الذي يمكننا مِن إنشاء قائمة List Box, المُعاملات الخاصة بهذا الكلاس هي : parent: الحاوي. id: الآيدي. choices, هذا إختياري, يجب كتابة اسمه كَي تقوموا بإضافته, وهو معامل يقبل قائمة list بها الخيارات التي يحتوي عليها الصندوق, على سبيل المثال: choices=["1", "2", "3"] RadioBox() : الكلاس الذي من خلاله يمكننا إنشاء أزرار الراديو, التي تمكننا اختيار خيار واحد فقط, المعاملات الخاصة بهذا الكلاس هي : الحاوي parent, الآيدي id, في هذا المعامل يمكننا كتابة العنوان الذي يظهر على هذه الأزرار أي لا نحتاج إلى StaticText بعدها choices, نفس الطريقة السابقة, مثال عليه, wx.RadioBox(p, -1, "النوع", choices=["حاسوب محمول", "حاسوب مكتبي"]) Append() : دالة تُستَخدَم مع الListBox, ComboBox, Choice, وضيفتها إضافة عنصر جديد, ويكون في نهاية القائمة. المعاملات المطلوبة, إسم العنصر فقط, تُستخدم كما هو إستخدامها مع الlist العادية. طبعًا هذه الدالة تُستخدم عنطريق اسم المتغير, مثلlb.Append("abdallah"). Insert() : دالة تُستَخدَم لإضافة عنصر في مكان محدد, نفس الدالة الخاصة بالlist, ولاكن هنا المعامل الأول هو العنصر, والثاني هو التسلسل)index), طبعًا تعمل مع الListBox, ComboBox, Choice. Delete() : دالة لحذف عنصر مِن الListBox, ComboBox, Choice, المعاملات 1 فقط, تسلسل العنصر أي الindex المراد حذفه. Count : متغير متوفر في الListBox, ComboBox, Choice, يمكنك مِن الحصول على عدد العناصر الموجودة في القائمة أو الباقين, مثال: print(lb.Count) Selection : متغير يحتوي على قيمة التحديد الحالي بالindex, يتوفر في الListBox, ComboBox, Choice, مثال : print(lb.Selection), أيضًا يستخدم لوضع التحديد, مثلا, lb.Selection=0. StringSelection : متغير متوفر في الListBox, ComboBox, Choice, يمكنك مِن الحصول على التحديد, ولاكن ليس بالindex, بال بالسلسلة النصية للعنصر المحدد. GetString() : دالة تمكنك من الحصول على السلسلة النصية الخاصة بالعنصر عنطريق الindex, المعامل المطلوب هو index, على سبيل المثال, lb.GetString(0), متوفر في الListBox, ComboBox, Choice. wx.LB_MULTIPLE : style متوفر في الListBox لعمل قائمة تقبل اختيارات متعددة. Selections : متغير في ListBox يمكنك من الحصول على قائمة list بها العناصر المحددة بالindex. wx.EVT_LISTBOX : الحدث الذي يتنفذ بمجرد التنقل بالقائمة ListBox. wx.EVT_CHOICE : الحدث الذي يتنفذ بمجرد التنقل بصندوق الإختيارات Choice. wx.EVT_COMBOBOX : الحدث الذي يتنفذ بمجرد التنقل بصندوق الإختيارات, ComboBox. wx.EVT_RADIOBOX : الحدث الذي يتنفذ بمجرد المرور على خيار مِن خيارات الراديو RadioBox.

تمرين على الدرس السادس:

الإخوة الكرام مرتادي دورة البايثون المتقدمة للمكفوفين, تحية طيبة للجميع. من خلال متابعتكم لدرس القوائم وصناديق الخيار, حاولوا بناء مخزن للروابط المهمة, وذلك وفقًا للمواصفات التالية: 1. صندوق من نوع ListBox أو ComboBox أو Choice لاستعراض الروابط. 2. زر لفتح الرابط المحدد. 3. زر لإضافة رابط يقوم بعرض GetTextFromUser لطلب الرابط الجديد. 4. زر لحذف الرابط المحدد. 5. خاصية خامسة للتحدي, وهي أن يقوم البرنامج بتخزين الروابط في ملف نصي وإعادة جلبها من جديد حين فتحه المرة القادمة.

الدرس السابع:

بسم الله الرحمن الرحيم الموجز الكتابي السابع لدورة البايثون المتقدمة للمكفوفين. في هذا الدرس, تم مناقشة النقاط التالية: * ربط عناصر القائمة ببعض البيانات المتعلقة بها. * تغيير محتوى القوائم * التعامل مع مربعات التحديد CheckBoxes * البداية مع شريط القوائم. أولًا: ربط عناصر القائمة بالبيانات المتعلقة بها. تخيل معي فكرة برنامج مشغل صوتيات على الإنترنت, أو حتى مخزن روابط للاحتفاظ بالمواقع المفضلة. عادة, هذه البرامج تقوم بعرض عناصرها من خلال صندوق خيارات بغض النظر عن شكله. يقوم هذا الصندوق بعرض أسماء جميلة منمقة للعناصر, وحين الضغط على أحد تلك العناصر فإن البرنامج بطريقة ما يتعرف على البيان المرتبط بهذا العنصر, الرابط الخاص به مثلًا, ويقوم بالتعامل معه وفق الغرض الذي وضعه المبرمج. كيف للمبرمج أن يفعل ذلك, وما هي الوظائف التي سيستخدمها؟ هذا ما تعرفنا عليه في الجزء الأول من الدرس السابع وما سنلخصه في الأسطر القادمة. لفهم هذه المسألة جيدًا, دعونا نطبق جزء بسيط من تمرين مخزن الروابط الذي طلبته منكم على إثر الدرس السادس. لن نضيف في البرنامج سوى خاصيتَي الإضافة والفتح. بدايةً سنقوم بتصميم واجهة البرنامج من خلال الكود التالي, وأظنكم قد اعتدتم على مثل هذا النمط من الأكواد import wx app = wx.App() class LinkStore(wx.Frame): def __init__(self): super().__init__(None, title="مخزن الروابط") self.Center() p = wx.Panel(self) self.links = wx.ListBox(p, -1) # هنا قمنا بتعريف القائمة دون وضع العناصر فيها لكي نسمح بالمستخدم أن يضيف عناصره بنفسه openButton = wx.Button(p, -1, "فتح الرابط") # الزر الخاص بفتح الرابط المحدد. openButton.SetDefault() # جعل زر فتح الروابط زرًا افتراضيًا بحيث يتم تنفيذه تلقائيًا عند الضغط على أحد عناصر القائمة. addButton = wx.Button(p, -1, "إضافة رابط...") # الزر الخاص بإضافة الروابط self.Show() LinkStore() app.MainLoop() جميل. لنقم الآن بربط زر الإضافة أولًأ بدالة الحدث. ضع سطرًا فارغًا قبل self.Show() واكتب addButton.Bind(wx.EVT_BUTTON, self.onAdd) الآن. انتقل إلى نهاية الكلاس وقم بتعريف دالة onAdd التي ربطتها بزر الإضافة def onAdd(self, event): نحن نريد الآن أن نسمح للمستخدم بأن يضيف الرابط مع تسمية خاصة لذلك الرابط, بشرط أن تظهر تسمية الرابط فقط عند تصفحه للقائمة. معنى ذلك أن قيمة العنصر ستكون اسم ذلك الرابط والبيان المرتبط به سيساوي رابط الموقع نفسه. دعون الآن نطلب هذين المدخلين من المستخدم عبر wx.GetTextFromUser name = wx.GetTextFromUser("اكتب اسم الرابط الجديد", "اسم الرابط") url = wx.GetTextFromUser("الصق رابط الموقع الجديد هنا", "رابط الموقع") رائع. طيب كيف نضيف الموقع الجديد مع الرابط الخاص به نهاية القائمة؟ في الحقيقة الأمر بسيط للغاية, فكل ما عليك هو استخدام دالة ال Append التي استخدمناها في درس الأمس لإضافة العناصر إلى نهاية القائمة, لكن مع تمرير البيان المرتبط بالعنصر إلى المعامل الثاني للدالة. يعني ذلك أنك ستكتب اسم القائمة أولًا ثم نقطة ثم اسم دالة الإضافة Append ثم قوس ثم قيمة العنصر الجديد وهي بطبيعة الحال اسم الموقع ثم فاصلة ثم الرابط الخاص بالموقع ثم تغلق القوس على هذا النحو: self.links.Append(name, url) هذا كل ما في الأمر. لنرى شكل دالة الإضافة كاملًا. def onAdd(self, event): name = wx.GetTextFromUser("اكتب اسم الرابط الجديد", "اسم الرابط") url = wx.GetTextFromUser("الصق رابط الموقع الجديد هنا", "رابط الموقع") self.links.Append(name, url) طيب نجحنا الآن في ربط العناصر بالبيانات الخاصة بها. إذًا كيف لنا أن نستفيد من هذه الخطوة عند فتح الرابط؟ لكي نجيب على هذا السؤال, دعونا نقوم بربط زر الفتح بدالة الحدث الخاصة به. ضع سطرًأ جديدًا فوق self.Show() واكتب جملة الربط على هذا النحو: openButton.Bind(wx.EVT_BUTTON, self.onOpen) اتجه الآن إلى نهاية الكلاس وعرف دالة onOpen بهذه الطريقة def onOpen(self, event): نحن نريد الآن أن نقوم بفتح رابط الموقع الذي يقع تحت مؤشر التحديد, بمعنى آخر نريد فتح رابط الموقع الذي اختاره المستخدم. قبل كل شيء, وبما أننا نريد التعامل مع التحديد الذي اختاره المستخدم؛ يجب أولًأ معرفة رقم التحديد كما تعلمنا في الدرس السابق. إذًا, سنقوم بتعريف متغير ولنسميه مثلًا selection وسنسند إليه قيمة التحديد بهذه الطريقة selection = self.links.Selection رائع. هناك الآن بيانات مرتبطة بكل عنصر من عناصر القائمة self.links ما نريده نحن الآن هو الإتيان بالرابط المضاف كبيان إلى العنصر الذي قام المستخدم بتحديده والذي علمنا أن ترتيبه موجود في المتغير selection إذًا كيف نأتي بذلكم الرابط؟ في الحقيقة, هناك وظيفة في كل أنواع القوائم اسمها GetClientData , تأخذ منك رقم العنصر الذي تريد معرفة البيان المرتبط به ثم تعيد لك ذلك البيان. سنقوم الآن بتنفيذ الدالة ووضع قيمتها في المتغير url لنستخدمه بعد ذلك في فتح الرابط. ولا ننسى أننا سنقوم بتمرير ترتيب العنصر المحدد كمعامل للوظيفة GetClientData url = self.links.GetClientData(selection) لم يتبقى الآن سوى فتح الرابط url الذي تحصلنا عليه من خلال الوظيفة GetClientData تعلمنا في دورة الأساسيات أن فتح الروابط يتم من خلال دالة باسم open في مكتبة webbrowser إذًا سننتقل الآن إلى بداية الملف ونستدعي مكتبة webbrowser هكذا import webbrowser عد الآن إلى دالة onOpen وبالتحديد تحت آخر سطر في الدالة وقم بتنفيذ دالة webbrowser.open بهذه الطريقة webbrowser.open(url) هذا كل ما في الأمر. كود وظيفة onOpen كاملًا سيكون بهذا الشكل: def onOpen(self, event): selection = self.links.Selection url = self.links.GetClientData(selection) webbrowser.open(url) وكود البرنامج كاملًأ سيكون هكذا: import webbrowser import wx app = wx.App() class LinkStore(wx.Frame): def __init__(self): super().__init__(None, title="مخزن الروابط") self.Center() p = wx.Panel(self) self.links = wx.ListBox(p, -1) # هنا قمنا بتعريف القائمة دون وضع العناصر فيها لكي نسمح بالمستخدم أن يضيف عناصره بنفسه openButton = wx.Button(p, -1, "فتح الرابط") # الزر الخاص بفتح الرابط المحدد. openButton.SetDefault() # جعل زر فتح الروابط زرًا افتراضيًا بحيث يتم تنفيذه تلقائيًا عند الضغط على أحد عناصر القائمة. addButton = wx.Button(p, -1, "إضافة رابط...") # الزر الخاص بإضافة الروابط addButton.Bind(wx.EVT_BUTTON, self.onAdd) openButton.Bind(wx.EVT_BUTTON, self.onOpen) self.Show() def onAdd(self, event): name = wx.GetTextFromUser("اكتب اسم الرابط الجديد", "اسم الرابط") url = wx.GetTextFromUser("الصق رابط الموقع الجديد هنا", "رابط الموقع") self.links.Append(name, url) def onOpen(self, event): selection = self.links.Selection url = self.links.GetClientData(selection) webbrowser.open(url) LinkStore() app.MainLoop() أمر آخر تطرقنا إليه ضمن حديثنا عن صناديق القوائم وهو وظيفة set وظيفة Set في صناديق الخيارات هي وضيفة تسمح لك بتغيير محتويات القائمة بشكل كامل, وذلك من خلال استبدال المحتوى الحالي بمحتوى آخر مختلف تمامًا. بمعنى آخر, وظيفة Set تقوم بحذف جميع العناصر السابقة وتستبدلها بعناصر جديدة. إذًا كيف يمكننا استخدام هذه الوظيفة؟ في الحقيقة أمر الوظيفة هذه بسيط جدًا لا يحتاج إلى مثال تفصيلي, فكل ما عليك لاستبدال عناصر القائمة السابقة بعناصر جديدة هو أن تكتب اسم القائمة متبوعة بنقطة ثم اسم الوظيفة Set ثم قوسين هلاليَين تضع بينهما قائمة بايثون بالعناصر الجديدة. مثال: self.links.Set([]) الكود السابق يقوم بحذف جميع محتويات القائمة self.links ويستبدلها بقائمة فارغة مثال 2: self.links.Set(["youtube", "twitter"]) الكود السابق يقوم بتجاهل جميع العناصر السابقة من القائمة self.links ويستبدلها بالعنصرين google و twitter ثانيًا: مربع التحديد wx.CheckBox. مربعات التحديد, هي كائنات رسومية, تستخدم عادة في محاورات الإعدادات ونماذج التسجيل, لسؤال المستخدم ما إن كان يريد تفعيل أمر ما أو تعطيله. يحتمل هذا الكائن الرسومي بحالته الطبيعية قيمتَين لا ثالث لهما, إما True إن كان محددًا, أو False إن كان غير ذلك. تذكير: يُطلق على القيمتين True و False في البرمجة عمومًا وفي بايثون على وجه التحديد بأنها قيم منطقية, ومن باب الربط بالشيء, تعتمد الجمل الشرطية في تنفيذها على إحدى تلك القيمتين. فلو أرجعت جملة الشرط الموضوعة بعد الأداة if القيمة True سيتم تنفيذ الشرط, وإن إرجعت False فسيتم تنفيذ الفرع else إن وجد. نعود إلى كائننا الجديد, ولنتعلم طريقة إنشاؤه عد إلى الكود السابق الذي وضحنا فيه مفهوم ال ClientData واكتب هذا السطر أسفل الكود الخاص بإنشاء قائمة الروابط: self.isRunning = wx.CheckBox(p, -1, "فتح الروابط") لو نركز قليلًا, سنلاحظ أن طريقة إنشاء CheckBox مطابقة تمامًا لما كنا نفعله مع الأزرار. حيث أننا نقوم أولًأ بتعريف متغير للإشارة إلى الكائن, بعدها نضع علامة المساواة, ثم نكتب اسم الكلاس الخاص بالكائن وهو في حالتنا هذه wx.CheckBox, ثم نفتح قوسًا هلاليًا ونضع في معامله الأول حاوي العنصر متبوعًا بفاصلة, ثم نضع المعرف الذي اتفقنا أن يكون -1 متبوعًا بفاصلة, ثم نضع تسمية العنصر التي ستظهر للمستخدم وذلك بوضعها بين علامتي تنصيص, وأخيرًأ نغلق القوس الهلالي. الإخوة الكرام, تذكروا أن الحالة الافتراضية لمربع التحديد هي أن يكون معطلًا, أي أن قيمته ستساوي False أما إن كنا نريد تغيير تلك القيمة إلى True فسنكتب اسم العنصر أولًأ ثم نقطة ثم اسم الخاصية Value ثم علامة المساواة ثم نضع القيمة الجديدة على هذا النحو. self.isRunning.value = True *ضع السطر السابق أسفل جملة تعريف مربع التحديد. طيب, الآن نريد الاستفادة من قيمة المربع بأن نسمح للمستخدم بأن يضغط على الزر الخاص بفتح الرابط المحدد فقط في حال لو كان ذلكم المربع محددًا. للقيام بذلك, سنحتاج إلى ربط مربع التحديد بحدث معين يتم إرساله للبرنامج بمجرد أن يقوم المستخدم بالضغط على مربع التحديد. حقيقةً, الوضع هنا مطابق تمامًا لربط الأزرار بالحدث, الذي سيختلف فقط هو أن اسم الحدث الخاص بمربعات التحديد سيكون wx.EVT_CHECKBOX بدلًا من wx.EVT_BUTTON ضع سطرًا فارغًا قبل self.Show() واكتب جملة الربط على هذا النحو self.isRunning.Bind(wx.EVT_CHECKBOX, self.onCheck) الآن بما أننا نريد التحكم بخصائص الزر openButton من دالة onCheck يجب الإشارة إلى الزر openButton من خلال الكلمة self. في كل من جملة الإنشاء وجملة تعيين الزر الافتراضي وجملة ربط الزر بالحدث. بعد الانتهاء من كل هذه الإجراءات سيتبقى أن نقوم بتعريف دالة onCheck التي ستعطل لنا زر الفتح إن كان مربع التحديد غير نشط وتفعل الزر إن كان المربع محددًا في نهاية الكلاس, اكتب التالي def onCheck(self, event): self.openButton.Enabled = self.isRunning.Value هذا كل ما في الأمر. سأرفق لكم الآن الكود كاملًا لتتأملوا فيه براحتكم. import wx import webbrowser app = wx.App() class LinkStore(wx.Frame): def __init__(self): super().__init__(None, title="مخزن الروابط") self.Center() p = wx.Panel(self) self.links = wx.ListBox(p, -1) self.isRunning = wx.CheckBox(p, -1, "فتح الروابط") self.isRunning.Value = True self.openButton = wx.Button(p, -1, "فتح الرابط") self.openButton.SetDefault() addButton = wx.Button(p, -1, "إضافة") addButton.Bind(wx.EVT_BUTTON, self.onAdd) self.openButton.Bind(wx.EVT_BUTTON, self.onOpen) self.isRunning.Bind(wx.EVT_CHECKBOX, self.onCheck) self.Show() def onAdd(self, event): name = wx.GetTextFromUser("اكتب اسم الموقع", "اسم الموقع") url = wx.GetTextFromUser("الصق رابط الموقع هنا", "رابط الموقع") self.links.Append(name, url) def onOpen(self, event): selection = self.links.Selection url = self.links.GetClientData(selection) webbrowser.open(url) def onCheck(self, event): self.openButton.Enabled = self.isRunning.Value LinkStore() app.MainLoop() ثالثًا: شريط القوائم شريط القوائم menu bar, هو جزء يظهر في المنطقة العلوية للبرنامج, يحتوي على مجموعة قوائم للتحكم في الوظائف المختلفة في ذلكم البرنامج. يمكن الوصول إلى هذا الجزء من البرنامج بمجرد الضغط على زر القوائم alt كما هو الحال مع الكثير من البرامج مثل المفكرة والجولدويف ومستكشف الملفات وغيرها. ولكي نفهم شريط القوائم هذا بشكل جيد, سنقوم بتنفيذ مشروع بسيط نحاكي فيه برنامج المفكرة. وبما أن هذا الملخص قد طال عليكم, سأكتفي بوضع ما تمكننا من إنجازه من المشروع وأعلق على الأكواد المهمة. ولا بأس من العودة إلى تسجيلات الدرس للاستزادة. الكود: import wx app = wx.App() class Notepad(wx.Frame): # كلاس نافذة البرنامج الرئيسية def __init__(self): super().__init__(None, title="بدون عنوان - المفكرة") self.Center() # توسيط النافذة self.SetSize(wx.GetDisplaySize()) # تعيين حجم نافذة البرنامج لتساوي حجم شاشة الجهاز, وقد تم التوصل إلى حجم شاشة الجهاز من خلال الدالة wx.GetDisplaySize() p = wx.Panel(self) self.content = wx.TextCtrl(p, -1, style=wx.TE_MULTILINE + wx.HSCROLL) # منطقة الكتابة menubar = wx.MenuBar() # شريط القوائم الذي سنضع فيه قوائمنا المختلفة file = wx.Menu() # إنشاء قائمة ملف exitItem = file.Append(-1, "خروج ctrl+w") # إضافة زر الخروج إلى قائمة ملف menubar.Append(file, "ملف") # إضافة قائمة ملف إلى شريط القوائم self.SetMenuBar(menubar) # إضافة شريط القوائم إلى النافذة self.Bind(wx.EVT_MENU, self.onExit, exitItem) # ربط زر الخروج بدالة الحدث, وهنا اختلفت طريقة الربط قليلًا بحيث جاء الربط مباشرة على النافذة, وتم تمرير عنصر القائمة المربوط إلى المعامل الثالث للدالة Bind self.Show() # إظهار النافذة def onExit(self, event): # دالة الحدث المرتبطة بعنصر القائمة exitItem wx.Exit() # أمر الخروج Notepad() # تنفيذ الكلاس app.MainLoop() # جعل البرنامج مستمرًا.

دليل الدرس السابع:

GetClientData() : في حال قمت بإستخدام الclient data, فيمكنك من خلال هذه الدالة الحصول على بيانات العنصر المحدد في القائمة, المعامل المطلوب هو الindex الخاص بالعنصر, يتم إستخدامها من خلال متغير كائن القائمة, مثال: self.links.GetClientData(0) Set() : دالة تستبدل عناصر القائمة بعناصر جديدة, المعامل المطلوب هي القائمة, list, على سبيل المثال: self.links.Set(['google.com', 'youtube.com', 'facebook.com']) wx.CheckBox() : الكلاس المسؤول عن مربعات التحديد, المعاملات : الحاوي:parent, الآيدي : id, الإسم الخاص به : label. Value : خاصية أي متغير تُستخدَم أيضًا للحصول على قيمة الcheckbox, وهي بالتأكيد فقط True أو False. wx.EVT_CHECKBOX : الحدث الذي يستخدَم في ربط مربعات التحديد, أي بمجرد التحديد أو إلغاء التحديد سيتنفذ هذا الحدث. Enabled : خاصية تتوفر في الأزرار, يمكننا إعطائها قيمة True أو False, وهي لتعطيل الزر, أي في حال كانت False سيتعطل الزر ولا يمكننا الضغط عليه, مثال, self.btn.Enabled=False. SetSize() : لوضع حجم النافذة, وهنا نستخدمها كدالة تتوفر بالكلاس, لأن موجودة في كلاس الإطار, لذا يمكننا كتابة self.SetSize(), المعامل المطلوب: الحجم بالبكسل. wx.GetDisplaySize() :للحصول على حجم الشاشة. wx.MenuBar() : الكلاس المسؤول عن إنشاء شريط القوائم. wx.Menu() : الكلاس المسؤول عن إنشاء القوائم التي نسندها إلى شريط القوائم, يمكنكم مراجعة المحاظرة لمعرفة المزيد. wx.EVT_MENU : لربط النافذة بحدث الضغط على عناصر القوائم, وهذا حدث عام, أي أي قائمة يتم الضغط على عنصر منها يتنفذ, راجعوا المحاظرة للمزيد.

الدرس الثامن:

بسم الله الرحمن الرحيم. الملخص الكتابي الثامن لدورة البايثون المتقدمة للمكفوفين. تم خلال هذا الدرس إكمال العمل على برنامج المفكرة, حيث قمنا بإضافة كل من خاصيتَي الحفظ والفتح إضافة إلى بعض المتفرقات الأخرى. المعلومات الجديدة التي تعرضنا إليها خلال العمل هي: * إنشاء FileDialog للفتح والحفظ. * رسائل MessageBox ذات الزرَين * تغيير عنوان النافذة. * حدث إغلاق النافذة wx.EVT_CLOSE. تنويه: هذا الملخص سيغطي المعلومات العامة حول المحاور السابقة, أما لمن أراد تطبيقًا عمليًا لتلك المحاور فله أن يطلع على الكود المصدري لمشروع المفكرة الذي سيتم إرفاقه نهاية هذا الملف. أولًأ: محاورة الفتح والحفظ. محاورات الفتح والحفظ هي إحدى تلك المحاورات التي يقوم نظام التشغيل نفسه بتجهيزه للغات البرمجة ومكتباتها ليتم استخدامها بطريقة موحدة. فلو أتيمنا على برامج تحرير النصوص, تحرير الصوت, وغيرها من البرامج التي تعتمد في عملها على استيراد ملف ما إلى البرنامج للعمل عليه فستجد أن جميع تلك البرامج تشترك في نفس المحاورة الخاصة بالفتح دون وجود فوارق تذكر. تمثيل هذه المحاورات في wx يتم عن طريق الكلاس FileDialog. ففي الحالات التي ترغب فيها بإظهار محاورة للمستخدم بأن يختار ملف من جهازه للفتح, أو تحديد مسار معين لحفظ ملف ما فإنك على الأغلب ستحتاج إلى استخدام الكلاس wx.FileDialog للقيام بذلك. طيب كيف يمكن استخدام هذا الكلاس؟ تتلخص فكرة العمل على wx.FileDialog في هذه الخطوات: 1. إنشاء كائن من الكلاس وتمرير الحاوي وعنوان المحاورة إليه. 2. عرض المحاورة للمستخدم وانتظار الإجراء الذي سيقوم بتنفيذه. 3. لو لم يضغط المستخدم على إلغاء, سنقوم باستخراج مسار الملف الذي اختاره المستخدم بالإضافة إلى اسم الملف لو احتجنا إليه. 4. التعامل مع المسار إما بقراءة الملف الذي يشير إليه أو الكتابة فيه. تمثيل الخطوات بالكود openDialog = wx.FileDialog(self, "فتح") # إنشاء الكائن وتمرير النافذة كحاوي للمحاورة, إلى جانب تمرير العنوان الخاص بالمحاورة result = openDialog.ShowModal() # إظهار المحاورة للمستخدم والاحتفاظ برقم الزر الذي قام بالضغط عليه. # تذكر أن الوظيفة ShowModal تستخدم لعرض المحاورات مع إيقاف عمل البرنامج إلى أن ينتهي المستخدم من العمل على المحاورة المنبثقة. if result == wx.ID_CANCEL: # لو ضغط المستخدم على إلغاء return # إيقاف الدالة # هنا سيكمل البرنامج عمله في حال لم يضغط المستخدم على إلغاء path = openDialog.Path # استخراج المسار الذي اختاره المستخدم من المحاورة filename = openDialog.Filename # استخراج اسم الملف, اختياري. # نهاية المثال. هذا ما كان بشأن محاورة الفتح. لا يختلف الأمر كثيرًأ فيما يتعلق بحفظ الملفات, فبطبيعة الحال ستستخدم نفس الكلاس wx.FileDialog مع تغيير النمط الافتراضي من فتح إلى حفظ بهذه الطريقة saveDialog = wx.FileDialog(self, "حفظ", style=wx.FD_SAVE) باقي الخطوات هي نفسها بالضبط, فقط مع تغيير اسم المتغير الذي أشرنا به إلى المحاورة من openDialog إلى saveDialog. هذا ما كان من أمر إنشاء تلك المحاورات وعرضها للمستخدم, وأما فيما يتعلق بتقييد أنواع الملفات المسموح باختيارها من خلال تلك المحاورات فهنا نحن نتحدث عن ما يسمى بال Wildcard ال Wildcard هي خاصية يتم ضبطها على wx.FileDialog تسمح لك بتوصيف أنواع الملفات المسموح بها في المحاورة. يتم كتابة توصيف الامتدادات على هذه الصورة: openDialog.Wildcard = "التسمية الظاهرية للصيغة|*.t=الاسم الحقيقي للصيغة|تسمية ظاهرية لصيغة أخرى|*.التسمية الحقيقية للصيغة الأخرى" على سبيل المثال, لو أردت إنشاء محاورة لفتح الملفات تحتمل النصوص التي من نوع txt بالإضافة إلى صفحات الإنترنت التي من نوع html openDialog = wx.FileDialog(self, "فتح") openDialog = "ملفات نصية (txt)|*.txt|صفحات إنترنت (html)|*.html" يجدر القول هنا أن بوسعك تضمين أكثر من امتداد تحت اسم تصنيفي واحد, وذلك بالفصل بين أسماء الامتدادات بفاصلة منقوطة. مثال openDialog.Wildcard = "ملفات صوتية|*.mp3;*.m4a;*.wav;*.aac|ملفات فيديو|*.mp4;*.wmv;*.mov" ثانيًا: الرسائل الحوارية ذات الزرين yes و no. منذ أن بدأنا في اعتماد wx.MessageBox لعرض الرسائل الحوارية للمستخدم بدلًا من الدالة print جميع رسائلنا كانت تحتوي على زر وحيد فقط ألا وهو زر الموافقة. في هذا الجزء من التلخيص, نجمل لكم كيف يمكنكم التعامل مع الرسائل الحوارية ذات الزرين (نعم ولا). في الحقيقة, ليس هناك الكثير ليختلف في هذا الصدد, ما ستفعله فقط هو أن تغير قيمة المعامل style الذي يأتي مباشرة بعد عنوان الرسالة ليأخذ قيمة النمط wx.YES_NO مثال: wx.MessageBox("هل تريد حفظ التغييرات", "حفظ", wx.YES_NO) لو جربت الكود السابق فسينتج لك نفس المحاورة التي اعتدت عليها لكن مع وجود زري نعم وإلغاء. طيب الآن نحن نريد معرفة الزر الذي قام المستخدم بالضغط عليه فما الذي علينا فعله يا ترى؟ ببساطة, ستقوم بإسناد قيمة الدالة wx.MessageBox إلى متغير ولتسميه مثلًا message على هذا النحو: message = wx.MessageBox("هل تريد حفظ التغييرات", "حفظ", wx.YES_NO) الآن ما عليك سوى أن تتحقق من قيمة المتغير message للتأكد ما إن كان المستخدم قد ضغط على نعم أو لا بهذه الطريقة if message == wx.YES: print("do something") ملاحظات: 1. wx.YES هو متغير في المكتبة wx يشير إلى قيمة الزر "نعم" (yes" لذلك قمنا بمقارنة ناتج الزر مع ذلك المتغير. 2. يمكنك كتابة أي كود تريد في جواب الشرط, أما جملة print التي استخدمناها في المثال فما هي إلا مجرد توضيح للفكرة. ثالثًا: تغيير عنوان النافذة. حينما نتعامل مع الكثير من البرامج في واقع حياتنا اليومية مثل الكروم والمفكرة ومشغل الصوتيات فلا بد وأننا سنلاحظ أن تلك البرامج تقوم بتغيير عنوان النافذة وفقًا للملف المفتوح مثلًا. ومنذ أن بدأنا رحلتنا مع wx كنا نقوم بضبط العنوان للوهلة الأولى فقط عند إنشاء الإطار, بالتحديد في دالة البناء الخاصة بال super class. لكن ماذا لو أردنا تغيير العنوان بشكل ديناميكي استجابة لضغطة زر مثلًا أو لحدث ما على البرنامج" حقيقة الأمر لا يعدو كتابة سطر واحد فقط على هذا النحو: self.Title = "العنوان الجديد" باعتبار أن self تشير إلى النافذة نفسها, و Title هي خاصية للنافذة تشير إلى العنوان. وكما علمنا أنه من بديهيات الكلاس والوراثة, أنك إذا إردت تغيير قيمة لخاصية ما فإنك ستكتب اسم الكائن أولًأ متبوعًا بنقطة ثم اسم الخاصية التي ترغب في تغييرها ثم علامة المساواة يليها أخيرًأ القيمة الجديدة للخاصية. رابعًا: حدث إغلاق النافذة. حدث إغلاق النافذة, أو كما يسمى في wx ب EVT_CLOSE هو حدث يتم إرساله إلى البرنامج عندما يقوم المستخدم بالضغط على alt + f4 لإغلاق النافذة, أو أن يضغط على عنصر الإغلاق من قائمة النظام العلوية. تأثير هذا الحدث نجده في بعض برامجنا المعروفة, كتطبيق الوورد مثلًا, وذلك على شكل رسالة حوارية لحفظ التعديلات تظهر مباشرة بالتزامن مع محاولتك لإغلاق البرنامج. عادة, يقوم هذا الحدث بتنفيذ أمر معد له مسبقًا وهو الإغلاق الفوري للبرنامج, أما إن كنت ترغب في تغيير هذا السلوك فستتبع الخطوات التالية: 1. قم بربط نافذتك بدالة الحدث مستخدمًا wx.EVT_CLOSE * أنشئ سطرًأ جديدًا قبل self.Show() واكتب جملة الربط بهذا الشكل: self.Bind(wx.EVT_CLOSE, self.onExit) 2. قم بتعريف دالة الحدث التي ربطتها بالنوع EVT_CLOSE. * انتقل إلى نهاية كلاس نافذتك وعرف الدالة على هذا النحو: def onExit(self, event): wx.Exit() حقيقة, لن يتغير شيء مع هذا الربط بحكم أنك اكتفيت بكتابة wx.Exit() الذي يعتبر هو السلوك الافتراضي لحدث الإغلاق. لكن على أي حال, يمكنك عمل أي شيء في الدالة حسب حاجة البرنامج كأن تقوم مثلًا بتشغيل صوت وداعي قبل إغلاق البرنامج, أو تقوم بعرض محاورة ما تطلب فيها انتباه المستخدم لأمر ما في البرنامج. أما الآن, فأترككم مع الكود المصدري لمشروع برنامج المفكرة الذي أكملنا بناءه ليلة أمس. import wx app = wx.App() class Notepad(wx.Frame): # كلاس الواجهة الرئيسية للبرنامج def __init__(self): super().__init__(None, title="بدون عنوان - المفكرة") self.SetSize(wx.GetDisplaySize()) # تعيين حجم البرنامج ليساوي حجم النافذة self.Center() # توسيط النافذة self.activeFile = None # خاصية من إنشائنا للاحتفاظ بمسار الملف المفتوح, وقد تم ضبطها إلى None بشكل مبدئي p = wx.Panel(self) self.content = wx.TextCtrl(p, -1, style=wx.TE_MULTILINE + wx.HSCROLL) # مربع الكتابة menubar = wx.MenuBar() # شريط القوائم file = wx.Menu() # قائمة ملف openItem = file.Append(-1, "فتح ctrl+o") # عنصر الفتح من قائمة ملف saveItem = file.Append(-1, "حفظ ctrl+s") # عنصر الحفظ من قائمة ملف exitItem = file.Append(-1, "خروج ctrl+w") # عنصر الخروج من قائمة ملف analysis = wx.Menu() # قائمة تحليل النص frequentItem = analysis.Append(-1, "أكثر الكلمات تكرارًا") # عنصر أداة استخراج أكثر الكلمات تكرارًأ في الملف menubar.Append(file, "ملف") # إضافة قائمة ملف إلى شريط القوائم menubar.Append(analysis, "تحليل") # إضافة قائمة تحليل إلى شريط القوائم self.SetMenuBar(menubar) # تركيب شريط القوائم على النافذة self.Bind(wx.EVT_MENU, self.onExit, exitItem) # ربط عنصر الخروج بدالة الحدث self.Bind(wx.EVT_MENU, self.onOpen, openItem) # ربط عنصر الفتح بدالة الحدث self.Bind(wx.EVT_MENU, self.onSave, saveItem) # ربط عنصر الحفظ بدالة الحدث self.Bind(wx.EVT_MENU, self.onFrequent, frequentItem) # ربط عنصر إيجاد أكثر الكلمات تكرارًا بدالة الحدث self.Bind(wx.EVT_CLOSE, self.onExit) # ربط حدث إغلاق النافذة بالدالة الخاصة بمعالجة ذلك, وهي نفس الدالة المرتبطة بعنصر الخروج بحكم أننا نريد من الحدثين تنفيذ نفس الأوامر self.Show() def onOpen(self, event): # دالة الحدث المرتبطة بعنصر الفتح openDialog = wx.FileDialog(self, "فتح") # محاورة اختيار ملف result = openDialog.ShowModal() # عرض المحاورة if result == wx.ID_CANCEL: # التحقق من كون أن المستخدم لم يضغط على إلغاء return # لو حصل أن كان قد ضغط على إلغاء بالفعل, يتم التوقف عند هذه النقطة وعدم إكمال باقي أكواد الدالة # سيكمل البرنامج إلى النقاط التالية لو ضغط المستخدم على شيء آخر غير الإلغاء path = openDialog.Path # استخراج مسار الملف المحدد من المحاورة # استخراج اسم الملف من المحاورة filename = openDialog.Filename file = open(path, "r", encoding="utf-8") # فتح الملف الذي اختاره المستخدم بوضعية القراءة self.content.Value = file.read() # قراءة المحتويات ووضعها في منطقة الكتابة file.close() #إغلاق الملف self.Title = f"{filename} - المفكرة" # تغيير عنوان النافذة لنظهر فيه اسم الملف المفتوح self.activeFile = path # ضبط قيمة الخاصية self.activeFile التي اتفقنا أن نشير فيها إلى الملف المفتوح بحيث تساوي الملف الذي تم فتحه def onSave(self, event): # محاورة الحفظ if not self.activeFile: # إذا لم يكن هنالك ملف مفتوح مسبقًا saveDialog = wx.FileDialog(self, "حفظ", style=wx.FD_SAVE) # أظهر محاورة الحفظ لتخزين المحتوى المكتوب إلى ملف جديد saveDialog.Wildcard = "ملفات نصية (.txt(|*.txt|جميع الملفات|*.*" result = saveDialog.ShowModal() # عرض المحاورة if result == wx.ID_CANCEL: return path = saveDialog.Path # أخذ المسار من محاورة الحفظ إذا لم يكن هنالك ملف مفتوح مسبقًا filename = saveDialog.Filename self.Title = f"{filename} - المفكرة" else: # وإلا, أي إذا كان هناك ملف مفتوح بالفعل path = self.activeFile # إخذ قيمة المسار من قيمة self.activeFile أي مسار الملف المفتوح file = open(path, "w", encoding="utf-8") # فتح المسار للكتابة عليه file.write(self.content.Value) # كتابة محتوى المربع النصي إلى الملف file.close() # إغلاق الملف self.activeFile = path # تعيين قيمة الخاصية التي تشير إلى الملف المفتوح إلى المسار الجديد self.content.SetModified(False) # إخبار المربع النصي أن محتوى الملف هو المحتوى الحالي ولم يتم تغييره, هذا يفيد حقيقة في مسألة الحفظ عند الخروج def onFrequent(self, event): # الدالة الخاصة بتحليل أكثر الكلمات تكرارًا content = self.content.Value # الإتيان بمحتوى المربع النصي words = content.split() # تقسيم المحتوى إلى كلمات word = max(words, key=words.count) # إيجاد أكثر كلمة تكرارًا من خلال الدالة max والمفتاح words.count count = words.count(word) # معرفة عدد المرات التي تكررت فيها الكلمة التي أخبرتنا دالة max أنها الأكثر تكرارًا wx.MessageBox(f"أكثر كلمة تكررت في النص هي {word} حيث تكررت {count} من المرات") def onExit(self, event): # الدالة المربوطة بكلًا من عنصر الخروج وحدث إغلاق النافذة if self.content.IsModified(): # إن كان قد تم التأشير على المربع النصي بأنه قد تم تعديله message = wx.MessageBox("هل تريد حفظ التعديلات", "حفظ", wx.YES_NO) # إظهار الرسالة التي تخص حفظ التعديلات if message == wx.YES: self.onSave(None) # تنفيذ دالة الحفظ لو ضغط المستخدم على نعم wx.Exit() # الخروج من البرنامج Notepad() # تنفيذ الكلاس app.MainLoop() # جعل البرنامج مستمرًا.

دليل الدرس الثامن:

FileDialog() : الكلاس المخصص في عرض محاورة الملف, وهي تحتمل إحتمالين, أما اختيار ملف أو حفظ الملف, المعاملات هي الحاوي, وهنا نكتب النافذة أي self. العنوان message, هو الذي يظهر عنوانًأ للمحاورة. ShowModal() : هذه الدالة نستخدمها مع المحاورات, وهي لإظهار المحاورة وإنتظار إنتهاء العمل منها كَي تقم بتنفيذ الأكواد التالية, أي توقف عمل الأكواد التالية لحين الإنتهاء منها. Path : خاصية تتوفر في كلاس FileDialog للحصول على المسار الذي تم إختياره. Filename : خاصية تتوفر في كلاس FileDialog للحصول على اسم الملف الذي تم إختياره. wx.FD_SAVE : نمط يُستخدَم مع كلاس FileDialog لجعله بوضعية الحفظ وليس الفتح, ويُكتَب كَمُعامِل style=wx.FD_SAVE. Title : خاصية تتوفر في كلاس الإطار )Frame) تحتوي على العنوان, أي يمكنكم التحكم به ممكن عرضه في مكاان معين أو تغييره, ممكن تغييره عن طريق إسناد له قيمة جديدة. self.Title="new window" EVT_CLOSE : الحدث الذي يتنفذ بمجرد محاولة الخروج من البرنامج عن طريق alt+f4 أو خيار ال close. wx.IsModified() : دالة تتوفر في كلاس TextCtrl للتحقق فيما اذا كان النص قد تم التغيير فيه عن النص الأول. wx.YES_NO : style يستخدَم مع الرسائل MessageBox, لجعل المحاورة تكون على شكل سؤال نعم و لا, طبعا هنا نسند الكلاس MessageBox إلى متغير في حال كنا نريده يظهر على شكل سؤال, ثم بعد ذالك نصل للإجابة عن طريق اسم المتغير ويكون بشرط مثلا if msg == wx.YES: تقم بتنفيذ الكود.

تمرين على الدرس الثامن:

الإخوة الكرام أسعد الله صباحكم وجميع أوقاتكم بالخيرات والمسرات. من خلال متابعتكم للدرسين السابع والثامن, قم بإعادة تصميم مشروع إدارة بيانات الطلاب الذي أنشأناه سابقًا مراعيًا النقاط الآتية. 1. قم بوضع العنصرين إضافة تسجيل وعرض التقرير على شكل عناصر قائمة, أي اصنع لها قائمة ولتسميها مثلًأ القائمة الرئيسية وضع فيها هذين العنصرَين. 2. أضف عنصر الخروج أيضًا إلى نهاية القائمة. 3. قم بإضافة زر جديد إلى محاورة عرض التقرير, مهمة هذا الزر هي حفظ الكشف إلى ملف جديد من خلال محاورة wx.FileDialog. 4. اختياريًا, انشئ قائمة للمساعدة بحيث تحتوي على عنصر لعرض معلومات حول التطبيق على شكل MessageBox. ملاحظة: الكود السابق لمشروع إدارة درجات الطلاب تجدوه في الملخص الخامس, لكم أن تعتمدوا عليه في هذا الحل إ شئتم وتغيير ما يلزم.

الدرس التاسع:

بسم الله الرحمن الرحيم الملخص الكتابي التاسع لدورة البايثون المتقدمة للمكفوفين. تم خلال الدرس التاسع مناقشة النقاط التالية: * قائمة السياق context menu * التعابير النمطية regular expressions * سؤال من المتدربين. أولًا: قائمة السياق context menu. قائمة السياق context menu هي قائمة منبثقة تظهر للمستخدم بمجرد أن يقوم بالضغط على مفتاح التطبيقات في لوحة المفاتيح, أو أن ينقر على زر الفأرة الأيمن. سميت قائمة السياق بهذا الاسم كون أنها في الغالب ترتبط بكائن معين من النافذة, صناديق الاختيار مثلًا, وتحتوي على عناصر تخص ذلك الكائن. كيف يمكن إنشاء قائمة السياق؟ في الواقع, لا يختلف أمر إنشاء قوائم السياق كثيرًا عن الذي كنا نفعله مع شريط القوائم, اللهم أنك هنا لست في حاجة إلى شريط للقوائم, أضف إلى أن قائمة السياق تمثل على شكل قائمة رئيسية واحدة تضم جميع العناصر, في حين أن شريط القوائم قد يضم أكثر من قائمة رئيسية في كل واحدة منها عناصر خاصة بها. لكي نفهم فكرة العمل بشكل جيد, دعونا نأخذ برنامج مخزن الروابط كمثال. سنقوم بحذف جميع العناصر التي لا نحتاجها من هذا البرنامج ونبقي على قائمة الروابط وزر الفتح. أما فيما يتعلق بإضافة رابط وكذلك مسألة حذف الروابط فسنجعلها جميعها في قائمة السياق. كود النافذة قبل إضافة شريط القوائم: import wx import webbrowser app = wx.App() class LinkStore(wx.Frame): def __init__(self): super().__init__(None, title="مخزن الروابط") self.Center() p = wx.Panel(self) self.links = wx.ListBox(p, -1) self.openButton = wx.Button(p, -1, "فتح الرابط") self.openButton.SetDefault() self.openButton.Bind(wx.EVT_BUTTON, self.onOpen) self.Show() def onAdd(self, event): name = wx.GetTextFromUser("اكتب اسم الموقع", "اسم الموقع") url = wx.GetTextFromUser("الصق رابط الموقع هنا", "رابط الموقع") self.links.Append(name, url) def onOpen(self, event): selection = self.links.Selection url = self.links.GetClientData(selection) webbrowser.open(url) LinkStore() app.MainLoop() الآن أنت أمام خيارَين اثنين, إما أن تكتب تعليمات إنشاء قائمة السياق وعناصره من دالة البناء __init__ بشكل مباشر, أو أن تنظم عملك قليلًا وتنشئ دالة منفصلة في الكلاس لإعداد قائمة السياق, ثم تقوم باستدعاء تلك الدالة من وظيفة البناء. وبما أننا نشجع دائمًا على التنظيم, سنتبع الفلسفة الثانية بالطبع, لذا فستنتقل إلى نهاية الكلاس وتنشئ لك وظيفة جديدة ولتسميها مثلًا contextSetup بهذا النحو: def contextSetup(self): بما أن قائمة السياق لا تحتاج إلى شريط معين لتظهر فيه فسننشئ القائمة مباشرة عن طريق wx.Menu() ونسندها إلى متغير باسم context مثلًا. context = wx.Menu() الآن قم بتعبئة هذه القائمة بالعناصر تمامًا كما كنت تفعل مع شريط القوائم سنبدأ بإدراج عنصر الإضافة إلى قائمة السياق هكذا addItem = context.Append(-1, "إضافة رابط") طيب ماذا لو أردنا إلحاق قائمة فرعية بقائمة السياق؟ ببساطة, أنشئ القائمة الفرعية كما لو أنك تنشئ قائمة عادية من خلال الكلاس wx.Menu هكذا editMenu = wx.Menu() الآن أضف عناصرك إلى القائمة الفرعية. من باب التجربة, سنضيف عنصر الحذف إلى القائمة الفرعية وليس إلى قائمة السياق بشكل مباشر deleteItem = editItem.Append(-1, "حذف الرابط المحدد") الآن أضف القائمة الفرعية إلى قائمة السياق, وهنا لن نستخدم الوظيفة Append كما نفعل مع العناصر العادية, وإنما هناك وظيفة خاصة بإلحاق القوائم الفرعية إلى القوائم الرئيسية. اسم الوظيفة هو AppendSubMenu وتستقبل في معاملها الأول الاسم البرمجي للقائمة الفرعية وفي المعامل الثاني عنوان القائمة الذي سيراه المستخدم. لنكتب الكود context.AppendSubMenu(editItem, "تحرير") الآن بقي أن نربط عناصر القائمة بالحدث. حرفيًا, ليس هناك أي شيء مختلف هنا عن شريط القوائم, كلما عليك هو كتابة self إشارة إلى النافذة ثم نقطة ثم Bind ثم قوس ثم اسم الحدث وهو بطبيعة الحال wx.EVT_MENU ثم فاصلة ثم اسم الدالة التي ستنفذ الحدث ثم فاصلة ثم اسم العنصر الذي سيرسل الحدث ثم إغلاق القوس من باب الاختصار سأكتفي بربط عنصر الإضافة بدالة الحدث كون أن دالتها موجودة مسبقًا. self.Bind(wx.EVT_MENU, self.onAdd, addItem) تبقت لدينا خطوة واحدة لننهي إعداد قائمة السياق, وهي الخطوة التي سنحتاج فيها إلى إخبار البرنامج بأن يظهر لنا القائمة context التي قمنا بإنشائها متى ما ضغط المستخدم على قائمة السياق. حقيقة هناك حدث ما يتم إرساله حين الضغط على مفتاح التطبيقات, اسم الحدث هو wx.EVT_CONTEXT_MENU المطلوب منك الآن أن تقوم بربط هذا الحدث بالكائن الذي تريد أن تظهر قائمة السياق, ولنقل مثلًأ نريد من القائمة أن تظهر في صندوق الروابط دون باقي عناصر النافذة. معنى ذلك أنك ستكتب اسم الكائن ثم نقطة ثم Bind ثم قوس ثم wx.EVT_CONTEXT_MENU وهو حدث السياق ثم فاصلة ثم دالة الحدث ثم تغلق القوس. self.links.Bind(wx.EVT_CONTEXT_MENU, self.onContext) مهلًا مهلًا. من المفترض الآن من الدالة onContext أن تنفذ لنا إيعاز واحد فقط وهو الإيعاز المسؤول عن إظهار القائمة context على الشاشة. الآن توجد لدينا إشكاليتين: 1. دالة onContext لن تحتوي إلى على تعليمة برمجية واحدة لا تستحق أن نفرد لها دالة منفصلة. 2. بما أن عملية إظهار القائمة ستتم من دالة حدث, بمعنى آخر أن العملية ستتم خارج نطاق دالة contextSetup لا بد أن نشير إلى قائمة السياق بالكلمة self. في كل موضع كتبنا اسم القائمة فيه, وهذا أمر مجهد في الحقيقة. ما الحل إذًا؟ يقول لك بايثون أن لديه نوع من أنواع الدوال يسمى lambda functions هذا النوع يسمح لك بإنشاء دالة مكونة من سطر برمجي واحد وتمرير هذه الدالة كمعامل لأي دالة أخرى. هذه الدالة, أي ال lambda function ليس لها اسم وإنما تبدأ فقط بالكلمة lambda متبوعة بمعاملات الدالة ثم نقطتين ثم السطر الوحيد الذي من المفترض أن تقوم الدالة بتنفيذه إذًا احذف آخر سطر كتبناه وهو self.links.Bind(wx.EVT_CONTEXT_MENU, self.onContext) واكتب بدله هذا السطر self.links.Bind(wx.EVT_CONTEXT_MENU, lambda event: self.PopupMenu(context)) ملاحظة: وظيفة PopupMenu هي إحدى الوظائف المتوفرة في الكلاس wx.Frame تسمح لك بإعطائها قائمة معينة لتقوم هي بدورها بعرض تلك القائمة على شكل PopupMenu أي قائمة منبثقة. بعد تجهيز كل هذه الأوامر في الوظيفة contextSetup بقي أن نقوم باستدعائها من وظيفة البناء. ضع سطرًا فارغًا قبل self.Show() واكتب جملة الاستدعاء هكذا self.contextSetup() الآن حينما يتم استدعاء الكلاس وتنفيذ دالة البناء الخاصة به سيتم المرور على هذه الوظيفة وتنفيذها مما يعني إعداد قائمة السياق بشكل أكثر تنظيمًا. الآن إلى الكود كاملًا لتتلمسوا الفرق. import wx import webbrowser app = wx.App() class LinkStore(wx.Frame): def __init__(self): super().__init__(None, title="مخزن الروابط") self.Center() p = wx.Panel(self) self.links = wx.ListBox(p, -1) self.openButton = wx.Button(p, -1, "فتح الرابط") self.openButton.SetDefault() self.openButton.Bind(wx.EVT_BUTTON, self.onOpen) self.contextSetup() self.Show() def onAdd(self, event): name = wx.GetTextFromUser("اكتب اسم الموقع", "اسم الموقع") url = wx.GetTextFromUser("الصق رابط الموقع هنا", "رابط الموقع") self.links.Append(name, url) def onOpen(self, event): selection = self.links.Selection url = self.links.GetClientData(selection) webbrowser.open(url) def contextSetup(self): context = wx.Menu() addItem = context.Append(-1, "إضافة رابط...") editMenu = wx.Menu() deleteItem = editMenu.Append(-1, "حذف الرابط المحدد") context.AppendSubMenu(editMenu, "تحرير") self.Bind(wx.EVT_MENU, self.onAdd) self.links.Bind(wx.EVT_CONTEXT_MENU, lambda event: self.PopupMenu(context)) LinkStore() app.MainLoop() ثانيًا: التعابير النمطية. بصفتك كمبرمج, تحتاج في كثير من الأحيان أن تبحث في نص ما عن بعض المعلومات, وهذا بالفعل ما تعلمناه في دورتنا الأساسية حينما تحدثنا عن دوال النصوص مثل find وrfind وغيرهما. لكن ماذا لو حصل أنك تريد البحث عن نصوص معقدة ذات نمط معين, أو التحقق من كون أن إدخالات المستخدم مطابقة لنمط بعينه, هذا ما تحله لك التعابير النمطية. إذًا نستطيع تعريف التعابير النمطية على أنها توصيف للنصوص المراد البحث عنها من خلال جملة من الإشارات والحروف التي يمثل كل واحد منها معنى خاص. لبدء العمل على التعابير النمطية في بايثون, سنقوم أولًأ باستدعاء المكتبة re التي تحتوي على الدوال والكلاسات التي تخص إنشاء واستخدام التعابير النمطية import re الآن قم بإنشاء تعبير نمطي جديد مستخدمًا الدالة compile من مكتبة re واكتب في معاملها الأول شكل التعبير النمطي الذي ترغب بالبحث من خلاله لكن قبل ذلك, دعونا نفرق بين أمرين في لغة التوصيف الخاصة بالتعابير النمطية لدينا ما يسمى بالإشارات النمطية ولدينا ما يعرف بالمكررات. دعونا أولًا نلقي النظر على الإشارات النمطية وسنعود للمكررات لاحقًأ إن شاء الله الإشارات النمطية هي رموز تترجم بمعاني معينة في النص, فعلى سبيل المثال, يقوم الرمز \d بالتعبير عن الأرقام الصحيحة, بينما يقوم الرمز \s بالتعبير عن المسافات. هنا قائمة ببعض الإشارات النمطية مقابل معناها في لغة التعابير النمطية \d أي رقم من 0 إلى 9 \w أي حرف أو رقم أو شرطة سفلية, *يُستخدم هذا الرمز للتعبير عن الكلمات. \sالمسافة أو التاب أو السطر الجديد . أي شيء باستثناء المسافات. قوسين مربعين: تُستخدم لوضع الاحتمالات, أي يمكن أن تحتوي الخانة الحالية على أحد الحروف أو الأرقام أو الرموز القابعة بين القوسين المربعين, يمكن أيضًا كتابة الاحتمالات داخل القوسين على شكل نطاق مثال: [m-z] يحتمل الحروف من m إلى z. الأقواس الهلالية (), تستخدم لتجميع عدة تعابير نمطية لتشكل وحدة نمطية منفصلة يمكن التعامل معها كتعبير قياسي قائم بذاته. التعابير النمطية, المثال الأول: افترض لو أنك تريد البحث في نص عن 3 أرقام متجاورة, أيًا كانت تلك الأرقام مستخدمًا التعابير النمطية في بايثون. الحل: # قبل كل شيء يجب استدعاء مكتبة re import re # الآن, عرف التعبير النمطي الخاص بك من خلال دالة re.compile بهذه الطريقة regexp = re.compile("\d\d\d") # هنا تم استخدام الرمز \d لتمثيل الأرقام, وقد تم تكراره 3 مرات كدلالة على أننا نريد البحث عن 3 أرقام متجاورة #الآن لنضع نصًا افتراضيًا للبحث فيه txt = "مرحبًا بكم 123" # استخدم الآن الوظيفة search من كائن التعابير النمطية الخاص بك هكذا. match = regexp.search(txt) # لو نجحت الوظيفة search في العثور على نص مطابق للتعبير النمطي المحدد فستقوم بإرجاع كائن من نوع match< أما إن لم تنجح في ذلك فلن يتم إرجاع أي شيء. # يمكن الاستفادة من هذه المعلومة بكتابة جملة شرطية تتحقق من كون أن المتغير match لا يساوي اللاشيء هكذا if match is not None: print(match.group()) else: print("لم يتم العثور على نتيجة") # لاحظ أننا استخدمنا الوظيفة group() من الكائن match لطباعة النص الذي تم العثور عليه. # نهاية المثال. ما هي المكررات؟ افترض لو أن عدد مرات تكرار تعبير نمطي معين أكبر من 3, أو أنك لا تريد تكرار التعبير النمطي أكثر من مرة واحدة كما في مثالنا حول البحث عن 3 أرقام متجاورة هنا ستحتاج إلى أن تلجأ إلى ما يعرف بالمكررات. والمكررات هي علامات خاصة يتم وضعها إلى جانب الإشارات النمطية للتعبير عن عدد المرات التي من المفترض للتعبير النمطي أن يتكرر. قائمة رموز المكررات: علىمة النجمة: تفترض أن يتكرر التعبير الذي يسبقها 0 أو أكثر من المرات. علامة الجمع: تفترض أن يتكرر التعبير الذي يسبقها مرة واحدة على الأقل. علامة الاستفهام: تفترض أن يتكرر التعبير الذي يسبقها 0 أو مرة واحدة فقط. الأقواس المزخرفة: تستخدم لتحديد عدد مرات التكرار بالضبط, ويتم كتابتها ب4 أشكال مختلفة: 1. قوسين مزخرفين بينهما رقم واحد فقط: أي أنه يجب أن يتكرر التعبير السابق بالعدد الذي تم وضعه بين الأقواس. مثال: التعبير \d{8} يشترط تكرر الأرقام 8 مرات. 2. قوسين مزخرفين بينهما رقمين مفصولين بعلامة الفاصلة: ويعني ذلك أن يتكرر التعبير ابتداءً من الرقم الأول إلى الرقم الثاني مثال: التعبير \d{8,10} يشترط أن يتكرر تعبير الأرقام 8 أو 9 أو 10 مرات. 3. قوسين مزخرفين بينهما رقم متبوعًا بفاصلة: ويعني ذلك أن يتكرر التعبير ابتداءً من الرقم المحدد إلى ما لا نهاية. مثال: الرمز \d{8,} يشترط أن يتكرر تعبير الأرقام ثمان مرات أو أكثر. 4. قوسين مزخرفين بينهما فاصلة يتبعها رقم: يشترط أن يتكرر التعبير النمطي 0 إلى الرقم الذي بعد الفاصلة. مثال: الرمر \d{,3} يشترط أن يتكرر تعبير الأرقام من 0 أو 1 أو 2 أو 3 أو 4 من المرات. التعابير القياسية: المثال الثاني. في سلطنة عمان: يتم توزيع أرقام الهواتف المكتبية والمحمولة بواقع 8 خانات لكل رقم, وذلك وفقًا للشروط التالية: 1. لأرقام المحمول: يجب أن يبدأ الرقم ب9 أو 7 متبوعًا ب 7 أرقام عشوائية. 2. للأرقام المكتبية: يبدأ الرقم ب2 ثم رقم آخر من 2 إلى 6 ثم ستة أرقام عشوائية. الكود الذي يعبر عن طريقة البحث عن الأرقام العمانية سيكون بهذا الشكل # استدعاء المكتبة import re # إنشاء التعبير القياسي regexp = re.compile("([97]\d{7})|(2[2-6]\d{6})") # النص الذي سنبحث فيه txt = """الرقم الأول: 97700830 الرقم الثاني: 98 98 93 5444444 الرقم الثالث: 24504055 الرقم الرابع: 78988989""" # يمكن الآن البحث إما من خلال الوظيفة search لإيجاد أول نتيجة في النص, أو الوظيفة findall للحصول على قائمة بجميع النتائج # من باب التغيير, سنستخدم الوظيفة الثانية numbers = regexp.findall(txt) # طباعة النتائج for number in numbers: print(numbers) # نهاية المثال. ثالثًا: سؤال من المتدربين ورد سؤال من أحد المتدربين يلي مختصره: كيف لنا أن نجعل لكل صندوق خيارات زرًا افتراضيًا خاص به بحيث يتم تنفيذ دالتين مختلفتين حين تنشيط عناصر كل قائمة. جوابًا على ذلك نقول: في مثل هذه الحالة, لا يمكن الاعتماد على فكرة الزر الافتراضي, أي أن أول خطوة لتنفيذ ذلك هو التخلص من الزر الافتراضي. الخطوة القادمة: قم بإنشاء جدول لاختصارات لوحة المفاتيح مستخدمًا ما يعرف بال AcceleratorTable ستقوم فيه بتحديد المفاتيح لكل مدخل من مدخلات الاختصارات مع تحديد المعرف ID للمهمة التي تريد ربطها. تم شرح الفكرة بالتفصيل في التسجيل, لكني سأكتفي هنا بإرفاق مثال توضيحي للعملية. import wx import webbrowser app = wx.App() class MultiLists(wx.Frame): def __init__(self): super().__init__(None, title="القوائم المتعددة") self.Center() p = wx.Panel(self) wx.StaticText(p, -1, "قائمة الروابط: ") self.links = wx.ListBox(p, -1, choices=["www.google.com", "www.youtube.com"]) wx.StaticText(p, -1, "قائمة عناوين البريد الالكتروني") self.emails = wx.ListBox(p, -1, choices=["Suleiman.alqusaimi@gmail.com", "Suleiman.ahmed@icloud.com"]) hotkeys = wx.AcceleratorTable([ (0, wx.WXK_RETURN, wx.ID_OPEN)]) self.links.SetAcceleratorTable(hotkeys) self.emails.SetAcceleratorTable(hotkeys) self.links.Bind(wx.EVT_MENU, self.onLinks, id=wx.ID_OPEN) self.emails.Bind(wx.EVT_MENU, self.onEmails, id=wx.ID_OPEN) self.Show() def onLinks(self, event): webbrowser.open(self.links.StringSelection) def onEmails(self, event): webbrowser.open(f"mailto:{self.emails.StringSelection}") MultiLists() app.MainLoop()

دليل الدرس التاسع:

wx.EVT_CONTEXT_MENU : الحدث الذي يربط النافذة بقائمة السياق. PopupMenu() : الدالة التي تجعل القائمة المنبثقة تظهر, وهي قائمة السياق, المعامل المطلوب هو القائمة. lambda : هي كلمة تعبر عن مفهوم الدوال ذات السطر الواحد في بايثون, ويتم كتابتها lambda : الكود, وإذا كانت تحتاج الدالة لمعامل فيمكننا كتابة lambda المتغير: الكود مثلا lambda e:self.PopupMenu(menu) AppendSubMenu() : دالة تُستَخدَم لإضافة قائمة فرعية إلى قائمة لتكن menubar أو ContextMenu, المعامل المطلوب هي القائمة. re : المكتبة التي يتم التعامل مع التعابير المنطقية من خلالها. الأشياء التالية كلها تخص التعابير المنطقية. compile() : دالة مِن مكتبة re تُستخدَم لتجهيز التعبير المنطقي المُراد, ثم نستخدم الدوال الخاصة بالتعابير المنطقية من خلال هذا الكائن. المعامل المطلوب هو التعبير المنطقي. search)( : دالة تقم بالبحث من خلال الكائن الذي يحتوي على كلاس الcompile الذي يحتوي على التعبير المنطقي, وهي تقم بإرجاع أول نتيجة يتم الوصول لها. findall() : نفس وضيفة دالة search ولاكنها تأتي بجميع النتائج على شكل قائمة list. sub() : دالة تُستخدم مِن خلال كائن الcompile لإستبدال النصوص بتعبير منطقي, المعاملات المطلوبة هي السلسلة النصية الذي سيتم الإستبدال بها, ثم المعامل الثاني هي السلسلة النصية التي تحتوي على النص المراد إستبداله. group() : دالة تستخدم مع الكائن مِن نوع match التي تقوم search بإرجاعه لنا, ووضيفتها إظهار النتيجة, دالة findall لا تحتاج إليها. \d : علامة تدل على الأرقام بالتعبيرات المنطقية. \w : علامة تدل على الكلمات بالتعابير المنطقية. \s : علامة تدل على المسافات بالتعابير المنطقية. * : علامة تدل على وجود الشيأ 0 مِن المرات أو أكثر. + : علامة تدل على وجود الشيأ 1 مِن المرات أو أكثر. ? : علامة تدل على وجود الشيأ 0 مِن المرات أو مرة واحدة فقط. {} : يتم إستخدام هذه الأقواس بالتعابير المنطقية لتكرار الحاجة كذا مرة, مثلا وجود 5 أرقام, فليس من الممكن نقم بكتابة \d كذا مرة فنقم بفتح القوس { ثم نكتب عدد المرات ثم نغلق القوس } [] : أقواس تُستخدم في الإحتمالات في التعابير المنطقية, مثل [2-8] أي إحتمال وجود الأرقام من 2 إلى 8 وتعمل أيضًا مع الأحرف. )( : لِجمع أكثر مِن تعبير منطقي مرة واحدة, يتم الفصل بينهم بعلامة الشريط. إلى هنا تنتهي هذه الدورة، وإلى لقاءٍ آخر في دوْرات أخرى جديدة ومفيدة. لا تنسوا ذكر الله.


تعليقات