MVVM part 2 – WPF Command

1 בDecember 2012

תגיות: , ,
3 תגובות

 

כדי שנוכל להתחיל לדבר על WPF  ועל MVVM  בפרט חשוב שנבין לפחות בצורה בסיסית כיצד משתמשים ב  Commands.

כאמור הצגתי את התבנית בעבר בהרחבה , כולל דוגמא לבניית מנגנון Redo/Undo  בעזרת התבנית,

החלק שחסר לנו זה המימוש הרלוונטי עבור WPF  כדי שדברים ירוצו כמו שצריך.

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

אבל מכל זה, החשוב יותר הוא שCommand יכול להיקרא מתוך XAML code !!

2 דברים חשובים שיש לנו וניתן לייעל איתם את העבודה עם Commands והם:  

Command Parameter, מאפשר לשלוח פרמטר מתוך
ה
xaml כשהפונקציה שהcommand מפעיל מחכה לפרמטר (לדוגמא
ListBox.SelectedItem)

וCan Execute  שנותן לנו את היכולת להגדיר מראש אם ניתן לבצע invoke לפונקציה או שלא (נניח שנדרשת תלות במשתנה
בוליאני) ואז ה
command פשוט לא יתבצע.

אז איך יראה Command כזה? לNET. יש רק אינטרפייס שנקרא ICommand ואנו נדרשים לבנות לבד קלאס שמממש אותו.

*הערה: כמובן שאותם
ספריות שמהוות תשתית פיתוח ל
MVVM מגיעות עם Command מובנה ואנו נשתמש בו בעתיד, אולם שוב
כדי “להרגיש” את הטכניקה אני ממליץ לנסות קודם לבד.

דוגמא בסיסית:

נפתח קלאס חדש ונקרא לו איכשהוא עם הצירוף Command אני קראתי לו MyCommand

מקובל מאוד לקרוא לו DelegateCommand שכן הוא משתמש בDelegate כדי להריץ פונקציות.

עכשיו נוסיף using  ל using System.Windows.Input;.

ונגדיר את הקלאס שלנו שהוא מממש את הICommand
Interface

ברגע שנוסיף את האינטרפייס נדרש לממש שתי פונקציות בסיסיות:

public void Execute(object parameter)

public bool CanExecute(object parameter)

 

בתוספת                                                                                                            public event EventHandler
CanExecuteChanged
;

בתור התחלה זה יעבוד גם אם נתעלם מפונקציית CanExecute ופשוט
נחזיר בקביעות
true

עכשיו ניזכר טיפה מה עושה Pattern command , הוא עוטף פונקציה באובייקט
ומאפשר לנו להתייחס אליה כמשתנה.

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

public  class MyCommand:ICommand

    {

        private Action _execute;

 

      public MyCommand(Action execute) 

        {

            _execute = execute;

      

        }

        public bool CanExecute(object parameter)

        {

            return true;

        }

 

        public event EventHandler CanExecuteChanged;

 

        public void Execute(object parameter)

        {

            if (_execute != null)

                _execute();

        }

    }

בעיקרון זה מספיק ! אבל אפשר כבר להתייחס מעט לCanExecute ולממש אותו.

הרעיון הוא מנגנון וולידציה מובנה שמאפשר לנו להחליט האם זה ביצוע
הפונקציה הוא חוקי או לא…

אז נשלים את הדוגמא בדרך הבאה: במקום לקבוע שזה תמיד true ניתן למשתמש לשלוח דלגייט שמחזיר bool   ולקבוע בזמן ריצה את הוולידציה
שלו. אם נחזור רגע לקוד הבסיסי שקיבלנו מהחוזה של
ICommand נראה את
ה”אירוע” שניתן להפעילו מבחוץ (
CanExecuteChanged) , עכשיו אנחנו צריכים להשתמש בו כדי לתחזק את הcommand שלנו.  

 נוסיף אצלינו בcommand class משתנה פרטי של bool
