快速生成WPF中的ViewModel属性?

8

阅读完这篇文章后,我在我的PersonViewModel类中有以下代码:

public Jurisdiction CountryResidence
{
    get
    {
        return Model.CountryResidence;
    }
    set
    {
        if (Model.CountryResidence == value)
            return;
        else
        {
            Model.CountryResidence = value;
            base.OnPropertyChanged("CountryResidence");
        }
    }
}

public Jurisdiction CountryBirth
{
    get
    {
        return Model.CountryBirth;
    }
    set
    {
        if (Model.CountryBirth == value)
            return;
        else
        {
            Model.CountryBirth = value;
            base.OnPropertyChanged("CountryBirth");
        }
    }
}

我还有CountryDomiciledCountryPassportLegalJurisdiction,它们都具有相同的格式。同样地,我有很多String 属性,它们都共享相同的格式。
这导致了大量重复的代码!但是,我无法想出如何使其更简洁。
是否有一种更好的方法来生成这些属性并保持它们的强类型性?

1
给“同样的代码”这个词汇造了一个新词,加1分!我一定要用它。 - Zann Anderson
2
如果您在使用C# 5,可以通过新的属性减少一些样板代码。请参见:http://bhrnjica.net/2012/03/18/new-feature-in-c-5-0-callermembername/ - Vlad
2
可能是重复问题 https://dev59.com/bHM_5IYBdhLWcg3wlEPO - GazTheDestroyer
Vlad的链接和GazTheDestroyer的重复问题链接是我认为最好的答案。 - Oliver
@Oliver:你可以将 OnPropertyChanged 的实现移动到一个公共的超类中。 :) 这样你的ViewModel就会有更少的代码。编辑:哦,你已经在做了。 - Vlad
显示剩余3条评论
6个回答

11

我使用Visual Studio的片段,它可以为我生成带有后备存储和事件引发的属性。只需创建一个名为propchanged(或其他名称,如果您愿意)的xml文件,并包含以下内容:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>propchanged</Title>
      <Shortcut>propchanged</Shortcut>
      <Description>Code snippet for property (with call to OnPropertyChanged) and backing field</Description>
      <Author>lazyberezovsky</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <ToolTip>Property type</ToolTip>
          <Default>string</Default>
        </Literal>
        <Literal>
          <ID>property</ID>
          <ToolTip>Property name</ToolTip>
          <Default>MyProperty</Default>
        </Literal>
        <Literal>
          <ID>field</ID>
          <ToolTip>The variable backing this property</ToolTip>
          <Default>myVar</Default>
        </Literal>
      </Declarations>
      <Code Language="csharp">
        <![CDATA[private $type$ $field$;

    public $type$ $property$
    {
        get { return $field$;}
        set 
    {
       if ($field$ == value)
          return;

       $field$ = value;
       OnPropertyChanged("$property$");
    }
    }
    $end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

将其放入文件夹 C:\Users\YourName\Documents\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets\ 中。

接下来,我从一些基本ViewModel继承我的ViewModel,该ViewModel实现INotifyPropertyChanged接口并提供受保护的方法OnPropertyChanged用于生成“PropertyChanged”事件。

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
    }
}

现在在 Visual Studio 中键入 propchanged,它会要求您输入属性类型和名称,并为您生成代码。

public class PersonViewModel : ViewModel
{
    // type here 'propchanged' (or other shortcut assigned for snippet)
}

更新:

另一个选择是使用AOP框架(如PostSharp)生成代码。在这种情况下,代码将在编译期间生成并添加(因此您的类将保持干净)。这里是通过PostSharp属性实现INotifyPropertyChanged的示例:

[Notify]
public class PersonViewModel
{
    public Jurisdiction CountryResidence { get; set; }
    public Jurisdiction CountryBirth { get; set; }
}

4

更新: NotifyPropertyWeaver已被废弃,现在以PropertyChanged.Fody的形式继续存在。这是一种完美的解决方案,它只在编译时进行操作。

以下内容将为您省略代码并减少麻烦:NotifyPropertyWeaver

使用此工具,您可以在不添加任何INotifyPropertyChanged相关代码的情况下实现属性,构建步骤会自动处理后续工作。

