2017-04-05 18 views
2

我目前正在寫一個實現了INotifyPropertyChanged接口在我的ViewModels如何通過同一個視圖模型

的的ViewModels有幾個特性,其中一些特性是基於其他計算性能的C#WPF應用程序的性能傳遞活動屬性值。

我想要做的就是簡化現有的代碼,以允許propertyChanged事件通過屬性傳遞,以便xaml中的相應綁定全部更新。

例如:viewmodel包含Total,BreadQuantity和BreadCost屬性。當BreadQuantity屬性發生變化時,它必須通知用戶界面對BreadQuantity和Total屬性的更改以更新相應的綁定。我只想調用BreadQuantity的PropertyChanged事件,因爲Total使用該屬性來計算總數,所以它的相應綁定也應該更新。

下面我已經包含了我的視圖模型繼承包含事件類以及與什麼可行,什麼我試圖做

下面的例子視圖模型的屬性是處理事件的類爲ViewModels。 OnPropertyChanged(字符串名稱)方法用於通知正在更新的屬性.NewOnPropertyChanged是一個新的,它執行相同的操作,但縮短了視圖模型中的代碼,並且還使用屬性來接收屬性名稱,以防止輸入錯誤從導致正確的事件不發射。

public class ObservableObject : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    public void OnPropertyChanged(string name) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 
    } 
    public void NewOnPropertyChanged<T>(ref T variable, T value, [CallerMemberName] string propertyName = "") 
    { 
     variable = value; 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

下面是從繼承視圖模型預期

public decimal TotalCost 
    { 
     get 
     { 
      decimal[] quant_list = new decimal[3] { BreadQuantity, MilkQuantity, CerealQuantity }; 
      decimal[] price_list = new decimal[3] { BreadPrice, MilkPrice, CerealPrice }; 
      return calc_cost.CalculateCostArray(quant_list, price_list); 
     } 
     set 
     { 
      NewOnPropertyChanged<decimal>(ref _total_cost, value); 
     } 
    } 

public decimal BreadPrice 
    { 
     get 
     { 
      return _bread_price; 
     } 
     set 
     { 
      NewOnPropertyChanged<decimal>(ref _bread_price, value); 
      OnPropertyChanged("TotalCost"); 
     } 
    } 

不過,我想找到一種方法,我可以避開OnPropertyChanged("TotalCost"); 對於被綁定到視圖中的每個屬性,作品的屬性。

此應用程序僅用於學習這些術語,但是測試應用程序的全面應用將做同樣的事情,但將有與性能相關的一些計算性能,將創造大量的冗餘樣板代碼和可能性錯別字

例如,如果有3點以上的屬性與它相關聯的則編號必須做

public int BreadQuantity 
    { 
     get 
     { 
      return _bread_quantity; 
     } 
     set 
     { 
      NewOnPropertyChanged<int>(ref _bread_quantity, value); 
      OnPropertyChanged("TotalCost"); 
      OnPropertyChanged("Inventory"); 
      OnPropertyChanged("CartItems"); 

     } 
    } 

這對我看起來像一個簡單的方法來引入錯誤和大量的緊耦合到程序中。如果後來我想重構代碼並將TotalCost重命名爲TotalCostOfItems,那麼我將無法使用visual studio「f2」命令來做到這一點,我將不得不搜索這些字符串來更新它們,這就是我正在嘗試的避免。

非常感謝您提前爲所有那些誰花時間來閱讀我的問題,並認爲解決

@@@@@@@@ 編輯 的@@@@@@@@

發現,在C#6.0,你可以用nameof(Property)獲取從屬性字符串,並且還允許您重構應用程序安全

回答

1

我使用公共的getter和私營/保護的setter方法計算性能做這個。

而不是更新計算屬性的後臺字段,我更新私人setter,這將提高該屬性的PropertyChanged

這要求計算的屬性存儲,而不是實時計算。

這裏是我的當前項目的一個片段:

private TimeSpan _duration; 
    public TimeSpan Duration 
    { 
     get { return _duration; } 
     set 
     { 
      if (SetValue(ref _duration, value)) 
      { 
       StopTime = StartTime + _duration; 
       FromTo = CalculateFromTo(); 
      } 
     } 
    } 

    private string CalculateFromTo() 
    { 
     return $"{StartTime:t} - {StopTime:t}"; 
    } 

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

這是存儲有關事件的信息的一類。有StartTime,StopTimeDuration屬性,計算出的字符串顯示對它們的名稱爲FromTo的友好顯示值。

SetValue是一個基類的方法,它設置了後臺字段,並且只有當值實際發生改變時纔會自動引發PropertyChanged。僅當值更改時纔會返回true

更改Duration將級聯到StopTimeFromTo

+0

你介意給我舉個例子嗎? – BossmanT

+0

更仔細地看你的問題,這種方法可能不適合你,因爲它依賴於「預先計算」計算屬性並存儲它,而不是在獲取器中請求時即時計算它。你還對一個例子感興趣嗎? –

+0

我假設你正在談論的是讓我的BreadQuantity屬性的set方法調用我的Total屬性的set方法,這樣我可以控制哪些屬性觸發事件 – BossmanT

0

在這裏我看到兩個選項:

  1. documentation

空值或空的propertyName參數表明,所有的屬性發生了變化。

所以只需調用OnPropertyChanged();OnPropertyChanged(null);它將更新所有屬性。

  • 你將不得不手動調用屬性改變TotalCost

    public void NewOnPropertyChanged<T>(ref T variable, T value, [CallerMemberName] string propertyName = "") 
    { 
        variable = value; 
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TotalCost")); 
    } 
    
  • 可以擴展該方法以接受的屬性名稱的數組,但你有一個想法。 但這看起來很醜,我會選擇第一個選項。

    +0

    是的,如果很多屬性相互依賴並重新計算,則選項1是最好的。否則,你將會跟蹤屬性依賴關係並觸發已更改的事件。 –

    +0

    我接受了其他答案,因爲它允許我控制更新哪些屬性,但是您的方法確實有效,但它讓我感到驚訝。如果我有3個ViewModel類,每個都有10+個屬性,並且所有3個ViewModel繼承自實現該事件的類,那麼null Hack會更新所有ViewModel的每個屬性? – BossmanT

    +0

    它會更新所調用實例的每個屬性綁定,而不是視圖模型類的其他實例。 –

    1

    我靈感,創造一個更好的方式來處理這個問題:

    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樣子:

    public class ObservableObject : INotifyPropertyChanged 
    { 
        public event PropertyChangedEventHandler PropertyChanged; 
    
        internal void RaisePropertyChanged(string propertyName) 
        { 
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
        } 
    
        protected bool SetValue<T>(ref T backingField, T newValue, [CallerMemberName] string propertyName = "") 
        { 
         if (EqualityComparer<T>.Default.Equals(backingField, newValue)) 
         { 
          return false; 
         } 
         backingField = newValue; 
         RaisePropertyChanged(propertyName); 
         return true; 
        } 
    } 
    

    它可以像這樣使用:

    class CascadingPropertyVM : ObservableObject 
    { 
        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。通過鏈接AddCascade函數,您可以根據需要添加儘可能多的級聯。

    我可能會更新此使用自定義屬性,以便沒有必要在cosntructor中完成。

    +0

    我喜歡它,你在'CascadingPropertyVM'構造函數中創建看起來像一個匿名類的東西,如何添加更多的級聯,這樣你就可以在'Name'觸發'DoubleName'和'TripleName'中產生一個「斷鏈」效果,但是如果您只需觸發'DoubleName',然後只觸發'TripleName'。 – BossmanT

    +0

    這看起來像這個新的PropertyChangeCascade (this).AddCascade(s => s.Name,t => new {t.DoubleName,t.TripleName})。AddCascade(s => s.DoubleName,t => t.TripleName);'在我的例子中,它不會做任何事情,因爲'DoubleName'是隻讀的。這也表明'target'可以是單個屬性,也可以是包含多個屬性的新對象。還有一個重載將源視爲字符串,並將其作爲字符串列表。 –

    +0

    默認情況下,如果您創建一個循環,它將被鎖定,但如果將PreventLoops設置爲「true」,則只會爲每個屬性通知一次,但會犧牲某些性能。 –

    相關問題