ממשק משתמש Python

עודכן לאחרונה: 19 ספטמבר, 2022

יש לכם שאלות? נשמח לדבר איתכם ולענות על הכל

מדריך פייתון לכתיבת GUI למחשבון – הקדמה

פייתון היא שפה מאוד פופולרית בתעשיית ההייטק בימים אלו, יש לה קהילה מאוד מפותחת של מפתחי פייתון והיא ממציאה את עצמה בכל פעם מחדש עם שימושים נוספים ע"י חברות ההייטק וסטארטאפים.

הסיבה לפופולריות של השפה טמונה במספר סיבות ויתרונות, כגון:

  • פייתון היא שפת תכנות חינמית להורדה ושימוש ויש גישה ישירה לקוד שלה.
  • פייתון ניתנת לשימוש על מגוון של מערכות הפעלה.
  • לימוד פייתון נחשב יחסית קל ביחס לשאר שפות התכנות.
  • ספריות רבות אשר מכילות קוד מוכן שמקצר את תהליך הפיתוח.

הצוות המנצח של Real Time College הכין במיוחד עבורכם מדריך מפורט לפיתוח GUI – ממשק משתמש בשפת פייתון. לטובת פיתוח זה השתמשנו ב API של ספריית tkinter.

מומלץ לכתוב את המדריך בעצמכם על מנת לתרגל את כתיבת הקוד ואת העבודה עם API, אתם כמובן מוזמנים להוסיף פונקציות נוספות, וכמובן כמו תמיד ליצור איתנו קשר במידה ויש שאלות או נושאים לא ברורים. לאחר התרגול תוכלו להמשיך ולקרוא על השפה ב- אתר שלנו, ולהמשיך להתמקצע וללמוד בקורס פייתון, אשר יפתח עבורכם הרבה דלתות לקריירה, בהצלחה!!

בואו נתחיל ונראה כיצד נוכל ליצור מחשבון פשוט, באמצעות ה- API של TkInter ב- Python.

python

בואו נלמד Python!!

ראשית נתכונן לפרוייקט ונכין את תכנית הפיתוח, נתאר כיצד נרצה שהמחשבון יראה – כדי שנוכל להחליט באילו בקרים להשתמש ואיך למקם אותם בתוך החלון. נסתכל בדיאגרמה הבאה:

תמונת אווירה

בדיאגרמה מופיעה תיבת טקסט שבה יופיע מה שהמשתמש יֶקליד וכן התוצאה שתוצג לאחר לחיצה על "=". בנוסף ישנם 15 כפתורים שמשנים את הקלט, כפתור "C" שמוחק את הקלט, ו- כפתור "=" שמחשב את התוצאה. אנחנו יכולים לראות שיש למחשבון מבנה טבלאי, כאשר חלק מהבקרים תופסים יותר מ- "תא" אחד. לכן לצורך סידור הבקרים נוכל להיעזר ב- גיאומטריית Grid.

נתחיל בכתיבת הקוד:

נוכל להתחיל לגשת בכתיבת הקוד. נתחיל עם קוד קצר יחסית כדי לקבל את ההרגשה של מה שאנחנו הולכים ליצור. ניצור חלון עם תיבת טקסט וכפתור בודד שמדפיס "1" בתוך תיבת הטקסט:


import tkinter as tk

root = tk.Tk()


txt_display = tk.Entry(root) txt_display.pack()
btn_1 = tk.Button( root, text="1", command=lambda: txt_display.insert(tk.END, "1")) btn_1.pack()

root.mainloop()

התוצאה שנקבל תראה כך:

תמונת אווירה

אמנם זה לא נראה מרשים, אך זו התחלה לקראת התוצר הסופי. לחיצה על הכפתור תשרשר את התו "1" לתוך תיבת הטקסט.

נסביר את הקוד:

נסביר את החלקים השונים של הקוד:



import tkinter as tk

שורה זו טוענת את המודול TkInter בזיכרון, המודול מכיל את הקוד, הפונקציות והמידע הדרוש ליצירת חלונות. כיוון שאנחנו הולכים להשתמש במודול זה בהרבה מקומות בקוד, אנחנו מקצרים את שמו ל-tk.



root = tk.Tk()

שורה זו מייצרת אובייקט חלון ראשי.



txt_display = tk.Entry(root)

txt_display.pack()

