Tkinter هو إطار عمل واجهة المستخدم الرسومية القياسي لبايثون، مما يجعله مناسبًا لتطوير واجهات المستخدم الرسومية. بصفته مكتبة متعددة المنصات، يضمن Tkinter ظهور تطبيقاتك بشكل أصلي على أنظمة Windows وmacOS وLinux. على الرغم من الانتقادات الموجهة لمظهره القديم، يظل Tkinter خيارًا عمليًا لإنشاء تطبيقات واجهة مستخدم رسومية وظيفية ومتعددة المنصات بسرعة.
ستغطي هذه الدورة أساسيات استخدام Tkinter، وإدارة الأدوات، وإنشاء تطبيقات تفاعلية. بعد إتقان هذه المهارات من خلال حل التمارين في نهاية كل قسم، ستتمكن من ربط كل شيء معًا ببناء تطبيقين. الأول هو محول درجة الحرارة، والثاني هو محرر نصوص. حان الوقت للبدء فورًا وتعلم كيفية بناء تطبيق باستخدام Tkinter!
إنشاء أول تطبيق واجهة مستخدم رسومية بلغة بايثون باستخدام Tkinter
النافذة هي العنصر الأساسي في واجهة المستخدم الرسومية Tkinter. النوافذ هي الحاويات التي تحتضن جميع عناصر واجهة المستخدم الرسومية الأخرى. تُعرف هذه العناصر، مثل مربعات النصوص والعلامات والأزرار، باسم “الأدوات”. توجد هذه الأدوات داخل النوافذ.
أولاً، أنشئ نافذة تحتوي على أداة واحدة. ابدأ جلسة جديدة في واجهة برمجة تطبيقات بايثون، وتابع!
بمجرد فتح Python shell، فإن أول شيء عليك القيام به هو استيراد وحدة بايثون GUI Tkinter:
>>> import tkinter as tk
النافذة هي مثال لفئة Tk في Tkinter. أنشئ نافذة جديدة وخصصها للمتغير window:
>>> window = tk.Tk()
عند تنفيذ الكود أعلاه، ستظهر نافذة جديدة على شاشتك. يعتمد شكلها على نظام التشغيل لديك:

ستشاهد خلال بقية هذا البرنامج التعليمي لقطات شاشة لنظام التشغيل Windows.
إضافة أداة
الآن وقد أصبحت لديك نافذة، يمكنك إضافة عنصر واجهة مستخدم. استخدم فئة tk.Label لإضافة نص إلى النافذة. أنشئ عنصر واجهة مستخدم “Label” بالنص “Hello, Tkinter” وعيّنه إلى متغير يُسمى greeting:
>>> greeting = tk.Label(text="Hello, Tkinter")
النافذة التي أنشأتها سابقًا لا تتغير. لقد أنشأتَ للتوّ أداة ” Label
“، ولكنك لم تُضِفها إلى النافذة بعد. هناك عدة طرق لإضافة أدوات إلى نافذة. الآن، يمكنك استخدام دالة .pack()
الخاصة بأداة ” Label
“:
>>> greeting.pack()
الآن تبدو النافذة بهذا الشكل:

عند ضغط عنصر واجهة مستخدم داخل نافذة، يُصغّر Tkinter حجم النافذة قدر الإمكان مع الحفاظ على تغطية كاملة للعنصر. الآن، نفّذ ما يلي:
>>> window.mainloop()
يبدو أنه لا يحدث شيء، لكن لاحظ عدم ظهور أي مطالبة جديدة في shell.
تطلب دالة ()window.mainloop من بايثون تشغيل حلقة أحداث Tkinter. تستمع هذه الدالة إلى الأحداث، مثل نقرات الأزرار أو ضغطات المفاتيح، وتمنع أي شيفرة تأتي بعدها من التشغيل حتى تُغلق النافذة التي استدعيت فيها الدالة. أغلق النافذة التي أنشأتها، وسترى موجه أوامر جديد معروضًا في واجهة الأوامر.
إنشاء نافذة باستخدام Tkinter لا يتطلب سوى بضعة أسطر برمجية. لكن النوافذ الفارغة ليست مفيدة جدًا! في القسم التالي، ستتعرف على بعض الأدوات المتاحة في Tkinter، وكيفية تخصيصها لتلبية احتياجات تطبيقك.
العمل مع الأدوات
الأدوات هي أساس إطار عمل واجهة المستخدم الرسومية Tkinter بلغة بايثون. إنها العناصر التي يتفاعل المستخدمون من خلالها مع برنامجك. كل أداة في Tkinter مُعرّفة بفئة. إليك بعض الأدوات المتاحة:
فئة الأدوات | وصف |
---|---|
Label | أداة تستخدم لعرض النص على الشاشة |
Button | زر يمكن أن يحتوي على نص ويمكنه تنفيذ إجراء عند النقر فوقه |
Entry | أداة إدخال نص تسمح بسطر واحد فقط من النص |
Text | أداة إدخال نص تسمح بإدخال نص متعدد الأسطر |
Frame | منطقة مستطيلة تستخدم لتجميع عناصر واجهة المستخدم ذات الصلة أو توفير مساحة بين عناصر واجهة المستخدم |
ستتعرف على كيفية استخدام كلٍّ منها في الأقسام التالية، ولكن تذكّر أن Tkinter يضمّ أدوات أكثر بكثير من المذكورة هنا. يصبح اختيار الأدوات أكثر تعقيدًا عند إضافة مجموعة جديدة كاملة من الأدوات ذات السمات المختلفة. في الجزء المتبقي من هذا الدليل، ستستخدم فقط أدوات Tkinter الكلاسيكية.
للاطلاع على قائمة كاملة بأدوات Tkinter، راجع قسمي “الأدوات الأساسية” و”المزيد من الأدوات” في دليل TkDocs. على الرغم من أن الدليل يصف الأدوات المواضيعية المُقدمة في Tcl/Tk 8.5، إلا أن معظم المعلومات الواردة فيه تنطبق أيضًا على الأدوات الكلاسيكية.
حقيقة ممتعة: تعني كلمة Tkinter حرفيًا “واجهة Tk” لأنها عبارة عن رابط Python أو واجهة برمجة لمكتبة Tk في لغة البرمجة النصية Tcl.
في الوقت الحالي، قم بإلقاء نظرة عن كثب على أداة Label
.
عرض النصوص والصور باستخدام أدوات Label
تُستخدم أدوات Label
لعرض النصوص أو الصور. لا يمكن للمستخدم تعديل النص المعروض بواسطة أداة Label
، فهي مخصصة للعرض فقط. كما رأيت في المثال في بداية هذا البرنامج التعليمي، يمكنك إنشاء أداة Label
عن طريق إنشاء مثيل لفئة Label
وتمرير سلسلة نصية إلى معلمة النص:
label = tk.Label(text="Hello, Tkinter")
تعرض أدوات Label النص بلون النظام الافتراضي ولون الخلفية الافتراضيين. عادةً ما يكون اللونان الأسود والأبيض على التوالي، ولكن قد يظهر لونان مختلفان إذا غيّرت هذه الإعدادات في نظام التشغيل.
يمكنك التحكم في نص الملصق وألوان الخلفية باستخدام معلمات foreground
و background
:
label = tk.Label(
text="Hello, Tkinter",
foreground="white", # Set the text color to white
background="black" # Set the background color to black
)
هناك العديد من أسماء الألوان الصالحة، بما في ذلك:
- أحمر
- برتقالي
- أصفر
- أخضر
- أزرق
- بنفسجي
تتوافق العديد من أسماء ألوان HTML مع Tkinter. للاطلاع على مرجع شامل، بما في ذلك ألوان نظامي macOS وWindows التي يتحكم بها سمة النظام الحالية، يُرجى مراجعة صفحة دليل الألوان.
يمكنك أيضًا تحديد لون باستخدام قيم RGB السداسية عشرية:
label = tk.Label(text="Hello, Tkinter", background="#34A2FE")
يؤدي هذا إلى ضبط خلفية الملصق إلى لون أزرق فاتح جميل. قيم RGB السداسية عشرية أكثر غموضًا من الألوان المسماة، ولكنها أيضًا أكثر مرونة. لحسن الحظ، تتوفر أدوات تُسهّل الحصول على رموز الألوان السداسية عشرية نسبيًا.
إذا كنت لا ترغب في كتابة الألوان foreground
وbackground
طوال الوقت، فيمكنك استخدام المعلمات المختصرة fg وbg لتعيين ألوان المقدمة والخلفية:
label = tk.Label(text="Hello, Tkinter", fg="white", bg="black")
يمكنك أيضًا التحكم في عرض وارتفاع الملصق باستخدام معلمات width
و height
:
وهذا هو شكل هذا الملصق في النافذة:

قد يبدو غريبًا أن يكون الملصق في النافذة غير مربع، رغم ضبط العرض والارتفاع على ١٠. ويرجع ذلك إلى أن العرض والارتفاع يُقاسان بوحدات نصية. تُحدَّد وحدة نصية أفقية واحدة بعرض الحرف ٠، أو الرقم صفر، في خط النظام الافتراضي. وبالمثل، تُحدَّد وحدة نصية رأسية واحدة بارتفاع الحرف ٠.
ملاحظة: بالنسبة لقياسات العرض والارتفاع، يستخدم Tkinter وحدات نصية، بدلاً من شيء مثل البوصات أو السنتيمترات أو البكسل، لضمان سلوك متسق للتطبيق عبر الأنظمة الأساسية.
قياس الوحدات بعرض الحرف يعني أن حجم الأداة يتناسب مع الخط الافتراضي على جهاز المستخدم. هذا يضمن ملاءمة النص مع العناوين والأزرار، بغض النظر عن مكان تشغيل التطبيق.
الملصقات رائعة لعرض بعض النصوص، لكنها لا تساعدك في الحصول على مُدخلات المستخدم. الأدوات الثلاث التالية التي ستتعرف عليها تُستخدم جميعها للحصول على مُدخلات المستخدم.
عرض الأزرار القابلة للنقر باستخدام أدوات الأزرار
تُستخدم أدوات Button
لعرض الأزرار القابلة للنقر. يمكنك ضبطها لاستدعاء دالة عند النقر عليها. ستتناول في القسم التالي كيفية استدعاء الدوال من خلال النقر على الأزرار. أما الآن، فتعرّف على كيفية إنشاء زر وتصميمه.
هناك أوجه تشابه عديدة بين عناصر واجهة المستخدم ” Button
” و”Label
“. فالزر، في كثير من الأحيان، هو مجرد ملصق يمكنك النقر عليه! ستعمل نفس الكلمات المفتاحية المستخدمة لإنشاء ” Label
” وتنسيقها مع عناصر واجهة المستخدم ” Button
“. على سبيل المثال، يُنشئ الكود التالي زرًا بخلفية زرقاء ونص أصفر. كما يُحدد العرض والارتفاع بـ 25 و5 وحدات نصية على التوالي:
button = tk.Button(
text="Click me!",
width=25,
height=5,
bg="blue",
fg="yellow",
)
وهذا هو شكل الزر في النافذة:

رائع جدًا! يمكنك استخدام الأداتين التاليتين لجمع بيانات المستخدم النصية.
الحصول على مدخلات المستخدم باستخدام أدوات Entry
عندما تحتاج إلى الحصول على نص من مستخدم، كاسم أو عنوان بريد إلكتروني، استخدم أداة Entry
. ستعرض هذه الأداة مربع نص صغيرًا يُمكن للمستخدم كتابة نص فيه. يعمل إنشاء أداة Entry
وتصميمها بشكل مشابه جدًا لأدوات Label
و Button
. على سبيل المثال، يُنشئ الكود التالي أداة بخلفية زرقاء، وبعض النصوص الصفراء، وعرض 50 وحدة نصية:
entry = tk.Entry(fg="yellow", bg="blue", width=50)
الشيء المثير للاهتمام في أدوات Entry
ليس كيفية تصميمها، بل كيفية استخدامها للحصول على مُدخلات من المستخدم. هناك ثلاث عمليات رئيسية يمكنك إجراؤها باستخدام أدوات Entry
:
- استرجاع النص باستخدام
.get()
- حذف النص باستخدام
.delete()
- إدراج النص باستخدام .
insert()
أفضل طريقة لفهم أدوات Entry
هي إنشاء واحدة والتفاعل معها. افتح shell بايثون واتبع الأمثلة في هذا القسم. أولًا، استورد tkinter وأنشئ نافذة جديدة:
>>> import tkinter as tk
>>> window = tk.Tk()
الآن قم بإنشاء عنصر واجهة مستخدم Label
و Entry
:
>>> label = tk.Label(text="Name")
>>> entry = tk.Entry()
يصف Label
نوع النص الذي يجب وضعه في عنصر واجهة المستخدم “Entry”. لا يفرض أي متطلبات على عنصر واجهة المستخدم، ولكنه يُخبر المستخدم بما يتوقعه برنامجك منه. عليك استخدام دالة .pack()
لدمج عناصر واجهة المستخدم في النافذة لتكون مرئية:
>>> label.pack()
>>> entry.pack()
وهذا ما يبدو عليه الأمر:

لاحظ أن Tkinter يُحدِّد تلقائيًا موضع التسمية فوق عنصر واجهة المستخدم “Entry” في النافذة. هذه ميزة من ميزات .pack()
، والتي ستتعرف عليها أكثر في الأقسام اللاحقة.
الآن لديك نص مُدخل في أداة Entry
، ولكن لم يُرسَل إلى برنامجك بعد. يمكنك استخدام .get()
لاسترجاع النص وتعيينه إلى متغير يُسمى name:
>>> name = entry.get()
>>> name
'pyarabic'
يمكنك أيضًا حذف نص. تأخذ هذه الدالة .delete()
وسيطة عددية تُحدد بايثون أي حرف يجب حذفه. على سبيل المثال، يوضح الكود التالي كيفية حذف الدالة .delete(0)
للحرف الأول من المدخل:
>>> entry.delete(0)
لاحظ أنه، تمامًا مثل كائنات سلسلة Python، يتم فهرسة النص في عنصر واجهة المستخدم الرسومية Entry بدءًا من 0.
إذا كنت بحاجة إلى حذف عدة أحرف من Entry
، فمرر مُعامل عدد صحيح ثانٍ إلى .delete()
يُشير إلى فهرس الحرف الذي يجب أن يتوقف عنده الحذف. على سبيل المثال، يحذف الكود التالي الأحرف الأربعة الأولى من المُدخل:
>>> entry.delete(0, 4)
تعمل دالة Entry.delete()
تمامًا مثل تقطيع السلاسل النصية. تُحدد الوسيطة الأولى فهرس البداية، ويستمر الحذف حتى الفهرس المُمرَّر كوسيطة ثانية، ولكن دون تضمينه. استخدم الثابت الخاص tk.END للوسيطة الثانية لـ .delete()
لإزالة كل النص في Entry:
>>> entry.delete(0, tk.END)
الحصول على إدخال المستخدم متعدد الأسطر باستخدام أدوات Text
تُستخدم أدوات Text
لإدخال النصوص، تمامًا مثل أدوات Entry
. الفرق هو أن أدوات Text
قد تحتوي على عدة أسطر نصية. باستخدام أداة Text
، يمكن للمستخدم إدخال فقرة كاملة أو حتى عدة صفحات من النص! وكما هو الحال مع أدوات Entry
، يمكنك إجراء ثلاث عمليات رئيسية باستخدام أدوات Text
:
- استرداد النص باستخدام
.get()
- حذف النص باستخدام
.delete()
- إدراج النص باستخدام
.insert()
مع أن أسماء التوابع هي نفسها توابع Entry
، إلا أن آلية عملها مختلفة بعض الشيء. حان الوقت لتجربة إنشاء أداة نصية وتجربة إمكانياتها.
في Python shell، قم بإنشاء نافذة فارغة جديدة وحزم عنصر واجهة المستخدم ()Text فيها:
>>> window = tk.Tk()
>>> text_box = tk.Text()
>>> text_box.pack()
مربعات النص أكبر بكثير من أدوات Entry
افتراضيًا. هكذا تبدو النافذة التي تم إنشاؤها أعلاه:

انقر في أي مكان داخل النافذة لتفعيل مربع النص. اكتب كلمة ” Hello
“. ثم اضغط على مفتاح الإدخال (Enter) واكتب كلمة ” World
” في السطر الثاني. ستظهر النافذة الآن كما يلي:

كما هو الحال مع أدوات Entry
، يمكنك استرداد النص من أداة Text
باستخدام دالة .get()
. مع ذلك، فإن استدعاء دالة .get()
بدون وسيطات لا يُرجع النص الكامل في مربع النص كما هو الحال مع أدوات Entry
، بل يُثير استثناء:
>>> text_box.get()
Traceback (most recent call last):
...
TypeError: get() missing 1 required positional argument: 'index1'
تتطلب دالة ()Text.get وسيطة واحدة على الأقل. يؤدي استدعاء دالة .get(
) بفهرس واحد إلى إرجاع حرف واحد. لاسترجاع عدة أحرف، يجب تمرير فهرس بداية وفهرس نهاية. تعمل الفهارس في عناصر واجهة المستخدم النصية بشكل مختلف عن عناصر واجهة المستخدم في عناصر واجهة المستخدم الإدخالية. بما أن عناصر واجهة المستخدم النصية يمكن أن تحتوي على عدة أسطر نصية، يجب أن يحتوي الفهرس على معلومتين:
- رقم سطر الحرف
- موضع الشخصية على هذا السطر
تبدأ أرقام الأسطر بالرقم 1، وتبدأ مواضع الأحرف بالرقم 0. لإنشاء فهرس، أنشئ سلسلة نصية بالشكل “<line>.<char>”، مع استبدال <line> برقم السطر و <char> برقم الحرف. على سبيل المثال، يُمثل “1.0” الحرف الأول في السطر الأول، ويُمثل “2.3” الحرف الرابع في السطر الثاني.
استخدم الفهرس “1.0” للحصول على الحرف الأول من مربع النص الذي قمت بإنشائه مسبقًا:
>>> text_box.get("1.0")
'H'
تتكون كلمة Hello من خمسة أحرف، وعدد أحرف الحرف o هو 4، لأن أرقام الأحرف تبدأ من 0، وتبدأ كلمة Hello من أول موضع في مربع النص. وكما هو الحال مع شرائح سلاسل بايثون، للحصول على كلمة Hello كاملةً من مربع النص، يجب أن يكون فهرس النهاية أكبر بواحد من فهرس آخر حرف يُقرأ.
لذا، للحصول على كلمة Hello من مربع النص، استخدم “1.0” للمؤشر الأول و”1.5″ للمؤشر الثاني:
>>> text_box.get("1.0", "1.5")
'Hello'
للحصول على كلمة World في السطر الثاني من مربع النص، قم بتغيير أرقام الأسطر في كل فهرس إلى 2:
>>> text_box.get("2.0", "2.5")
'World'
للحصول على النص بأكمله في مربع النص، اضبط الفهرس الابتدائي على “1.0” واستخدم الثابت الخاص tk.END للفهرس الثاني:
>>> text_box.get("1.0", tk.END)
'Hello\nWorld\n'
لاحظ أن النص المُعاد بواسطة .get()
يتضمن أي أحرف سطر جديد. كما يمكنك أن ترى من هذا المثال أن كل سطر في أداة Text
يحتوي على حرف سطر جديد في نهايته، بما في ذلك السطر الأخير من مربع النص.
تُستخدم دالة .delete()
لحذف الأحرف من مربع النص. تعمل تمامًا مثل دالة .delete()
لأدوات Entry
. هناك طريقتان لاستخدام .delete()
:
- بحجة واحدة
- مع حجتين
باستخدام إصدار الوسيطة المفردة، تُمرر إلى .delete()
فهرس حرف واحد مطلوب حذفه. على سبيل المثال، يحذف الأمر التالي الحرف الأول، H، من مربع النص:
>>> text_box.delete("1.0")
الآن يصبح السطر الأول من النص في النافذة هو ello:

باستخدام الإصدار ذي الوسيطتين، يمكنك تمرير مؤشرين لحذف نطاق من الأحرف بدءًا من المؤشر الأول وحتى المؤشر الثاني، ولكن لا يشمله.
على سبيل المثال، لحذف الحرف المتبقي في السطر الأول من مربع النص، استخدم الفهارس “1.0” و”1.4″:
>>> text_box.delete("1.0", "1.4")
لاحظ أن النص قد اختفى من السطر الأول. هذا يترك سطرًا فارغًا بعد كلمة “World” في السطر الثاني.

حتى لو لم تتمكن من رؤيته، لا يزال هناك حرف في السطر الأول. إنه حرف سطر جديد! يمكنك التحقق من ذلك باستخدام .get():
>>> text_box.get("1.0")
'\n'
إذا قمت بحذف هذا الحرف، فسوف ينتقل باقي محتويات مربع النص إلى أعلى سطر:
>>> text_box.delete("1.0")
الآن، أصبح World على السطر الأول من مربع النص:

حاول مسح بقية النص في مربع النص. عيّن “1.0” كمؤشر البداية، واستخدم tk.END للمؤشر الثاني:
>>> text_box.delete("1.0", tk.END)
مربع النص فارغ الآن:

يمكنك إدراج نص في مربع النص باستخدام .insert()
:
>>> text_box.insert("1.0", "Hello")
يؤدي هذا إلى إدراج كلمة Hello في بداية مربع النص، باستخدام نفس تنسيق “<line>.<column>” المستخدم بواسطة .get()
لتحديد موضع الإدراج:

شاهد ماذا يحدث إذا حاولت إدراج كلمة World في السطر الثاني:
>>> text_box.insert("2.0", "World")
بدلاً من إدراج النص في السطر الثاني، يتم إدراج النص في نهاية السطر الأول:

تعيين عناصر واجهة المستخدم للإطارات باستخدام عناصر واجهة المستخدم Frame
في هذا البرنامج التعليمي، سوف تعمل مع خمس أدوات فقط:
Label
Button
Entry
Text
Frame
هذه هي العناصر الأربعة التي شاهدتها حتى الآن، بالإضافة إلى أداة Frame
. تُعدّ أدوات Frame
مهمة لتنظيم تخطيط أدواتك في التطبيق.
قبل الخوض في تفاصيل تصميم العرض المرئي لأدواتك، ألقِ نظرة فاحصة على آلية عمل أدوات Frame
، وكيفية تخصيص أدوات أخرى لها. يُنشئ البرنامج النصي التالي أداة Frame
فارغة، ويُخصصها لنافذة التطبيق الرئيسية:
import tkinter as tk
window = tk.Tk()
frame = tk.Frame()
frame.pack()
window.mainloop()
دالة ()frame.pack تُجمّع الإطار في النافذة بحيث يُصغّر حجم النافذة قدر الإمكان ليشمل الإطار. عند تشغيل البرنامج النصي أعلاه، ستحصل على نتائج غير مُثيرة للاهتمام:

أداة Frame
الفارغة تكاد تكون غير مرئية. يُفضّل اعتبار الإطارات حاويات لأدوات أخرى. يمكنك تعيين أداة لإطار عن طريق ضبط سمة ” master
” للأداة:
frame = tk.Frame()
label = tk.Label(master=frame)
لفهم آلية عمل هذا، اكتب نصًا برمجيًا يُنشئ عنصري Frame
، أحدهما يُسمى frame_a والآخر يُسمى frame_b. في هذا النص، يحتوي frame_a على عنوان “I’m in Frame A”، بينما يحتوي frame_b على عنوان “I’m in Frame B”. إليك إحدى الطرق للقيام بذلك:
import tkinter as tk
window = tk.Tk()
frame_a = tk.Frame()
frame_b = tk.Frame()
label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()
label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()
frame_a.pack()
frame_b.pack()
window.mainloop()
لاحظ أن frame_a مُدمج في النافذة قبل frame_b. تُظهر النافذة التي تفتح التسمية في frame_a فوق التسمية في frame_b:

الآن انظر ماذا يحدث عندما تقوم بتبديل ترتيب frame_a.pack() و frame_b.pack():
import tkinter as tk
window = tk.Tk()
frame_a = tk.Frame()
label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()
frame_b = tk.Frame()
label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()
# Swap the order of `frame_a` and `frame_b`
frame_b.pack()
frame_a.pack()
window.mainloop()
يبدو الناتج كالتالي:

الآن، أصبح label_b في الأعلى. بما أن label_b مُخصص لـ frame_b، فإنه ينتقل إلى أي مكان يوجد فيه frame_b.
جميع أنواع الأدوات الأربعة التي تعلمتها – Label
, Button
, Entry
, و Text
– لها سمة master
تُضبط عند إنشائها. بهذه الطريقة، يمكنك التحكم في Frame
الذي تُخصص له الأداة. تُعدّ أدوات Frame
مثالية لتنظيم الأدوات الأخرى بشكل منطقي. يمكن تخصيص الأدوات ذات الصلة للإطار نفسه، بحيث تبقى الأدوات ذات الصلة مترابطة في حال تحريك الإطار في النافذة.
بالإضافة إلى تجميع عناصر واجهة المستخدم منطقيًا، تُضفي عناصر واجهة المستخدم Frame
لمسةً مميزةً على العرض المرئي لتطبيقك. تابع القراءة لمعرفة كيفية إنشاء حدود متنوعة لعناصر واجهة المستخدم Frame
.
ضبط مظهر الإطار باستخدام النقوش البارزة
يمكن إعداد أدوات Frame
باستخدام سمة relief
التي تُنشئ حدودًا حول الإطار. يمكنك ضبط relief
بأيٍّ من القيم التالية:
- tk.FLAT: بدون تأثير حدود (القيمة الافتراضية)
- tk.SUNKEN: يُنشئ تأثيرًا غائرًا
- tk.RAISED: يُنشئ تأثيرًا بارزًا
- tk.GROOVE: يُنشئ تأثير حدود مُخدّد
- tk.RIDGE: يُنشئ تأثيرًا مُخدّدًا
لتطبيق تأثير الحدود، يجب ضبط سمة borderwidth
على قيمة أكبر من 1. تضبط هذه السمة عرض الحدود بالبكسل. أفضل طريقة لتجربة شكل كل تأثير هي مشاهدته بنفسك. إليك نص برمجي يجمع خمس أدوات Frame
في نافذة واحدة، ولكل منها قيمة مختلفة لمعامل relief
:
import tkinter as tk
border_effects = {
"flat": tk.FLAT,
"sunken": tk.SUNKEN,
"raised": tk.RAISED,
"groove": tk.GROOVE,
"ridge": tk.RIDGE,
}
window = tk.Tk()
for relief_name, relief in border_effects.items():
frame = tk.Frame(master=window, relief=relief, borderwidth=5)
frame.pack(side=tk.LEFT)
label = tk.Label(master=frame, text=relief_name)
label.pack()
window.mainloop()
فيما يلي تفصيل لهذا البرنامج النصي:
- الأسطر من 3 إلى 9 تُنشئ قاموسًا مفاتيحه هي أسماء تأثيرات الإغاثة المختلفة المتوفرة في Tkinter. القيم هي كائنات Tkinter المقابلة. يُخصص هذا القاموس لمتغير border_effects.
- يبدأ السطر 13 حلقة for لتكرار كل عنصر في قاموس border_effects.
- يُنشئ السطر ١٤
Frame
جديد ويُخصّصه لكائن النافذة. تُعيّن سمةrelief
إلى النقش البارز المقابل في قاموس border_effects، وتُعيّن سمة border إلى ٥ ليكون التأثير مرئيًا. - يُجمّع السطر 15 الإطار في النافذة باستخدام دالة
.pack()
. تُحدّد الكلمة المفتاحية side لـ Tkinter الاتجاه الذي يجب أن يُجمّع فيه عناصر الإطار. ستتعرّف على كيفية عمل ذلك في القسم التالي. - يقوم الخطان 16 و17 بإنشاء عنصر
Label
لعرض اسم النقش البارز ودمجه في كائنframe
الذي قمت بإنشائه للتو.
تبدو النافذة التي تم إنتاجها بواسطة البرنامج النصي أعلاه مثل هذا:

