شرح مكتبة tkinter لإنشاء واجهات خفيفة عملية بالبايثون

بسم الله الرحمن الرحيم والصلاة والسلام على سيدنا محمد وعلى آله وصحبه ومن تبعهم بإحسان إلى يوم الدين وبعد:

موضوعنا اليوم إن شاء الله عن مكتبة تصميم الواجهات الشهيرة Tkinter (اختصارا لـ Tk interface).

ما هي tkinter وما علاقتها بـ ( Tcl/Tk)؟

قبل أن نبدأ بشرح العلاقة بين tcl/tk و tkinter علينا أولا أن نوضح معنى كلمة wrapper.
wrapper -وتعني حرفيًا بالمغلف- هي كلمة تستخدم لوصف البرنامج/الكود الذي يغلف كود آخر مكتوب بلغة أخرى سواء لإضافة المزيد من الخصائص أو نقل الميزة إلى لغة جديدة أو برنامج جديد أو الحاجة إلى تسهيل التعقيد الخاص بالكود ليسهل استخدامه في وظيفة أخرى بحيث لا نضطر لإعادة كتابة الكود من البداية ولكن بدلا من ذلك سنقوم فقط بتغليف الكود القديم بحيث يتجاوب مع متطلباتنا. للكلمة معاني أخرى أيضا لكن هذا خارج نطاق هذه المقالة.

لغة tcl تنطق “تيكل” اختصارا لـ (Tool Command Language) وهي لغة نصية قام بإنشاءها شخص يدعى John Ousterhout (جون أوسترهوت) في ثمانينيات القرن الماضي وتم إصدارها في عام 1988 لكي يتم إستخدامها بسهولة في عدة برامج كلغة مفسرة بحيث تعطي مكونات تمكن المبرمجين من إضافة خواص أخرى للغة لكي يتم توظيفها في التطبيقات بلا تحديدات أو معوقات. حيث أن أهداف اللغة كانت 1- أن يسهل على أي برنامج يستخدم اللغة أن يضيف ميزاته الخاصة بلا صعوبة. 2- أن توفر اللغة السهولة والعملية بحيث يمكن استخدامها في عدة برامج بلا تعقيد عملية إضافة الخواص والمميزات. 3- أن تعمل اللغة على “لصق” مكونات التطبيق معًا بحيث توفر اللغة سهولة دمج عدة أجزاء معا.

tk في فترة كانت فيها واجهات المستخدم المرئية (GUI) تكبر بسرعة كانت هناك حاجة لصنع أداة توفر إمكانية إضافة مكونات متعددة معا ولصقها معا لتكون تطبيق ذا واجهة. فكر الدكتور جون في عمل عدة مكونات بسيطة باستخدام لغة tcl وقام بعد ذلك باختبار تلك المكونات وجمعها معا في تطبيق واحد وهنا حيث ولدت Tk كأحد تطبيقات لغة tcl.

tkinter تم عمل تي كي انترفيس كمغلف لأداة tk مع بعض الإضافات التي تعطي تجربة مناسبة للغة بايثون .

مكونات المكتبة:
تحتوي المكتبة على الكلاس Tk والذي يقوم عادة بإنشاء الواجهة الرئيسية للتطبيق ويقوم أيضا بإنشاء نسخة من مفسر للغة tcl حيث لكل نسخة من Tk مفسر خاص بها.
يحتوي الكلاس Tk على الصفات (attributes) التالية:

tk وهو التطبيق الخاص ب tk والذي يوفر وصول إلى مفسر tcl. كل عناصر الواجهة التابعة لكلاس ال Tk والتي تم إنشاءها كإبن له (مثلا Label, Button…إلخ) جميعهم يتشارك نفس الصفة (attribute) tk

master بالعربية سيد (كالعلاقة بين العبد والسيد) وهو يعبر عن عنصر الواجهة التي يحتوي العنصر الحالي. وبالنسبة للواجهة الرئيسية Tk السيد لها None حيث انه لا يترأسها أية عناصر في الواجهة.
بينما مثلا لو أخذنا عنصر مثل ال Label والذي وضع كإبن للواجهة الرئيسية فسيكون ال master لهذا ال Label هو كلاس Tk الخاص بالواجهة الرئيسية.