שורות אלו מייצרות תיבת טקסט (Entry), בעת היצירה של בקר של TkInter, ניתן לציין כפרמטרים את המאפיינים של הבקר. הפרמטר הראשון הינו המאסטר (master) של הבקר שמחליט למי שייך הבקר, במקרה שלנו אנו אומרים שהבקר שייך לחלון הראשי שיצרנו.

לאחר יצירת הבקר אנו מחליטים כיצד הוא יופיע בחלון. בתור התחלה אנחנו מציגים אותו באמצעות pack שממקמת את הבקרים לפי סדר ברשימה, מלמעלה למטה. בהמשך נשנה זאת באמצעות grid (סידור טבלאי) כפי שתכננו במקור.



btn_1 = tk.Button(

    root,

    text="1",

    command=lambda: txt_display.insert(tk.END, "1"))



btn_1.pack()

כאן אנו מייצרים כפתור - תהליך היצירה זהה לזה של תיבת הטקסט שיצרנו לעיל, אך לכפתור פרמטרים נוספים. פרמטר text מגדיר את הטקסט שיופיע על גבי החלון, פרמטר command מגדיר איזו פונקציה תתבצע בעת לחיצה על הכפתור. בד"כ אנחנו אמורים ליצור את הפונקציה לפני-כן, ואז לכתוב את שמה כפרמטר ל-command, אבל בגלל שהפונקציה של ביטוי בודד, נוכל לכתוב אותה באמצעות ביטוי lambda שמייצר לנו פונקציה אנונימית.

מה פונקציית  Lambda מבצעת, זה לגשת לתיבת הטקסט שיצרנו ולבצע לתוכו הכנסת טקסט, דבר זה נעשה באמצעות מתודת insert שמקבלת שני פרמטרים. הראשון הוא המיקום בו נרצה שהטקסט יתווסף, והשני הוא הטקסט שיש להוסיף. המיקום יכול להיות מספר מ- 0 ומעלה שמציין את המקום, או לחילופין הוא יכול להיות הערך הקבוע END, ערך זה מציין את המקום שאחרי התו האחרון בתיבת הטקסט ולכן זה הערך שנשתמש בו. גם את הכפתור נמקם בינתיים על-פי גיאומטריית pack.

על בסיס הקוד שלעיל, נוכל ליצור את כל הכפתורים האחרים עם שינוי ה- text למה שהכפתור מציג, ושינוי ה- command כך שיוסיף את הטקסט הזה. יש לשים לב, שלצורך דוגמה זו, הכפתור "X" יזין את הטקסט "*" היות וזה מה שפייתון מזהה בתור כפל. בנוסף, הכפתורים של מחיקה "C" ושל חישוב התוצאה "=" יכתבו באופן שונה כיוון שאלו יצטרכו פונקציות אחרות ב-command שלהם.



root.mainloop()

פקודה זו מריצה את הקוד שמנהל את התצוגה וההפעלה של החלון עד לסגירתו ריצה את החלון. בלי פקודה זו החלון לא יתפקד, פקודה זו צריכה להופיע בסוף הקוד לאחר יצירת הבקרים והגדרתם.

קוד עבור כפתור המחיקה "C"

עבור כפתור המחיקה "C" נוסיף את הקוד הבא (לפני קריאה ל- mainloop ):


btn_clear = tk.Button(

    root,

    text="C",

    command=lambda: txt_display.delete(0, tk.END))



btn_clear.pack()

המתודה delete מקבלת- ב- פרמטר ראשון את המיקום שממנו יש למחוק (כולל), אנו בוחרים ב- 0 שמייצג את התו הראשון. ו- הפרמטר השני הוא המיקום שעד אליו יש למחוק (אבל לא כולל), אנחנו בוחרים ב- END שמייצג את המקום אחרי התו האחרון (ולכן ימחוק את התו האחרון).

קןד כפתור התוצאה:

עבור כפתור התוצאה נוסיף את הקוד הבא:


def display_result():

    string = txt_display.get()

    try:

        result = eval(string)

    except:

        result = "ERROR"



    txt_display.delete(0, tk.END)

    txt_display.insert(0, str(result))



btn_equal = tk.Button(

    root,

    text="=",

    command=display_result)



btn_equal.pack()

היות והקוד של ההשוואה יותר מורכב אנו יוצרים פונקציה עבורו בשם "dsiplay_result" באמצעות def במקום להשתמש ב- lambda. הפונקציה שולפת את המחרוזת שבתוך תיבת הטקסט באמצעות מתודת get, ולאחר מכן היא מנסה לבצע פונקציית eval את המחרוזת הזו. פונקציית eval היא פונקציה מובנית בפייתון שמאפשרת לתרגם מחרוזת לביטוי בפייתון. כך למשל אם המחרוזת מכילה "2+3" תוחזר התוצאה 5.

