MVVM - 如何使格式化的属性保持最新?

时间:2014-02-23 02:23:02

标签: c# mvvm windows-phone-8 windows-phone

我正在使用一个使用MVVM的Windows Phone应用程序,但我正在努力实现MVVM,以便需要从模型类格式化以在视图中显示。

假设我有一个名为Person的简单模型类。

public class Person {

   public string Name { get; set; }
   public DateTime Birthday { get; set; }

}

有一个从本地保存的文件加载的Person个对象列表,我想在列表页面上显示一个人员列表,然后让用户点击一个人来导航到一个细节有关此人的详细信息的页面。

在列表页面上,我想将此人的生日显示为“生日:2/22/1980”(其中“2/22/1980”是该人的格式Birthday

在详细信息页面上,我想以不同的格式显示此人的生日:“Eric的生日是1980年2月22日”(其中“Eric”是该人的Name和“2/22/1980” “是该人的格式Birthday)。

通常情况下,我会创建一个正确格式化Birthday的视图模型:

public class PersonViewModel {

   private Person person;

   public PersonViewModel(Person person) {
      this.person = person;
   }

   public string BirthdayForList {
      get {
         return "Birthday: " + person.Birthday.ToString("ddd", CultureInfo.CurrentCulture);
      }
   }

   public string BirthdayForDetails {
      get {
         return person.Name + "'s birthday is " + person.Birthday.ToString("ddd", CultureInfo.CurrentCulture);
      }
   }

}

为了在UI中显示这些值,我将创建这些视图模型对象的集合(并将它们绑定到视图):

ObservableCollection<PersonViewModel> Items

现在,如果更新某人的生日(详细信息页面上的某个位置),我该怎么办,并确保Items已使用最新BirthdayForList和{{1}更新}属性,同时在本地保存BirthdayForDetails

我希望保持一切简单,每次需要更新值时,不必手动更新已保存的Person对象列表和Person对象列表。

这样做的最佳方法是什么?我应该使用PersonViewModelObservableCollection个对象吗?另外,我在本网站的几个地方读过模型类不应该实现PersonViewModel

(注意:我已经简化了这个问题的问题。您应该假设我还需要许多其他方法来格式化整个应用程序中的NotifyPropertyChanged属性以及模型类中需要的其他属性在不同的页面上进行不同的格式化。)

6 个答案:

答案 0 :(得分:5)

为什么不要简单地在xaml中完成所有事情并且不要使用&#34;计算的属性&#34;?

<TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="{}{0}'s birthday is {1:ddd}">
            <Binding Path="Person.Name">
            <Binding Path="Person.BirthDay">
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

然后你需要做的就是在Person类中实现INotifyPropertyChanged并在setter中引发事件。

编辑:我还建议您使用MVVM light之类的框架,以便为对象使用ViewModelObservableObject基类,并且能够使用使用INotifyPropertyChanged

的实现
public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        RaisePropertyChanged(() => FirstName);
    }
}
private string _firstName;

答案 1 :(得分:2)

转换器和XAML格式化在它们工作时是很好的解决方案,但有时你只需要在ViewModel中完成它。通常,您需要实现INotifyPropertyChanged并在其任何依赖项更改时为计算属性引发PropertyChanged事件。

管理这些依赖关系是一个很大的痛苦......事实上,我对这个问题非常厌倦,我MVVM framework called Catwalk允许你在ViewModel中执行这些类型的计算属性。如果您使用框架,则可以使用

之类的代码
  public string BirthdayForDetails
  {
    get
    {
      return Calculated(() => this.Name + "'s birthday is " + this.Birthday.ToString("ddd", CultureInfo.CurrentCulture));
    }
  }

如果Name或Birthday更改,model的基类将自动为BirthdayForDetails引发PropertyChanged事件。你只需要继承ObservableModel和Birthday&amp;名称必须是可观察的属性,如

  public string Name
  {
    get { return GetValue<string>(); }
    set { SetValue(value); }
  }

如果您决定尝试一下,请告诉我您的想法。