children تحتوي تلك الصفة (attribute) على قاموس (dict) به كل العناصر التابعة لتلك الواجهة.
فمثلا الواجهة الرئيسية إذا كانت تحتوي على مربع إدخل، نص، زر فإن القاموس children يحتوي على تلك العناصر بحيث يكون المفتاح يعبر عن اسم العنصر وقيمة المفتاح داخل القاموس يعبر عن ال instance الخاصة بذلك العنصر بحيث يمكن استدعاءها وإجراء تعديلات عليها.

وسيتضح لنا كل ذلك إن شاء الله مع الأمثلة العملية.

عناصر الواجهات

تحتوي tkinter على نوعين من الواجهات:
النوع الأول يأتي ضمن tk ويحتوي على 12 widgets (عناصر الواجهة مثل الازرار والنصوص وغيرها) وهذا النوع يعطيك الحرية في اختيار الألوان والخطوط وغير ذلك من الإعدادات غير أن هذا النوع لا يتشابه مع المكتبات الاخرى التي تعطي واجهات Native أي تشابه في شكلها عناصر الواجهة المقدمة من النظام.

النوع الثاني يأتي ضمن tkinter.ttk ويحتوي بالإضافة إلى الـ 12 عنصرا الموجودة في tk على 6 عناصر إضافية.
ميزة هذا الخيار هو أنها تأتي تصاميم مظهرها جيد وتشبه في تصميمها تصاميم النظام. لكنها تأتي مع Style شكل معين موجود مسبقا بها.

يوجد لعناصر الواجهة تسلسل معين، حيث توجد علاقة بين العناصر كعلاقة (أب - ابن) حيث أن لكل عنصر غالبا ما يوجد أب له أو ابن أو عدة أبناء.

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

يوجد أيضا لعناصر الواجهة خيارات متعددة (options) تمكننا من إضافة تعديلات على مظهر أو تصرفات عنصر الواجهات أو النص التي تحتويه وغيرها من الخيارات سنتعرف عليها في الشرح العملي إن شاء الله.

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

كما يوجد أيضا لعناصر الواجهة معالجة للأحداث (Event handlers) والتي تمكن العناصر من التفاعل مع أحداث معينة، فمثلا نحتاج إلى معالجة حدث النقر على الزر أو حدث الكتابة في مربع الإدخال، أو الحدث الذي يهتم بتحديث كيف يبدوا مظهر العنصر.

تحتوي tk على 12 widgets وهم (Button, Checkbutton, Entry, Frame, Label, LabelFrame, Menubutton, PanedWindow, Radiobutton, Scale, Scrollbar, and Spinbox).

كما تقدم ttk عناصر إضافية وهي (Combobox, Notebook, Progressbar, Separator, Sizegrip and Treeview).

كما قلنا فإن عناصر ttk سواء تلك الموجودة في tk أم تلك الموجودة فقط في ttk فكلها تشارك نفس التصميم طالما أنك قمت باستيرادها من tkinter.ttk وليس من tkinter مباشرة.

نبدأ بتعريف عنصر مهم من عناصر الواجهة وهو ال Frame أو الإطار.

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

from tkinter import Tk, Frame, Label, Button

root = Tk()
frame1 = Frame(root)
frame1.pack()
label1 = Label(frame1, text="hello aosus community")
label1.pack()
button1 = Button(frame1, text="submit")
button1.pack()

root.mainloop()

Frame a Label and a Button children

نلاحظ بدأنا باستيراد المكونات التي سنستخدمها في بناء هذه الواجهة البسيطة

from tkinter import Tk, Frame, Label, Button

ثم عرفنا النافذة الرئيسية والتي أيضا ستقوم بإنشاء tcl interpreter (مفسر للغة tcl) وتعرض لنا الواجهة الرئيسية التي ستحمل كل الواجهات التي سنستخدمها.
وهي عبارة عن instance من الكلاس Tk والذي تحدثنا عنه منذ قليل.

