• בלוג
  • פיתוח ממשק משתמש לסקריפט פייתון

פיתוח ממשק משתמש לסקריפט פייתון

16/07/2015

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

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

1. לפני הקוד: הדברים שאתם חייבים לדעת על ממשק משתמש גרפי

קוד הבונה ממשק משתמש גרפי ליישום (GUI) כולל 3 חלקים מרכזיים:

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

בדיון על ממשק משתמש גרפי המילה פקד משמשת לתיאור רכיב ממשק משתמש המופיע על המסך, למשל תיבת טקסט, רשימת פריטים, כפתור, טבלא וכן הלאה. בפוסט זה אציג את ספריית Tkinter של פייתון, המהווה בסך הכל עטיפה דקה לספריית Tk של Tcl. בספריה זו המילה האנגלית לתיאור פקד הינה Widget. כך למשל כדי לראות כיצד נראים הפקדים של Tk תוכלו לחפש בגוגל תמונות את צמד המילים tk widgets.

מנקודת מבט של המשתמש, תוכנית GUI ממשיכה לרוץ עד שסוגרים אותה ומגיבה רק כשמבצעים פעולות. כדי לתרגם התנהגות זו לקוד עלינו לבנות מעין לולאה שנקראת הלולאה הראשית של התוכנית (או ה Main Loop) הממתינה לאירועים. בכל פעם שמשתמש ילחץ על כפתור או אירוע אחר יקרה, הלולאה תחפש את הפונקציה המתאימה להפעיל ותקרא לה. כך במקום שהתוכנית תרוץ שורה-אחר-שורה כמו בסקריפט רגיל, הקוד שלנו ייקרא בחלקים וכל חלק בתגובה לאירוע אחר.
כך בערך נראה פסאודו קוד עבור לולאה מרכזית שכזו:

while True:
     event = wait_for_something_interesting()
     for w in all_widgets:
          if w.wants_to_know_about(event):
               w.notify(event)

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

2. ממשק המשתמש הגרפי הראשון שלי

הקוד הבא בפייתון יציג על המסך חלון עם הטקסט Hello World:

from Tkinter import *

# create a new Label widget
l = Label(text="Hello World")

# Place the label widget on screen
l.pack()

# Start the main loop
l.mainloop()

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

3. שילוב מספר פקדים בממשק המשתמש

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

from Tkinter import *

f = Frame()

lbl_name = Label(f, text="Name: ", anchor="w")
lbl_name.pack(fill="x")

inp_name = Entry(f)
inp_name.pack()

lbl_email = Label(f, text="Email: ", anchor="w")
lbl_email.pack(fill="x")

inp_email = Entry(f)
inp_email.pack()

btn = Button(f, text="Go")
btn.pack(fill="x")

f.pack()

f.mainloop()

אנו עדיין משתמשים באותה התבנית רק שעכשיו יש לנו פקד אחד ראשי מסוג Frame ובתוכו הפקדים לפי הסדר. הפרמטר fill לפקודה pack גורם לתיבה לתפוס את כל רוחב הפריים, ואילו הפרמטר anchor ביצירת פקד הטקסט גורם לטקסט להופיע בצד שמאל. למידע נוסף על הפקדים והאפשרויות שכל פקד יכול לקבל מוזמנים להכנס לתיעוד בכתובת:
http://effbot.org/tkinterbook/

4. הוספת פעולות

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

from Tkinter import *

def click():
    val = int(lbl_count["text"])
    lbl_count["text"] = val + 1

f = Frame()

lbl_count = Label(f, text="0")
lbl_count.pack(side="left")

btn = Button(f, text="Go", command=click)
btn.pack(side="right")

f.pack(padx=10, pady=10)

f.mainloop()

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

5. חיבור פקדי Tk למשתנים בפייתון

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

from Tkinter import *
import tkFileDialog

root = Tk()

def click():
    filename = tkFileDialog.askopenfilename()
    count = 0
    with open(filename, "r") as f:
        for line in f: 
            count += 1
    tv.set("File has %d lines" % count)



tv = StringVar()
tv.set("Please Select A File...")

f = Frame()
lbl = Label(f, textvariable=tv)
lbl.pack()

btn = Button(f, text="Open", command=click)
btn.pack(fill="x")

f.pack(padx=10, pady=10)

f.mainloop()

כדי להגדיר משתנה מתווך עלינו לקרוא לפונקציה Tk() בתחילת התוכנית. הפקודה StringVar() מגדירה ומחזירה משתנה מתווך מסוג String (ויש סוגים נוספים למשל עבור מספר וערך בוליאני). לאחר הגדרת המשתנה המתווך אנו משנים את ערכו באמצעות פונקציית set. אם נרצה לקרוא את הערך נצטרך להשתמש בפונקציה get.
פקדי Tk רבים משתמשים במשתנים מתווכים. תיבת טקסט משתנה מסוג Entry יכולה לקבל משתנה מתווך שיכיל את הטקסט בתיבה, פקד CheckBox משתמש במשתנה מתווך כדי לדעת אם התיבה בחורה או לא. 

6. סיכום וקריאת המשך

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

בנוסף אני ממליץ לקרוא את הפרק על מיקום פקדים על המסך ב Tkinter בקישור:
http://effbot.org/zone/tkinter-geometry.htm
בפוסט הצגתי רק את שיטת המיקום pack שמסדרת את הפקדים אחד מתחת לשני. כמעט לכל ממשק מעניין שתרצו לבנות תצטרכו משהו מתוחכם יותר מזה. שיטה פופולרית למיקום פקדים ב Tk היא באמצעות grid ובקישור תוכלו למצוא יכולות נוספות של pack וגם כיצד למקם את הפקדים על גבי רשת באמצעות הפונקציה grid.