اذهب إلى المحتوى

برمجة الواجهات الرسومية باستخدام Tkinter


أسامة دمراني

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

  • مفاهيم بناء الواجهات الرسومية البسيطة.
  • الوِدجات البسيطة.
  • هيكل برنامج Tkinter بسيط.
  • الواجهات الرسومية والبرمجة الكائنية التوجه، تطابق مثالي.
  • wxPython بديل Tkinter.

مبادئ الواجهات الرسومية

لن نتعرض لمفاهيم برمجية جديدة هنا، فبرمجة الواجهات الرسومية تشبه أي نوع آخر من البرمجة، إذ تحوي تسلسلات sequences، ووحدات modules، وفروعًا branches، وحلقات تكراريةً loops، أما الأمر الإضافي فيها فهو استخدام صندوق أدوات Toolkit، واتباعنا نمطًا معينًا في تصميم البرمجيات يحدده من كتب صندوق الأدوات ذاك، لذا يكون لكل صندوق أدوات مجموعته الخاصة من الوحدات والأصناف والدوال، والتي تعرف باسم واجهة برمجة التطبيقات Application Programming Interface، واختصارًا API، كما يحتوي صندوق الأدوات على مجموعة من قواعد التصميم، وعلينا -نحن المبرمجون- أن نتعلم واجهة برمجة التطبيقات وقواعد التصميم معًا، ولهذا يحاول أغلبنا اعتماد صناديق أدوات قليلةً تكون متاحةً في عدة لغات برمجة، لأن تعلم استخدام صندوق الأدوات أصعب من تعلم لغة البرمجة نفسها.

صناديق أدوات الواجهات الرسومية

تأتي أغلب لغات برمجة نظام التشغيل ويندوز مع صندوق أدوات مضمن فيها، ويكون طبقةً خفيفةً فوق صندوق الأدوات البدائي المضمن في نظام النوافذ نفسه، ويوجد في لغات مثل Visual Basic وDelphi وVisual C++/.NET، أما جافا فتختلف عن لغات ويندوز في أنها تحتوي على صندوق أدوات الرسوم الخاص بها، بل أكثر من صندوق واحد في الواقع، وتعمل هذه الصناديق على أي منصة تعمل عليها جافا، وهذا يعني جميع المنصات تقريبًا، وتوجد صناديق أدوات أخرى يمكن الحصول عليها بشكل مستقل وتُستخدم في أي نظام تشغيل سواء كان لينكس أو ويندوز أو ماك، وتحتوي محولات adapters لتسمح للغات المختلفة باستخدامها، وبعض تلك الصناديق تجاري مدفوع، وبعضها مجاني أو حر، والأمثلة على هذه الصناديق تشمل GTK وQt وTk وwxWidegets، ولها جميعًا مواقع وتدعم ويندوز وماك ولينكس:

  • wxPython: النسخة الخاصة ببايثون من wxWidgets، وهو مكتوب بلغة C++‎، وتسمى رابطة بايثون إلى wxWidgets باسم WxPython.
  • PyQt, the Qt toolkit: يحتوي على روابط لأغلب لغات البرمجة، وتُعرف روابط بايثون إلى Qt باسم PyQt.
  • GTK+‎: مجموعة من العناصر والأدوات لإنشاء واجهات رسومية، ورابطة بايثون فيه اسمها PyGTk.

تُكتب أغلب برامج لينكس باستخدام Qt وGTk، وكلاهما مجاني للاستخدامات غير التجارية، ويوفر Qt رخصةً تجاريةً لمن شاء، في حين أن رخصة GTk هي رخصة Gnu GPL التي لها شروطها الخاصة بها.

صندوق الأدوات الرسومي الخاص ببايثون والذي يأتي افتراضيًا مع اللغة هو Tkinter، المبني على Tk، وهو صندوق أدوات قديم متعدد نظم التشغيل، وسندرسه هنا، حيث توجد منه إصدارات للغات Tcl وHaskell وRuby وPerl وبايثون، وتختلف المبادئ المستخدمة في Tk عن صناديق الأدوات الأخرى قليلًا، لذا سنذكر باختصار نظرةً على صندوق أدوات رسومي آخر لبايثون و C و C++‎، يكون تقليديًا في منظوره العام، لكن سنتعرف أولًا على بعض المفاهيم العامة.

عناصر الواجهات الرسومية الأساسية

ذكرنا سابقًا أن التطبيقات الرسومية ذات طبيعة حدَثية -مدفوعة بالأحداث event-driven- في الغالب، ويمكن الرجوع للمقال السابق، للاطلاع على هذا المفهوم، وسنفترض أنك مستخدم معتاد على التعامل مع الواجهات الرسومية، وسنركز على كيفية عمل البرامج الرسومية من منظور المبرمج، ولن ندخل في تفاصيل كيفية كتابة واجهات رسومية معقدة وكبيرة لها نوافذ متعددة أو واجهات MDI أو غيرها، بل سنكتفي بأساسيات إنشاء تطبيق وحيد النافذة فيه بعض العناوين والأزرار والحقول النصية وصناديق الرسائل.

نحتاج أولًا إلى التحقق من المصطلحات التي سنستخدمها، فلبرمجة الواجهات مجموعتها الخاصة من المصطلحات البرمجية، وأكثر المصطلحات استخدامًا فيها هي:

النافذة window

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

المتحكم Control

كائن رسومي يُستخدم للتحكم في التطبيق، وتحتوي المتحكمات على خصائص properties، وتولّد أحداثًا events، وتستجيب عادةً لكائنات على مستوى التطبيق، حيث تُرفَق الأحداث بتوابع الكائن الموافق corresponding object، فإذا وقع الحدث نفّذ الكائن أحد توابعه، وتوفر الواجهة الرسومية آليات لربط الأحداث بالتوابع.

الودجِت Widget

متحكم مقيَّد عادةً بالمتحكمات المرئية، إذ يمكن ربط بعض المتحكمات -مثل المؤقتات timers- بنافذة ما، لكن دون أن تكون مرئيةً، أما الودجات فهي فئة مرئية فرعية من المتحكمات، ويمكن للمستخدم أو المبرمج أن يعدل فيها.

