2014-02-23 31 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