ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Community.MvvmToolkit教程

2022-08-15 05:00:08  阅读:259  来源: 互联网

标签:教程 string MvvmToolkit get Community private value public 属性


通知单个属性值改变

  1. 不传参

    private string _firstName;
    
    public string FirstName {
        get { return _firstName; }
        set {
            _firstName = value;
            OnPropertyChanged(); // 不传参,CallerMemberNameAttribute修饰方法参数,不传参时默认实参是调用方的标识符
        }
    }
    
  2. nameof

    private string _lastName;
    
    public string LastName {
        get { return _lastName; }
        set {
            _lastName = value;
            OnPropertyChanged(nameof(LastName)); // nameof(属性标识符)
        }
    }
    
  3. Expression Tree

    private int _age;
    
    public int Age {
        get { return _age; }
        set {
            _age = value;
            OnPropertyChanged(() => Age); // Expression Tree
        }
    }
    
  4. 常量字符串

    private string _email;
    
    public string Email {
        get { return _email; }
        set {
            _email = value;
            OnPropertyChanged("Email"); // 常量字符串
        }
    }
    

优缺点分析:

不传参:简便

nameof:防"笔误",避免敲错属性名

Expression Tree:防"笔误",避免敲错属性名,但因为要解析Expression Tree,性能较低

常量字符串:容易"笔误",敲错属性名

推荐使用优先级

无显示传参 > nameof > 常量字符串 > Expression Tree

通知多个属性值改变

当多个属性的值存在依赖关系,即其中一个属性的值发生了变化,其他的属性的值也受影响跟着发生了变化,这种情况下,在被修改的属性的setter中,也应该通知其他的属性的值发生了变化。

开发者可以多次调用通知单个属性的值发生了变化的api,也可以调用一次通知多个属性的值发生了变化的api.

案例:FullName = FirstName + LastName,修改了FirstName或LastName,FullName的值也会被改变。

class Person : ObservableObject
{
    private string _firstName;

    public string FirstName // 无忌   名
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(FullName));
        }
    }

    private string _lastName;

    public string LastName // 张    姓
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged(nameof(LastName), nameof(FullName));
        }
    }

    public string FullName //  张无忌  姓名
    {
        get
        {
            return FirstName + " " + LastName;
        }
    }

}

通知所有属性值改变

实参为空或空字符串,通知数据源的所有的属性的值发生了变化。

OnPropertyChanged(string.Empty);
OnPropertyChanged((string)null);

案例:Name被修改,UI未更新;Age被修改,UI未更新;Email被修改,此时会通知UI Name,Age,Email的值发生了变化,此时,UI才会显示出先前被修改后的Name和Age的新值。

我们也可以发现:属性=value与通知UI更新并不需要一定要同时出现在setter中,我们在想刷新UI时,随时调用OnPropertyChanged()即可。

class Person : ObservableObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    private string _email;
    public string Email
    {
        get => _email;
        set
        {
            _email = value;
            OnPropertyChanged("");
        }
    }
}

取消不必要的通知提高程序性能

当我们修改源属性的值时,如果新值和原值相同,那么我们就没必要将新值赋予给属性,这样能免去不必要的UI刷新,这对提高程序的性能有很大帮助。假设我们开发一个实时显示气温的App,我们每秒采集一次温度刷新UI,但气温是个缓慢的渐变量,可能连续采集100次的气温都相同,那么我们就能减少99次不必要的UI刷新。

TrumpX.Toolkit.Mvvm提供了相应的Api,该Api能自动判断新值和旧值是否相等,若相等则不为属性赋值且不通知UI刷新。因为属性分为back-field property和logic property,所以TrumpX.Toolkit.Mvvm有两个此类Api,分别用于操作上述两种类型的属性。

back-field property

protected bool SetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null);

field是属性的字段

newValue是新值value,SetProperty内部进行判等决定是否更新属性

comparer比较器,为null时采用默认的比较器(从Object继承的bool Equals(Object obj))判等,但可自定义比较器使用自定义的规则判等

class TempertureMonitor : ObservableObject
{
    public TempertureMonitor()
    {
        PropertyChanged += TempertureMonitor_PropertyChanged;
    }