root = Tk()

ثم بعد ذلك عرفنا ثلاثة مكونات في الواجهة؛ Frame, Label, Button
حيث مررنا ال root لل Frame لكي يكون أب له. كما مررنا ال Frame إلى ال Button وال Label لأنه سيكون أب لهما.
نلاحظ أيضا أننا قد مررنا text إلى الكلاس الخاص بالزر والنص وهذا عبارة عن النص الذي يظهرفي النافذة. وسنشرحه لاحقا مع المدخلات الأخرى الممكنة.

# root is the parent of frame1
frame1 = Frame(root)
frame1.pack()
# frame1 is the parent of label1
label1 = Label(frame1, text="hello aosus community")
label1.pack()
# frame1 is also the parent of button1
button1 = Button(frame1, text="submit")
button1.pack()

قد تتسائل ما هي وظيفة الميثود pack ولم استخدمتها. هذه الميثود تعتبر من أحد طرق التحكم بمواضع العناصر داخل الواجهة. ومن المهم جدا استعمالها على ال widgets لأنها أيضا المسؤولة عن ظهور العناصر في الواجهة، وبدون استعمالها على ال widget فلن تظهر في الواجهة.
يتحكم بال packer (.pack()) بأماكن العناصر داخل الأب. في الوضع الطبيعي تكون العناصر فوق بعضها (يعني متتالية وليست منطبقة فوقها) ويمكن التحكم بمكان ظهور العنصر عبر تمرير side

label1 = Label(frame1, text="hello aosus community")
label1.pack(side="left")
button1 = Button(frame1, text="submit")
button1.pack(side="right")

side-packer

الـ side تأخذ أربع قيم محتملة 'left', 'right', 'top', 'bottom'.
إذا لم تعطها قيمة فستأخذ افتراضيا 'top' أي أعلى. كما تأخذ pack عدة مدخلات أخرى سنغطيها في وقت لاحق إن شاء الله.

2- Label

ال Label هو أحد عناصر الواجهة، ويستخدم لملء جزء من الواجهة بالنص، كما يمكن استخدامه أيضًا في إظهار الصور.

# pip install Pillow 
from tkinter import Tk, Label
from PIL import ImageTk, Image

root = Tk()image1 = Image.open("black-cat-on-top-of-car.jpg")
image1 = image1.resize((300, 300))
image_ob = ImageTk.PhotoImage(image1)

cat_label = Label(root, image=image_ob)
cat_label.pack()

text_label = Label(root, text="a cute black cat", background="black", foreground="white",
underline=2, padx=4, pady=4, height=2, )
text_label.pack()
root.mainloop()

ونستعرض بعض المدخلات الممكنة لل Label
المدخل الوصف
text وهذا هو النص الذي سيظهر في الواجهة
bg, background كلاهما يقوم بتغيير لون خلفية النص ويمكن استعمال أيهما
fg, foreground وهذان لتغيير لون النص نفسه ويمكن استعمال أيهما
font وهذا لتغيير الخط المستخدم في كتابة النص على الواجهة
image وهذا لعرض صورة في الواجهة مكان ال label
padx وهذا لإضافة مسافة قبل وبعد النص المراد بداخل ال label وتكون على الجانبين وتكون أيضا بداخل ال label أي تأخذ الألوان المقدمة لخلفية ال label
pady نفس السابق لكن تضيف المسافة أعلى وأسفل ال label بدلا من يمن ويسار
textvariable وهذا إذا ما كنت تريد إضافة نص إلى ال label بشكل تفاعلي أي مثلا عند ضغط زر او ما شابه وسنشرحها لاحقا باستفاضة
underline ويمكن هذا المدخل من إضافة خط أسفل الحرف الذي ستمرر له (الإندكس) مثلا underline=5 أي الحرف السادس
width لتحديد العرض الخاص بال label وهذا بناء على عدد الأحرف وليس ال pixel أي أن النص لن يسع أكثر من الاحرف الذي تكتبها
height وهذا لتحديد الطول العمودي الخاص بال label

هذه بعض الخيارات ويوجد المزيد لكن لا يسعنا ذكرها في هذه المقالة.