הקוד של eval מופיע תחת הכותרת try שמאפשרת לקוד להחליט, מה לבצע במידה ונזרקת שגיאה (למשל אם המשתמש מזין משהו לא חוקי כגון "12…334++"). אם נזרקת שגיאה, הקוד שבכותרת except יתבצע ויקבע את התוצאה ל- error.

לאחר חישוב התוצאה נעדכן את תיבת הטקסט כך שתכיל אותה, לשם כך נצטרך לבצע שתי פעולות, פעולת delete כדי למחוק את כל הטקסט הישן, ופעולת insert כדי להכניס את result (כיוון ש- result עלול להיות מספר ולא מחרוזת אנחנו ממירים אותו למחרוזת באמצעות פונקציית str). לאחר מכן הגדרת הכפתור היא כפי שיצרנו את הכפתורים האחרים רק שה- command מקושר לפונקציה שיצרנו.

התוצר הסופי נראה כך- לחיצה על הכפתורים מראה שהקוד עובד. כדי לבדוק את פקודת ההשוואה נוכל להזין טקסט של פעולה חשבונית ואז ללחוץ על הכפתור כדי לקבל את התוצאה.

 

קוד עבור שאר הכפתורים:

כעת נוכל להוסיף את שאר הכפתורים ובהזדמנות זו לעבור לגיאומטריה בשיטה grid, נסתכל שוב במודל שיצרנו ונחלק אותו לעמודות ושורות:

תמונת אווירה

אנחנו יכולים לזהות איך כל בקר צריך להיות ממוקם בטבלה:

תיבת הטקסט צריכה להתחיל בשורה 0 עמודה 0 ולתפוס 5 עמודות (מ-0 עד 4).
הכפתור "1" צריך להיות ממוקם בשורה 1 עמודה 0,
הכפתור "2" צריך להיות ממוקם בשורה 1 עמודה 1, וכולי...
לגבי שאר הכפתורים שתופסים תא בודד.

הכפתור "0" ימוקם בשורה 4 עמודה 0 ויתפוס 2 שורות.
הכפתור "=" ימוקם בשורה 2 עמודה 4 ויתפוס 3 עמודות.

כדי למקם את הכפתורים, נחליף בקוד את הקריאה למתודה pack בקריאה למתודה grid. מתודה יכולה לקבל את הפרמטרים הבאים:

column – עמודת התחלה
row – שורת התחלה
columnspan – כמה עמודות לתפוס
rowspan – כמה שורות לתפוס
sticky – קובע כיצד על הבקר שבתוך הטבלה "להיצמד" לגבולות שלה. אנחנו נשתמש בערך "NESW" שמשמעותו צפון-דרום-מזרח-מערב, כדי לומר לבקר לתפוס את המקום המרבי שהוא יכול בטבלה.

הקוד שנקבל יראה כך:


import tkinter as tk



def display_result():

    string = txt_display.get()

    try:

        result = eval(string)

    except:

        result = "ERROR"



    txt_display.delete(0, tk.END)

    txt_display.insert(0, str(result))



root = tk.Tk()



txt_display = tk.Entry(root)

txt_display.grid(row=0, column=0, columnspan=5, sticky='NESW')





btn_1 = tk.Button(root, text="1",

    command=lambda: txt_display.insert(tk.END, "1"))

btn_1.grid(row=1, column=0, sticky='NESW')



btn_2 = tk.Button(root, text="2",

    command=lambda: txt_display.insert(tk.END, "2"))

btn_2.grid(row=1, column=1, sticky='NESW')



btn_3 = tk.Button(root, text="3",

    command=lambda: txt_display.insert(tk.END, "3"))

btn_3.grid(row=1, column=2, sticky='NESW')



btn_4 = tk.Button(root, text="4",

    command=lambda: txt_display.insert(tk.END, "4"))

btn_4.grid(row=2, column=0, sticky='NESW')



btn_5 = tk.Button(root, text="5",

    command=lambda: txt_display.insert(tk.END, "5"))

btn_5.grid(row=2, column=1, sticky='NESW')



btn_6 = tk.Button(root, text="6",

    command=lambda: txt_display.insert(tk.END, "6"))

btn_6.grid(row=2, column=2, sticky='NESW')



btn_7 = tk.Button(root, text="7",

    command=lambda: txt_display.insert(tk.END, "7"))

btn_7.grid(row=3, column=0, sticky='NESW')



btn_8 = tk.Button(root, text="8",

    command=lambda: txt_display.insert(tk.END, "8"))

btn_8.grid(row=3, column=1, sticky='NESW')



btn_9 = tk.Button(root, text="9",

    command=lambda: txt_display.insert(tk.END, "9"))

btn_9.grid(row=3, column=2, sticky='NESW')





btn_0 = tk.Button(root, text="0",

    command=lambda: txt_display.insert(tk.END, "0"))

btn_0.grid(row=4, column=0, columnspan=2, sticky='NESW')



btn_dot = tk.Button(root, text=".",

    command=lambda: txt_display.insert(tk.END, "."))



btn_dot.grid(row=4, column=2, sticky='NESW')



btn_plus = tk.Button(root, text="+",

    command=lambda: txt_display.insert(tk.END, "+"))



btn_plus.grid(row=1, column=3, sticky='NESW')



btn_minus = tk.Button(root, text="-",

    command=lambda: txt_display.insert(tk.END, "-"))

btn_minus.grid(row=2, column=3, sticky='NESW')



btn_mul = tk.Button(root, text="*",

    command=lambda: txt_display.insert(tk.END, "*"))

btn_mul.grid(row=3, column=3, sticky='NESW')



btn_div = tk.Button(root, text="/",

    command=lambda: txt_display.insert(tk.END, "/"))

btn_div.grid(row=4, column=3, sticky='NESW')



btn_back = tk.Button(root, text="C", command=lambda: txt_display.delete(0, tk.END))

btn_back.grid(row=1,column=4, sticky='NESW')



btn_equal = tk.Button(root, text="=", command=display_result)

btn_equal.grid(row=2,column=4, rowspan=3, sticky='NESW')



root.mainloop()

והתוצר שנקבל, אם נריץ את הקוד יראה כך:

נוכל לחסוך בקוד ולשפר את התחזוקה שלו, ע"י שימוש בלולאה שתיצור את הכפתורים מ-1 עד 9 ושתמקם אותם בטבלה. ע"י חלוקת שלמים (//) וחלוקת מודולו (% שארית) נוכל גם לחשב את המיקום של כל כפתור לפי ערכו.

כדי לחשב את מספר העמודה, ניקח את ערך הכפתור, נחסר 1, נחלק ב- 3 ונשמור את השארית
כדי לחשב את מספר השורה, נחלק את ערך הכפתור, נחסר 1, נחלק ב- 3 (חלוקת שלמים) ולתוצאה נוסיף 1 (ההוספה של 1 היא בגלל הכפתורים מתחילים שורה אחת מתחת לשורה הראשונה).

ז"א שאת הקוד שאחראי על יצירת הכפתורים מ- 1 עד 9, נוכל להחליף בקוד הלולאה הבא:


for num in range(1,10):

    btn = tk.Button(

        root,

        text=str(num),

        command = lambda x=num: txt_display.insert(tk.END, str(x)))



    btn.grid(column=(num-1)%3, row=(num-1)//3+1, sticky = 'NESW')

הלולאה תעבור על כל ערך מ-1 עד 10 לא כולל (ז"א עד 9) ותשמור ב-num, ועבוד ערך זה ניצור את כפתור במקום המתאים. המשמעות של x=num בקוד היא, ליצור משתנה לוקלי x ש"ילכוד" את ערכו של num בכל סיבוב. בלעדיו, כשהפונקציה תרוץ היא תמיד תקרא את ערכו האחרון של num (שהוא 0).

בזאת למעשה סיימנו את היצירה של המחשבון שלנו. בהמשך נציג שינויים נוספים לשיפור המראה של החלון.

 

קוד עבור שיפור המראה של המחשבון:

נוסיף אובייקט פונט, שייצג את הפונט שנשתמש בו עבור הכפתורים:


import tkinter.font



# ...



font_buttons=tkinter.font.Font(root, family='Arial', size=20,

                               weight='bold')

נגדיר את הכותרת, שתופיע בראש החלון ע"י root.title:


root.title("Calculator")

ונעדכן כל חלק בקוד שמייצר כפתור, כך שיכלול פרמטר נוסף font ונקבע אותו לאובייקט שיצרנו, כך למשל עבור כפתור "+":


btn_plus = tk.Button(root, text="+", font = font_buttons

    command=lambda: txt_display.insert(tk.END, "+"))

עבור תיבת הטקסט נגדיר פונט משלו, לא חייבים ליצור אובייקט פונט לשם כך, נוכל להזין מחרוזת עם פרטי הפונט. בנוסף נרצה להגדיר שהטקסט ייושר מימין לשמאל במקום משמאל לימין. אז הקוד לתיבת הטקסט שלנו יעודכן כלהלן:


txt_display = tk.Entry(root, font="Courier 40 bold", justify='right')

txt_display.grid(row=0, column=0, columnspan=5, sticky='NESW')

בנוסף, ע"י הפונקצייה root.rowconfigure והמתודה root.columnconfigure, נוכל לתת "משקל" לשורות ולעמודות של הטבלה. המשקל הוא ערך שקובע כמה חלק מהחלון יש לתת לשורה או עמודה ביחס לשאר השורות והעמודות. כך למשל משקל 2 יתפוס פי שניים יותר מקום ממשקל 1.

כברירת מחדל המשקל הוא 0, כך שהתאים יקבלו גודל מינימלי ולא ישנו את גודלם, אם נשנה את גודל החלון. אם נשנה את משקלם ל-1 הם ישנו את גודלם. נוכל לעשות זאת בלולאה של 5 סיבובים מ-0 עד 4 בה נגדיר את המשקל לשורות ולעמודות:


for i in range(5):

    root.rowconfigure(i, weight=1)

    root.columnconfigure(i, weight=1)

הקוד הסופי של המחשבון:

הקוד הסופי:


import tkinter as tk

import tkinter.font



def display_result():

    string = txt_display.get()

    try:

        result = eval(string)

    except:

        result = "ERROR"



    txt_display.delete(0, tk.END)

    txt_display.insert(0, str(result))



root = tk.Tk()

root.title("Basic Calculator")



font_buttons = tkinter.font.Font(root, family='Arial', size=20, weight='bold')



for i in range(5):

    root.rowconfigure(i, weight=1)

    root.columnconfigure(i, weight=1)



txt_display = tk.Entry(root, font="Courier 40 bold", justify='right')

txt_display.grid(row=0, column=0, columnspan=5, sticky='NESW')

for num in range(1,10):

    btn = tk.Button(

        root,



        text=str(num),

        font=font_buttons,

        command=lambda x=num: txt_display.insert(tk.END, str(x)))



    btn.grid(column=(num-1)%3, row=(num-1)//3+1, sticky = 'NESW')



btn_0 = tk.Button(root, text="0", font=font_buttons,

    command=lambda: txt_display.insert(tk.END, "0"))

btn_0.grid(row=4, column=0, columnspan=2, sticky='NESW')



btn_dot = tk.Button(root, text=".", font=font_buttons,

    command=lambda: txt_display.insert(tk.END, "."))

btn_dot.grid(row=4, column=2, sticky='NESW')



btn_plus = tk.Button(root, text="+", font=font_buttons,

    command=lambda: txt_display.insert(tk.END, "+"))

btn_plus.grid(row=1, column=3, sticky='NESW')



btn_minus = tk.Button(root, text="-", font=font_buttons,

    command=lambda: txt_display.insert(tk.END, "-"))

btn_minus.grid(row=2, column=3, sticky='NESW')



btn_mul = tk.Button(root, text="*", font=font_buttons,

    command=lambda: txt_display.insert(tk.END, "*"))

btn_mul.grid(row=3, column=3, sticky='NESW')



btn_div = tk.Button(root, text="/", font=font_buttons,

    command=lambda: txt_display.insert(tk.END, "/"))

btn_div.grid(row=4, column=3, sticky='NESW')



btn_back = tk.Button(root, text="C", font=font_buttons,

    command=lambda: txt_display.delete(0, tk.END))

btn_back.grid(row=1,column=4, sticky='NESW')



btn_equal = tk.Button(root, text="=", font=font_buttons,

    command=display_result)

btn_equal.grid(row=2,column=4, rowspan=3, sticky='NESW')





root.mainloop()

כל הכבוד! סיימתם את המדריך לכתיבת מחשבון TkInter בפייתון!


תחומי לימוד הכי מבוקשים בהייטק בשנת 2024

יש לכם שאלות? נשמח לדבר איתכם ולענות על הכל
© כל הזכויות שמורות Real Time Group