سنغطي في هذا المقال الودجات Frame وLabel وButton وText Entry وMessage box، كما سنتعرض لاحقًا للودجات Text box وRadio Button، أما الودجات التي لن نذكرها مطلقًا فهي Canvas الخاصة بالرسم، وCheck button الخاصة بالاختيار المتعدد، وImage الخاص بعرض صور BMP وGIF وJPEG وPNG، وListbox للقوائم، و Menu/MenuButton لبناء القوائم المنسدلة menus، وScale/Scrollbar التي توضح الموضع.

الإطار Frame

نوع من الودجات يُستخدم لدمج ودجات أخرى معًا، ويُستخدم عادةً لتمثيل النافذة ككل، ويمكن دمج إطارات أخرى فيه.

التخطيط Layout

توضع المتحكمات في إطار وفقًا لمجموعة محددة من القواعد أو الإرشادات، وتشكل تلك القواعد ما يعرف بالتخطيط، وقد يُحدَّد بعدة طرق، مثل استخدام إحداثيات محددة بالبكسل على الشاشة، أو باستخدام مواضع نسبية لمكونات أخرى (مثل اليسار، الأعلى)، أو باستخدام شبكة أو جدول. ومن السهل فهم نظام الإحداثيات، لكن تصعب إدارته عند تعديل حجم النوافذ مثلًا، ويُنصح المبتدئون باستخدام نوافذ لا يمكن تغيير حجمها إذا استخدموا تخطيطات مبنيةً على إحداثيات.

الأب/الابن Parent/Child

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

شجرة الاحتواء Containment tree

من المفاهيم التي يجب استيعابها في برمجة الواجهات الرسومية هرمية الاحتواء containment hierarchy، حيث تُحتوى الودجات داخل هيكل شجري تتحكم فيه ودجت المستوى الأعلى بالواجهة كلها، ويكون لذلك الهيكل ودجات فرعية يمكن أن تحتوي بدورها على ودجات أخرى فرعية خاصة بها، وتصل الأحداث إلى ودجت فرعية تمرر الحدث -إذا لم تستطع معالجته- إلى الودجت الرئيسية (الأب) لها، وهكذا إلى أن نصل إلى ودجت المستوى الأعلى، وإذا أُعطي أمر لرسم ودجت ما فسيرسَل الأمر إلى الودجات الفرعية، وعليه فإن أمر الرسم إلى ودجت المستوى الأعلى قد يعيد رسم التطبيق كله، في حين أن أمر الرسم المرسَل إلى زر ما لن يعيد إلا رسم الزر فقط.

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

guitree.png

توضح الصورة أعلاه ودجت المستوى الأعلى التي تحتوي على إطار Frame واحد يمثل الحد border الخارجي للنافذة، والذي يحتوي بدوره على إطارين آخرين، في الأول منهما ودجت Text Entry، وفي الثاني زران Buttons يُستخدمان للتحكم في التطبيق، وسنشير إلى هذا المخطط لاحقًا حين نأتي لبناء الواجهة الرسومية.

نظرة على بعض الودجات الشائعة

سنستخدم محث بايثون التفاعلي في هذا القسم لإنشاء بعض النوافذ والودجات البسيطة، ورغم أن IDLE نفسه ما هو إلا تطبيق Tkinter لكن لا يُمكن الاعتماد عليه في تشغيل تطبيقات Tkinter داخله، وإنما نستطيع إنشاء الملفات باستخدامه مثل محرر، ثم نشغلها في بيئة التطوير IDE كالمعتاد، رغم احتمال حدوث أشياء غير متوقعة هنا، فإذا وقعت تصرفات غريبة فيجب أن نشغل التطبيق من سطر أوامر النظام قبل أن نجرب شيئًا آخر، فربما تكون المشكلة تعارضًا بين إطار Tkinter الداخلي لبيئة IDLE وبين نسخته لبرنامجنا، فإذا لم تُحل المشكلة فسنبحث عن الزلات البرمجية bugs في شيفرة البرنامج. أما مستخدمو Pythonwin فيستطيعون تشغيل تطبيقات Tkinter مباشرةً دون مشاكل، لأن Pythonwin لا تستخدم Tkinter داخليًا، لكن حتى هنا قد تحدث سلوكيات غير متوقعة في تطبيقات Tkinter، ولهذا نستخدم محث بايثون الأساسي من نافذة نظام التشغيل الطرفية دومًا:

>>> from tkinter import * 

هذا أول متطلبات أي برنامج Tkinter، وهو استيراد أسماء الودجات، يجب أن نستورد الوحدة، لكننا لا نريد كتابة tkinter قبل كل اسم مكون، لذا نستخدم الاسم البديل tk:

 >>> import tkinter as tk

وهذا يعني أننا نحتاج إلى سبق الأسماء بـ tk.‎ فقط، لكن بما أننا نجرب هنا فمن الأسهل استيراد كل شيء:

>>> top = Tk()

ينشئ هذا ودجت المستوى الأعلى في هرمية الودجات، ونلاحظ حالة الأحرف في Tk، حيث كان اسم الوحدة بالأحرف الصغيرة، لكن اسم الودجت بأحرف كبيرة، وتُنشأ جميع الودجات الأخرى فروعًا لودجت top.

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

سنضيف المكونات إلى هذه النافذة أثناء بنائنا للتطبيق، ولننظر الآن إلى ما لدينا:

>>> dir(top)
[....lots of stuff!...]

توضح دالة dir()‎ الأسماء المعروفة للوسيط، ونستطيع استخدامها على الوحدات لكننا ننظر هنا إلى المكونات الداخلية للكائن top، وهو نسخة من الصنف Tk، حيث نلاحظ وجود سمات كثيرة للكائن top، نخص بالذكر منها children وmaster اللتان تمثلان روابط إلى شجرة احتواء الودجت، كما نلاحظ سمة _tclCommands_، لأن Tkinter بُني على صندوق أدوات من Tcl اسمه Tk.

