2014-02-23 24 views
4

我正在使用MVVM的Windows Phone应用程序,但是我正在努力为MVVM的实现寻求需要从模型类格式化以显示在视图中的属性。MVVM - 如何使格式化的属性保持最新?

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

public class Person { 

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

} 

有是从本地保存的文件加载Person对象的列表,我要显示一个列表页面上的人员名单,然后让一个人的用户水龙头导航到细节页面,其中有关于此人的更多详细信息。

在列表页,我想说明人的生日定为“生日:1980年2月22日”(其中“1980年2月22日”是人的格式化Birthday

在详细信息页面,我想以不同的格式显示此人的生日:“Eric的生日是2/22/1980”(其中“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已经更新了最新BirthdayForListBirthdayForDetails属性,同时在本地保存Person

我想保持一切都很简单,无需每次需要更新值时手动更新Person对象的保存列表和PersonViewModel对象的列表。

这样做的最好方法是什么?我是否应该使用PersonViewModel对象的ObservableCollection?另外,我在这个网站上的几个地方看过,模型类不应该实现NotifyPropertyChanged。 (注意:我已经简化了这个问题的问题,您应该假设我需要许多其他方法来在整个应用程序中格式化Birthday属性,以及需要格式化的模型类中的其他属性。不同在不同的页面)

回答

0

你有两个选择:

  1. 只是格式化XAML的日期。 Here is an example

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

你不应该做的是将格式存储在视图模型中。数据的格式只是一个视图/演示文稿。所以,上述方法的好处是你不需要仅仅因为格式化而保留单独的列表。

+0

感谢您的回答,鲍勃。对于第一种选择,我将如何实现“['User']'的生日是['Birthday']”?我想为此使用一个“TextBlock”。对于第二种选择,我关心这将如何扩展。我需要创建几十个转换器。实际的应用程序比上面的示例更复杂,需要格式化更多的属性。另外,为什么我不想在视图模型中存储格式? – epaps

+0

您可以使用MultiBinding实现第一个选项。但是,这在WP上不可用。使用Cimbalino Windows Phone工具包的MultiBindingBehavior获得相同的结果。 http://www.pedrolamas.com/2013/05/17/cimbalino-windows-phone-toolkit-multibindingbehavior/ – sacha

2

转换器和XAML格式化是很好的解决方案,但有时你只需要在ViewModel中完成。通常情况下,您需要实施INotifyPropertyChanged并在计算属性的任何依赖关系发生变化时引发PropertyChanged事件。

管理这些依赖关系是一个王室的痛苦......事实上,我厌倦了这个问题,我可以让你在你的ViewModel中执行这些类型的计算属性MVVM framework called Catwalk。如果你使用的框架,你可以有一个像

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

代码凡模型的基类会自动提出一个PropertyChanged事件的BirthdayForDetails如果任何名字或生日变化。你只需要从ObservableModel和生日继承&名称必须像

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

观察的属性如果你决定尝试一下,让我知道你在想什么。

+0

在模型中是'Name'而在视图模型中是'BirthdayForDetails'?我是不是还需要管理一个“Person”视图模型? – epaps

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的转换器(即现在通知属性更改)并移除您计算的属性,或者您坚持使用属性并找到标记相关属性的更简单方法。

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

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

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

+0

我只是看了一下Matts的答案,Catwalk框架似乎也提供了一个非常优雅的解决方案。 – Robert

5

为什么不简单地在xaml中完成整个事情并且不使用“计算属性”?

<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,提高在二传手的事件。

编辑:我也建议使用像MVVM light一个框架,这样你就可以使用ViewModelObservableObject基类的对象,只是能够使用他们的INotifyPropertyChanged

public string FirstName 
{ 
    get { return _firstName; } 
    set 
    { 
     _firstName = value; 
     RaisePropertyChanged(() => FirstName); 
    } 
} 
private string _firstName; 
+0

它看起来像Windows Phone不支持'MultiBinding'。有任何想法吗? – epaps

+0

没有尝试过它,你可以使用这些解决方案之一,我认为你应该能够编写 A)一个更通用的格式转换器或 B)应用的StringFormat的“子” -Bindings HTTP之一: //www.scottlogic.com/blog/2010/05/10/silverlight-multibinding-solution-for-silverlight-4.html http://www.codeproject.com/Articles/286171/MultiBinding-in-Silverlight -5 – Staeff

+0

或者使用计算的属性,但在两个引用者中都包含'RaisePropertyChanged((=)=> BirthDayForList) – Staeff

0

实现你可以简单地请在您的“人物”属性的设置器中调用PropertyChanged方法

这样的

private Person myPerson; 
public Person MyPerson 
{ 
    get { return myPerson; } 
    set 
    { 
     myPerson = value; 
     PropertyChanged("MyPerson"); 
     PropertyChanged("BirthdayForList"); 
     PropertyChanged("BirthdayForDetails"); 
    } 
} 
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