WPF

MVVM 1 INotifyPropertyChanged và INotifyCollectionChanged

WPF - Binding Notification ExampleKhi sử dụng data binding, các control hiển thị dữ liệu sẽ động được cập nhật mỗi khi dữ liệu bị thay đổi. Để làm được điều này, các đối tượng dữ liệu được hiện thực interface INotifyPropertyChanged và INotifyCollectionChanged.

 

INotifyPropertyChanged

Namespace: System.ComponentModel

INotifyPropertyChanged chỉ có duy nhất một thành viên là event mang tên PropertyChanged. Khi định nghĩa một class để dùng cho binding, bạn cần kích hoạt event này trong setter cho mỗi property trong class đó.

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person:INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }
    }
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

Trong trường hợp dự án của bạn có nhiều class cần hiện thực interface này, tốt nhất là nên tạo một lớp abstract để khỏi phải định nghĩa lại event PropertyChanged trong mỗi class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
class Person : PropertyChangedBase
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                // comment the following line to see the difference when you edit an item
                OnPropertyChanged("Name");
            }
        }
    }
}

Tuy nhiên cách trên vẫn chưa hiệu quả và vẫn còn khá dài dòng. Vậy làm cách nào để có thể khai báo các property một cách đơn giản hơn. Sau một thời gian nghiên cứu một số phương pháp thực hiện, tôi nhận thấy cách đơn giản nhất là sử dụng kĩ thuật reflection và lập trình dynamic của .NET 4. Nếu quan tâm, bạn có thể đọc bài giới thiệu về phương pháp này tại đây:

WPF – Tự động hiện thực INotifyPropertyChanged với DynamicObject.

INotifyCollectionChanged và ObservableCollection(Of T)

Namespace:

System.Collections.Specialized

System.Collections.ObjectModel

Tương tự như interface trên, bạn có thể đoán được công dụng của INotifyCollectionChanged

là cung cấp chức năng thông báo để cập nhật lại giao diện mỗi khi một collection bị thay đổi, thông qua event NotifyCollectionChangedEventHandler CollectionChanged. Các thay đổi này có thể là thêm, xóa, di chuyển, thay thế các phần tử của collection. Trong trường hợp thay đổi các property của một phần tử trong collection, thì phần tử đó phải được hiện thực INotifyPropertyChange thì giao diện mới được cập nhật.

Tham số NotifyCollectionChangedEventArgs của event này xác định kiểu tác động đến collection thông qua enum NotifyCollectionChangedAction. Với mỗi kiểu tác động cần phải sử dụng một constructor tương ứng để tạo NotifyCollectionChangedEventArgs. Bạn sẽ thấy rõ hơn điều này khi tôi làm ví dụ trong phần sau.

Enum NotifyCollectionChangedAction bao gồm các giá trị sau:

Member name

Description

Add One or more items were added to the collection.
Remove One or more items were removed from the collection.
Replace One or more items were replaced in the collection.
Move One or more items were moved within the collection.
Reset The content of the collection changed dramatically.

.NET cung cấp class ObservableCollection(Of T) hiện thực sẵn interface này. Vì thế khi sử dụng data binding trong WPF, bạn hãy sử dụng kiểu collection này thay vì các kiểu truyền thống như ArrayList, List(Of T),…

Trong trường hợp cần chuyển đổi một collection sang kiểu ObservableCollection(Of T), hãy khởi tạo một đối tượng ObservableCollection(Of T) và truyền collection cần chuyển đổi vào constructor của nó.

Tạo một Notifiable Collection

Để hiểu thêm về cách hoạt động của ObservableCollection(Of T), cách tốt nhất là tạo một collection có chức năng tương tự. Tôi sẽ tạo một generic collection thừa kế từ class System.Collections.ObjectModel.Collection(Of T), dĩ nhiên cũng cần phải hiện thực INotifyCollectionChanged.