>>> F = Frame(top)

لننشئ ودجت إطار Frame هنا تحتوي المتحكمات/الودجات الفرعية التي نستخدمها، ويحدد Frame كائن top على أنه معامله الأول -والوحيد في هذه الحالة-، وعليه فإن F ستكون ودجت فرعيةً للكائن top، ,يمكن التحقق من هذا بسهولة كما يلي:

>>> top.children
{'!frame': <tkinter.Frame object .!frame>}
>>> F.master
<tkinter.Tk object .> 

نرى هنا أن top.children ما هو إلا قاموس يربط اسمًا غريبًا قليلًا هو ‎'!frame'‎ بمرجع كائن object reference، وهذه التسمية الغريبة خاصة بـ Tcl/Tk وانتقلت إلى Tkinter، كما أن F.master ما هو إلا مرجع إلى top، وننصحك هنا بالتدرب على السمتين master وchildren لودجاتنا، مما يسهل عليك فهم الصلة بين الودجات وشجرة الاحتواء التي ذكرناها من قبل.

>>> F.pack()

لاحظ الآن تقلص نافذة Tk -إذا كانت مرئيةً- إلى حجم ودجت الإطار المضاف، وهي صغيرة للغاية لأن الودجت فارغة، لكن ينبغي أن نكون قادرين على تغيير حجمها بالفأرة وأزرار التصغير والتكبير في شريط عنوانها.

يستدعي التابع pack()‎ مدير تخطيط يُعرف باسم المحزِّم Packer، وهو سهل الاستخدام في التخطيطات البسيطة، لكنه يصبح صعبًا كلما زاد تعقيد التخطيط، وسنستخدمه الآن لسهولته، حيث يكدس هذا المحزِّم الودجات بعضها فوق بعض، ونلاحظ أن الودجات لن تكون مرئيةً في تطبيقنا حتى نحزمها أو نستخدم تابع مدير تخطيط آخر، وسنتحدث عن مدراء التخطيطات لاحقًا، بعد إنهاء هذا البرنامج.

>>> lHello = Label(F, text="Hello world")

ننشئ هنا كائنًا جديدًا هو lHello، وهو نسخة من الصنف Label مع ودجت أب هي F، وسمة text قيمتها "Hello World"، نلاحظ أنه من المعتاد استخدام تقنية المعامِل المسمى بتمرير الوسائط إلى كائنات Tkinter بسبب ميل منشئات كائنات Tkinter لامتلاك عدة معامِلات لكل منها قيمته الافتراضية، كما نلاحظ أن الكائن ليس مرئيًا بعد لأننا لم نحزّمه.

كما نلاحظ استخدام اصطلاح تسمية هنا، وهو حرف l الذي يشير إلى كلمة Label قبل الاسم Hello الذي يذكرنا بالغرض منه، ومسألة اصطلاحات التسمية هذه هي في الحقيقة أمور شخصية، لكنها مفيدة لهذا الغرض الذي ذكرناه.

>>> lHello.pack()

نستطيع الآن أن نراها، وينبغي أن تكون النافذة التي أنشأتها لديك شبيهة بهذه:

tk-hello.png

تُحدَّد خصائص Label -مثل الخط واللون- باستخدام معامِلات منشئ الكائن أيضًا، ونستطيع الوصول إلى الخصائص الموافقة corresponding باستخدام التابع configure الخاص بودجات Tkinter كما يلي:

>>> lHello.configure(text="Goodbye")

لقد تغيرت الرسالة هنا، لذا نرى أن تقنية configure ممتازة عند تغيير عدة خصائص مرةً واحدةً لتمريرها جميعًا على أنها وسطاء، لكن إذا أردنا تغيير خاصية واحدة فقط في كل مرة كما فعلنا أعلاه فيمكن معاملة الكائن على أنه قاموس، وهذا أقصر وأسهل في الفهم:

>>> lHello['text'] = "Hello again"

إن ودجات العناوين مملة، ولا تعرض إلا نصوصًا قابلة للقراءة فقط، وإن كانت بألوان وخطوط مختلفة، رغم إمكانية استخدامها لعرض رسوم بسيطة، لكننا لن نتطرق إلى هذا هنا، نقول هذا لننظر في نوع كائن آخر، لكن ثمة شيء نفعله وهو إعداد عنوان النافذة، وذلك باستخدام تابع من ودجت المستوى الأعلى top:

>>> F.master.title("Hello")

كان بإمكاننا استخدام top مباشرةً، لكن الوصول من خلال الخاصية الرئيسية للإطار مفيد، كما سنرى لاحقًا:

>>> bQuit = Button(F, text="Quit", command=F.quit)

ننشئ هنا ودجت جديدةً هي زر له عنوان Quit، ويرتبط بالأمر F.quit، لاحظ أننا نمرر اسم التابع ولا نستدعيه بإضافة أقواس بعده، وهذا يعني أننا يجب أن نمرر كائن دالة وفقًا لبايثون، سواء كان تابعًا مضمنًا في Tkinter -كما ف حالتنا- أو أي دالة أخرى نعرّفها.

يجب ألا تأخذ الدالة أو التابع أي وسطاء، ويُعرَّف التابع quit في صنف أساسي -وكذلك التابع pack- وترثه جميع ودجات Tkinter، لكنه يُستعدى في الغالب في مستوى النافذة العليا للتطبيق.

>>> bQuit.pack()

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

>>> top.mainloop()

وأخيرًا نبدأ حلقة حدث Tkinter، لاحظ اختفاء محث بايثون ‎>>>‎، وهذا يخبرنا أن Tkinter هو المتحكم الآن، فإذا ضغطنا زر Quit فسيعود المحث ليثبت لنا أن خيار command يعمل، لا تتوقع أن تنغلق النافذة، فلا زال مفسر بايثون يعمل ولم نرد إلا الخروج من دالة mainloop، فإذا خرجنا من بايثون فستُدمَّر الودجات الموجودة، ويكون هذا -في البرامج الحقيقية- بعد انتهاء mainloop مباشرة.

