简体   繁体   English

以类型安全的方式处理PropertyChanged

[英]Handling PropertyChanged in a type-safe way

There have been plenty of articles about how to use reflection and LINQ to raise PropertyChanged events in a type-safe way, without using strings. 有很多文章关于如何使用反射和LINQ以类型安全的方式引发PropertyChanged事件,而不使用字符串。

But is there any way to consume PropertyChanged events in a type-safe manner? 但有没有办法以类型安全的方式使用 PropertyChanged事件? Currently, I'm doing this 目前,我正在这样做

void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "Property1":
            ...
        case "Property2":
            ...

        ....               
    }
}

Is there any way to avoid hard-coding strings in a switch statement to handle the different properties? 有没有办法避免在switch语句中对字符串进行硬编码来处理不同的属性? Some similar LINQ- or reflection-based approach? 一些类似LINQ或基于反射的方法?

Let's declare a method that can turn a lambda expression into a Reflection PropertyInfo object ( taken from my answer here ): 让我们声明一个方法,可以将lambda表达式转换为Reflection PropertyInfo对象( 取自我的答案 ):

public static PropertyInfo GetProperty<T>(Expression<Func<T>> expr)
{
    var member = expr.Body as MemberExpression;
    if (member == null)
        throw new InvalidOperationException("Expression is not a member access expression.");
    var property = member.Member as PropertyInfo;
    if (property == null)
        throw new InvalidOperationException("Member in expression is not a property.");
    return property;
}

And then let's use it to get the names of the properties: 然后让我们使用它来获取属性的名称:

void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == GetProperty(() => Property1).Name)
    {
        // ...
    }
    else if (e.PropertyName == GetProperty(() => Property2).Name)
    {
        // ...
    }
}

Unfortunately you can't use a switch statement because the property names are no longer compile-time constants. 遗憾的是,您不能使用switch语句,因为属性名称不再是编译时常量。

With C# 6.0 you can use nameof . 使用C#6.0,您可以使用nameof You can also reference a class' property without creating an instance of that class. 您还可以在不创建该类实例的情况下引用类的属性。

void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case nameof(ClassName.Property1):
            ...
        case nameof(ClassName.Property2):
            ...

        ....               
    }
}

A recent solution I have come up with is to encapsulate the event dispatch logic into a dedicated class. 我提出的最新解决方案是将事件调度逻辑封装到专用类中。

The class has a public method called Handle which has the same signature as the PropertyChangedEventHandler delegate meaning it can be subscribed to the PropertyChanged event of any class that implements the INotifyPropertyChanged interface. 该类有一个名为Handle的公共方法,它与PropertyChangedEventHandler委托具有相同的签名,这意味着它可以订阅实现INotifyPropertyChanged接口的任何类的PropertyChanged事件。

The class accepts delegates like the often used DelegateCommand used by most WPF implementations meaning it can be used without having to create subclasses. 该类接受委托,就像大多数WPF实现使用的常用DelegateCommand一样,这意味着它可以在不必创建子类的情况下使用。

The class looks like this: 这个类看起来像这样:

public class PropertyChangedHandler
{
    private readonly Action<string> handler;
    private readonly Predicate<string> condition;
    private readonly IEnumerable<string> properties;

    public PropertyChangedHandler(Action<string> handler, 
        Predicate<string> condition, IEnumerable<string> properties)
    {
        this.handler = handler;
        this.condition = condition;
        this.properties = properties;
    }

    public void Handle(object sender, PropertyChangedEventArgs e)
    {
        string property = e.PropertyName ?? string.Empty;

        if (this.Observes(property) && this.ShouldHandle(property))
        {
            handler(property);
        }
    }

    private bool ShouldHandle(string property)
    {
        return condition == null ? true : condition(property);
    }

    private bool Observes(string property)
    {
        return string.IsNullOrEmpty(property) ? true :
            !properties.Any() ? true : properties.Contains(property);
    }
}

You can then register a property changed event handler like this: 然后,您可以注册属性更改事件处理程序,如下所示:

var eventHandler = new PropertyChangedHandler(
    handler: p => { /* event handler logic... */ },
    condition: p => { /* determine if handler is invoked... */ },
    properties: new string[] { "Foo", "Bar" }
);

aViewModel.PropertyChanged += eventHandler.Handle;

The PropertyChangedHandler takes care of checking the PropertyName of the PropertyChangedEventArgs and ensures that handler is invoked by the right property changes. PropertyChangedHandler负责检查PropertyNamePropertyChangedEventArgs并确保通过正确的属性更改调用handler

