מדריך WPF

מדריך WPF – שימוש ב Data Binding: שימוש ב INotifyPropertyChange

‏ • Sela

בואו ננסה לחשוב לרגע על הסוגיה הבאה: כיצד מנגנון ה Binding יודע על כל שינוי באובייקט המקור?

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

אז מי מספק את אותם אירועים בכל פעם תכונת מקור משתנה? זה תלוי.

אם תכונת המקור היא מסוג Dependency Property אזי דיווח על שינויים הוא אחד הדברים שתכונה שכזו מוסיפה, כלומר מנגנון ה Binding משתמש בעבודה שתכונת המקור היא Dependency Property.

אם תכונת המקור היא תכונה רגילה אזי אין דרך למנגנון ה Binding לדעת על השינוי!

 

דוגמא שלא עובדת

בדוגמא הבאה יש לנו מחלקת נתונים בשם MyData הממומשת באופן  הבא:


namespace NotifyTargetBad
{
 
public class MyData
  {
   
public string User { get; set
; }
   
public string Password { get; set; }
  }
}

וכן נגדיר ממשק משתמש המכיל שתי תיבות טקסט וכפתור שיגרום לשינוי המידע, להלן הגדרת החלון:

<Window x:Class="NotifyTargetBad.MainWindow"
       >="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       >:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow"
       Height="350"
       Width="525">
  <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
>
</
Window
>

 

מדריך WPF – שימוש ב Data Binding: שימוש ב INotifyPropertyChange

והנה הקוד שגורם לאתחול הנתונים הראשון ולשינוי הנתונים כאשר לוחצים על הכפתור:

using System.Windows;

namespace
NotifyTargetBad
{
   
/// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
       
private MyData
_myData;

       
public
MainWindow()
        {
            InitializeComponent();

            _myData =
new MyData
()
            {
                User =
"Arik"
,
                Password =
"123456"
            };
            stackPanel.DataContext = _myData;
        }

       
private void Button_Click(object sender, 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 מלאים, בעלי משתנה פרטי מפורש כדי שנוכל לכתוב קוד בתוך המימוש שלהם.

שאר הקוד בתוכנית נותר ללא שינוי אבל כעת לחיצה על הכפתור מעדכנת את השדות שהשתנו!

מדריך WPF – שימוש ב Data Binding: שימוש ב INotifyPropertyChange

 

דוגמא עובדת באמצעות שימוש ב Dependency Properties

כמובן שיכולנו לעשות גם שימוש בתכונות מסוג Dependency Properties ואז ליהנות מדיווח אוטומטי על שינויים בתכונות. לשם כך יש לשנות את המחלקה MyData כך שתירש מ DependencyObject ותהיה בעלת שתי תכונות מתאימות מסוג Dependency Property:

using System.Windows;

namespace
NotifyTargetUsingDependecyProperties
{
 
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 UIPropertyMetadata(""
));

   
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 UIPropertyMetadata(""));
  }
}

 

אמנם בשיטה זו מקבלים בחינם את הדיווח על שינויים (ועוד תכונות טובות של Dependency Properties) אבל אנו משלמים על כך שאנו חייבים לרשת מאובייקט DependencyObject ישירות או בעקיפין. ישנם הרבה מקרים שבהם זה בלתי אפשרי (בגלל שניתן לרשת רק ממחלקה אחת) ואז הפתרון הוא להשתמש ב INotifyPropertyChanged.


 

תגיות: , , , ,

arikp

אריק פוזננסקי הוא יועץ בכיר ומרצה בסלע. הוא השלים שני תארי B.Sc. במתמטיקה ומדעי המחשב בהצטיינות יתרה בטכניון. לאריק ידע נרחב בטכנולוגיות מיקרוסופט, כולל .NET עם C#, WPF, Silverlight, WinForms, Interop, COM/ATL, C++ Win32 ו reverse engineering.

תגובות בפייסבוק