3- Button

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

تتشارك ال Button مع ال Label في عدة خيارات منها bg fg font height width underline padx pady
image text في حالة ما أردت إظهار صورة في الزر بدلا من النص يمكنك استعمال خيار image لدينا أيضا عدة خيارات أخرى في ال Button نستعرضها في ما يلي:

المدخل الوصف
activebackground عن طريق هذا الخيار يمكنك التحكم بلون خلفية الزر عند النقر عليه أو لمسه إذا ما كان بالهاتف
activeforeground عن طريق هذا الخيار يمكنك التحكم بلون النص داخل الزر عند النقر عليه أو لمسه
bd ويمكنك هذا الخيار من تحديد حجم الإطار (border) أو الحواف الخاصة بالزر وقيمته الإفتراضية 2
state ويمكنك هذا الخيار من تحديد حالة الزر حيث يمكن جعل الزر غير ممكن الضغط عليه واستخدامه إلا عند انطباق شروط معينة
command يمكنك هذا الخيار من إعطاء الزر دالة أو ميثود يتم استدعاءها عند الضغط على الزر
highlightcolor هذا الخيار يمكنك من تحديد لون الزر عند عمل focus له أي تركيز في الواجهة

4- Entry

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

يتشارك ال Entry مع ال Button في عدة خيارات منها bg fg font width bd command
وبالإضافة إلى ذلك هناك خيارات أخرى:

المدخل الوصف
cursor يتحكم هذا الخيار بشكل مؤشر الماوس إذا مر على هذه الواجهة وينطبق هذا الخيار أيضا على Label, Button
selectbackground هذا الخيار يوفر لك إمكانية تغيير لون خلفية النص الذي تم اختياره داخل ال Entry
selectforeground هذا الخيار يوفر لك إمكانية تغيير لون النص نفسه الذي تم اختياره داخل ال Entry
selectborderwidth هذا الخيار يتيح لك التحكم بحجم الإطار (border) حول النص المختار داخل ال Entry
show هذا الخيار يتيح لك التحكم بشكل النص على الشاشة في حالة ما لم ترد إظهار النص كحالة كلمة السر وغير ذلك
state هذا الخيار يحدد ما إذا كان المستخدم يستطيع الكتابة داخل ال Entry أم أن الكتابة/التعديل ممنوع
textvariable هذا الخيار يستخدم لتمرير المتغير الذي سنقوم من خلاله بتغيير حالة النص داخل ال Entry وسنشرحه لاحقا

والآن حان دور الجزء الممتع!

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

ما الذي سنبنيه؟
سنقوم إن شاء الله بإنشاء واجهة “تسجيل الدخول” وستكون ذا تصميم بسيط، وسنستعرضها فيما يلي.

2022-11-20_00-18-57

نلاحظ في البرنامج هناك العناصر الآتية:
بالأعلى لدينا Label مكتوب فيها “welcome to the login page”
بعد ذلك لدينا
2 Labels
2 Entry
1 Button

والكود كالآتي:

from tkinter import Tk, Frame, Label, Button, Entry, StringVar, messagebox

root = Tk()

root.config(bg="black")
root.wm_title("Tkinter tutorial")
root.minsize(width=506, height=190)
username_var = StringVar()
password_var = StringVar()


def handle_submit(e=None):
	assumed_username = "admin"
	assumed_password = "admin"
	username = username_var.get()
	password = password_var.get()
	if assumed_username == username and assumed_password == password:
		print("valid")
		messagebox.showinfo("valid", f"welcome back {assumed_username}")
	elif username != assumed_username:
		print("username not found")
		messagebox.showerror("error", f"{username} not found")
	elif assumed_password != password:
		print("wrong password")
		messagebox.showerror("error", "invalid password")


def enter_event(e):
	e.widget.config(bg="white", fg="black")


def leave_event(e):
	e.widget.config(bg="black", fg="white")


main_frame = Frame(root, bg="black")
main_frame.pack(expand=True, anchor='center')
title = Label(main_frame, text="Welcome To The Login Page", bg="black", fg="white", font=("Arial", 23))
title.grid(column=0, row=0, columnspan=2)

username_label = Label(main_frame, fg="white", text="Username:", bg="black", width=20, font=("Arial", 13))
username_label.grid(column=0, row=1, sticky="nsew")
username_entry = Entry(main_frame, bg="black", fg="white", width=28, font=("Arial", 13), selectbackground="white",
					   selectforeground="black", insertbackground="white", textvariable=username_var)
username_entry.grid(column=1, row=1, padx=2, pady=6, sticky="nsew")
password_label = Label(main_frame, fg="white", text="Password:", bg="black", width=20, font=("Arial", 13))
password_label.grid(column=0, row=2, sticky="nsew")
password_entry = Entry(main_frame, bg="black", fg="white", width=28, show="*", selectbackground="white",
					   selectforeground="black", validate='focus', insertbackground="white", relief="sunken",
					   textvariable=password_var)
password_entry.grid(column=1, row=2, padx=2, pady=6, sticky="nsew",)

login_button = Button(main_frame, text="Log In", fg="white", background="black", width=15, command=handle_submit)
login_button.grid(row=3, column=1, columnspan=2, sticky="nsew")

login_button.bind("<Enter>", enter_event)
login_button.bind("<Leave>", leave_event)

root.bind("<Return>", handle_submit)

root.mainloop()

للوهلة الأولى، الكود يبدوا معقد وصعب لكن سنفصله إن شاء الله ولن تكون هناك أية صعوبات.

قمنا أولا وكما المعتاد باستيراد ال widgets التي سنقوم باستخدماها

from tkinter import Tk, Frame, Label, Button, Entry, StringVar, messagebox

ثم بعد ذلك قمنا بتعريف النافذة الرئيسة والتي تعبر أيضا عن برنامجنا. كما قمنا أيضا بتعديل بعض خصائص النافذة
قمنا أولا بتغير لون الخلفية bg إلى الأسود
ثم غيرنا عنوان النافذة إلى “Tkinter tutorial” عن طريق wm_title
ثم بعد ذلك قمنا بتعيين الحد الأدنى لعرض وطول النافذة بحيث لا يمكن تصغير النافذة عن أقل من ذلك ولكن يمكن تكبيرها أكثر

root = Tk()

root.config(bg="black")
root.wm_title("Tkinter tutorial")
root.minsize(width=506, height=190)

سنذهب إلى السطر الذي نقوم فيه بتعريف باقي عناصر النافذة، ثم سنعود إلى الكود الذي تخطيناه.

قمنا بتعريف ال Frame وأعطيناه خلفية bg سوداء
ثم بعد ذلك قمنا بتعيين بعض الخيارات لل pack والتي كما قلنا مسؤولة عن عرض العنصر في الشاشة.

قمنا بتمرير expand=True حتى يمكن أن يتمدد ال Frame بلا مشكلة.
ثم بعد ذلك ذلك مررنا له anchor="center" حتى يظل العنصر في المنتصف دائما.

main_frame = Frame(root, bg="black")
main_frame.pack(expand=True, anchor='center')

والآن نقوم بتعريف باقي العناصر، حيث بدأنا بالعنوان الرأسي. كما نلاحظ فقد استخدمنا font لتغيير الخط وحجمه
حيث يأخذ tuple بها نوع الخط وحجمه ويمكن أيضا طريقة ظهور الخط من normal bold roman italic underline overstrike .

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

في الشكل السابق، نرى بأن الواجهة مقسمة لأربعة صفوف، وعمودين. أستطيع سماع ذلك الشخص الذي يقول، مهلا! العنوان يأخذ عرض الصفحة هل تكذب علينا؟؟
في الحقيقة لا يا صديقي، لكن من روعة ال grid أنه يعطيك حرية كبيرة، وخيارات عديدة، لتقسيم واجهتك كما تشاء. وسنرى الآن كيف.