    private void TempertureMonitor_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        Console.WriteLine($"属性{e.PropertyName}的值变化成{sender.GetType().GetProperty($"{e.PropertyName}").GetValue(sender)}...");
    }

    private double _currentTemperture;
    public double CurrentTemperture
    {
        get => _currentTemperture;
        set
        {
            SetProperty(ref _currentTemperture, value);
        }
    }
}

logic property

protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null);

oldValue旧值

newValue新值,SetProperty内部进行判等决定是否更新属性

callback是具有一个参数的回调,此回调是Logic Property的Setter的赋值逻辑,在SetProperty内被调用,实参是newValue,仅新值与旧值不相等时才会被执行

comparer比较器,为null时采用默认的比较器(从Object继承的bool Equals(Object obj))判等,但可自定义比较器使用自定义的规则判等

class Person : ObservableObject
{
    public Person()
    {
        PropertyChanged += Person_PropertyChanged;
    }

    private void Person_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "FullName")
        {
            MessageBox.Show($"属性{e.PropertyName}的值变化成{sender.GetType().GetProperty($"{e.PropertyName}").GetValue(sender)}...");
        }
    }

    private string _firstName;
    private string _lastName;
    public string FirstName { get => _firstName; } // 无setter
    public string LastName { get=>_lastName; } // 无setter

    public string FullName
    {
        get => FirstName + " " + LastName;
        set
        {
            SetProperty(FullName, value, (v) =>
            {
                string[] subs = v.Split(' ');
                _firstName = subs[0];
                _lastName = subs[1];
                OnPropertyChanged(nameof(FirstName), nameof(LastName)); // 不相等才会调用回调方法,才会执行这行通知代码
            });
        }
    }
}

自定义比较器

上述的两个API都有参数IEqualityComparer<T> comparer,支持使用自定义判等器进行判等决定是否刷新UI.

public class ModelEqualityCompare : EqualityComparer<TModel>
{
    public override bool Equals(TModel x, TModel y)
    {
        return x.Equals(y);
    }

    public override int GetHashCode(TModel obj)
    {
        return EqualityComparer<TModel>.Default.GetHashCode();
    }
}

封装无通知机制的Model

开发应用程序都会复用底层的一些Model,但是底层开发不会也不应该考虑上层使用者,比如给WPF使用,所以Model一般都不会继承INotifyPropertyChanged。Microsoft.Toolkit.Mvvm提供了利用无通知的Model快速在ViewModel中封装一个具有通知能力的数据源的Api,其核心思想就是在封装时,用Model实例代替back-field来存储属性的值,并且加入了判等决定是否更新属性及刷新UI,回调方法决定setter的为属性赋值的逻辑。

SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null) where TModel : class

oldValue:原值,即model中相应的属性的值

newValue: 新值,即setter的value

model:Model实例

callback:logic peoperty的setter逻辑,参数是TModel model和T newValue,一般是将newValue赋值给model的相应属性

comparer: 原值和新值的比较器,默认调用从Object继承的Equals,但可自定义比较器实现判等规则

propertyName: 通知的源属性名称,默认是setter的属性,也可以用nameof显示传递属性名称

model

class Student // 无通知机制的Model
{
    public string Name { get; set; }
}

viewmodel

class ObservableStudent : ObservableObject // 具有通知机制的ViewModel,可以与UI双向绑定
{
    private Student _student;
    
    public ObservableStudent(Student student) {
        _student = student;
    }
    
    public string Name {
        get => _student.Name;
        set {
            SetProperty(_student.Name, value, _student, (model, newValue) => model.Name = newValue);
        }
    }
}

SetProperty取舍

protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null);
SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null) where TModel : class

后者是前者的一个特殊情况,即后者完全用前者替代。但后者是针对封装无通知机制的Model特供的API,书写和运行效率略优于前者。

监视Task属性

开发者可以预先创建有若干Task类型的属性的数据源,并将Task类型的属性与目标属性绑定;当需要被监听的任务被创建后,将任务实例的引用赋值给数据源Task类型的属性,这样就能通过UI监控任务的进度和结果。