ونلاحظ أنه إذا شغلنا هذا من IDLE أو Pythonwin فلم نكن لنرى شيئًا إلى الآن، ولحصلنا على نتيجة مختلفة قليلًا، وإذا حدث هذا معك فاكتب الأوامر التي كتبناها إلى الآن في سكربت بايثون ثم شغلها من سطر أوامر النظام، ومن المناسب الآن أن تجرب ذلك على أي حال بما أنه الكيفية التي ستشغل بها برامج Tkinter في الممارسة العملية، واستخدم الأوامر الأساسية التي شرحناها حتى الآن وكما وضحنا، واستخدم نمط الاستيراد المفضل:

import tkinter as tk

# إعداد النافذة ذاتها
top = tk.Tk()
F = tk.Frame(top)
F.pack()

# إضافة الودجات
lHello = tk.Label(F, text="Hello")
lHello.pack()
bQuit = tk.Button(F, text="Quit", command=F.quit)
bQuit.pack()

# تشغيل الحلقة التكرارية
top.mainloop()

يبدأ استدعاء التابع top.mainloop حلقة حدث Tkinter لتوليد الأحداث، والحدث الوحيد الذي نلتقطه في هذه الحالة سيكون حدث ضغط الزر المتصل بالتابع F.quit، وينهي الأخير التطبيق وستنغلق النافذة هذه المرة لأن بايثون قد خرجت هي الأخرى، جربها بنفسك الآن، ينبغي أن تبدو كما يلي:

tk-hellbut.png

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

استكشاف التخطيط

سنبدأ من الآن بإعطاء الأمثلة في ملفات سكربتات بايثون بدلًا من أوامر في محث ‎>>>‎، وسنوفر مقتطفات من الشيفرات في الغالب لتكتب أنت الاستدعاءات إلى Tk()‎ وmainloop()‎ بنفسك، فاستخدم البرنامج السابق قالبًا، وسننظر في هذا القسم في توضع الودجات في النافذة في Tkinter، وقد رأينا ودجات Frame وLabel وButton من قبل، وهي كل ما نحتاج إليه في هذا القسم، وقد استخدمنا التابع pack الخاص بالودجت في المثال السابق لتحديد موقعها في الودجت الأب لها، والواقع أن ما نفعله هو استدعاء مدير تخطيط المحزِّم الخاص بـ Tk، ويطلق عليه أحيانًا اسم المدير الهندسي Geometry Manager، ووظيفته تحديد أفضل تخطيط للودجات بناءً على الإرشادات التي يوفرها المبرمج، إضافةً إلى القيود مثل حجم النافذة التي يتحكم بها المستخدم، ويستخدم بعض مدراء التخطيطات نفس المواقع داخل النافذة محددةً بالبكسل، وهذا أمر شائع في بيئات ويندوز مثل Visual Basic.

ويحتوي Tkinter على مدير تخطيط واضع placer layout manager يستطيع تنفيذ ذلك من خلال تابع place، ولن ننظر فيه لوجود خيارات أخرى أفضل وأكثر ذكاءً للمدراء، لأنها توفر علينا التفكير -نحن المبرمجين- في ما يحدث عند تغيير حجم نافذة ما، وأبسط مدير تخطيط في Tkinter هو برنامج التحزيم الذي كنا نستخدمه، وهو يكدس الودجات بعضها فوق بعض، ويمكن تغيير هذا السلوك لتكديس الودجات يسارًا ويمينًا لكن هذا محدود للغاية، ونادرًا ما نريد ذلك من الودجات العادية، لكن إذا أردنا بناء تطبيقاتنا من إطارات Frames فيجب أن نكدس الإطارات فوق بعضها، ثم نستطيع وضع الودجات الأخرى في الإطارات باستخدام المحزِّم أو مدير تخطيط آخر داخل كل إطار حسب الحاجة، ويمكن أن يكون لكل إطار مدير التخطيط الخاص به، لكننا لا نستطيع الجمع بين المدراء في إطار واحد.

يوفر المحزِّم -وإن كان بسيطًا- عدة خيارات، إذ نستطيع ترتيب ودجاتنا رأسيًا أو أفقيًا، ونستطيع تعديل أحجامها وتعديل الفواصل بينها والإطار التي تحاذيه، لننظر في المثال التالي على التحزيم الأفقي:

lHello = tk.Label(F, text="Hello")
lHello.pack(side="left")
bQuit = tk.Button(F, text="Quit", command=F.quit)
bQuit.pack(side="left")

سيؤدي هذا إلى إجبار الودجات على الانتقال إلى اليسار، لذا ستظهر الودجت الأولى -وهي العنوان- في أقصى اليسار، متبوعةً بالودجت التالية -الزر-، فإذا عدّلنا الأسطر في المثال أعلاه فسيبدو كما يلي:

tk-leftpack.png

وإذا غيرنا "left" إلى "right" فسيظهر العنوان على أقصى اليمين، وسيظهر الزر على يساره، كما يلي:

tk-rightpack.png

يجب أن نلاحظ أن الشكل غير لطيف، لأن الودجات مضغوطة إلى جانب بعضها البعض، ويزودنا المحزِّم ببعض المعامِلات للتعامل مع ذلك، لعل أسهلها الحشو padding الذي يُحدَّد بحشو أفقي padx ورأسي pady، وتُكتب تلك القيم بالبكسل، لنضف حشوًا أفقيًا إلى مثالنا:

lHello.pack(side="left", padx=10)
bQuit.pack(side='left', padx=10)

يجب أن يبدو كما يلي:

tk-padx.png

إذا حاولنا تغيير عرض النافذة فسنرى أن الودجات تحافظ على مواضعها النسبية، لكنها ستظل في مركز النافذة، لأننا -رغم إضافتنا الحشو إلى اليسار- حزمنا الودجات في إطار Frame؛ وحزّمنا الإطار نفسه دون جانب side، لذا فإن موضعه يكون إلى الأعلى والمنتصف، وهو الافتراضي للمحزِّمات، فإذا أردنا أن تبقى الودجات في الجانب الصحيح من النافذة فيجب أن نحزم الإطار إلى الجانب الصحيح كذلك:

F.pack(side='left')

نلاحظ هنا أن الودجات تظل في المنتصف إذا غيرنا حجم النافذة الرأسي، وهذا هو السلوك الافتراضي للمحزِّمات أيضًا، سنترك لك تجربة تغيير padx وpady لترى تأثير القيم المختلفة عليها. وتسمح side وpadx/pady بمرونة كبيرة في تحديد مواضع الودجات باستخدام المحزِّم، وتوجد خيارات أخرى يضيف كل منها شكلًا خفيفًا من أشكال التحكم، يُرجَع فيها إلى توثيق Tkinter.

يوجد عدة مدراء تخطيطات آخرين في Tkinter مثل الشبكة grid والواضع placer، إضافةً إلى وحدة Tix التي تعزز Tkinter وتوفر مدير التخطيط Form، لكننا لن نشرح هذه الوحدة هنا لأنها أُهملت في المكتبة القياسية رسميًا.

نستخدم grid()‎ إذا أردنا استخدام مدير الشبكة grid manager، بدلًا من pack()‎ التي استخدمناها أعلاه، أما في الواضع placer فنستدعي place()‎ بدلًا من pack()‎، ولكل منها مجموعة خيارات خاصة، وبما أننا سنشرح المحزِّم فقط هنا فيُرجَع إلى توثيق Tkinter لمزيد من التفاصيل عن هؤلاء المدراء، لكن النقاط الأساسية التي نريد الإشارة إليها هنا هي ما يلي:

  • تنظم الشبكة المكونات في "شبكة" داخل النافذة، وهذا مفيد في الصناديق الحوارية التي تحوي صناديق إدخال نصيةً مرتبةً مثلًا، ويفضل العديد من مستخدمي Tkinter استخدام الشبكة على المحزِّم، لكن قد يحتاج المبتدئ وقتًا حتى يتعلمها، خاصةً عندما يريد أن يشغل مكون عدة خلايا من الشبكة.
  • يستخدم الواضع إحداثيات ثابتةً بالبكسل أو إحداثيات نسبيةً داخل النافذة، وتسمح الأخيرة بتغيير حجم المكونات مع تغير حجم النافذة، كأن تظل المساحة التي تشغلها 75% من المساحة الرأسية للنافذة مثلًا، لكن قد يبدو مظهر الأزرار غريبًا إذا زاد عرضها كثيرًا، وتظهر فائدة هذا في التصاميم المعقدة للنوافذ، لكنها تحتاج إلى كثير من التخطيط المسبق، لذا يُنصح باستخدام الورق والقلم!

التحكم في المظهر باستخدام الإطارات والمحزم

تحتوي ودجت الإطار Frame على العديد من الخصائص المفيدة التي يمكن استخدامها، فمن الجميل أن يكون لدينا إطار منطقي غير مرئي حول المكونات، لكننا قد نرغب في رؤيته، خاصةً عند جمع عدة متحكمات مثل أزرار الانتقاء radio buttons أو صناديق الاختيار check boxes، ويحل الإطار هذه المشكلة بتوفير حد يُعرف بخاصية المساعدة relief property، على غرار العديد من ودجات Tkinter الأخرى، يمكن أن تأخذ Relief إحدى القيم التالية:

  • sunken: غائر.
  • raised: مرتفع.
  • groove: محفور.
  • ridge: مشطوف.
  • flat: مسطح.

لنستخدم القيمة sunken على صندوق حوارنا البسيط، بتغيير سطر إنشاء الإطار Frame إلى ما يلي:

 F = Frame(top, relief="sunken", border=1) 

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

ومما يجب ملاحظته أيضًا أن الإطار لا يملأ النافذة، ونستطيع إصلاح ذلك بخيار محزِّم آخر هو fill، فنفعل ما يلي عند تحزيم الإطار:

F.pack(fill="x")

ستُملأ النافذة عرضيًا كما هو واضح من الإحداثي x، فإذا أردنا ملأها كلها فنستخدم fill='y'‎ أيضًا، ويوجد خيار ملء خاص هو both بسبب شيوع هذه العملية:

F.pack(fill="both")

ينبغي أن تكون النتيجة النهائية بعد تشغيل السكربت كما يلي:

tk-sunken.png

إضافة ودجات أخرى

لننظر الآن في الودجت Entry، وهو السطر الواحد ذو الشكل المألوف لصندوق الإدخال النصي، والذي يتشارك الكثيرَ من التوابع مع ودجت النص متعدد الأسطر الذي استخدمناه في المقال السابق، كما سنستخدمه في مقال لاحق، وسنستخدم Entry لالتقاط النص الذي يكتبه المستخدم، ولمحو ذلك النص عند الحاجة.

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

import tkinter as tk

# أنشئ معالج الحدث لمسح النص
def evClear():
   eHello.delete(0,tk.END)

# أنشئ نافذة/إطار المستوى الأعلى
top = tk.Tk()
F = tk.Frame(top)
F.pack(fill="both")

# والآن الإطار الذي فيه الإدخال النصي
fEntry = tk.Frame(F, border=1)
eHello = tk.Entry(fEntry)
fEntry.pack(side="top")
eHello.pack(side="left")

# وأخيرًا الإطار الذي فيه الأزرار 
# سنجعل هذا غاطسًا لنبرزه
fButtons = tk.Frame(F, relief="sunken", border=1)
bClear = tk.Button(fButtons, text="Clear Text", command=evClear)
bClear.pack(side="left", padx=5, pady=2)
bQuit = tk.Button(fButtons, text="Quit", command=F.quit)
bQuit.pack(side="left", padx=5, pady=2)
fButtons.pack(side="top", fill="x")

# والآن شغل حلقة الحدث
F.mainloop()