这是一个简单的项目(也可通过Nuget获取),它会自动向实现INotifyPropertyChanged接口的任何类的属性中注入OnPropertyChanged回调。它只在编译时完成操作(因此没有运行时负担),您的代码可以只有自动实现属性(除非您使用单独的支持对象)。

它甚至包括值相等检查,因此涵盖了全部逻辑。我还未测试过手动实现的属性,但值得一试。

编辑: 我现在已经测试过了,它运行良好:手动实现的属性将“一切正常”。


作者在我的回答中发表了评论:“但是,如果可能的话,我更喜欢一种将共享功能保留在一个地方的方法,而不是为每个属性克隆它。” 您的答案如何满足这种需求?虽然我不会对您的帖子投反对票。 - EvAlex
@EvAlex - “将其放在一个地方”要求的目的是为了降低维护成本,这对所有代码都是如此。但是采用AOP方法后,开发人员不需要维护任何代码。AOP如何注入必要的代码在这方面并不重要。 - Peter Lillevold

2

这并不是你想要的答案,但是顺便说一下,通过反转逻辑测试,您可以每个属性节省两行代码:

public Jurisdiction CountryResidence
{
    get
    {
        return Model.CountryResidence;
    }
    set
    {
        if (Model.CountryResidence != value)
        {
            Model.CountryResidence = value;
            base.OnPropertyChanged("CountryResidence");
        }
    }
}

我会点个赞的,但它并没有实际回答问题,所以我不会这么做。把它作为评论会更好。 - Rachel
同意,从未打算将其作为完整答案。已经点赞了Dan的回答,使其成为最佳答案。 - GazTheDestroyer

1

我不知道有没有内置的方法,但是你可以记录一个宏来为你生成代码。我发现最简单的方法是开始录制一个宏,然后只使用键盘创建任何您想要的内容(您可以在这里找到有用的键盘快捷键列表)

例如,我有一个生成公共版本属性的宏,所以我只需输入private string _someValue;并按下我的宏,它就会生成带有属性更改通知的公共属性。

尽管如此,请记住,在MVVM中将整个Model暴露给View以简化和方便起见是完全有效的。因此,您可以仅为您的Model对象创建单个属性,而不是分别公开每个Model属性。

public Model SomeModel
{
    get
    {
        return Model;
    }
    set
    {
        if (Model == value)
            return;
        else
        {
            Model= value;
            base.OnPropertyChanged("SomeModel");
        }
    }
}

并像这样绑定到模型的属性:

<TextBox Text="{Binding SomeModel.SomeProperty}" />

1
为了获得一个“强类型”引用属性名称(这是为了启用重构所必需的),您可以使用表达式。将以下方法放在您的视图模型的基类中,或许会有帮助:
protected void RaisePropertyChanged<T>(Expression<Func<T>> property)
{
    var handler = PropertyChanged;
    if (handler == null) return;

    var propertyName = NotifyPropertyChangedHelper.GetPropertyNameFromExpression(property);
    handler(sender, new PropertyChangedEventArgs(propertyName));
}

在你的视图模型中,你将能够做到以下事情:
public Jurisdiction CountryResidence
{
    get { return Model.CountryResidence; }
    set 
    {         
        if (Model.CountryResidence == value)
            return;

        Model.CountryResidence = value;
        OnPropertyChanged(() => CountryResidence);
    }
}

现在,重构属性名称会自动被捕捉到,因为引用是针对实际属性的。

这解决了硬编码属性名称的痛点,尽管仍需要4-5行样板代码。像notifypropertyweaverPostSharp这样的面向方面的方法真正消除了视图模型中的所有手动编码。


0
你试过使用 Visual Studio 代码片段,比如 prop 或者 propfull 吗?只需要输入它们并按两次 Tab 键即可。问题是 propfull 代码片段不会触发PropertyChanged事件。
但实际上应该有第三方代码片段可以完成这个任务。下面是我找到的一个链接:property snippet with INPC

谢谢,我以前没见过这些 - 它们看起来真的很不错。然而,如果可能的话,我更喜欢把共享功能放在一个地方,而不是为每个属性克隆它。 - Oliver
这并没有解决问题,因为你仍然需要复制代码。 - Dan Puzey
@Dan Puzey,问题中并没有提到代码重复的事情。“有没有更好的方法来生成这些属性并保持它们强类型?”我回答道:“有:代码片段”。 - EvAlex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接