يمكن أعطاء ال grid تلك الخيارات حتى نوجه العنصر إلى المكان المناسب:
column وهذا رقم العمود الخاص بال gird ويبدأ من الصفر. فالعمود الأول مكانه 0 ومن المفترض أن يظهر النص في العمود الأول فقط ولكن.
row وهذا رقم الصف وقد أعطيناه الصف رقم 0 .
columnspan يمكنك هذا الخير من جعل العنصر يحجز مكانين في الصف بحيث لا يكتفي فقط بالعمود الذي هو فيه، لكن بالإضافة إلى ذلك سيقوم أيضا بحجز المكان أو الأماكن المجاورة حسبما تحدد. وهنا فقط قلنا لها بعرضه في عمودين فقط…

قمنا بعد ذلك بتعريف الـ Label الخاص باسم المستخدم أعطيناه خيارتنا المعتادة قمنا بتغيير لون النص fg إلى الأبيض، لون الخلفية إلى الأسود، كما اعطيناه عرض 20 وحجم الخط 13.
نلاحظ أننا أيضا استخدمنا sticky="nsew" هذا الخيار سيجعل العنصر يأخذ كامل مساحة الخلية المخصصة له داخل العموم - الصف
sticky تاخذ كل اتجاهات البوصلة (شمال - جنوب - شرق - غرب)
North south east west
لدينا Entry بعد ذلك قمنا بإعطاها نفس الألوان ونفس حجم الخط لكن غيرنا العرض قليلا
أيضا أضفنا إليها متفير وظيفته أن يقوم بالتعامل مع مدخلات المستخدم بكل ديناميكية وسنشرحه بعد قليل.
قمنا أيضا بتغيير اللون الذي يعطى للنص وخلفية النص عند تحديده، بدلا من ذلك اللون الأزرق الذي يعطى غالبا.
نلاحظ أننا أعطينا ال Entry الخاص بكلمة السر متغير textvariable مختلف عن الآخر حتى يكن لنا وصول للإثنين ببياناتهم المختلف فلا يمكن أن يتشاركا نفس المتغير.
أيضا نلاحظ أن لدينا مدخل في ال Entry اسمه validate وهذا فائدته أن يتم تنبيهنا عندنا يحدث حدث معين وفي هذه الحالة الحدث هو ال focus وهنا لم نحدد أي طرق للتعامل مع الحدث (بصراحة ليس ذا فائدة الآن سنشرحه في الجزء الثاني).
لدينا ال Button حددنا فيها command ومررنا فيها دالة ستعمل عندنا يتم الضغط على الزر.

حسنا أرجو ألا أكون قد اسقطت شيئا. هكذا نكون انتهينا من هذا الجزء.

title = Label(main_frame, text="Welcome To The Login Page", bg="black", fg="white", font=("Arial", 23))
title.grid(column=0, row=0, columnspan=2)

username_label = Label(main_frame, fg="white", text="Username:", bg="black", width=20, font=("Arial", 13))
username_label.grid(column=0, row=1, sticky="nsew")
username_entry = Entry(main_frame, bg="black", fg="white", width=28, font=("Arial", 13), selectbackground="white",
					   selectforeground="black", insertbackground="white", textvariable=username_var)
username_entry.grid(column=1, row=1, padx=2, pady=6, sticky="nsew")
password_label = Label(main_frame, fg="white", text="Password:", bg="black", width=20, font=("Arial", 13))
password_label.grid(column=0, row=2, sticky="nsew")
password_entry = Entry(main_frame, bg="black", fg="white", width=28, show="*", selectbackground="white",
					   selectforeground="black", validate='focus', insertbackground="white", relief="sunken",
					   textvariable=password_var)
password_entry.grid(column=1, row=2, padx=2, pady=6, sticky="nsew",)

login_button = Button(main_frame, text="Log In", fg="white", background="black", width=15, command=handle_submit)
login_button.grid(row=3, column=1, columnspan=2, sticky="nsew")

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