نلاحظ هنا أننا عرّفنا معالج الحدث مثل أي دالة أخرى، وبما أننا نريد إسنادها إلى حدث الأمر command event لزر فنعرف أنها يجب ألا تحتوي على معامِلات، رغم أن بعض معالجات الأحداث -مثل أحداث الفأرة- قد تأخذ معامِلات، لكن يجب التحقق من التوثيق لمعرفة المطلوب للحدث.

لاحظ أيضًا أننا نمرر أسماء معالجات الأحداث evClear وF.quit -دون أقواس- قيمًا للمعامل command للأزرار، ولاحظ استخدام اصطلاح التسمية evXXX لربط معالج الحدث بالودجت XXX الموافقة له، لذا سيكون evClear هو معالج الحدث لودجت bClear.

يستدعي معالج الحدث التابع delete الخاص بالودجت Entry، ورغم أن نظام الفهرسة المستخدم للوسطاء معقد قليلًا إلا أننا نستطيع في هذا المستوى أن نقول بأنه يمسح النص من الموضع 0 -أي من البداية- إلى الموضع tk.END -آخر موضع-، ولاحظ أن tk.END ثابت معرَّف في tkinter، ويوجد غيره مما يمكن استخدامه بدلًا من السلاسل الاختيارية right وleft وtop وغيرها، وذلك راجع لما يفضله كل منا، وبتشغيل البرنامج نحصل على النتيجة التالية:

tk-entry.png

فإذا كتبت شيئًا في صندوق الإدخال النصي فاضغط Clear Text لمسحه مرةً أخرى.

لاحظ أننا بنينا الواجهة الرسومية التي رسمها مخطط الاحتواء في بداية هذا المقال، فللودجت العليا إطار Frame تحتها، وفيه إطاران تحته، واحد فيه ودجت إدخال والآخر فيه زران، وهذا ما نراه في المخطط بالضبط.

ولا توجد فائدة تذكر من وجود ودجت إدخال إلا إذا كنا نستطيع الوصول إلى النص الذي فيها، ونفعل ذلك باستخدام التابع get الخاص بالودجت، وسنوضح هذا بنسخ النص من الودجت إلى عنوان label قبل مسحه، لنستطيع رؤية النص الأخير الذي كان في الودجت، ولفعل ذلك نحتاج إلى إضافة ودجت عنوان تحت ودجت الإدخال، وتوسيع معالج الحدث evClear لينسخ النص، وسنلون نص العنوان بلون أزرق فاتح لإبرازه.

import tkinter as tk

# أنشئ معالج الحدث لمسح النص
def evClear():
  lHistory['text'] = eHello.get()
  eHello.delete(0,tk.END)

# أنشئ نافذة/إطار المستوى الأعلى
top = tk.Tk()
F = tk.Frame(top)
F.pack(fill="both")

# والآن الإطار الذي فيه الإدخال النصي
fEntry = tk.Frame(F, border=1)
eHello = tk.Entry(fEntry)
eHello.pack(side="left")
lHistory = tk.Label(fEntry, foreground="steelblue")
lHistory.pack(side="bottom", fill="x")
fEntry.pack(side="top")

# وأخيرًا الإطار الذي فيه الأزرار 
# سنجعل هذا غائرًا لنبرزه
fButtons = tk.Frame(F, relief="sunken", border=1)
bClear = tk.Button(fButtons, text="Clear Text", command=evClear)
bClear.pack(side="left", padx=5, pady=2)
bQuit = tk.Button(fButtons, text="Quit", command=F.quit)
bQuit.pack(side="left", padx=5, pady=2)
fButtons.pack(side="top", fill="x")

# والآن شغل حلقة الحدث
F.mainloop()

نرى هنا أننا أضفنا السطر التالي عند إنشاء معالج الحدث الذي يمسح النص:

  lHistory['text'] = eHello.get()

كما أضفنا الأسطر التالية في الإطار الذي يحتوي على إدخال نصي:

lHistory = tk.Label(fEntry, foreground="steelblue")
lHistory.pack(side="bottom", fill="x")

من الممكن إسناد النص إلى متغير بايثون عادي لنستخدمه لاحقًا في برنامجنا، رغم أننا أسندناه مباشرةً إلى خاصية Label هنا.

أحداث الربط: من الودجات إلى الشيفرة

لقد استخدمنا الخاصية command للأزرار إلى الآن لربط دوال بايثون مع أحداث الواجهة الرسومية، لكننا قد نريد تحكمًا أكثر من هذا أحيانًا، لالتقاط تجميعة مفاتيح معينة مثلًا، ونفعل ذلك باستخدام دالة bind لربط حدث ما مع دالة بايثون صراحةً.

سنعرِّف الآن مفتاحًا ساخنًا مثل CTRL+C لحذف النص في المثال أعلاه، سنحتاج هنا أن نربط تجميعة المفاتيح CTRL+C بنفس معالج الحدث الخاص بزر Clear، لكننا سنواجه مشكلةً غير متوقعة هنا، إذ يجب ألا تأخذ الدالة المحددة أي وسطاء عند استخدامنا للخيار command، أما حين نستخدم دالة الربط bind لأداء نفس الوظيفة فيجب أن تأخذ الدالة المحددة وسيطًا واحدًا، لذا نحتاج إلى إنشاء دالة جديدة ذات معامِل وحيد تستدعي evClear، أضف الشيفرة التالية بعد تعريف evClear:

def evHotKey(event):
    evClear()

ثم أضف السطر التالي مباشرةً بعد تعريف ودجت الإدخال eHello:

eHello.bind("<Control-c>",evHotKey) # تعريف المفتاح حساس لحالة الأحرف

شغّل البرنامج الآن مرةً أخرى، ستستطيع مسح النص الآن بضغط الزر أو باستخدام تجميعة المفاتيح CTRL+C، ويمكن استخدام الربط لالتقاط نقرات الفأرة أو فقدان تركيز النافذة Focus -أي كونها نشطةً أو غير نشطة-، أو حتى كون النافذة مرئيةً أم لا، ويُرجَع في هذا إلى توثيق Tkinter لمزيد من المعلومات، وسيكون الجزء الأصعب هو معرفة صيغة وصف الحدث.

