תכנות מונחה-אירועים שיעור מס' 12: תכנות אסינכרוני תכנות סדרתי סדר ביצוע הפקודות נקבע ע"י קוד התוכנית. קלט מן הסביבה נעשה באופן יזום ע"י התוכנית. במקרים בהם מבקשים קלט ממשתמש, התוכנית עוצרת את הביצוע עד לקבלת הקלט. )למשל פקודת.)inputdlg המעבד פעיל כל זמן שהתוכנית מתבצעת )גם כאשר התוכנית עוצרת לקבלת קלט מן המשתמש או כל עצירה אחרת, למשל פקודת.)pause התלות של מהלך התוכנית במשתנים סביבתיים )למשל סוגים שונים של קלט( נקבעת מראש ע"י התוכנית, בד"כ באמצעות פקודות התנייה. תכנות מונחה-אירועים סדר ביצוע הפקודות נקבע ע"י אירועים בסביבה )למשל פעולות של המשתמש(. הסביבה יוזמת אירועים שמועברים למחשב באמצעות פסיקה.)interrupt( לכל סוג פסיקה משויכת תוכנית שמופעלת כאשר מגיעה פסיקה מסוג זה. המעבד פעיל רק כאשר מתבצעת תוכנית-פסיקה. משתנים סביבתיים משפיעים על מהלך הטיפול באירועים. ממשק-משתמש הוא מקרה פרטי של תכנות מונחה-אירועים. האירועים הם הקשה על אחד מכפתורי העכבר או המקלדת, כאשר הסמן נמצא על עצם גרפי מסוים. בדרך כלל תוכנית לממשק משתמש בנויה באופן הבא: התוכנית הראשית כוללת פקודות היוצרות את העצמים המרכיבים את הממשק, ובהתאם לצורך גם מאתחלת פרמטרים או ערכי ברירת מחדל. התוכנית הראשית מסתיימת, ובמטלב הבקרה חוזרת לחלון-הפקודות. לכל עצם גרפי ניתן לשייך פונקציה שתתבצע כאשר יקרה אירוע הקשור לעצם. פונקציה זו נקראת CallBack )קריאה- לאחור(. שיוך הפונקציות האלה לעצמים הגרפים בממשק נעשה בשלב האתחול )התוכנית הראשית( ע"י ציון תכונות העצם הרלוונטיות. לאחר שהתוכנית הראשית סיימה, אפשר להשתמש בממשק כל עוד החלון הגרפי פתוח. הקשה על העצמים בחלון שהוגדרו כפעילים תפעיל את פונקציות ה- CallBack שהוגדרו וכך יתבצע האלגוריתם המבוקש. המעבד פעיל רק כאשר מתבצעות פונקציות.CallBack דוגמא 1: הפונקציה d12save_plot מופעלת כאשר יש חלון גרפי פתוח עם שרטוט קיים. המטרה היא לצפות בשרטוט במספר צורות של הגדלה ו/או הזזה ולשמור חלק מן התצוגות, כל אחת מהן בקובץ נפרד בשמות סדרתיים. הפונקציה הראשית משייכת לצירים פונקציית- תגובה להקשה על הצירים, וכן מאתחלת מונה עבור שמות הקבצים ומסתיימת. המשתמש משנה את התצוגה ומקיש עם העכבר על שטח הצירים בכל פעם שרוצה לשמור את התצוגה. פונקציית התגובה שומרת את הפלוט לקובץ ומקדמת את המונה. הסקריפט d12plot מבקש מן המשתמש לבחור קובץ הקלטה ומבצע את הדברים הבאים: קורא את הקובץ, מציג אותו בגרף ומפעיל את.d12save_plot עמ' 1 מתוך 8
ButtonDownFcn KeyPressFcn KeyReleaseFcn CreateFcn DeleteFcn ResizeFcn CloseRequestFcn WindowButtonDownFcn WindowButtonUpFcn WindowButtonMotionFcn WindowScrollWheelFcn WindowKeyPressFcn WindowKeyReleaseFcn CurrentCharacter SelectionType CurrentPoint סוגי אירועים התכונות הבאות משייכות פונקציה לעצם גרפי, כאשר קורים האירועים הבאים: לחיצה על אחד מכפתורי העכבר כאשר הסמן נמצא מעל העצם לחיצה על מקש/ים במקלדת כאשר הסמן נמצא מעל העצם שחרור מקש/ים במקלדת כאשר הסמן נמצא מעל העצם יצירת העצם. )שימושי להגדרת תכונות ברירת-מחדל( מחיקת העצם. )שימושי לשמירת מידע לפני שהעצם נעלם(. שינוי ממדי העצם. )שימושי כאשר שינוי ממדים מחייב שינויי עיצוב(. התכונות הבאות רלוונטיות רק לחלון גרפי :)figure( ניסיון לסגור את החלון )למשל ע"י הקשה על צלמית הסגירה(. לחיצה על אחד מכפתורי העכבר כאשר הסמן נמצא מעל החלון שחרור אחד מכפתורי העכבר כאשר הסמן נמצא מעל החלון תנועה של הסמן כשהוא נמצא מעל החלון שימוש בגלגל הגלילה בעכבר כאשר הסמן נמצא מעל החלון לחיצה על מקש/ים במקלדת כאשר הסמן נמצא מעל החלון שחרור מקש/ים במקלדת כאשר הסמן נמצא מעל החלון אפשר לקבל מידע נוסף על האירוע באמצעות התכונות הבאות: שם המקש במקלדת שהוקש לאחרונה כאשר הסמן היה מעל העצם שם כפתור העכבר שהוקש כאשר הסמן היה מעל העצם. מקרים נפוצים הם normal עבור כפתור שמאלי, alt עבור כפתור ימני. קואורדינטות הנקודה שבה הוקש על כפתור העכבר כאשר הסמן היה מעל העצם. המספרים ביחידות של העצם )ברירת המחדל היא pixel עבור חלון ו- data coordinates עבור מערכת צירים(. קוד עבור callback הקוד עבור תכונת callback )שיתבצע כשיקרה אירוע( יכול להיות אחד משני הדברים הבאים: code' 'MATLAB מחרוזת תווים שמטלב מבצע כאילו הוקלדה בחלון הפקודות. אם המחרוזת א. כוללת שמות של פונקציה או סקריפט, מטלב מבצע )במידה וזיהה את התוכנית המבוקשת(. אם יש שמות שזוהו כמשתנים במרחב-העבודה הכללי של מטלב, יילקחו הערכים השמורים במשתנים אלה. function-handle מזהה לפונקציה, שמטלב מפעיל עם שני הארגומנטים הבאים: ב. hobject מזהה handle של העצם שבו קרה האירוע. 1. EventStructure מידע על האירוע.)structure( 2. למשל, אירועים הקשורים במקלדת כוללים את השדות הבאים: Character התו כפי שמוצג כתוצאה של כלל המקשים. )למשל.)A=a+SHIFT Key שם המקש )אות קטנה,lowre case או שם עבור מקשים אחרים(. Modifier מערך-תאים עם שמות המקשים הנוספים shift(.)control, alt, function cb(h,ev) לסיכום, שורת הכותרת בפונקציית callback תיראה כך: עמ' 2 מתוך 8
בדרך כלל המזהה h שימושי, ואילו המידע על האירוע )בארגומנט )ev פחות משמעותי. בכל מקרה יש לשמור על התבנית של שני ארגומנטים. ניתן להעביר לפונקציה ארגומנטים נוספים, באופן הבא: מגדירים עבור תכונת callback מערך-תאים, אשר התא הראשון בו הוא function-handle והתאים הבאים מכילים את ערכי הארגומנטים הנוספים לפונקציה. בכל פעם שמטלב יפעיל את הפונקציה, יועברו אליה שני הארגומנטים הסטנדרטיים ובהמשך הארגומנטים ממערך-התאים. מומלץ להשתמש תמיד בפונקציות )אפשרות ב' למעלה( ולא במנגנון של סקריפט עם משתנים גלובליים. דוגמא 2: התוכנית d12draw_curves מבצעת ממשק המאפשר למשתמש לציין קואודינטות ולשרטט קווים שבורים בעזרת העכבר. בכל קטע מוצג תחילה קו מרוסק העוקב אחרי תנועת העכבר, ואשר הופך ל"קבוע" לאחר לחיצה על כפתור שמאלי. לחיצה על כפתור ימני מסיימת את הקו השבור כך שלחיצה נוספת על כפתור שמאלי מתחילה קו חדש. הקובץ כולל פונקציה ראשית ושתי פונקציות :callback אחת לתגובה על הקשה על כפתור עכבר ואחת לתגובה על תנועת עכבר. טיפול בתזמון אירועים מאחר שסדר ביצוע הפונקציות נקבע ע"י פעולות של המשתמש, יכול להיווצר מצב שאירוע קורה כאשר אחד מן ה- callbacks עדיין מתבצע. הסבירות למצב כזה גדלה, כאשר הביצוע כרוך בחישובים ארוכים או בכל פעולה מורכבת אחרת )למשל קליטת נתונים ממערכת חיצונית(. במיוחד יכול להיווצר מצב לא מוגדר, שבו פעולה מתחילה להתבצע ב- callback ובינתים קורה אירוע נוסף המפעיל אותה מחדש. ככלל, כל עוד פונקציית callback מתבצעת באופן שוטף, לא מתאפשרת הפעלה של callback נוסף )גם לא של אותה פונקציה( ואירועים נוספים שקורים בינתים נשמרים ב"תור".)queue( לאחר שהפונקצייה מסיימת, מתבצעות הקריאות ל- callbacks שהושעו, לפי הסדר שבו נכנסו לתור. ניתן להפעיל פונקציה נוספת רק כאשר הפונקציה המתבצעת מגיעה לאחת הפקודות הבאות: drawnow figure getframe pause waitfor במקרה זה, מתבצעות הפונקציות שבתור ולאחר שהן מסתיימות חוזרים לבצע את הפונקציה שהופסקה. תכונות רלוונטיות לטיפול בתזמון של אירועים המשויכים לעצם גרפי Interruptible BusyAction BeingDeleted אם on אז פונקציות אחרות יכולות להפסיק את ביצוע ה- callback המשויך לעצם )אבל רק אם יש בתוכנית אחת הפקודות שצוינו למעלה(. אם off אז לא ניתן להפסיק את התוכנית. מה לעשות אם קורה אירוע המשויך לעצם ויש פונקציה אחרת שמתבצעת. אם queue אז האירוע נכנס לתור. אם cancel אז האירוע מבוטל. האם העצם נמצא בתהליך מחיקה )אשר יכול להתמשך אם משויך אליו callback שמבצע תהליך ארוך, למשל כתיבה לקובץ(. עמ' 3 מתוך 8
hndl=gcbo hndl=gcbf פקודות רלוונטיות לטיפול בתזמון של אירועים המשויכים לעצם גרפי קבלת המזהה handle של העצם שה- callback שלו מתבצע עכשיו. אם אין callback שמתבצע, התוצאה היא מערך ריק. קבלת המזהה handle של החלון הכולל את העצם שה- callback שלו מתבצע עכשיו. הצגת תיבת הודעות יש במטלב סדרה של פקודות שמייצרות חלון במטרה להציג מידע. פקודות אלה מחזירות מזהה )handle( לחלון שנוצר, ולא עוצרות את מהלך התוכנית. דוגמא לאחת הפקודות: נפתח חלון גרפי עם כפתור OK וכן טקסט כמצויין ב- message. לחיצה על הכפתור תסגור את החלון. הצגת כותרת לחלון: הצגת צלמית: כאשר icon יכול להיות אחד מאלה: דוגמא: לעצירת התוכנית עד שהעצם עם מזהה h נמחק עצירת התוכנית עד שהעצם h משנה את תכונה property לעצירת התוכנית עד שהמשתמש לוחץ על כפתור OK בתיבת הודעות: hndl = msgbox(message) hndl = msgbox(message,title) hndl = msgbox(message,title,icon) 'none' 'error' 'help' 'warn' 'custom' h = msgbox('no file was found','message','error') waitfor(h) waitfor(h,property) h = msgbox(message) waitfor(h) דוגמא 3: הפונקציה d12draw_message פועלת כמו d12draw_curves בשינויים הבאים: המשתמש מציין מראש את מספר העקומות. המעבר מעקומה לעקומה הוא סדרתי בתוך לולאת.for כדי לציין סיום עקומה המשתמש לוחץ על כפתור OK בחלונית הודעה. חיפוש עצמים גרפיים מציאת העצם בפוקוס הפקודות הבאות מחזירות מזהה handle לעצם גרפי שהוא: hndl=findobj(propertyname,propertyvalue) hndl=findobj(p1,v1,p2,v2,p3,v3,p4,v4) hndl=findobj(parents,propname,propvalue) חיפוש לפי תכונות מציאת עצם/ים שלתכונה מסוימת יש ערך מסוים. ניתן לחפש לפי מספר תכונות. מתקבלים העצמים שמקיימים את כל התנאים. ניתן להגביל את החיפוש לעצמים מסוימים )כולל ה"ילדים" שלהם(. עמ' 4 מתוך 8
דוגמא: מציאת כל הטקסטים השייכים למערכת הצירים הנוכחית. htext=findobj(gca,'type','text') דוגמא 4: הפונקציה d12curve_num "מחלצת" את הנתונים הנומריים המוצגים בחלון גרפי מסוים. אם יש בחלון יותר ממערכת הצירים אחת, המשתמש מתבקש לבחור בהקשת עכבר מאיזו מערכת צירים יילקחו הנתונים. לאחר מכן מחפשים "בנים" למערכת צירים זו שהם מסוג עקומה )כדי לא לכלול גם טקסטים(. מכל אחד מן העצמים שנמצאו מקבלים את ערכי x,y ואוספים את הנתונים במשתנה אחד מסוג.Cell Array כדוגמא להפעלת הפונקציה, נקח את קובץ השרטוט שנוצר בדוגמא מס' 3. מחיקת עצמים מחיקת עצם גרפי אשר המזהה שלו.hndl xydata = d12curve_num('curves5.fig') xydata = {4 2 double} {8 2 double} {3 2 double} {7 2 double} {4 2 double} אם hndl מערך של מזהים, יימחקו העצמים שאלה המזהים שלהם. דוגמא: מחיקת כל הטקסטים במערכת הצירים הנוכחית. שיטות להעברת מידע בין הפונקציות delete(hndl) delete(findobj(gca,'type','text')) בדרך כלל פונקציית callback משתמשת במידע שאיננו קשור רק לעצם שהפעיל אותה. בנוסף, עצמים שונים קולטים מידע )למשל פרמטרים לחישוב, שמות קבצים לקריאה או לשמירה( שמיועד לשימוש כלל חלקי התוכנית. כאמור למעלה, רצוי להימנע ממשתנים גלובאליים, ולכן יש להשתמש באחת מן השיטות הבאות: 1. הגדרת כל הפונקציות המשתפות מידע כפונקציות פנימיות של התוכנית הראשית. באופן זה, משתנים משותפים נגישים לכל הפונקציות שמשתמשות בהם ועדכון באחת מהן משתקף אוטומטית בכל היתר.. 2. ציון ארגומנטים נוספים בשלב הגדרת פונקציית ה- callback )ע"י מערך-תאים(. 3. שיוך המידע המשותף לתכונת UserData של אחד העצמים. 4. שימוש בתכונת :guidata משתנה אחד שניתן לשייכו לחלון גרפי מסוים. 5. שימוש בעצם מסוג application data שניתן לשייכו לכל עצם גרפי )לא רק לחלון(. ניתן לציין סדרת תכונות לפי הגדרת המתכנת, ולתת להן ערכים. set(h,'userdata',mydata) Mydata = get(h,'userdata'); giudata(h,mydata) Mydata = guidata; setappdata(h,'p1',v1) setappdata(h,'p2',v2) v1 = getappdata('p1') strc = getappdata(h) 6. לקבלת מזהים handles של עצמים שנוצרו באתחול, ניתן לציין עבורם תכונת.Tag עקרונית ניתן לאחזר מזהה של כל עצם כזה ע"י חיפוש findobj לפי ערך התג. לנוחיות המתכנת, קיימת במטלב פקודה שמחזירה עבור חלון גרפי את כל המזהים שהוצמדו להם תגים, בתוך :structure שמות התגיות הם שמות השדות, וערכי השדות הם המזהים שלהם משויכים התגים. strc = guihandles דוגמא 5: הפוקנציה d12curve_select מאפשרת למשתמש לבחור מספר עקומות מתוך סדרת עקומות בחלון גרפי. המשתמש מציין מראש כמה עקומות ייבחרו. עבור כל עקומה, מוצגת תיבת הודעות שהמשתמש סוגר לאחר שבחר את העקומה. התוכנית מחכה כל עוד התיבה לא נסגרה. עקומה נבחרת ע"י הקשה בעכבר עליה, והיא מודגשת בקו עבה למשך כשנייה. ניתן להקיש על כמה עקומות לפני שמאשרים )האחרונה נקלטת(. כל עקומה שנבחרת נמחקת מן התצוגה )כדי למנוע בחירה חוזרת(, ומשורטטת בחלון נפרד. כאשר נבחרו עקומות כמספר שצוין, התוכנית מחזירה מערך-תאים עם כל הנתונים המספריים של העקומות שנבחרו. עמ' 5 מתוך 8
כשמפעילים את הפונקציה מופיעה חלונית קלט, בה מספר העקומות המקסימלי שניתן לבחור )כלומר מספר העקומות בגרף(. לאחר הזנת מספר העקומות ולחיצה על,OK מוצגת התמונה וניתן לסמן עקומה אחת בכל פעם, עד שלוחצים על OK בחלונית ההודעות. אחר כל לחיצה, נמחקת העקומה מהחלון המקורי )1 )figure ונוספת לחלון הפלט ( figure 2( בצבע המקורי. תכנות בעבודה עם GUIDE שינוי ברירות-מחדל ממשק חדש נוצר עם ברירת-מחדל עבור התכונות הכלליות. תכונות אלה הן: האם החלון הגרפי ניתן לשינוי או שממדיו קבועים )ברירת מחדל(. מהם התנאים להעברת הפוקוס לחלון הגרפי של הממשק: כאשר מתבצע Callback )ברירת מחדל( או בתנאים אחרים. האם ליצור רק את העיצוב הגרפי או ליצור גם mfile )ברירת מחדל: שניהם(. אם נוצר :mfile o האם ליצור תבניות עבור פונקציות Callback )ברירת מחדל: "כן"(. o האם כל הפעלה של הממשק פותחת חלון גרפי נוסף, או הפעלה יחידה - singleton )ברירת מחדל הפעלה יחידה(: הפעלה נוספת כאשר הממשק כבר פתוח מעבירה אליו את הפוקוס )ולא פותחת חלון חדש(. o האם להשתמש בסכמת הצבעים של המערכת כרקע )"כן" ברירת מחדל(. אם רוצים לשנות מאפיינים אלה, יש לעשות זאת לפני שמתחילים לעצב את הממשק, ע"י בחירה מתפריט.tools->GUI Options הערה: גם כאשר עובדים במסגרת של יצירה אוטומטית של תבניות עבור,Callbacks ניתן לערוך את ברירת המחדל עבור תכונת Callback ולכתוב במקומה כל דבר שמתאים לתכונה זו, כפי שתואר בשיעור 10. עבודה בצורה זו איננה מומלצת בדרך כלל, אבל עדיפה כאשר תגובת הממשק פשוטה ביותר ואין צורך בהגדרה מיוחדת של פונקציה. כלים להעברת מידע בין הפונקציות GUIDE משתמש במנגנון של guidata כדי לשמור מידע, בין השאר על guihandles אבל לא רק. נזכיר את האפשרויות לשמירת מידע שצוינו בשיעור 10: הגדרת כל הפונקציות המשתפות מידע כפונקציות פנימיות של התוכנית הראשית: אפשרות זו לא ישימה כשעובדים עם,GUIDE מאחר שאין לגעת בקוד התוכנית הראשית, ואילו כל ה- callbacks מוגדרות כפונקציות משניות. שמירת המידע המשותף בתכונת UserData שניתן לשייכה לכל עצם גרפי. ישים גם ב- GUIDE. שימוש בעצם מסוג Application Data שניתן לשייכו לכל עצם גרפי. ישים גם ב- GUIDE. עמ' 6 מתוך 8
שימוש בתכונת guidata אשר משויכת לחלון הגרפי. ניתן להשתמש בתכונה זו גם ב- GUIDE, אבל בזהירות: GUIDE עצמו משתמש בתכונה זו לשמירת handles וכן עוד שדה אחד לפחות.)output( אפשר להוסיף שדות נוספים לצורך שמירת מידע, אבל לא "לדרוך" על המשתנה ולא למחוק שדות קיימים. יש לזכור שלמרות השם handles המשתנה מכיל גם מידע שאיננו מזהים של עצמים גרפיים. כמו-כן יש לעדכן את guidata לאחר כל שינוי שמבוצע ב- handles )בדומה לעדכון UserData או.)Application Data דוגמא 6: ממשק d12heartgui מצייר תמונה של לב, כאשר ניתן לשנות את הפרמטרים של הלב גם ע"י סליידרים וגם ע"י הקלדתם בחלונית טקסט. כמו-כן ניתן לשנות את צבע הלב ע"י בחירה מתפריט. אלה. נקודות לציון על המימוש בתוכנה: GUIDE מספק תבניות לפונקציות callback של עצמים הנראים בממשק הגרפי הסטטי. עצמים נוספים, למשל קווים שיש לעדכן בהמשך ו/או שמייצרים אירועים, יש להגדיר בגוף הקובץ וכן לכתוב עבורם callbacks ולשייכם לעצמים הצורה הגרפית מאותחלת בשלב אתחול הממשק, ובהמשך רק מעדכנים אותה. כל האתחולים מתבצעים ב- Function.Opening העברת מידע בין הפונקציות בעזרת Application Data המשויך לחלון הראשי. קווים מנחים לעיצוב ממשק משתמש פשטות יש להציג רק מידע נחוץ. עיקרון זה נכון גם למידע המוצג וגם לעצמים לממשק. אם בשלב מסוים חלק מן המידע איננו נחוץ או רלוונטי, יש להעלים אותו. )במקרה של עצמים גרפיים לממשק, יש להשבית את העצמים שאינם רלוונטיים(. מיידיות אם ניתן להציג את התוצאות מייד, יש לעשות זאת. למשל לאחר תזוזה של סליידר יש להציג מייד את התוצאה )אם החישוב לא ארוך מדי( ולא להתנות את ההצגה בלחיצה על כפתור "הצג". עמ' 7 מתוך 8
גמישות במגבלות המקום בחלון הגרפי וכמות המידע שהמשתמש אמור לספק, יש לאפשר חלופות לקליטת מידע )למשל ע"י תיבת טקסט וגם סליידר(. עקביות יש לעצב ממשק לפעולות דומות באופן חזותי דומה. כמו-כן יש להשתמש בהליכים מוכרים, למשל בקליטת שם-קובץ מן המשתמש יש להפעיל uigetfile מאחר שהעיצוב של ממשק זה מתאים למוסכמות של המערכת. זה עדיף על עיצוב ייחודי של ממשק להצגת רשימת קבצים לבחירה. טיפול בשגיאות יש לחזות עד כמה שניתן מצבים שונים ולהיערך אליהם. במיוחד הממשק אמור לפעול גם אם המשתמש מגיב בדרכים "שגויות". משוב יש לספק למשתמש משוב על מצב המערכת בכל רגע נתון. בדרך כלל המשוב הוא הצגת התוצאות הרצויות. במקרים בהם קליטת הנתונים מורכבת מכמה שלבים, יש לספק רמזים מהו הצעד הבא. כמו-כן יש לתת משוב על מידע שגוי, לא מתאים או לא מספיק. סוף מעשה במחשבה תחילה... לתכנן מראש את הממשק, לפני שמתחילים בעיצוב בפועל: מהן המטלות. איזה מידע רלוונטי בכל שלב של התהליך, מה חשוב להציג בו-זמנית. איזה מידע המשתמש מספק, ובאילו דרכים )אינטראקטיבי, קריאה מקובץ(. אילו עצמים יקלטו מידע מן המשתמש ובאיזו דרך )סליידר, טקסט, כפתור וכו'(. איזה מידע יישמר בסופו של דבר, ובאיזו דרך. איך המערכת תגיב לפעולה של המשתמש בכל מצב שבו היא נמצאת. עמ' 8 מתוך 8