Loading

WPF之数据绑定基类

数据绑定方法

在使用集合类型作为列表控件的ItemsSource时一般会考虑使用ObservalbeCollection,它实现了INotifyCollectionChangedINotifyPropertyChanged接口,能把集合的变化立刻通知显示它的列表控件,改变会立刻显现出来。

在使用自定义类型作为界面的数据源时,自定义类型需要自己实现INotifyPropertyChanged接口,一般会把INotifyPropertyChanged接口的实现放到一个基类中。

BindableBase基类

下面的BindableBase类来自Prism,代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

/// <summary>
/// 实现 <see cref="INotifyPropertyChanged"/>
/// </summary>
public abstract class BindableBase : INotifyPropertyChanged
{
    /// <summary>
    /// 属性值更改时发生
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// 检查属性是否已与设置值相等,设置属性并仅在必要时通知侦听器。
    /// </summary>
    /// <typeparam name="T">属性的类型</typeparam>
    /// <param name="storage">对同时具有getter和setter的属性的引用</param>
    /// <param name="value">属性的所需值</param>
    /// <param name="propertyName">用于通知侦听器的属性的名称,此值是可选的,从支持CallerMemberName的编译器调用时可以自动提供。</param>
    /// <returns>如果值已更改,则为True;如果现有值与所需值匹配,则为false。</returns>
    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(storage, value)) return false;

        storage = value;
        RaisePropertyChanged(propertyName);

        return true;
    }

    /// <summary>
    /// 检查属性是否已与设置值相等,设置属性并仅在必要时通知侦听器。
    /// </summary>
    /// <typeparam name="T">属性的类型</typeparam>
    /// <param name="storage">对同时具有getter和setter的属性的引用</param>
    /// <param name="value">属性的所需值</param>
    /// <param name="propertyName">用于通知侦听器的属性的名称,此值是可选的,从支持CallerMemberName的编译器调用时可以自动提供。</param>
    /// <param name="onChanged">属性值更改后调用的操作。</param>
    /// <returns>如果值已更改,则为True;如果现有值与所需值匹配,则为false。</returns>
    protected virtual bool SetProperty<T>(ref T storage, T value, Action onChanged, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(storage, value)) return false;

        storage = value;
        onChanged?.Invoke();
        RaisePropertyChanged(propertyName);

        return true;
    }

    /// <summary>
    /// 引发此对象的PropertyChanged事件。
    /// <param name="propertyName">用于通知侦听器的属性的名称,此值是可选的,从支持CallerMemberName的编译器调用时可以自动提供。</param>
    protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    /// 引发此对象的PropertyChanged事件。
    /// </summary>
    /// <param name="args">PropertyChangedEventArgs参数</param>
    protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
    {
        PropertyChanged?.Invoke(this, args);
    }
}

上面的代码有两点需要注意:

  • 使用EqualityComparer .Default属性创建通用比较,而不是使用Object.Equals()。Default属性检查类型T是否实现System.IEquatable(Of T)接口,如果是,则返回使用该实现的EqualityComparer(Of T)。否则,它返回一个EqualityComparer(Of T),它使用T提供的Object.Equals和Object.GetHashCode的替代。

  • 使用CallerMemberName特性代替常规的属性名称(也可以使用nameof()运算符关键字来传递属性名称),CallerMemberNameAttribute 类允许获取方法调用方的方法或属性名称,而不必使用字符串文字、 慢速反射代码、复杂的表达式树逻辑或代码编织。

使用BindableBase

实现一个StudentViewModel类(继承BindableBase),代码如下

class StudentViewModel : BindableBase
{
    private int _id;
    public int Id
    {
        get { return _id; }
        set { SetProperty(ref _id, value); }
    }

    private string _name;
    public string Name 
    {
        get { return _name; }
        set { SetProperty(ref _name, value,new Action(()=> { Id++; })); }
    }

    private int _age;
    public int Age
    {
        get { return _age; }
        set { SetProperty(ref _age, value,nameof(this.Age)); }
    }
}

主界面的XAML代码如下:

<StackPanel x:Name="stack" Background="LightBlue">
    <StackPanel.DataContext>
        <local:StudentViewModel Id="6" Age="29" Name="Tim"/>
    </StackPanel.DataContext>
    <Grid>
        <StackPanel>
            <TextBox Text="{Binding Path=Id}" Margin="5"/>
            <TextBox Text="{Binding Path=Name}" Margin="5"/>
            <TextBox Text="{Binding Path=Age}" Margin="5"/>
            <Button Content="Age+" Click="Button_Click"/>
        </StackPanel>
    </Grid>
</StackPanel>

主界面的后台实现Button_Click,代码如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
    ((StudentViewModel)this.stack.DataContext).Age ++;
}

运行程序,查看数据绑定的效果:

参考资料

PrismLibrary/Prism/BindableBase.cs
INotifyPropertyChanged,.NET 4.6方式
MVVM模式解析和在WPF中的实现(二)数据绑定

posted @ 2021-05-05 22:30  二次元攻城狮  阅读(901)  评论(0编辑  收藏  举报