答案 2 :(得分:0)

您有两种选择:

  1. 只需在XAML中格式化日期即可。 Here is an example

  2. 对于更复杂的转化,you can use converters

  3. 您不应该做的是将格式存储在视图模型中。数据格式仅是视图/演示文稿的关注点。因此,上述方法的好处是您不需要仅仅因为格式化而保留单独的列表。

答案 3 :(得分:0)

将所有PersonViewModel放在ObservableCollection中只能解决在从集合中添加/删除新的PersonViewModel时UI需要更新的问题。

然而,这并没有解决问题,集合中的一个对象发生了变化。因此,如果列表中第一个人的出生日期发生变化,则集合保持不变。

所以你需要达到的目的是通知UI这个集合中的一个对象发生了变化。 您可以通过让ViewModel实现INotifyPropertyChanged或从DependencyObject派生它(讨论更好的解决方案:INotifyPropertyChanged vs. DependencyProperty in ViewModel)来实现。

我建议使用INotifyPropertyChanged。实现该接口将为您提供PropertyChanged事件。每当您的某个属性发生变化时,您需要引发该事件。不幸的是,这还要求您在ViewModel中创建其他属性,以便在发生更改时收到通知。

最简单的(绝对不是最好的)方法是为每个依赖的属性调用OnPropertyChanged。

public class PersonViewModel : INotifyPropertyChanged
{
    private Person person;

    public PersonViewModel(Person person)
    {
        this.person = person;
    }

    public DateTime Birthday
    {
        get { return person.Birthday; }
        set 
        { 
            person.Birthday = value;
            OnPropertyChanged("Birthday");
            OnPropertyChanged("BirthdayForList");
            OnPropertyChanged("BirthdayForDetails");
        }
    }

    public string Name
    {
        get { return person.Name; }
        set 
        { 
            person.Name = value; 
            OnPropertyChanged("Name");
            OnPropertyChanged("BirthdayForDetails");
        }
    }

    public string BirthdayForList
    {
        get
        {
            return "Birthday: " + Birthday.ToString("ddd", CultureInfo.CurrentCulture);
        }
    }

    public string BirthdayForDetails
    {
        get
        {
            return Name + "'s birthday is " + Birthday.ToString("ddd", CultureInfo.CurrentCulture);
        }
    }

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

我已经提到过这个解决方案不是最好的。您很可能会添加另一个依赖属性,您必须记住将OnPropertyChanged添加到您依赖的属性中。

因此,您可以为ViewModel使用转换器(现在通知属性更改)并删除计算出的属性,或者您坚持使用属性并找到更简单的方法来标记相关属性。

值得庆幸的是,有人已经解决了依赖/计算属性的问题。 googeling“INotifyPropertyChanged依赖属性”时会出现很多结果。我真正喜欢的就是这个(Handling INotifyPropertyChanged for dependant properties),因为它使用干净且可读的属性来标记依赖关系。

此外还有几个MVVM框架,其中包括针对所述问题的解决方案。

我希望其中一个建议的解决方案可以帮助您解决问题。

答案 4 :(得分:0)

您只需在“person”属性的设置器中调用PropertyChanged方法

即可 像这样

private Person myPerson;
public Person MyPerson
{
    get { return myPerson; }
    set
    {
        myPerson = value;
        PropertyChanged("MyPerson");
        PropertyChanged("BirthdayForList");
        PropertyChanged("BirthdayForDetails");
    }
}

答案 5 :(得分:0)

您可以使用嵌入在TextBlock元素中的多个元素的组合

<TextBlock Foreground="DarkGray" VerticalAlignment="Bottom" Margin="0,0,0,8"><Run Text="total length "/><Run Text="{Binding TotalHours}" FontSize="48"/><Run Text="h "/><Run Text=":" FontSize="48"/><Run Text="{Binding TotalMinutes}" FontSize="48"/><Run Text="m "/></TextBlock>

有点像这个样本https://stackoverflow.com/a/8130843/3214300