الرسالة القصيرة

من الممكن إبلاغ رسائل قصيرة للمستخدمين باستخدام MessageBox، وهذا سهل للغاية في Tk ويمكن تنفيذه باستخدام دوال وحدة messagebox كما يلي:

from tkinter import messagebox
messagebox.showinfo("Window Text", "A short message") 

هناك أيضًا صناديق الخطأ والتحذير وصناديق نعم ولا Yes/No وموافق وإلغاء Ok/Cancel التي يمكن استخدامها من خلال دوال showXXX المختلفة، ويمكن تمييزها بأيقوناتها وأزرارها المختلفة، ويستخدم الصندوقان الأخيران askxxx بدلًا من showxxx، ويعيد قيمةً لتوضيح على أي زر ضغط المستخدم، كما يلي:

res = messagebox.askokcancel("Which?", "Ready to stop?")
print res

فيما يلي بعض الأمثلة لصناديق الرسائل في Tkinter:

tk-info.png

tk-error.png

tk-yesno.png

وهذا شبيه بصناديق alert وMsgBox التي استخدمناها في برامج الويب من جافاسكربت وVBScript في دروسنا الأولى.

كما توجد صناديق حوارية قياسية يمكن استخدامها للحصول على أسماء الملفات أو المجلدات من المستخدم، تشبه صناديق "Open File" أو "Save File"، ولن نشرحها هنا لكن يمكن الاطلاع على أمثلة عنها في صفحات Tkinter المرجعية، تحت قسم Standard Dialogs.

تغليف التطبيقات مثل الكائنات

من الشائع في برمجة الواجهات الرسومية أن نغلف التطبيق كله مثل صنف واحد، وهذا يطرح سؤال كيف نلائم ودجات تطبيق Tkinter في هيكل هذا الصنف؟

لدينا خياران هنا، فإما أن نقرر جعل التطبيق نفسه صنفًا فرعيًا من إطار Tkinter، وإما أن نجعل أحد الحقول الأعضاء (الخاصيات) يخزن مرجعًا إلى نافذة المستوى الأعلى، ويُستخدم المنظور الثاني بكثرة في صناديق الأدوات الأخرى لذا سنتبعه، أما المنظور الأول فيمكن رؤيته في المقال السابق، الذي يحوي أيضًا توضيحًا لاستخدام بسيط لودجت النص الخاصة بـ Tkinter، إضافةً إلى مثال آخر لاستخدام bind.

سنحول المثال أعلاه إلى هيكل كائني التوجه باستخدام حقل إدخال وزر Clear وزر Quit، لكننا سننشئ أولًا صنف تطبيق، ونجمّع الأجزاء المرئية للواجهة الرسومية داخل الباني Constructor، ثم نسند الإطار الناتج إلى self.mainWindow، سامحين بهذا للتوابع الأخرى للصنف بالوصول إلى إطار المستوى الأعلى، وتُسنَد الودجات الأخرى التي قد نحتاج إلى الوصول إليها -مثل حقل الإدخال- إلى متغيرات أعضاء member variables للتطبيق.

تصبح معالجات الأحداث توابعًا لصنف التطبيق باستخدام هذه التقنية، ويكون لها وصول إلى أي أعضاء بيانات data members لتطبيق -رغم عدم وجود أي منها في حالتنا هنا- من خلال مرجع self، مما يوفر تكاملًا سلسًا للواجهة الرسومية مع كائنات التطبيق الأساسية:

import tkinter as tk

# أنشئ معالج الحدث لمسح النص
class ClearApp:
    def __init__(self, parent):
        # create the top level window/frame
        self.mainWindow = tk.Frame(parent)
        self.eHello = tk.Entry(self.mainWindow)
        self.eHello.insert(0,"Hello world")
        self.eHello.pack(fill="x", padx=5, pady=5)
        self.eHello.bind("<Control-c>", self.evHotKey)

        # والآن أنشئ الإطار ذا الأزرار. 
        fButtons = tk.Frame(self.mainWindow, height=2)
        self.bClear = tk.Button(fButtons, text="Clear",
                             width=10, height=1,command=self.evClear)
        self.bQuit  = tk.Button(fButtons, text="Quit",
                             width=10, height=1, command=self.mainWindow.quit)
        self.bClear.pack(side="left", padx=15, pady=1)
        self.bQuit.pack(side="right", padx=15, pady=1)
        fButtons.pack(side="top", pady=2, fill="x")
        self.mainWindow.pack()
        self.mainWindow.master.title("Clear")

    def evClear(self):
        self.eHello.delete(0,tk.END)

    def evHotKey(self, event):
        self.evClear()

# والآن أنشئ التطبيق وشغل حلقة الحدث
top = tk.k()
app = ClearApp(top)
top.mainloop()

ستكون النتيجة كما يلي:

tk-oopentry.png

تبدو النتيجة مشابهةً للصورة السابقة، رغم أننا عدلنا بعض الإعدادات وخيارات التحزيم لتبدو أشبه بمثال wxPython أدناه.

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

يحتوي Tkinter بدءًا من الإصدار 3.1 على بعض المزايا الجديدة التي تُعرف باسم الودجات ذات السمات themed widgets، وتوجد في وحدة tkinter.ttk، وهي تحسن كثيرًا من مظهر Tkinter إلى حد يصعب التفريق بين نوافذه وبين ودجات النظام المضمّنة، لكننا لن نشرحها هنا، ويُرجَع فيها إلى موقع Tcl/Tk.

صندوق الأدوات البديل wxPython

توجد عدة صناديق أدوات أخرى للواجهات الرسومية، لعل أشهرها صندوق WxPython الذي يغلف ودجات صندوق أدوات C++‎، وهذا الصندوق -أي WxPython- أكثر شيوعًا من صندوق أدوات Tkinter عمومًا بين صناديق الواجهات المرئية، حيث يوفر وظائف قياسيةً افتراضيًا أكثر من Tk، مثل التلميحات tooltips، وشرائط الحالة status bars وغيرها، أما في Tk فيجب إنشاؤها يدويًا، وسنستخدم wxPython لإعادة إنشاء مثال Hello World أعلاه.