في هذه الصورة، يمكنك رؤية التأثيرات التالية:
- يقوم
tk.FLAT
بإنشاء إطار يبدو مسطحًا. - يضيف
tk.SUNKEN
حدودًا تمنح الإطار مظهرًا وكأنه غارق في النافذة. - يعطي
tk.RAISED
للإطار حدودًا تجعله يبدو وكأنه يبرز من الشاشة. - يضيف
tk.GROOVE
حدودًا تظهر على شكل أخدود غائر حول إطار مسطح. - يعطي
tk.RIDGE
مظهر شفة مرتفعة حول حافة الإطار.
تمنح هذه التأثيرات تطبيق Tkinter الخاص بك بعض الجاذبية البصرية.
فهم اصطلاحات تسمية عناصر واجهة المستخدم
عند إنشاء عنصر واجهة مستخدم، يمكنك تسميته بأي اسم تريده، بشرط أن يكون مُعرّف بايثون صالحًا. يُنصح عادةً بتضمين اسم فئة عنصر واجهة المستخدم في اسم المتغير الذي تُعيّنه لمثيل عنصر واجهة المستخدم. على سبيل المثال، إذا استُخدم عنصر واجهة مستخدم “Label” لعرض اسم مستخدم، فيمكنك تسميته “label_user_name”. أما عنصر واجهة مستخدم “Entry” المُستخدم لجمع عمر المستخدم، فقد يُسمى “entry_age”.
عند تضمين اسم فئة الأداة في اسم المتغير، فإنك تساعد نفسك وأي شخص آخر يحتاج إلى قراءة الكود على فهم نوع الأداة التي يشير إليها اسم المتغير. مع ذلك، قد يؤدي استخدام الاسم الكامل لفئة الأداة إلى أسماء متغيرات طويلة، لذا يُنصح باستخدام اختصار للإشارة إلى كل نوع من أنواع الأدوات. في بقية هذا البرنامج التعليمي، ستستخدم البادئات المختصرة التالية لتسمية الأدوات:
فئة الأدوات | بادئة اسم المتغير | مثال |
---|---|---|
Label | lbl | lbl_name |
Button | btn | btn_submit |
Entry | ent | ent_age |
Text | txt | txt_notes |
Frame | frm | frm_address |
في هذا القسم، تعلمت كيفية إنشاء نافذة، واستخدام الأدوات، والتعامل مع الإطارات. في هذه المرحلة، يمكنك إنشاء نوافذ بسيطة لعرض الرسائل، ولكنك لم تُنشئ بعد تطبيقًا متكاملًا. في القسم التالي، ستتعلم كيفية التحكم في تخطيط تطبيقاتك باستخدام أدوات إدارة الأشكال الهندسية الفعّالة من Tkinter.
التحكم في التخطيط باستخدام مديري الهندسة
حتى الآن، كنتَ تُضيف عناصر واجهة المستخدم إلى النوافذ وعناصر واجهة المستخدم للإطارات باستخدام دالة .pack()
، ولكنك لم تتعرف على وظيفة هذه الطريقة تحديدًا. لنوضح الأمر! يتم التحكم في تخطيط التطبيقات في Tkinter باستخدام مديري الهندسة. على الرغم من أن .pack()
يُعدّ مثالًا على مدير الهندسة، إلا أنه ليس الوحيد. لدى Tkinter مديران آخران:
.place()
.grid()
يمكن لكل نافذة أو إطار في تطبيقك استخدام مدير هندسة واحد فقط. ومع ذلك، يمكن للإطارات المختلفة استخدام مديري هندسة مختلفين، حتى لو تم تعيينها لإطار أو نافذة باستخدام مدير هندسة آخر. ابدأ بدراسة .pack()
عن كثب.
مدير الهندسة ()pack
يستخدم مدير هندسة .pack()
خوارزمية حزم لوضع عناصر واجهة المستخدم في إطار أو نافذة بترتيب محدد. لكل عنصر واجهة مستخدم، تتكون خوارزمية الحزم من خطوتين رئيسيتين:
- احسب مساحة مستطيلة تسمى قطعة أرض يبلغ طولها (أو عرضها) ما يكفي لحمل الأداة وتملأ العرض (أو الارتفاع) المتبقي في النافذة بمساحة فارغة.
- قم بوضع الأداة في منتصف الطرد ما لم يتم تحديد موقع مختلف.
دالة .pack()
قوية، ولكن قد يكون من الصعب تصورها. أفضل طريقة لفهم دالة .pack()
هي الاطلاع على بعض الأمثلة. شاهد ماذا يحدث عند استخدام .pack()
لثلاثة عناصر واجهة مستخدم للتسميات في إطار:
import tkinter as tk
window = tk.Tk()
frame1 = tk.Frame(master=window, width=100, height=100, bg="red")
frame1.pack()
frame2 = tk.Frame(master=window, width=50, height=50, bg="yellow")
frame2.pack()
frame3 = tk.Frame(master=window, width=25, height=25, bg="blue")
frame3.pack()
window.mainloop()
يضع .pack()
كل إطار أسفل الإطار السابق بشكل افتراضي، بالترتيب الذي تم تعيينه به للنافذة:

يُوضع كل إطار في أعلى موضع متاح. لذلك، يُوضع الإطار الأحمر أعلى النافذة. ثم يُوضع الإطار الأصفر أسفل الأحمر مباشرةً، والإطار الأزرق أسفل الأصفر مباشرةً.
هناك ثلاث حزم غير مرئية، تحتوي كل منها على إحدى أدوات الإطار الثلاثة. كل حزمة بعرض النافذة وارتفاع الإطار الذي تحتويه. نظرًا لعدم تحديد نقطة ارتكاز عند استدعاء دالة .pack()
لكل إطار، فإن جميعها متمركزة داخل حزمها. ولهذا السبب، يكون كل إطار متمركزًا في النافذة.
تقبل دالة .pack()
بعض وسيطات الكلمات المفتاحية لضبط موضع عناصر واجهة المستخدم بدقة أكبر. على سبيل المثال، يمكنك تعيين وسيطة الكلمة المفتاحية fill لتحديد اتجاه ملء الإطارات. الخيارات هي tk.X للتعبئة أفقيًا، وtk.Y للتعبئة رأسيًا، وtk.BOTH للتعبئة في كلا الاتجاهين. إليك كيفية تكديس الإطارات الثلاثة بحيث يملأ كل إطار النافذة بأكملها أفقيًا:
import tkinter as tk
window = tk.Tk()
frame1 = tk.Frame(master=window, height=100, bg="red")
frame1.pack(fill=tk.X)
frame2 = tk.Frame(master=window, height=50, bg="yellow")
frame2.pack(fill=tk.X)
frame3 = tk.Frame(master=window, height=25, bg="blue")
frame3.pack(fill=tk.X)
window.mainloop()
لاحظ أن العرض غير محدد في أي من عناصر واجهة المستخدم الرسومية للإطار. لم يعد العرض ضروريًا لأن كل إطار يضبط .pack()
ليتم تعبئته أفقيًا، متجاوزًا أي عرض قد تحدده.
تبدو النافذة التي ينتجها هذا البرنامج النصي كما يلي:

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

