Behaviors חלק 6– פתרון בעיית ה Commands ב MVVM

10 באוגוסט 2012

תגובה אחת

בפוסט הקודם ראינו איך כותבים Action מאפס בעצמנו. בפוסט הנוכחי נראה את אחד השימושים הכי שימושיים ב Actions – ולמעשה איך נפתרת אחת הבעיות המציקות בשימוש ב Commands תחת MVVM.

 

בעיית ה Commands.

תחת MVVM, הדרך היחידה לחבר בין ה View לבין ה ViewModel זה על ידי Binding ו Commands. כך מתקבלת הפרדה טובה יותר מאשר הייתה לפני כן.
בגדול, המידע יעבור בין ה ViewModel ל View ע”י Binding, ופעולות יעברו מה View ל ViewModel על ידי Commands.

כדוגמא, ניצור חלון שמחובר ל ViewModel. ב ViewModel נגדיר Command בסיסי (אני משתמש ב RelayCommand מתוך MVVM Light). כך יראה ה ViewModel:

 

   1: public class MainViewModel : INotifyPropertyChanged

   2: {

   3:     public RelayCommand SomeCommand { get; set; }

   4:  

   5:     public MainViewModel()

   6:     {

   7:         SomeCommand = new RelayCommand(() =>

   8:             { 

   9:                 // Just to see that we catch the command:

  10:                 MessageBox.Show("Command caught");

  11:             });

  12:     }

  13:  

  14:     public event PropertyChangedEventHandler PropertyChanged;

  15: }

 

(כשכמובן בעולם האמיתי לעולם לא נרים MessageBox מתוך ה ViewModel).
בתוך הView נשים כפתור, שמחובר ל Command שמוגדר ב ViewModel. ה View יראה כך:

 

   1: <Window x:Class="ActionDemo.MainWindow"

   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   4:         xmlns:local="clr-namespace:ActionDemo"

   5:         Title="MainWindow" Height="350" Width="525">

   6:     <Window.DataContext>

   7:         <local:MainViewModel />

   8:     </Window.DataContext>

   9:     <Grid>

  10:         <Button Content="Activate Command" Command="{Binding SomeCommand}" />

  11:     </Grid>

  12: </Window>

 

וכשנריץ – לחיצה על הכפתור אכן תרים את ה Command כפי שציפינו:

 

image

 

הבעיה היא ש Commands זה פיצ’ר טיפה מוגבל. ל Commands אפשר להרשם רק מקונטרולים שחושפים את הפרופרטי Command, שהם אך ורק קונטרולים שיורשים מ ButtonBase או MenuItem. במידה ואנו רוצים להשתמש ב Command על TextBox לדוגמא, אנחנו תקועים. יותר מזה, גם הקונטרולים שכן עובדים עם Command יודעים להרים אותו רק בלחיצה, ולא כתגובה לשום אירוע אחר. אם נרצה שה Command יופעל כתוצאה מהאירוע MouseEnter אנחנו תקועים.

 

פתרון ע”י שימוש ב Action

נוכל לפתור את הבעיה בצורה מאוד אלגנטית ע”י שימוש ב Actions.
נגדיר Action חדש בשם InvokeCommandAction: (נא לא לשכוח להוסיף רפרנס ל System.Windows.Interactivity)

 

 

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.Text;

   5: using System.Windows.Interactivity;

   6: using System.Windows;

   7:  

   8: namespace ActionDemo

   9: {

  10:     public class InvokeCommandAction : TriggerAction<UIElement>

  11:     {

  12:         protected override void Invoke(object parameter)

  13:         {

  14:             throw new NotImplementedException();

  15:         }

  16:     }

  17: }

 

