【问题标题】:INotifyPropertyChanged custom attributeINotifyPropertyChanged 自定义属性
【发布时间】:2022-02-23 13:39:54
【问题描述】:

我喜欢对需要INotifyPropertyChanged 接口和自定义属性的完整属性进行一些不那么重复和浪费的编码。

背景

今天,为了在窗口中使用MVVM和动态更新值,我们需要做以下事情:

private string _SomeProp;
public string SomeProp
{
    get => _SomeProp;
    set
    {
        _SomeProp = value;
        OnPropertyChanged();
    }
}

public void OnPropertyChanged([CallerMemberName] string name = null) 
    => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

public event PropertyChangedEventHandler PropertyChanged;

建议和问题

自定义属性
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MyProject.Models;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class PropertyChangedAttribute : Attribute, INotifyPropertyChanged
{
    public PropertyChangedAttribute([CallerMemberName] string propertyName = null) 
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
    public event PropertyChangedEventHandler PropertyChanged;
}
将该自定义属性与属性一起使用:
[PropertyChanged]
public string SomeProp { get; set; }

所以基本上我不必为窗口中的每个字段创建完整的属性,而只需要简单的属性。

但由于某种原因,它不起作用,并且在调试时,编译器甚至没有进入自定义属性类。

更新

因此,在对该主题进行研究之后,属性CallerMemberName 在编译器级别进行处理,这意味着编译器正在寻找该属性,并且它本身将属性/方法/等...名称传递给使用该属性的方法属性。

所以基本上,如果不编辑编译器代码及其行为,这样的事情是不可能做到的。

【问题讨论】:

  • 您认为 PropertyChangedAttribute 是在哪里调用的?不在属性的set; 中。
  • 等一下,因为每次输入 set; 时,该事件都没有在集合内触发,所以该属性也不起作用?
  • 是的,所以,你不能走捷径。
  • 知道了。我也试过这样做:public string SomeProp { get; [PropertyChnged] set; } 但它什么也没做,对我来说它应该工作是有道理的,对吧?
  • 永远不要因为不知道某事而感到羞耻。我们每天都在学习。

标签: c#


【解决方案1】:

您可以使用实现INotifyPropertyChanged 的通用基类和为标记为PropertyChanged 的字段生成属性的源生成器。根据您的需要,您可能会找到一个现有的工具,或者您必须自己实现它。

一个类最终可能会像这样(我为自己编写的类支持一些属性以生成属性,例如 setter 可见性):

public partial class MyCLass : NotifyPropertyChanges
{
    [NotificationProperty(SetterVisibility = Visibility.Private)]
    private int count;
}

生成的代码可能如下所示:

partial class MyCLass
{
    [System.Runtime.CompilerServices.CompilerGenerated]
    public int Count
    {
        [System.Runtime.CompilerServices.CompilerGenerated]
        get => this.count;
        [System.Runtime.CompilerServices.CompilerGenerated]
        private set => SetValue(ref this.count, value, "Count");
    }
}

SetValue 是这个东西的“常见”实现:

protected bool SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
    if (EqualityComparer<T>.Default.Equals(field, value))
        return false;

    OnPropertyChanging(new PropertyChangingEventArgs(propertyName));
    field = value;
    OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    return true;
}

注意:如果项目 AFAIK (see this question) 中有任何 .xaml 文件,源代码生成器将无法工作,因此您必须将该逻辑放在单独的项目中。

您也可以按照 Rija 的建议将此方法与现有框架一起使用。

【讨论】:

  • 如果他必须从一个具有 setvalue 的类继承,那么生成该代码有什么意义呢?也可能有一个 get 值......他最终将编写一个 getter 和一个 setter 而不是一个字段和一个属性。无论项目中有什么,它都会起作用......同样的努力......更多报道
  • @L.Trabacchin 关键是要有一个类来处理所有接口的东西,并且在某一时刻,无论如何都必须处理属性,无论是在框架中,像这里的生成器或其他任何东西。
  • 当然,但是生成器更难编写,并且有时可能无法像您指出的那样工作。我宁愿完全放弃这个属性。只是一个基类(他最终还是会使用它)还有一个 GetValue ,它使用道具名称作为键将值存储在 Dictionary 中(也可以像你一样使用 ref 但它会成为多行代码)更简单,并且可以在任何地方工作......也可以使用并发字典实现线程安全......如果我们正在谈论前端开发,那么性能无论如何都不会引人注目......
  • 我的意思是你必须记住属性,所以必须记住使用 getvalue setvalue 是一样的。属性写一行,字段写一行,与getter写一行,setter写一行是一样的……
  • @L.Trabacchin 好的,现在我明白你的意思了。但无论如何,单个属性不会做任何事情,因为他必须引发 PropertyChanged 事件,因此您必须使用多个属性。
【解决方案2】:

尝试使用 MVVMLight Toolkit for WPF :

从 nuget 包安装 MVVMLight,

以下是使用 RaisedPropertyChanged 方法的示例:

xaml 代码:

<Window x:Class="TestMVVMLight.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestMVVMLight"
        mc:Ignorable="d"
        DataContext="{Binding Main, Source={StaticResource Locator}}"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <TextBox HorizontalAlignment="Left" Height="35" Margin="27,23,0,0" TextWrapping="Wrap" Text="{Binding FirstName}" VerticalAlignment="Top" Width="119"/>
        <TextBox HorizontalAlignment="Left" Height="35" Margin="171,23,0,0" TextWrapping="Wrap" Text="{Binding LastName}" VerticalAlignment="Top" Width="120"/>
        <TextBox HorizontalAlignment="Left" Height="35" Margin="329,23,0,0" TextWrapping="Wrap" Text="{Binding FullName, Mode=OneWay}" VerticalAlignment="Top" Width="291"/>
    </Grid>

</Window>

MainViewModel 代码:

using GalaSoft.MvvmLight;

namespace TestMVVMLight.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel() { }

        private string _firstname = "";
        private string _lastname = "";
        
        public string FirstName 
        { 
            get =>  this._firstname;
            set 
            {
                this._firstname = value;
                RaisePropertyChanged(() => FullName);
            }
        }

        public string LastName
        {
            get => this._lastname;
            set
            {
                this._lastname = value;
                RaisePropertyChanged(() => FullName);
            }
        }

        public string FullName => $"{FirstName} {LastName}";
      }
    }

【讨论】:

  • 感谢您的回答和时间,但我的目标是通过在属性上方添加属性来仅拥有属性而不是完整属性。
猜你喜欢
  • 2011-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-27
  • 2010-12-12
  • 2011-01-24
  • 1970-01-01
相关资源
最近更新 更多