Class Collection(Of T) chứa sẵn các phương thức public cần thiết để thao tác với các phần tử. Các phương thức public hay indexer của nó đều được hoạt động bằng cách gọi các phương thức protected. Do đó, bạn chỉ cần override các phương thức protected để thêm việc kích hoạt event CollectionChanged.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class NotifiableCollection : Collection, INotifyCollectionChanged
{
    public NotifiableCollection() { }
    protected override void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
    }
    protected override void RemoveItem(int index)
    {
        T item = base[index];
        base.RemoveItem(index);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
    }
    protected override void ClearItems()
    {
        base.ClearItems();
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    public void Move(int oldIndex, int newIndex)
    {
        T item = base[oldIndex];
        base.SetItem(newIndex, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move,
            item, newIndex, oldIndex));
    }
    protected override void SetItem(int index, T item)
    {
        T oldValue = base[index];
        base.SetItem(index, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                item, oldValue));
    }
    #region INotifyCollectionChanged Members
    protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    #endregion
}

Ví dụ minh họa

MainWindow.xaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<Window x:Class="DataChangedNotificationDemo.MainWindow"
        Title="Binding Notification" Height="320" Width="300">
    <StackPanel Margin="5">
        <TextBox Height="24"
                 Name="textBox1" Text="[Your name]" Width="180" />
        <Grid HorizontalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Content="Add" Height="30" Width="80" Margin="5"
                Name="addButton" Click="addButton_Click" />
            <Button Content="Update" Height="30" Width="80" Margin="5"
                    Grid.Column="1"
                Name="updateButton" Click="updateButton_Click" />
            <Button Content="Replace" Height="30" Width="80" Margin="5"
                    Grid.Row="1"
                Name="replaceButton" Click="replaceButton_Click" />
            <Button Content="Clear" Height="30" Width="80" Margin="5"
                    Grid.Column="1" Grid.Row="1"
                Name="clearButton" Click="clearButton_Click" />
        </Grid>
        <ListBox Name="listBox1" ItemsSource="{Binding}"
                 Height="150" Width="200" Margin="5">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <DockPanel>
                        <Button Content="X" Width="20" Height="20"
                                Click="deleteButton_Click"
                                Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"/>
                        <TextBlock Text="{Binding Name}" Margin="5,3,0,0"/>
                    </DockPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</Window>

MainWindow.xaml.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public partial class MainWindow : Window
{
    NotifiableCollection _persons;
    public MainWindow()
    {
        InitializeComponent();
        _persons = new NotifiableCollection
        {
            new Person{Name="Eros"},
            new Person{Name="Tethys"},
            new Person{Name="Atlas"},
            new Person{Name="Apollo"},
            new Person{Name="Hades"},
        };
        this.DataContext = _persons;
    }
    private void deleteButton_Click(object sender, RoutedEventArgs e)
    {
        var p = (sender as Button).DataContext as Person;
        _persons.Remove(p);
    }
    private void addButton_Click(object sender, RoutedEventArgs e)
    {
        _persons.Add(new Person { Name = textBox1.Text });
    }
    private void updateButton_Click(object sender, RoutedEventArgs e)
    {
        if (listBox1.SelectedItem != null)
            _persons[listBox1.SelectedIndex].Name  = textBox1.Text;
    }
    private void clearButton_Click(object sender, RoutedEventArgs e)
    {
        _persons.Clear();
    }
    private void replaceButton_Click(object sender, RoutedEventArgs e)
    {
        if (listBox1.SelectedItem != null)
        {
            _persons[listBox1.SelectedIndex] = new Person { Name = textBox1.Text };
            listBox1.Items.Refresh();
        }
    }
}

Giao diện:

WPF - Binding Notification Example

Tổng kết

Ví dụ đơn giản trên là cách thông thường để viết ứng dụng và nó không theo mô hình nào cả. Sự trộn lẫn giữa các tầng giao diện, xử lý, dữ liệu khiến cho ứng dụng thiếu linh hoạt và khó phát triển.

Trong phần tiếp theo, bạn sẽ thấy cách tôi sử dụng Command để thay thế phương thức xử lý sự kiện. Việc ứng dụng Command giúp cho các phần của ứng dụng được tách biệt rõ ràng hơn. Lý do là các Command được định nghĩa trong phần ViewModel, và theo nguyên tắc chúng không thể truy xuất được các thành phần của View (giao diện).

https://yinyangit.wordpress.com

Bài liên quan

Cơ bản về MVVM (Model – View – ViewModel) Pattern

https://yinyangit.wordpress.com

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

w

Connecting to %s