מדריך Windows Phone – שימוש ב Data Binding: שימוש ב INotifyPropertyChange
בואו ננסה לחשוב לרגע על הסוגיה הבאה: כיצד מנגנון ה Binding יודע על כל שינוי באובייקט המקור?
כדי שזה יקרה אנחנו צריכים שיהיה איזשהו מנגנון של הודעות או אירועים כך שבכל פעם שיש שינוי באובייקט המקור, מנגנון ה Binding מקבל את האירוע ובעקבותיו מפעיל את תהליך הסנכרון לתוך היעד (ובאופן דומה לכיוון ההפוך).
אז מי מספק את אותם אירועים בכל פעם תכונת מקור משתנה? זה תלוי.
אם תכונת המקור היא מסוג Dependency Property אזי דיווח על שינויים הוא אחד הדברים שתכונה שכזו מוסיפה, כלומר מנגנון ה Binding משתמש בעבודה שתכונת המקור היא Dependency Property.
אם תכונת המקור היא תכונה רגילה אזי אין דרך למנגנון ה Binding לדעת על השינוי!
דוגמא שלא עובדת
בדוגמא הבאה יש לנו מחלקת נתונים בשם MyData הממומשת באופן הבא:
namespace NotifyTargetBad
{
public class MyData
{
public string User { get; set; }
public string Password { get; set; }
}
}
וכן נגדיר ממשק משתמש המכיל שתי תיבות טקסט וכפתור שיגרום לשינוי המידע, להלן הגדרת התוכנית:
<phone:PhoneApplicationPage
x:Class="PhoneDemo.MainPage"
>="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
>:x="http://schemas.microsoft.com/winfx/2006/xaml"
>:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
>:d="http://schemas.microsoft.com/expression/blend/2008"
>:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
>:local="clr-namespace:PhoneDemo"
mc:Ignorable="d"
d:DesignWidth="480"
d:DesignHeight="800"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
Orientation="Portrait"
SupportedOrientations="Portrait">
<Grid>
<Grid.RowDefinitions>
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
x:Name="stackPanel"
Orientation="Horizontal"
Grid.Row="0">
<TextBlock
Text="User: " />
<TextBox
Text="{Binding Path=User}" />
<TextBlock
Text="Password: " />
<TextBox
Text="{Binding Path=Password}" />
</StackPanel>
<Button
Grid.Row="1"
Content="ChangeData"
Click="Button_Click" />
</Grid>
</phone:PhoneApplicationPage>
והנה הקוד שגורם לאתחול הנתונים הראשון ולשינוי הנתונים כאשר לוחצים על הכפתור:
using Microsoft.Phone.Controls;
namespace PhoneDemo
{
public partial class MainPage : PhoneApplicationPage
{
private MyData _myData;
// Constructor
public MainPage()
{
InitializeComponent();
_myData = new MyData()
{
User = "Arik", Password = "123456"
};
stackPanel.DataContext = _myData;
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
_myData.Password = "555555";
}
}
}
הרצת הדוגמא הזו תראה לכם שלחיצה על הכפתור לא משנה שום דבר בפקדי ה UI למרות שהנתון של הסיסמא השתנה. הסיבה היא שהתכונות שיצרנו באובייקט MyData הן תכונות פשוטות (לא Dependency Properties) ולכן מנגנון ה Binding אינו יכול לדעת על השינוי!
דוגמא עובדת באמצעות שימוש ב INotifyPropertyChanged
כדי לגרום למנגנון ה Binding לדעת על שינויים בתכונות של אובייקט מקור יש לדאוג לכך שאובייקט המקור יממש את הממשק INotifyPropertyChanged. ממשק זה מגדיר אירוע אחד בלבד בשם PropertyChanged שלאובייקט המקור צריך להיות. כמובן שלא מספיק שיהיה אירוע אלא צריך לדאוג להפעיל אותו בכל פעם שתכונה של האובייקט משתנה.
להלן הקוד שמראה את הדרך הנכונה לכתוב את המחלקה MyData:
using System.ComponentModel;
namespace NotifyTargetUsingInterface
{
public class MyData : INotifyPropertyChanged
{
private string _user;
public string User
{
get
{
return _user;
}
set
{
_user = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("User"));
}
}
}
private string _password;
public string Password
{
get
{
return _password;
}
set
{
_password = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Password"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
נעיר כי נאלצנו להחליף את ה Properties האוטומטיים מהדוגמא הקודמת בProperties מלאים, בעלי משתנה פרטי מפורש כדי שנוכל לכתוב קוד בתוך המימוש שלהם.
שאר הקוד בתוכנית נותר ללא שינוי אבל כעת לחיצה על הכפתור מעדכנת את השדות שהשתנו!
דוגמא עובדת באמצעות שימוש ב Dependency Properties
כמובן שיכולנו לעשות גם שימוש בתכונות מסוג Dependency Properties ואז ליהנות מדיווח אוטומטי על שינויים בתכונות. לשם כך יש לשנות את המחלקה MyData כך שתירש מ DependencyObject ותהיה בעלת שתי תכונות מתאימות מסוג Dependency Property:
using System.Windows;
namespace PhoneDemo
{
public class MyData : DependencyObject
{
public string User
{
get { return (string)GetValue(UserProperty); }
set { SetValue(UserProperty, value); }
}
public static readonly DependencyProperty UserProperty =
DependencyProperty.Register("User",
typeof(string), typeof(MyData), new PropertyMetadata(""));
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password",
typeof(string), typeof(MyData), new PropertyMetadata(""));
}
}
אמנם בשיטה זו מקבלים בחינם את הדיווח על שינויים (ועוד תכונות טובות של Dependency Properties) אבל אנו משלמים על כך שאנו חייבים לרשת מאובייקט DependencyObject ישירות או בעקיפין. ישנם הרבה מקרים שבהם זה בלתי אפשרי (בגלל שניתן לרשת רק ממחלקה אחת) ואז הפתרון הוא להשתמש ב INotifyPropertyChanged.
תגובות בפייסבוק