Delegate
וקונסטרטור שתומך בקבלת שני הפרמטרים וזה
ייראה כך:

  private Func<bool> _canExecute;

 

      public MyCommand(Action  execute, Func<bool> canExecute) 

        {

            _execute = execute;

            _canExecute = canExecute;

 

נוסיף גם EventHandler שיופעל כתוצאה מהדלגייט
שנקבל ופונקציה שמשנה אותו ונעטוף את ה
event המקורי כמו
פרופרטי באמצעות
add/remove :

 private event EventHandler _canExecuteChanged;

 

public void RaiseCanExecuteChanged()

        {

            if (_canExecuteChanged != null)

                _canExecuteChanged(thisnew EventArgs());

        }

 

                                                                                                                        public event EventHandler CanExecuteChanged

        {

            add { _canExecuteChanged += value; }

            remove { _canExecuteChanged -= value; }

        }

עכשיו נחזור לקוד המקורי של CanExecute שאמרנו לו להחזיר תמיד true ונשנה אותו שיחזיר את מה שחוזר  מהדלגייט:

 public bool CanExecute(object parameter)

        {

            if (_canExecute != null)

                return _canExecute();

            return false;

        }

 

זהו, הcommand  המשוכלל שלנו בנוי ומוכן לעבודה, כדוגמא מלאה זה
ייראה כך:

 

 public class MyCommand : ICommand

    {

        private Action _execute;

        private Func<bool> _canExecute;

        private event EventHandler _canExecuteChanged;

 

        public MyCommand(Action execute, Func<bool> canExecute)

        {

            _execute = execute;

            _canExecute = canExecute;

        }

        public MyCommand(Action execute)

        {

            _execute = execute;

            _canExecute = ()=> true;

 

              }

        public bool CanExecute(object parameter)

        {

            if (_canExecute != null)

                return _canExecute();

            return false;

        }

        public void Execute(object parameter)

        {

            if (_execute != null)

                _execute();

        }

 

        public event EventHandler CanExecuteChanged

        {

            add { _canExecuteChanged += value; }

            remove { _canExecuteChanged -= value; }

        }

        public void RaiseCanExecuteChanged()

        {

            if (_canExecuteChanged != null)

                _canExecuteChanged(thisnew EventArgs());

        }

 

    }

ומה לגבי Command Parameter שהזכרתי בהתחלה כאחד משני
הדברים שצריך להכיר ב
 Commands.

אז מתי זה טוב? כשיש לנו צורך בפונקציה שמקבלת פרמטרים  private
void F(int I, string s)
ועכשיו טנו רוצים לעטוף אותה בקלאס לצורך command ואין לנו ידע מוקדם באיזה
טיפוס אנו אמורים לטפל..

לכן נהפוך את ה Command class שלנו להיות גנרי וכך
נתמוך בהכל!! אין צורך בהרבה שינויים שימו לב למראה ה”חדש”, ברגע זה
אפשר לשלוח דרכו פונקציות שדורשות פרמטרים,

public class MyCommand<T> : ICommand

    {

        private Action<T> _execute;

        private Func<T, bool> _canExecute;

        public MyCommand(Action<T> execute) 

        {

            _execute = execute;

        }

      

        public MyCommand(Action<T> execute, Func<T, bool> canExecute)

        {

            _execute = execute;

            _canExecute = canExecute;

      

        }

 

        private event EventHandler _canExecuteChanged;

        public event EventHandler CanExecuteChanged

        {

            add

            {       

                _canExecuteChanged += value;

 

            }

            remove

            {

               

                _canExecuteChanged -= value;

            }

        }

 

        public void RaiseCanExecuteChanged()

        {

            if (_canExecuteChanged != null)

                _canExecuteChanged(thisnew EventArgs());

        }

 

        public void Execute(object parameter)

        {

            T param = (T)parameter;

            if (_execute != null)

                _execute(param);

        }

 

        public bool CanExecute(object parameter)

        {

            T param = (T)parameter;

            if (_canExecute != null)

                return _canExecute(param);

            return false;

        }

    }

 

*הערה חשובה :  במעט תמיד אפשר להתעלם מcommand parameter  ולקבל את המידע באמצעות Binding אבל טוב שיהיה לנו הידע להשתמש גם בזה.

את דוגמת השימוש בcommand  שלנו כולל שימוש בCan Execute   ובפרמטר הגנרי שלו (אם צריך) נדגים בפוסט הבא
כשניצור סוף סוף דוגמא מלאה ל
MVVM  בסיסי.

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

הוסף תגובה
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

3 תגובות

  1. אש8 בDecember 2012 ב 16:36

    אחלה כתבה . תמשיך ככה

    Reply
  2. tcIT24 בJune 2013 ב 6:18

    שימושי מאוד הכי קצר שמצאתי !

    Reply
  3. moses Jounior30 בApril 2014 ב 22:01

    hebrew ??? never mind for copy paste is one of the best

    Reply