private bool SetPropertyAndNotifyOnCompletion<TTask>(ITaskNotifier<TTask> taskNotifier, TTask newValue, Action<TTask> callback, [CallerMemberName] string propertyName = null) where TTask : Task
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T> taskNotifier, Task<T> newValue, Action<Task<T>> callback = null, [CallerMemberName] string propertyName = null)

共计两个API,一个用于无返回值的任务,一个用于有返回值的任务。

taskNotifier : 包含一个Task属性的 TaskNotifier,TaskNotifier可以隐士转换成Task。

newValue : 需要被监控的Task实例的引用。

callback : 带有一个参数的回调方法,实参是newValue,在newValue完成后执行。

dll

// 推荐全局单例,存放与UI关联的Task属性。哪一个任务的引用赋值到此单例的Task属性,哪一个任务就被监控。
public class MonitorTasks : ObservableObject
{
    public static MonitorTasks Instance = new MonitorTasks();
    private MonitorTasks() { }
    private TaskNotifier<string> _myTask;

    public Task<string> MyTask
    {
        get => _myTask;
        set => SetPropertyAndNotifyOnCompletion(ref _myTask, value);
    }
}

exe

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <StackPanel.Resources>
        <local:TaskResultConvertrt x:Key="Trc"/>
        <local:TaskStatusConverter x:Key="Tsc"/>
    </StackPanel.Resources>
    <StackPanel Orientation="Horizontal" Margin="5" >
        <TextBlock Text="结果:"/>
        <TextBox Width="150" BorderBrush="Aqua" Text="{Binding Path=MyTask ,Converter={StaticResource Trc}}"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal" Margin="5">
        <TextBlock Text="进度:"/>
        <TextBox Width="150" BorderBrush="Aqua" Text="{Binding Path=MyTask, Converter={StaticResource Tsc}}"/>
    </StackPanel>
    <Button Content="开启任务" Width="150" HorizontalAlignment="Left" Click="ButtonBase_OnClick" Margin="15,5,5,5"/>
</StackPanel>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = MonitorTasks.Instance;
    }

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        MonitorTasks.Instance.MyTask = IsHitTestVisible();
        await MonitorTasks.Instance.MyTask;

        async Task<string> IsHitTestVisible()
        {
            await Task.Delay(1000);
            return DateTime.Now.ToString(CultureInfo.InvariantCulture);
        }
    }
}


public class MonitorTasks : ObservableObject
{
    private TaskNotifier<string> _myTask;

    public Task<string> MyTask
    {
        get => _myTask;
        set => SetPropertyAndNotifyOnCompletion(ref _myTask, value);
    }
}

class TaskResultConvertrt:IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Task<string> task)
        {
            if (task.IsCompleted)
            {
                return task.Result;
            }
            else if(task.IsFaulted)
            {
                return "任务失败...";
            }
            else if (task.IsCanceled)
            {
                return "任务已取消...";
            }
            else
            {
                return "正在计算中...";
            }
        }
        else
        {
            return "还未指派任务";
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}


class TaskStatusConverter:IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Task<string> task)
        {
            return task.Status.ToString();
        }
        else
        {
            return "还未指派任务";
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

17点13分 2022年4月10日

检查属性是否存在

ObservableObject.EnableVerifyPropertyName = true;

ObservableObject.EnableVerifyPropertyName = false;

private int _age;

public int Age {
    get { return _age; }
    set {
        _age = value;
        OnPropertyChanged("age"); // “笔误”,传错属性名,无法通知Age值改变
    }
}

ObservableObject所有通知属性值变化的方法,都会调用void VerifyPropertyName(string propertyName)检查开发者传递的属性名称对应的属性在数据源中是否存在,不存在时会抛出异常。这让开发者很容易发现自己的"笔误",如上述的代码,误将Age写成age。

但检查属性是否存在会使用反射,导致程序的性能受损,ObservableObject提供了开关属性EnableVerifyPropertyName,true开启检查功能,false关闭检查功能,默认关闭。开发者应当在开发期开启,在正式发布的产品关闭。

标签:教程,string,MvvmToolkit,get,Community,private,value,public,属性
来源: https://www.cnblogs.com/LiuwayLi/p/16586921.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有