ويعني ذلك عندنا <Enter> يعني عندما يدخل الماوس حيذ ال login_button ستقوم المكتبة بتشغيل دالة enter_event
وعندما يخرج الماوس من مساحة الزر ستقوم المكتبة بتشغيل leave_event. بالإضافة إلى ذلك ستمرر المكتبة حدث الدخول او الخروج إلى تلك الدوال حتى تخبرها أي حدث هذا وأي عنصر عنده هذا الحدث وغير ذلك. وربما يتسنى لنا الوقت الحديث عن الأحداث في مقال قادم إن أحيانا الله.
أما عند root فقد قمنا بعمل تسجيل حدث جديد وهو الضغط على زر <Return> وهو زر ال Enter في الكيبورد. وفائدة هذا الأمر أن يمكن للمستخدم الضغط على زر <Enter> لستجيل الفورم، بدلا من الحاجة للضغط على الزر.

login_button.bind("<Enter>", enter_event)
login_button.bind("<Leave>", leave_event)

root.bind("<Return>", handle_submit)

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

root.mainloop()

والآن ماذا يحدث عندما يدخل المستخدم اسم وكلمة سر، ثم يضغط على الزر؟

كما نعرف فقد سجلنا مسبقا دالة تعمل عند النقر على الزر. هذه الدالة ستقوم بالتواصل مع المتغيرين داخل الزر textvariable وتقوم باستخراج النص الذي ادخله المستخدم فيها

	username = username_var.get()
	password = password_var.get()

بعد ذلك ستقوم بالتحقق من كلمة السر واسم المستخدم ما إن كانا يطابقان اسم المستخدم وكلمة السر المسجلة عندنا، فإذا كانت مطابقة سنقوم بإرجاع رسالة تفيد بذلك

messagebox.showinfo("valid", f"welcome back {assumed_username}")

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

messagebox.showerror("error", f"{username} not found")
messagebox.showerror("error", "invalid password")

فائدة messagebox أنها تقوم بإرجاع رسالة على شكل نافذة جديد تظهر للمستخدم، دون المساس بالواجهة الرئيسية.
يوجد العديد من أنواع الرسائل التي من الممكن أن تظهر. هنا استخدمنا نوعين وهما: رسالة المعلومات، والتي قمنا باخبار المستخدم فيها بالنتيجة. ورسالة الخطأ، والتي فيها نقوم بإخبار المستخدم بالخطأ.


def handle_submit(e=None):
	assumed_username = "admin"
	assumed_password = "admin"
	username = username_var.get()
	password = password_var.get()
	if assumed_username == username and assumed_password == password:
		print("valid")
		messagebox.showinfo("valid", f"welcome back {assumed_username}")
	elif username != assumed_username:
		print("username not found")
		messagebox.showerror("error", f"{username} not found")
	elif assumed_password != password:
		print("wrong password")
		messagebox.showerror("error", "invalid password")

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

username_var = StringVar()
password_var = StringVar()

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

كما نلاحظ أن الدالة تأخذ مدخل e اختصارا لـ event أي حدث. وفي هذا المدخل معلومات عن الحدث، وبه أيضا الزر widget الذي حدث به التغيير، حيثا قمنا بتغيير الألوان باستخدام ميثود config والتي تقوم بتغيير خيارات العناصر.

def enter_event(e):
	e.widget.config(bg="white", fg="black")


def leave_event(e):
	e.widget.config(bg="black", fg="white")

وبهذا نكون إنتهينا من شرح مثالنا بفضل الله.

الملخص

مكتبة tkinter هي مكتبة واسعة جدًا بها العديد من الخيارات، والمميزات، التي تجعلها عملية، وبنفس الوقت سهلة التعامل معها.
تتكون الواجهة من عناصر كثيرة يمكن جمعها داخل عناصر أخرى، فمثلا نستطيع جمع عدة عناصر داخل Frame وعدة Frame داخل Frame آخر.
عناصر الواجهة يجب أن يتم تحديد مكان لها في الواجهة حتى تظهر إبتداءا، مثل pack أو grid أو غيرها.
هناك الكثير من الأحداث التي يمكن استغلالها لجعل الواجهة أكثر فعالية.
تتحكم ال mainloop برسم العناصر وتسجيل ومعالجة الأحدات وتتابع نظام التطبيق.

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

4 إعجابات

الله يعافيك :heart:

إعجاب واحد (1)

merci thanks