لكن المشكلة هنا هي أن wxPython ليس متاحًا بعد للإصدار الثالث من بايثون حتى الوقت الذي كتبنا فيه هذه الكلمات، مما يعني أننا سنعامل شيفرة الإصدار الثاني أدناه على أنها مجرد تدريب للقراءة لا غير، أو يمكن تثبيت الإصدار 2.7 من بايثون إذا أردنا التطبيق العملي، وتنزيل الحزمة من موقع wxPython، إذ لن ندخل كثيرًا في التفاصيل هنا، وعمومًا يعرِّف صندوق الأدوات إطار عمل يسمح لنا بإنشاء نوافذ وملئها بعناصر التحكم، وربط توابع بهذه المتحكمات، وبما أن هذا كائني التوجه فيجب استخدام التوابع وليس الدوال، ويبدو المثال كما يلي:

import wx

# --- عرّف إطارًا مخصصًا. سيكون هو النافذة الأساسية ---
class HelloFrame(wx.Frame):
   def __init__(self, parent, id, title, pos, size):
        super().__init__(parent, id, title, pos, size)
       # we need a panel to get the right background
        panel = wx.Panel(self)

        # أنشئ ودجتي النص والزر
        self.tHello = wx.TextCtrl(panel, -1, "Hello world", pos=(3,3), size=(185,22))
        bClear = wx.Button(panel, -1, "Clear", pos=(15, 32))
        self.Bind(wx.EVT_BUTTON, self.OnClear, bClear)
        bQuit = wx.Button(panel, -1, "Quit", pos=(100, 32))
        self.Bind(wx.EVT_BUTTON, self.OnQuit, bQuit)

   # هذه معالجات الأحداث الخاصة بنا
   def OnClear(self, event):
       self.tHello.Clear()

   def OnQuit(self, event):
       self.Destroy()

# --- عرّف كائن التطبيق ---
# يجب أن تعرّف صنف تطبيق wxPython لاحظ أن كل برامج  
# wx.App مشتق من 
class HelloApp(wx.App):
   def OnInit(self):
       frame = HelloFrame(None, -1, "Hello", (200,50), (200,90) )
       frame.Show(True)
       self.SetTopWindow(frame)
       return True

# أنشئ نسخة وابدأ حلقة الحدث
HelloApp().MainLoop()

wx-hello.png

نلاحظ استخدام اصطلاح التسمية onXXXX للتوابع التي يستدعيها إطار العمل، واستخدام ثوابت EVT_XXX لربط الأحداث بالودجات، وتوجد مجموعة كبيرة منها.

يحتوي wxPython على ودجات كثيرة، أكثر من Tk، ويمكن بناء واجهات رسومية معقدة بها، لكنها للأسف تميل لاستخدام نظام موضعة مبني على الإحداثيات، وهذا متعب في العمل، لكن يمكننا استخدام نظام شبيه بمحزِّم Tk، مع أنه غير موثَّق جيدًا.

وقد يكون من المهم ملاحظة أن هذا المثال ومثال Tkinter الشبيه به أعلاه يحتويان على نفس عدد الأسطر البرمجية تقريبًا، إذ يحتوي مثال Tk على 23 سطرًا، ومثال wxPython على 21، فإذا رغبنا في واجهة رسومية سريعة لأداة نصية فسيكون Tk كافيًا، أما إذا أردنا بناء تطبيقات كاملة تعمل على عدة منصات تشغيل فينبغي استخدام wxPython.

ومن صناديق الأدوات الأخرى MFC و‎.NET، ولا ننسى Curses التي هي واجهة رسومية مبنية على نصوص، ويمكن تطبيق كثير من الدروس التي تعلمناها مع Tkinter على كل صناديق الأدوات هذه، لكن لكل منها مزاياه وعيوبه، فاختر واحدًا وتعرف عليه واستمتع ببرمجة الواجهات الرسومية.

أخيرًا نشير إلى أن العديد من صناديق الأدوات لها أدوات بناء رسومية، مثل Blackadder لـ Qt ، وGlade لــ GTK ، وكذلك يحتوي wxPython على بانٍ رسومي هو Boa Constructor رغم أنه لا زال في مرحلة الإصدار Alpha مما يعني أنه غير مستقر، كما يوجد بانٍ رسومي لصندوق Tk يسمى GUI Builder كان مخصصًا ابتداءً لبناء واجهات Tcl/Tk، لكنه يستطيع توليد شيفرات في عدة لغات بما فيها بايثون.

توجد عدة كتب أخرى لاستخدام Tcl/Tk وعدة كتب أخرى من بايثون لديها فصول عن Tk، وسنعود إليه في مقال قادم، حين نشرح طريقة تغليف برنامج وضع باتش batch mode في واجهة رسومية لتحسين الاستخدام.

خاتمة

نرجو في نهاية هذا المقال أن تكون تعلمت ما يلي:

  • تُعرف عناصر تحكم الواجهات الرسومية بالودجات.
  • تُجمَّع الودجات في هرمية احتوائية.
  • توفر صناديق أدوات الواجهات الرسومية مجموعات مختلفةً من الودجات، رغم وجود مجموعة أساسية افتراضية فيها جميعًا.
  • تسمح الإطارات بجمع الودجات المتشابهة، وتشكيل أساس لمكونات واجهة رسومية قابلة للاستخدام.
  • ترتبط دوال معالجة الأحداث أو التوابع بالودجات من خلال ربط أسمائها بخاصية command الخاصة بالودجات.
  • تستطيع البرمجة كائنية التوجه تبسيط برمجة الواجهات الرسومية كثيرًا بإنشاء كائنات تتوافق مع مجموعات الودجات، وتوابع تتوافق مع الأحداث.

ترجمة -بتصرف- للفصل التاسع عشر: GUI Programming with Tkinter من كتاب Learn To Program لصاحبه Alan Gauld.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...