Notice that the PropertyChangedHandler also accepts a predicate so that the handler delegate can be conditionally dispatched. 请注意,PropertyChangedHandler还接受谓词,以便可以有条件地调度处理程序委托。 The class also allows you to specify multiple properties so that a single handler can be bound to multiple properties in one go. 该类还允许您指定多个属性,以便可以一次将单个处理程序绑定到多个属性。

This can easily be extended using some extensions methods for more convenient handler registration which allows you to create the event handler and subscribe to the PropertyChanged event in a single method call and specify the properties using expressions instead of strings to achieve something that looks like this: 这可以使用一些扩展方法轻松扩展,以便更方便地处理程序注册,这允许您在单个方法调用中创建事件处理程序并订阅PropertyChanged事件,并使用表达式而不是字符串指定属性,以实现如下所示的内容:

aViewModel.OnPropertyChanged(
    handler: p => handlerMethod(),
    condition: p => handlerCondition,
    properties: aViewModel.GetProperties(
        p => p.Foo,
        p => p.Bar,
        p => p.Baz
    )
);

This is basically saying that when either the Foo , Bar or Baz properties change handlerMethod will be invoked if handlerCondition is true. 这基本上是说,当任何一个FooBarBaz性质改变handlerMethod如果将被调用handlerCondition是真实的。

Overloads of the OnPropertychanged method are provided to cover different event registration requirements. 提供了OnPropertychanged方法的重载以涵盖不同的事件注册要求。

If, for example, you want to register a handler that is called for any property changed event and is always executed you can simply do the following: 例如,如果要注册为任何属性更改事件调用的处理程序并且始终执行,则可以执行以下操作:

aViewModel.OnPropertyChanged(p => handlerMethod());

If, for example, you want to register a handler that is always executed but only for a single specific property change you can do the following: 例如,如果要注册始终执行但仅针对单个特定属性更改的处理程序,则可以执行以下操作:

aViewModel.OnPropertyChanged(
    handler: p => handlerMethod(),
    properties: aViewModel.GetProperties(p => p.Foo)
);

I have found this approach very useful when writing WPF MVVM applications. 我发现这种方法在编写WPF MVVM应用程序时非常有用。 Imagine you have a scenario where you want to invalidate a command when any of three properties change. 想象一下,当有三个属性中的任何一个发生更改时,您希望使命令无效。 Using the normal method you would have to do something like this: 使用常规方法,您必须执行以下操作:

void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "Foo":
        case "Bar":
        case "Baz":
            FooBarBazCommand.Invalidate();
            break;
        ....               
    }
}

If you change the name of any of the viewModel properties you will need to remember to update the event handler to select the correct properties. 如果更改任何viewModel属性的名称,则需要记住更新事件处理程序以选择正确的属性。

Using the PropertyChangedHandler class specified above you can achieve the same result with the following: 使用上面指定的PropertyChangedHandler类,您可以使用以下方法获得相同的结果:

aViewModel.OnPropertyChanged(
    handler: p => FooBarBazCommand.Invalidate(),
    properties: aViewModel.GetProperties(
        p => p.Foo,
        p => p.Bar,
        p => p.Baz
    )
);

This now has compile-time safety so If any of the viewModel properties are renamed the program will fail to compile. 这现在具有编译时安全性,因此如果重命名任何viewModel属性,程序将无法编译。

Josh Smith的MVVM Foundation包含一个PropertyObserver类,可以执行您想要的操作。

I avoid the switch by combining the command pattern and some Expression logic. 我通过组合命令模式和一些表达式逻辑来避免切换。 You encapsulate the case-action in a command. 您将case-action封装在命令中。 I'll illustrate this using a Model View Controller structure. 我将使用模型视图控制器结构来说明这一点。 real world code - WinForms,but it is the same idea 现实世界的代码 - WinForms,但它是一样的想法

the example loads a tree in a view, when the Tree property is set in the model. 当在模型中设置Tree属性时,该示例在视图中加载树。

a custom ICommand 自定义ICommand

void Execute();
string PropertyName  { get;  }

Concrete Command 具体命令

 public TreeChangedCommand(TreeModel model, ISelectTreeView selectTreeView,Expression<Func<object>> propertyExpression )
    {
        _model = model;
        _selectTreeView = selectTreeView;

        var body = propertyExpression.Body as MemberExpression;
        _propertyName = body.Member.Name;

    }

constructor controller 构造函数控制器

 //handle notify changed event from model
  _model.PropertyChanged += _model_PropertyChanged;
  //init commands
  commands = new List<ICommand>();
  commands.Add(new TreeChangedCommand(_model,_mainView,()=>_model.Tree));

propertyChanged handler propertyChanged处理程序

void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    //find the corresponding command and execute it. (instead of the switch)
    commands.FirstOrDefault(cmd=>cmd.PropertyName.Equals(e.PropertyName)).Execute();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM