2017-07-18 20 views
1

何種模式可用於確保在UI中連接多個源時更新屬性。使用MVVM更新WPF中的派生屬性

例如我有一個窗口標題的字符串屬性。它提供應用程序名稱(常量字符串),程序集版本(只讀字符串)以及根據用戶輸入加載的類型的實例屬性。

有沒有辦法讓標題屬性訂閱實例屬性,以便當實例加載時標題自動更新?

現在當食譜加載時,它會更新標題屬性。但是我想反過來讓配方不知道標題。它只是廣播它被加載,然後任何需要對正在加載的配方作出反應的東西都會孤立地處理事件。

什麼設計模式適合這個?

+2

Err ..你聽說過綁定嗎? INotifyPropertyChanged的?他們一起給「標題屬性訂閱實例屬性,以便當實例加載時標題自動更新」效果 – ASh

+0

我希望你不介意,但我稍微編輯了你的問題的標題,使其更符合我們的指導方針,並更密切地匹配你的實際問題。 –

回答

1

我在我的MVVM庫中使用以下類來允許屬性更改級聯到相關屬性。隨意使用它,如果你認爲這將是對你有用:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Text; 
using System.Threading.Tasks; 

namespace AgentOctal.WpfLib 
{ 
    public class PropertyChangeCascade<T> where T : ObservableObject 
    { 

     public PropertyChangeCascade(ObservableObject target) 
     { 
      Target = target; 

      Target.PropertyChanged += PropertyChangedHandler; 
      _cascadeInfo = new Dictionary<string, List<string>>(); 
     } 

     public ObservableObject Target { get; } 
     public bool PreventLoops { get; set; } = false; 

     private Dictionary<string, List<string>> _cascadeInfo; 

     public PropertyChangeCascade<T> AddCascade(string sourceProperty, 
                List<string> targetProperties) 
     { 
      List<string> cascadeList = null; 

      if (!_cascadeInfo.TryGetValue(sourceProperty, out cascadeList)) 
      { 
       cascadeList = new List<string>(); 
       _cascadeInfo.Add(sourceProperty, cascadeList); 
      } 

      cascadeList.AddRange(targetProperties); 

      return this; 
     } 

     public PropertyChangeCascade<T> AddCascade(Expression<Func<T, object>> sourceProperty, 
                Expression<Func<T, object>> targetProperties) 
     { 
      string sourceName = null; 
      var lambda = (LambdaExpression)sourceProperty; 

      if (lambda.Body is MemberExpression expressionS) 
      { 
       sourceName = expressionS.Member.Name; 
      } 
      else if (lambda.Body is UnaryExpression unaryExpression) 
      { 
       sourceName = ((MemberExpression)unaryExpression.Operand).Member.Name; 
      } 
      else 
      { 
       throw new ArgumentException("sourceProperty must be a single property", nameof(sourceProperty)); 
      } 

      var targetNames = new List<string>(); 
      lambda = (LambdaExpression)targetProperties; 

      if (lambda.Body is MemberExpression expression) 
      { 
       targetNames.Add(expression.Member.Name); 
      } 
      else if (lambda.Body is UnaryExpression unaryExpression) 
      { 
       targetNames.Add(((MemberExpression)unaryExpression.Operand).Member.Name); 
      } 
      else if (lambda.Body.NodeType == ExpressionType.New) 
      { 
       var newExp = (NewExpression)lambda.Body; 
       foreach (var exp in newExp.Arguments.Select(argument => argument as MemberExpression)) 
       { 
        if (exp != null) 
        { 
         var mExp = exp; 
         targetNames.Add(mExp.Member.Name); 
        } 
        else 
        { 
         throw new ArgumentException("Syntax Error: targetProperties has to be an expression " + 
                "that returns a new object containing a list of " + 
                "properties, e.g.: s => new { s.Property1, s.Property2 }"); 
        } 
       } 
      } 
      else 
      { 
       throw new ArgumentException("Syntax Error: targetProperties has to be an expression " + 
              "that returns a new object containing a list of " + 
              "properties, e.g.: s => new { s.Property1, s.Property2 }"); 
      } 

      return AddCascade(sourceName, targetNames); 
     } 

     public void Detach() 
     { 
      Target.PropertyChanged -= PropertyChangedHandler; 
     } 

     private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) 
     { 
      List<string> cascadeList = null; 

      if (_cascadeInfo.TryGetValue(e.PropertyName, out cascadeList)) 
      { 
       if (PreventLoops) 
       { 
        var cascaded = new HashSet<string>(); 
        cascadeList.ForEach(cascadeTo => 
        { 
         if (!cascaded.Contains(cascadeTo)) 
         { 
          cascaded.Add(cascadeTo); 
          Target.RaisePropertyChanged(cascadeTo); 
         } 
        }); 
       } 
       else 
       { 
        cascadeList.ForEach(cascadeTo => 
        { 
         Target.RaisePropertyChanged(cascadeTo); 
        }); 
       } 
      } 
     } 
    } 
} 

ObservableObject僅僅是基類實現INotifyPropertyChanged。你應該能夠很容易地替代你自己的。

你使用這樣的:

class CascadingPropertyVM : ViewModel 
{ 
    public CascadingPropertyVM() 
    { 
     new PropertyChangeCascade<CascadingPropertyVM>(this) 
      .AddCascade(s => s.Name, 
      t => new { t.DoubleName, t.TripleName }); 
    } 

    private string _name; 
    public string Name 
    { 
     get => _name; 
     set => SetValue(ref _name, value); 
    } 

    public string DoubleName => $"{Name} {Name}"; 
    public string TripleName => $"{Name} {Name} {Name}"; 
} 

在構造線掛接級聯Name財產來DoubleNameTripleName性質的變化。默認情況下,出於性能原因,它不會檢查級聯中的循環,所以它依賴於不創建它們。您可以選擇將級聯上的PreventLoops設置爲true,並且它將確保PropertyChanged僅針對每個屬性引發一次。

-1

灰正確,你需要綁定工作。快速谷歌搜索有很多結果,但是這一次似乎涵蓋你所需要的:

Jerry Nixon Blog Post

+0

看起來更像是一個評論,而不是對我的真實答案。 –

+0

您給出的其他答案非常有建設性:https://stackoverflow.com/a/45175103/858757但是,這個答案可以通過一些解釋來改進。即使你引用了必要的文字,只要它真的回答了這個問題,這可以改善你的答案。 – Silvermind

+0

我同意你對答覆的評估,並讚賞建設性的反饋。然而,我只是開始提供貢獻,並且沒有權限添加評論,所以這是我可以就此問題提供輸入的唯一方式。 –

0

不知道這是理想的,但我的解決辦法是處理由MVVMLight

提供的屬性更改事件
private Model.Recipe _recipe; 
    public Model.Recipe Recipe 
    { 
     get { return _recipe; } 
     set { Set(ref _recipe, value); } 
    } 

    public string MyProperty 
    { 
     get { return "Test " + Recipe.MyProperty; } 
    } 

    public MainViewModel() 
    { 
     PropertyChanged += MainViewModel_PropertyChanged; 
     Recipe = new Model.Recipe(); 
    } 

    private void MainViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
    { 
     switch (e.PropertyName) 
     { 
      case "Recipe": RaisePropertyChanged("MyProperty"); break; 
     } 
    } 

不太喜歡這個MainViewModel_PropertyChanged將成爲一個巨大的開關語句來處理所有的變化。另一種方法是使用信使。

private Model.Recipe _recipe; 
    public Model.Recipe Recipe 
    { 
     get { return _recipe; } 
     set { if (Set(ref _recipe, value)) { Messenger.Default.Send(value, "NewRecipe"); } } 
    } 

    public string MyProperty 
    { 
     get { return "Test " + Recipe.MyProperty; } 
    } 

    public MainViewModel() 
    { 
     Messenger.Default.Register<Model.Recipe>(this, "NewRecipe", NewRecipe); 
     Recipe = new Model.Recipe(); 
    } 

    private void NewRecipe(Recipe obj) 
    { 
     RaisePropertyChanged("MyProperty"); 
    } 

這種方法的好處是,如果myProperty的是在不同的視圖模型它仍然會收到通知,並且他們將不會被緊密耦合。任何需要處理配方更改的東西都可以註冊該消息並接收通知,而無需處理每個屬性更改事件的巨大方法。