נוסיף ל Action שלנו פרופרטי מסוג Command. הרעיון הוא שכשה Action יופעל – הוא יפעיל את ה Command שניתן לו כפרמטר.
בנוסף חשוב ליצור את הפרופרטי כ DependencyProperty היות ואנו רוצים שזה יתמוך ב Binding. לפיכך נוסיף את הפרופרטי הבא:

 

   1: public ICommand Command

   2: {

   3:     get { return (ICommand)GetValue(CommandProperty); }

   4:     set { SetValue(CommandProperty, value); }

   5: }

   6:  

   7: // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...

   8: public static readonly DependencyProperty CommandProperty =

   9:     DependencyProperty.Register("Command", typeof(ICommand), typeof(InvokeCommandAction), new UIPropertyMetadata(null));

 

כמעט סיימנו. כל מה שנותר לנו עכשיו הוא להפעיל את ה Command במידה וה Action מופעל. את זה נעשה בתוך המתודה Invoke:

 

   1:  

   2:         protected override void Invoke(object parameter)

   3:         {

   4:             if (Command != null)

   5:                 Command.Execute(null);

   6:         }

 

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

   1: <Window x:Class="ActionDemo.MainWindow"

   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   4:         xmlns:local="clr-namespace:ActionDemo"

   5:         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

   6:         Title="MainWindow" Height="350" Width="525">

   7:     <Window.DataContext>

   8:         <local:MainViewModel />

   9:     </Window.DataContext>

  10:     <Grid>

  11:         <Button Content="Activate Command" >

  12:             <i:Interaction.Triggers>

  13:                 <i:EventTrigger EventName="MouseEnter">

  14:                     <local:InvokeCommandAction Command="{Binding SomeCommand}" />

  15:                 </i:EventTrigger>

  16:             </i:Interaction.Triggers>

  17:         </Button>

  18:     </Grid>

  19: </Window>

 

בהרצת האפליקציה, נכניס את סמן העכבר מעל לכפתור ונראה שאכן ה Command הופעל כתגובה לאירוע רגיל!

 

image

 

ה Action שבנינו הוא מאוד שימושי, אם כי לא סיימנו. עקרונית כדאי להוסיף פרופרטי שני בשם CommandParameter בשביל שנוכל לשלוח גם פרמטר עם ה Command, אבל זה כבר מאוד פשוט… במקום לכתוב את זה מאפס – נשתמש במה שכבר כתוב –
ה Action שבנינו כל כך שימושי, שלמעשה הוא כבר קיים בתוך System.Windows.Interactivity – זה אחד ה Action – ים היחידים שכבר כתובים היות והוא כל כך שימושי. בשביל להשתמש בו, פשוט נחליף את ה Action שלנו ב Action מתוך מרחב השמות של ה Behaviors – i:

 

   1: <Button Content="Activate Command" >

   2:     <i:Interaction.Triggers>

   3:         <i:EventTrigger EventName="MouseEnter">

   4:             <!--<local:InvokeCommandAction Command="{Binding SomeCommand}" />-->

   5:             <i:InvokeCommandAction Command="{Binding SomeCommand}" />

   6:         </i:EventTrigger>

   7:     </i:Interaction.Triggers>

   8: </Button>

ונקבל את אותה תוצאה בדיוק.

ה Action שכתבנו בסופו של יום כבר כתוב כחלק מה Behaviors, אבל בכל זאת אפשר לראות כאן בסיס לפתרון של הרבה מאוד בעיות ב MVVM  כפי שנראה בהמשך. יתרה מזאת, כפי שנראה, Behaviors יעזרו לנו לקבל תובנה לגבי סוגים של הפרדות – הפרדה של רכיבים גנרים לעומת הפרדה של רכיבים לא גנרים בעלי אחריות ספציפית – שמהווה אבן יסוד בארכיטקטורת קליינט. אבל עד שנגיע לשם יש עוד כמה פוסטים.. Smile

בפוסט הבא נראה איך אפשר לממש כמה פיצ’רים מאוד שימושיים ע”י Behaviors – כשנתחיל עם Drag and Drop.

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

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

תגובה אחת

  1. Santhosh25 באוגוסט 2012 ב 10:27

    With the bases lodaed you struck us out with that answer!

    הגב