ومع ذلك، لاحظ أن عناصر واجهة المستخدم الرسومية للإطار لا تتوسع في الاتجاه الرأسي.
تحدد الكلمة المفتاحية “side” في .pack()
جانب النافذة الذي يجب وضع الأداة فيه. إليك الخيارات المتاحة:
tk.TOP
tk.BOTTOM
tk.LEFT
tk.RIGHT
إذا لم تُعيّن ا side
، فسيستخدم .pack()
تلقائيًا tk.TOP ويضع عناصر واجهة المستخدم الجديدة في أعلى النافذة، أو في الجزء العلوي منها الذي لا يشغله عنصر واجهة مستخدم. على سبيل المثال، يضع النص التالي ثلاثة إطارات جنبًا إلى جنب من اليسار إلى اليمين، ويوسع كل إطار لملء النافذة رأسيًا:
import tkinter as tk
window = tk.Tk()
frame1 = tk.Frame(master=window, width=200, height=100, bg="red")
frame1.pack(fill=tk.Y, side=tk.LEFT)
frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.Y, side=tk.LEFT)
frame3 = tk.Frame(master=window, width=50, bg="blue")
frame3.pack(fill=tk.Y, side=tk.LEFT)
window.mainloop()
هذه المرة، يجب عليك تحديد وسيطة الكلمة الأساسية height على إطار واحد على الأقل لإجبار النافذة على الحصول على بعض الارتفاع.
تبدو النافذة الناتجة مثل:

تمامًا كما هو الحال عندما قمت بتعيين fill=tk.X لجعل الإطارات مستجيبة عند تغيير حجم النافذة أفقيًا، يمكنك تعيين fill=tk.Y لجعل الإطارات مستجيبة عند تغيير حجم النافذة رأسيًا:
لجعل التصميم متجاوبًا تمامًا، يمكنك تحديد حجم أولي للإطارات باستخدام سمتي العرض والارتفاع. بعد ذلك، اضبط قيمة معامل كلمة fill في .pack()
على tk.BOTH، وقيمة معامل كلمة expand على True:
import tkinter as tk
window = tk.Tk()
frame1 = tk.Frame(master=window, width=200, height=100, bg="red")
frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
frame3 = tk.Frame(master=window, width=50, bg="blue")
frame3.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
window.mainloop()
عند تشغيل البرنامج النصي أعلاه، سترى نافذةً تبدو في البداية مشابهةً للنافذة التي أنشأتها في المثال السابق. الفرق هو أنه يمكنك الآن تغيير حجم النافذة كما تشاء، وستتوسع الإطارات وتملأ النافذة تلقائيًا.

مدير الهندسة .
يمكنك استخدام .place()
للتحكم في الموقع الدقيق الذي يجب أن تشغله الأداة في نافذة أو إطار. يجب عليك توفير معامليْن رئيسيين، x وy، لتحديد إحداثيات x وy للزاوية العلوية اليسرى من الأداة. يُقاس كلٌّ من x وy بالبكسل، وليس بوحدات النص.
تذكر أن نقطة الأصل، حيث x وy تساويان 0، هي الزاوية العلوية اليسرى للإطار أو النافذة. لذا، يمكنك اعتبار وسيطة y في دالة .place()
عدد وحدات البكسل من أعلى النافذة، ووسيطة x عدد وحدات البكسل من الحافة اليسرى للنافذة.
فيما يلي مثال لكيفية عمل مدير الهندسة .place()
:
import tkinter as tk
window = tk.Tk()
frame = tk.Frame(master=window, width=150, height=150)
frame.pack()
label1 = tk.Label(master=frame, text="I'm at (0, 0)", bg="red")
label1.place(x=0, y=0)
label2 = tk.Label(master=frame, text="I'm at (75, 75)", bg="yellow")
label2.place(x=75, y=75)
window.mainloop()
إليك كيفية عمل هذا الكود:
- ينشئ السطران 5 و6
Frame
يسمىframe
، ويبلغ عرضه 150 بكسل وطوله 150 بكسل، ويحزمه في النافذة باستخدام.pack()
. - ينشئ السطران 8 و9
Label
جديد تسمىlabel1
بخلفية حمراء ويضعونها في الإطار 1 في الموضع (0، 0). - يقوم السطران 11 و12 بإنشاء
Label
ثانية تسمىlabel2
ذات خلفية صفراء ووضعها في الإطار 1 في الموضع (75، 75).
هذه هي النافذة التي ينتجها الكود:

لاحظ أنه إذا شغّلت هذا الكود على نظام تشغيل مختلف يستخدم أحجام وأنماط خطوط مختلفة، فقد تُحجب العلامة الثانية جزئيًا بحافة النافذة. لهذا السبب، لا يُستخدم .place()
بكثرة. بالإضافة إلى ذلك، له عيبان رئيسيان:
- قد يكون من الصعب إدارة التخطيط باستخدام
.place()
. هذا صحيح بشكل خاص إذا كان تطبيقك يحتوي على الكثير من عناصر واجهة المستخدم. - التخطيطات المُنشأة باستخدام .
place()
ليست مُتجاوبة، ولا تتغير مع تغيير حجم النافذة.
أحد التحديات الرئيسية لتطوير واجهة المستخدم الرسومية عبر الأنظمة الأساسية هو إنشاء تخطيطات تبدو جيدة بغض النظر عن النظام الأساسي الذي يتم عرضها عليه، و.place()
هو خيار ضعيف لإنشاء تخطيطات مستجيبة ومتعددة الأنظمة الأساسية.
هذا لا يعني أنه لا يجب عليك استخدام .place()
أبدًا! في بعض الحالات، قد يكون هذا هو ما تحتاجه تمامًا. على سبيل المثال، إذا كنت تُنشئ واجهة مستخدم رسومية لخريطة، فقد يكون .place()
الخيار الأمثل لضمان وضع عناصر واجهة المستخدم على المسافة الصحيحة من بعضها البعض على الخريطة.
عادةً ما يكون .pack()
خيارًا أفضل من .place()
، ولكن حتى .pack()
له بعض العيوب. يعتمد ترتيب عناصر واجهة المستخدم على ترتيب استدعاء .pack()
، لذا قد يكون من الصعب تعديل التطبيقات الحالية دون فهم كامل للكود الذي يتحكم في التخطيط. يحل مدير هندسة .grid()
العديد من هذه المشكلات، كما سترى في القسم التالي.
مدير الهندسة .grid()
مدير الهندسة الذي من المرجح أن تستخدمه في أغلب الأحيان هو .grid()
، والذي يوفر كل قوة .pack() بتنسيق أسهل للفهم والصيانة.
تعمل دالة .grid()
على تقسيم النافذة أو الإطار إلى صفوف وأعمدة. يمكنك تحديد موقع عنصر واجهة المستخدم عن طريق استدعاء .grid()
وتمرير مؤشرات الصفوف والأعمدة إلى الكلمتين المفتاحيتين row وclumn، على التوالي. يبدأ مؤشرا الصفوف والأعمدة من 0، لذا فإن مؤشر الصف 1 ومؤشر العمود 2 يُخبران دالة .grid()
بوضع عنصر واجهة مستخدم في العمود الثالث من الصف الثاني.
يقوم البرنامج النصي التالي بإنشاء شبكة 3 × 3 من الإطارات مع عناصر واجهة المستخدم الرسومية Label مدمجة فيها:
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack()
window.mainloop()
وهذا هو شكل النافذة الناتجة:

في هذا المثال، تستخدم مديرَي هندسة. كل إطار مُرفق بنافذة باستخدام مدير الهندسة .grid()
:
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack()
window.mainloop()
يتم إرفاق كل ملصق بإطاره الرئيسي باستخدام .pack()
:
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack()
window.mainloop()
من المهم هنا إدراك أنه على الرغم من استدعاء .grid()
لكل كائن إطار، فإن مدير الهندسة ينطبق على كائن window
. وبالمثل، يتم التحكم في تخطيط كل إطار باستخدام مدير الهندسة .pack()
.
الإطارات في المثال السابق مُرتبة بشكل مُتقارب. لإضافة مساحة حول كل إطار، يُمكنك ضبط حشو كل خلية في الشبكة. الحشو هو مساحة فارغة تُحيط بالأداة وتُميّز محتواها بصريًا.
هناك نوعان من الحشو: الحشو الخارجي والحشو الداخلي. يُضيف الحشو الخارجي مساحةً حول الجزء الخارجي من خلية الشبكة. يتم التحكم فيه باستخدام معاملين رئيسيين لـ .grid()
:
- يضيف padx الحشو في الاتجاه الأفقي.
- يضيف pady الحشو في الاتجاه الرأسي.
يُقاس كلٌّ من padx وpady بالبكسل، وليس بوحدات النص، لذا فإن ضبط كليهما على نفس القيمة سيؤدي إلى نفس مقدار الحشو في كلا الاتجاهين. حاول إضافة بعض الحشو حول الإطار الخارجي من المثال السابق:
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j, padx=5, pady=5)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack()
window.mainloop()
وهذه هي النافذة الناتجة:

يحتوي .pack()
أيضًا على معلمات padx وpady. الكود التالي مطابق تقريبًا للكود السابق، باستثناء إضافة خمس بكسلات إضافية للحشو حول كل علامة في كلا الاتجاهين x وy:
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j, padx=5, pady=5)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack(padx=5, pady=5)
window.mainloop()
إن الحشو الإضافي حول عناصر واجهة المستخدم الرسومية “Label
” يمنح كل خلية في الشبكة مساحة صغيرة للتنفس بين حدود الإطار والنص الموجود في Label
:

يبدو جميلاً! لكن إذا حاولت توسيع النافذة في أي اتجاه، ستلاحظ أن التصميم ليس سريع الاستجابة.

تظل الشبكة بأكملها في الزاوية العلوية اليسرى أثناء توسع النافذة.
باستخدام دالتي .columnconfigure()
و.rowconfigure()
على كائن window
، يمكنك ضبط حجم صفوف وأعمدة الشبكة مع تغيير حجم النافذة. تذكر أن الشبكة متصلة ب window
، حتى لو كنت تستدعي .grid()
على كل عنصر واجهة مستخدم في Frame
. يأخذ كلٌّ من .columnconfigure()
و.rowconfigure()
ثلاثة معاملات أساسية:
- الفهرس: فهرس عمود أو صف الشبكة الذي تريد تكوينه أو قائمة الفهارس لتكوين صفوف أو أعمدة متعددة في نفس الوقت
- الوزن: وسيطة كلمة رئيسية تسمى
weight
والتي تحدد كيفية استجابة العمود أو الصف لتغيير حجم النافذة، بالنسبة للأعمدة والصفوف الأخرى - الحجم الأدنى: وسيطة كلمة رئيسية تسمى
minsize
والتي تحدد الحجم الأدنى لارتفاع الصف أو عرض العمود بالبكسل
الوزن مُعيَّن افتراضيًا على 0، مما يعني أن العمود أو الصف لا يتمدد مع تغيير حجم النافذة. إذا كان وزن كل عمود أو صف 1، فإن جميعها تتمدد بنفس المعدل. إذا كان وزن أحد الأعمدة 1 ووزن الآخر 2، فإن العمود الثاني يتمدد بضعف معدل الأول. عدّل النص السابق لتحسين التعامل مع تغيير حجم النافذة:
import tkinter as tk
window = tk.Tk()
for i in range(3):
window.columnconfigure(i, weight=1, minsize=75)
window.rowconfigure(i, weight=1, minsize=50)
for j in range(0, 3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j, padx=5, pady=5)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack(padx=5, pady=5)
window.mainloop()
يتم وضع .columnconfigure()
و.rowconfigure()
في نص حلقة for الخارجية. يمكنك إعداد كل عمود وصف خارج حلقة for بشكل صريح، ولكن هذا يتطلب كتابة ستة أسطر إضافية من التعليمات البرمجية.
في كل تكرار للحلقة، يُضبط وزن العمود والصف i ليكون 1. يضمن هذا توسع الصف والعمود بنفس المعدل عند تغيير حجم النافذة. قيمة minsize
للحجم 75 لكل عمود و50 لكل صف. يضمن هذا أن يعرض عنصر واجهة المستخدم “Label
” نصه دائمًا دون حذف أي أحرف، حتى لو كان حجم النافذة صغيرًا جدًا.
النتيجة هي تخطيط شبكة يتوسع ويتقلص بسلاسة عند تغيير حجم النافذة:

افتراضيًا، تكون عناصر واجهة المستخدم متمركزة في خلايا الشبكة. على سبيل المثال، يُنشئ الكود التالي عنصري واجهة مستخدم “Label
” ويضعهما في شبكة ذات عمود واحد وصفين:
import tkinter as tk
window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)
label1 = tk.Label(text="A")
label1.grid(row=0, column=0)
label2 = tk.Label(text="B")
label2.grid(row=1, column=0)
window.mainloop()
يبلغ عرض كل خلية في الشبكة 250 بكسل وارتفاعها 100 بكسل. توضع العلامات في منتصف كل خلية، كما هو موضح في الشكل التالي:

يمكنك تغيير موقع كل تسمية داخل خلية الشبكة باستخدام المعلمة sticky، والتي تقبل سلسلة تحتوي على حرف واحد أو أكثر من الأحرف التالية:
- “n” أو “N” للمحاذاة مع الجزء العلوي الأوسط من الخلية
- “e” أو “E” للمحاذاة إلى الجانب الأيمن الأوسط من الخلية
- “s” أو “S” للمحاذاة مع الجزء السفلي الأوسط من الخلية
- “w” أو “W” للمحاذاة إلى الجانب الأيسر الأوسط من الخلية
الحروف “n” و”s” و”e” و”w” مُشتقة من الاتجاهات الأساسية: الشمال والجنوب والشرق والغرب. يُؤدي ضبط “n” على كلا الملصقين في الكود السابق إلى وضع كل ملصق في أعلى منتصف خلية الشبكة.
import tkinter as tk
window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)
label1 = tk.Label(text="A")
label1.grid(row=0, column=0, sticky="n")
label2 = tk.Label(text="B")
label2.grid(row=1, column=0, sticky="n")
window.mainloop()
وهنا الناتج:

يمكنك دمج عدة أحرف في سلسلة واحدة لوضع كل تسمية في زاوية خلية الشبكة الخاصة بها:
import tkinter as tk
window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)
label1 = tk.Label(text="A")
label1.grid(row=0, column=0, sticky="ne")
label2 = tk.Label(text="B")
label2.grid(row=1, column=0, sticky="sw")
window.mainloop()

عند وضع عنصر واجهة مستخدم مع خاصية sticky، يكون حجمه كافيًا لاحتواء أي نص أو محتوى آخر بداخله. ولن يملأ خلية الشبكة بالكامل. لملء الشبكة، يمكنك تحديد “ns” لإجبار عنصر واجهة المستخدم على ملء الخلية رأسيًا، أو “ew” لملء الخلية أفقيًا. لملء الخلية بالكامل، اضبط خاصية sticky على “nsew”. يوضح المثال التالي كلًا من هذه الخيارات:
import tkinter as tk
window = tk.Tk()
window.rowconfigure(0, minsize=50)
window.columnconfigure([0, 1, 2, 3], minsize=50)
label1 = tk.Label(text="1", bg="black", fg="white")
label2 = tk.Label(text="2", bg="black", fg="white")
label3 = tk.Label(text="3", bg="black", fg="white")
label4 = tk.Label(text="4", bg="black", fg="white")
label1.grid(row=0, column=0)
label2.grid(row=0, column=1, sticky="ew")
label3.grid(row=0, column=2, sticky="ns")
label4.grid(row=0, column=3, sticky="nsew")
window.mainloop()
وهذا هو الشكل الذي يبدو عليه الناتج:

يوضح المثال أعلاه أنه يمكن استخدام مُعامل الالتصاق (sticky) في مُدير الهندسة .grid()
لتحقيق نفس تأثيرات مُعامل التعبئة (fill) في مُدير الهندسة .pack()
.
جعل تطبيقاتك تفاعلية
لديك الآن فكرة جيدة عن كيفية إنشاء نافذة باستخدام Tkinter، وإضافة بعض الأدوات، والتحكم في تصميم التطبيق. هذا رائع، ولكن لا ينبغي للتطبيقات أن تكون جميلة المظهر فحسب، بل يجب أن تؤدي وظيفةً ما! في هذا القسم، ستتعلم كيفية إضفاء الحيوية على تطبيقاتك من خلال تنفيذ إجراءات عند حدوث أحداث معينة.
استخدام الأحداث ومعالجات الأحداث
عند إنشاء تطبيق Tkinter، يجب عليك استدعاء دالة window.mainloop()
لبدء حلقة الحدث. أثناء حلقة الحدث، يتحقق تطبيقك من وقوع حدث. في هذه الحالة، سينفذ بعض الشيفرة استجابةً لذلك.
يوفر لك Tkinter حلقة الأحداث، لذا لا داعي لكتابة أي شيفرة برمجية للتحقق من الأحداث بنفسك. مع ذلك، عليك كتابة الشيفرة البرمجية التي سيتم تنفيذها استجابةً للحدث. في Tkinter، تكتب دوالًا تُسمى معالجات الأحداث للأحداث التي تستخدمها في تطبيقك.
ملاحظة: الحدث هو أي إجراء يحدث أثناء حلقة الحدث والذي قد يؤدي إلى إثارة بعض السلوكيات في التطبيق، مثل عندما يتم الضغط على مفتاح أو زر الماوس.
عند وقوع حدث، يُصدر كائن حدث، مما يعني إنشاء مثيل لفئة تُمثل الحدث. لا داعي للقلق بشأن إنشاء هذه الفئات بنفسك، إذ سيُنشئ لك Tkinter مثيلات لفئات الحدث تلقائيًا.
ستكتب حلقة أحداث خاصة بك لفهم آلية عمل حلقة أحداث Tkinter بشكل أفضل. بهذه الطريقة، يمكنك معرفة كيفية تكامل حلقة أحداث Tkinter مع تطبيقك، والأجزاء التي تحتاج إلى كتابتها بنفسك.
لنفترض وجود قائمة تُسمى ” events
” تحتوي على كائنات أحداث. يُضاف كائن حدث جديد تلقائيًا إلى events
في كل مرة يقع فيها حدث في برنامجك. لا داعي لتطبيق آلية التحديث هذه، بل يحدث ذلك تلقائيًا في هذا المثال المفاهيمي. باستخدام حلقة لا نهائية، يمكنك التحقق باستمرار من وجود أي كائنات أحداث في events
:
# Assume that this list gets updated automatically
events = []
# Run the event loop
while True:
# If the event list is empty, then no events have occurred
# and you can skip to the next iteration of the loop
if events == []:
continue
# If execution reaches this point, then there is at least one
# event object in the event list
event = events[0]
حاليًا، حلقة الأحداث التي أنشأتها لا تُجري أي تغييرات على event
. لنُغير ذلك. لنفترض أن تطبيقك يحتاج إلى الاستجابة لضغطات المفاتيح. عليك التحقق من أن event
قد تم توليده بواسطة ضغط المستخدم على مفتاح على لوحة المفاتيح، وفي هذه الحالة، مرر event
إلى دالة معالجة الأحداث لضغطات المفاتيح.
افترض أن الحدث يحتوي على سمة .type
مضبوطة على السلسلة النصية “keypress” إذا كان الحدث كائن حدث ضغط مفتاح، وسمة .char
تحتوي على حرف المفتاح الذي تم ضغطه. أنشئ دالة ()handle_keypress جديدة، وحدِّث كود حلقة الحدث:
events = []
# Create an event handler
def handle_keypress(event):
"""Print the character associated to the key pressed"""
print(event.char)
while True:
if events == []:
continue
event = events[0]
# If event is a keypress event object
if event.type == "keypress":
# Call the keypress event handler
handle_keypress(event)
عند استدعاء دالة ()window.mainloop، يتم تشغيل حلقة مشابهة للحلقة السابقة. تتولى هذه الطريقة معالجة جزأين من الحلقة:
- ويحتفظ بقائمة الأحداث التي وقعت.
- يتم تشغيل معالج الأحداث في أي وقت تتم إضافة حدث جديد إلى تلك القائمة.
قم بتحديث حلقة الحدث الخاصة بك لاستخدام ()window.mainloop بدلاً من حلقة الحدث الخاصة بك:
import tkinter as tk
# Create a window object
window = tk.Tk()
# Create an event handler
def handle_keypress(event):
"""Print the character associated to the key pressed"""
print(event.char)
# Run the event loop
window.mainloop()
دالة .mainloop()
تُعنى بالكثير، ولكن هناك شيء مفقود في الكود أعلاه. كيف يعرف Tkinter متى يستخدم ()handle_keypress؟ تحتوي أدوات Tkinter على دالة تُسمى .bind()
لهذا الغرض تحديدًا.
استخدام
لاستدعاء مُعالِج أحداث عند وقوع حدث على عنصر واجهة مستخدم، استخدم .bind()
. يُقال إن مُعالِج الأحداث مُرتبط بالحدث لأنه يُستدعى في كل مرة يقع فيها. ستُتابع مع مثال الضغط على المفتاح من القسم السابق، وستستخدم .bind()
لربط ()handle_keypress بحدث الضغط على المفتاح:
import tkinter as tk
window = tk.Tk()
def handle_keypress(event):
"""Print the character associated to the key pressed"""
print(event.char)
# Bind keypress event to handle_keypress()
window.bind("<Key>", handle_keypress)
window.mainloop()
هنا، يرتبط معالج حدث ()handle_keypress بحدث “<Key>” باستخدام دالة ()window.bind. عند الضغط على أي مفتاح أثناء تشغيل التطبيق، سيطبع برنامجك حرف المفتاح الذي تم الضغط عليه.
ملاحظة: لا يُطبع مُخرَج البرنامج المذكور أعلاه في نافذة تطبيق Tkinter، بل يُطبع في مسار تيارات البيانات الموحدة –.
إذا شغّلت البرنامج في وضع IDLE، فسترى النتيجة في النافذة التفاعلية. أما إذا شغّلته من نافذة طرفية، فسترى النتيجة في نافذة طرفية.
.bind()
يأخذ دائمًا وسيطتين على الأقل:
- حدث يتم تمثيله بسلسلة من النموذج “<event_name>”، حيث يمكن أن يكون event_name أيًا من أحداث Tkinter
- معالج الحدث هو اسم الدالة التي سيتم استدعاؤها كلما حدث الحدث
يرتبط مُعالج الحدث بالأداة التي تُستدعى عليها الدالة .bind()
. عند استدعاء مُعالج الحدث، يُمرر كائن الحدث إلى دالة مُعالج الحدث.
في المثال أعلاه، مُعالج الأحداث مُرتبط بالنافذة نفسها، ولكن يُمكنك ربط مُعالج الأحداث بأي عنصر واجهة مستخدم في تطبيقك. على سبيل المثال، يُمكنك ربط مُعالج الأحداث بعنصر واجهة مستخدم Button
يُنفّذ إجراءً ما عند الضغط على الزر:
def handle_click(event):
print("The button was clicked!")
button = tk.Button(text="Click me!")
button.bind("<Button-1>", handle_click)
في هذا المثال، يرتبط حدث “<Button-1>” في عنصر واجهة المستخدم بمعالج حدث handle_click. يقع حدث “<Button-1>” عند الضغط على زر الماوس الأيسر أثناء مرور الماوس فوق عنصر واجهة المستخدم. هناك أحداث أخرى لنقرات زر الماوس، بما في ذلك “<Button-2>” للزر الأوسط و”<Button-3>” للزر الأيمن.
يمكنك ربط أي معالج حدث بأي نوع من عناصر واجهة المستخدم باستخدام .bind()
، ولكن هناك طريقة أكثر مباشرة لربط معالجات الحدث بنقرات الأزرار باستخدام سمة command
الخاصة بعنصر واجهة المستخدم ” Button
“.
استخدام command
يحتوي كل عنصر واجهة مستخدم Button
على سمة command
يُمكنك تخصيصها لوظيفة. عند الضغط على الزر، تُنفَّذ الوظيفة.
ألقِ نظرة على مثال. أولًا، ستُنشئ نافذةً تحتوي على عنصر واجهة مستخدم “Label
” يحمل قيمةً رقمية. ستضع أزرارًا على يمين ويسار Label
. يُستخدم الزر الأيسر لتقليل قيمة Label
، والزر الأيمن لزيادة قيمتها. إليك كود النافذة:
import tkinter as tk
window = tk.Tk()
window.rowconfigure(0, minsize=50, weight=1)
window.columnconfigure([0, 1, 2], minsize=50, weight=1)
btn_decrease = tk.Button(master=window, text="-")
btn_decrease.grid(row=0, column=0, sticky="nsew")
lbl_value = tk.Label(master=window, text="0")
lbl_value.grid(row=0, column=1)
btn_increase = tk.Button(master=window, text="+")
btn_increase.grid(row=0, column=2, sticky="nsew")
window.mainloop()
تبدو النافذة بهذا الشكل:

بعد تحديد تصميم التطبيق، يمكنك تنشيطه عن طريق إعطاء بعض الأوامر للأزرار. ابدأ بالزر الأيسر. عند الضغط عليه، يُفترض أن تُقلل قيمة التسمية بمقدار واحد. للقيام بذلك، عليك أولاً الحصول على إجابة لسؤالين:
- كيف تحصل على النص في
Label
؟ - كيف تقوم بتحديث النص في
Label
؟
لا تحتوي أدواتLabel
على دالة .get()
مثل أدوات Entry
و Text
. مع ذلك، يمكنك استرجاع النص من التسمية بالوصول إلى سمة النص text
باستخدام تدوين أسفلي على غرار القاموس:
label = tk.Label(text="Hello")
# Retrieve a label's text
text = label["text"]
# Set new text for the label
label["text"] = "Good bye"
الآن بعد أن تعرفت على كيفية الحصول على نص الملصق وتعيينه، اكتب دالة ()increased التي تزيد القيمة في lbl_value بمقدار واحد:
import tkinter as tk
def increase():
value = int(lbl_value["text"])
lbl_value["text"] = f"{value + 1}"
# ...
دالة ()increased تحصل على النص من lbl_value وتحوله إلى عدد صحيح باستخدام ()int. ثم تزيد هذه القيمة بمقدار واحد، وتضبط سمة نص الملصق على هذه القيمة الجديدة.
ستحتاج أيضًا إلى استخدام ()reduce لتقليل القيمة في value_label بمقدار واحد:
# ...
def decrease():
value = int(lbl_value["text"])
lbl_value["text"] = f"{value - 1}"
# ...
ضع ()increased و()decrease في الكود الخاص بك مباشرة بعد عبارة الاستيراد.
لربط الأزرار بالوظائف، عيّن الوظيفة إلى سمة command . يمكنك القيام بذلك عند إنشاء الأزرار. على سبيل المثال، حدّث السطرين اللذين يُنشئان الأزرار إلى ما يلي:
# ...
btn_decrease = tk.Button(master=window, text="-", command=decrease)
btn_decrease.grid(row=0, column=0, sticky="nsew")
lbl_value = tk.Label(master=window, text="0")
lbl_value.grid(row=0, column=1)
btn_increase = tk.Button(master=window, text="+", command=increase)
btn_increase.grid(row=0, column=2, sticky="nsew")
window.mainloop()
هذا كل ما عليك فعله لربط أزرار الزيادة والنقصان، ولجعل البرنامج يعمل بكفاءة. حاول حفظ التغييرات وتشغيل التطبيق! انقر على الأزرار لزيادة القيمة وتقليلها في منتصف النافذة.
في القسمين التاليين، ستُنشئ تطبيقات أكثر فائدة. أولًا، ستُنشئ مُحوّلًا لدرجة الحرارة يُحوّل قيمة درجة الحرارة من فهرنهايت إلى مئوية. بعد ذلك، ستُنشئ مُحرّر نصوص يُمكنك من فتح ملفات النصوص وتحريرها وحفظها!
بناء تطبيق محول درجة الحرارة
في هذا القسم، ستُنشئ تطبيقًا لتحويل درجة الحرارة، يُمكّن المستخدم من إدخال درجة الحرارة بالفهرنهايت، ثم الضغط على زر لتحويلها إلى مئوية. سنشرح الكود خطوة بخطوة.
ملاحظة: للحصول على أقصى استفادة من هذا القسم، قم بمتابعته في Python shell.
قبل البدء بالبرمجة، عليك أولاً تصميم التطبيق. ستحتاج إلى ثلاثة عناصر:
Entry
: أداة تسمى ent_temperature لإدخال قيمة فهرنهايتLabel
: أداة تسمى lbl_result لعرض نتيجة درجة الحرارة المئويةButton
: أداة تسمى btn_convert تقرأ القيمة من أداةEntry
، وتحولها من فهرنهايت إلى مئوية، وتضبط نص أداةLabel
على النتيجة عند النقر عليها
يمكنك ترتيبها في شبكة، بحيث يكون لكل عنصر واجهة مستخدم صف واحد وعمود واحد. هذا يُتيح لك تطبيقًا يعمل بشكل محدود، ولكنه ليس سهل الاستخدام. يجب أن يكون لكل عنصر تسميات.
ستضع ملصقًا مباشرةً على يمين أداة ent_temperature يحتوي على رمز فهرنهايت (℉) ليعلم المستخدم أن قيمة ent_temperature يجب أن تكون بدرجات فهرنهايت. للقيام بذلك، اضبط نص الملصق على “\N{DEGREE FAHRENHEIT}”، والذي يستخدم دعم أحرف Unicode المُسمّاة في بايثون لعرض رمز فهرنهايت.
يمكنك إضافة لمسة جمالية إلى btn_convert بتعيين نصه إلى القيمة “\N{RIGHTWARDS BLACK ARROW}”، والذي يعرض سهمًا أسود يشير إلى اليمين. ستتأكد أيضًا من أن lbl_result يحمل دائمًا رمز الدرجة المئوية (℃) بعد نص التسمية “\N{DEGREE CELSIUS}” للإشارة إلى أن النتيجة بالدرجات المئوية. هكذا ستبدو النافذة النهائية:

الآن بعد أن عرفتَ الأدوات التي تحتاجها وشكل النافذة، يمكنك البدء ببرمجتها! أولًا، استورد tkinter وأنشئ نافذة جديدة:
import tkinter as tk
window = tk.Tk()
window.title("Temperature Converter")
window.resizable(width=False, height=False)
دالة ()window.title تُعيّن عنوان نافذة موجودة، بينما دالة ()window.resizable، مع ضبط كلا الوسيطتين على False، تُثبّت حجم النافذة. عند تشغيل هذا التطبيق، سيظهر نص “محوّل درجة الحرارة” في شريط عنوان النافذة. بعد ذلك، أنشئ عنصر واجهة المستخدم ent_temperature بتسمية lbl_temp، ثم عيّن كليهما إلى عنصر واجهة مستخدم Frame يُسمى frm_entry:
# ...
frm_entry = tk.Frame(master=window)
ent_temperature = tk.Entry(master=frm_entry, width=10)
lbl_temp = tk.Label(master=frm_entry, text="\N{DEGREE FAHRENHEIT}")
سيُدخل المستخدم قيمة فهرنهايت في حقل ent_temperature، ويُستخدم حقل lbl_temp لتمييز ent_temperature برمز فهرنهايت. يجمع حقل frm_entry قيمتي ent_temperature وlbl_temp معًا.
تريد وضع lbl_temp مباشرةً على يمين ent_temperature. يمكنك عرضهما في frm_entry باستخدام مُدير الهندسة .grid()
مع صف واحد وعمودين:
# ...
ent_temperature.grid(row=0, column=0, sticky="e")
lbl_temp.grid(row=0, column=1, sticky="w")
لقد ضبطتَ مُعامل الالتصاق على “e” لـ ent_temperature ليُثبت دائمًا على أقصى يمين خلية الشبكة. كما عيّنتَ مُعامل الالتصاق على “w” لـ lbl_temp ليُثبت دائمًا على أقصى يسار خلية الشبكة. هذا يضمن أن يكون lbl_temp دائمًا على يمين ent_temp مباشرةً.
الآن، قم بإنشاء btn_convert وlbl_result لتحويل درجة الحرارة المدخلة إلى ent_temperature وعرض النتائج:
# ...
btn_convert = tk.Button(
master=window,
text="\N{RIGHTWARDS BLACK ARROW}"
)
lbl_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")
كما هو الحال مع frm_entry، يتم تعيين كلٍّ من btn_convert وlbl_result إلى window. تُشكّل هذه الأدوات الثلاثة معًا الخلايا الثلاث في شبكة التطبيق الرئيسية. استخدم .grid()
لعرضها الآن:
# ...
frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=1, pady=10)
lbl_result.grid(row=0, column=2, padx=10)
وأخيرًا، قم بتشغيل التطبيق:
# ...
window.mainloop()
يبدو رائعًا! لكن الزر لا يعمل حاليًا. في أعلى ملف البرنامج النصي، أسفل سطر الاستيراد مباشرةً، أضف دالة تُسمى فهرنهايت إلى مئوية (()fahrenheit_to_celsius):
import tkinter as tk
def fahrenheit_to_celsius():
"""Convert the value for Fahrenheit to Celsius and insert the
result into lbl_result.
"""
fahrenheit = ent_temperature.get()
celsius = (5 / 9) * (float(fahrenheit) - 32)
lbl_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"
# ...
تقوم هذه الوظيفة بقراءة القيمة من ent_temperature، وتحويلها من فهرنهايت إلى مئوية، ثم عرض النتيجة في lbl_result.
انتقل الآن إلى السطر الذي قمت بتعريف btn_convert فيه وقم بتعيين معلمة الأمر الخاصة به إلى fahrenheit_to_celsius:
# ...
btn_convert = tk.Button(
master=window,
text="\N{RIGHTWARDS BLACK ARROW}",
command=fahrenheit_to_celsius # <--- Add this line
)
# ...
هذا كل شيء! لقد أنشأتَ تطبيقًا متكاملًا لتحويل درجات الحرارة في ستة وعشرين سطرًا فقط! رائع، أليس كذلك؟
في هذا البرنامج التعليمي، تعلمت كيفية البدء ببرمجة واجهة المستخدم الرسومية بلغة بايثون. يُعد Tkinter خيارًا مثاليًا لإطار عمل واجهة المستخدم الرسومية بلغة بايثون، لأنه مُدمج في مكتبة بايثون القياسية، كما أن إنشاء التطبيقات باستخدام هذا الإطار سهل نسبيًا.
بعد أن أتقنتَ أساسيات برمجة واجهة المستخدم الرسومية بلغة بايثون باستخدام Tkinter، الخطوة التالية هي بناء بعض تطبيقاتك الخاصة. ماذا ستُنشئ؟ شارك مشاريعك الممتعة في التعليقات أدناه!
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.