36866


ご教授よろしくお願いします。

MyViewModelクラスはObservableCollection型のプロパティを持つModel-Aがあります。  
Model-AはSumというプロパティがあります。これはModel-BがもつNumberプロパティの合計です。

質問内容はModel-BのNumberが変わった時、もしくはObservableCollectionの数が変わった時にModel-
AのSumの値の画面上に表示させたいのですが、更新されません。。。

どなたかご教授よろしくお願いします。

ソースは以下の通りです。(*ViewModelBaseクラスはINotifyを実装しています。)

[code]

    public class MyViewModel:NotifyBase
    {
        private modelA;
        public ModelA
        {
          get
          {
            return modelA;
          }
          set
          {
            modelA = value;
          }
        }
    ｝
    
    public class Model-A:NotifyBase
    {
        private ObservableCollection<Model-B> modelbcollection;
        public ObservableCollection<Model-B> ModelBCollection
        {
            get
            {
                return modelbcollection;
            }
            set
            {
                modelbcollection = value;
            }
        }
    
        public double Sum
        {
            get
            {
                return ModelBCollection.Sum(c=>c.Number)
            }
        }
    }
    
    public class ModelB:NotifyBase
    {
        private int number;
        public int Number
        {
            get
            {
                return number;
            }
            set
            {
                number = value;
                OnPropertyChanged("Number");
            }
        }
    }
[/code]


CC BY-SA 3.0
2017-08-02T01:33:13.460
36864
2017-08-03T04:37:56.530
12388
question
c# wpf
ObservableCollection<T>型のプロパティの値が変更した事を知りたい。


WPFデータバインドに値の変更を認識させるためには、`Sum`プロパティの値が変わったタイミングで`PropertyChanged`イベントを発生させる必要があります。

イベントを発生させる方針としては`Model-A`から汎用のイベントを観測する方法と、`Model-B`から明示的に値を更新する方法が考えられます。

前者の場合、まず`ModelBCollection`のセッターで`CollectionChanged`イベントハンドラーを設定します。

[code]

    public ObservableCollection<Model-B> ModelBCollection
    {
        get
        {
            return modelbcollection;
        }
        set
        {
            if (modelbcollection != null)
            {
                modelbcollection.CollectionChanged -= Modelbcollection_CollectionChanged;
            }
    
            modelbcollection = value;
    
            if (modelbcollection != null)
            {
                modelbcollection.CollectionChanged += Modelbcollection_CollectionChanged;
            }
        }
    }
    
[/code]

`Modelbcollection_CollectionChanged`の発生時には`Sum`の変更通知と、各要素の`Number`プロパティの変更を監視しなければなりません。

[code]

    private void Modelbcollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Move:
                return;
    
            case NotifyCollectionChangedAction.Add:
            case NotifyCollectionChangedAction.Remove:
            case NotifyCollectionChangedAction.Replace:
            // TODO: それぞれ実装する
    
            case NotifyCollectionChangedAction.Reset:
            default:
                if (_Items != null)
                {
                    foreach (var item in _Items)
                    {
                        item.PropertyChanged -= Item_PropertyChanged;
                    }
                }
                _Items = ((IEnumerable<ModelB>)sender).ToArray();
    
                // 各要素にイベントハンドラーを設定する。
                foreach (var item in _Items)
                {
                    item.PropertyChanged += Item_PropertyChanged;
                }
                break;
        }
    
        // 変更通知
        OnPropertyChanged(nameof(Sum));
    }
    
[/code]

`Item_PropertyChanged`では`e.PropertyName`を判定し、`Number`であれば`Sum`の変更を通知します。

[code]

    private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(ModelB.Number))
        {
            // 変更通知
            OnPropertyChanged(nameof(Sum));
        }
    }
    
[/code]

この方法は要素が変更不能な型であっても使用できますが、`CollectionChanged`周りが煩雑になりますので変えられるのであれば`Model-B`に`Model-A`の参照を持たせて変更時に何らかのメソッドを呼ばせる形の方が簡潔です。

まず`Model-A`に通知用メソッドを用意します。

[code]

    class ModelA
    {
        internal void NotifySumChanged()
        {
            OnPropertyChanged(nameof(Sum));
        }
    }
    
[/code]

`ModelbCollection`には専用の型を使用し、各メソッドをオーバーライドします。

[code]

    class CustomCollection : ObservableCollection<ModelB>
    {
        private ModelA _Owner;
        public CustomCollection(ModelA owner)
        {
            _Owner = owner;
        }
    
        protected override void ClearItems()
        {
            foreach (var e in this)
            {
                e.Owner = null;
            }
    
            base.ClearItems();
    
            _Owner.NotifySumChanged();
        }
        protected override void InsertItem(int index, ModelB item)
        {
            item.Owner = _Owner;
    
            base.InsertItem(index, item);
    
            _Owner.NotifySumChanged();
        }
        // MoveItemは変更不要
        //protected override void MoveItem(int oldIndex, int newIndex)
        //{
        //    base.MoveItem(oldIndex, newIndex);
        //}
        protected override void RemoveItem(int index)
        {
            this[index].Owner = null;
    
            base.RemoveItem(index);
    
            _Owner.NotifySumChanged();
        }
    
        protected override void SetItem(int index, ModelB item)
        {
            this[index].Owner = null;
            item.Owner = _Owner;
    
            base.SetItem(index, item);
    
            _Owner.NotifySumChanged();
        }
    }
    
[/code]

この型は`ModelbCollection`のゲッターで使用します。

[code]

    private CustomCollection modelbcollection;
    public ObservableCollection<Model-B> ModelBCollection
    {
        get
        {
            return modelbcollection ?? (modelbcollection = new CustomCollection(this));
        }
        set
        {
            if (value == modelbcollection)
            {
                return;
            }
            modelbcollection?.Clear();
            if (value?.Count > 0)
            {
                var list = ModelBCollection;
                foreach (var item in value)
                {
                    list.Add(item);
                }
            }
        }
    }
    
[/code]

上記で`Model-B`の`Owner`が設定されているので、プロパティの変更時にメソッドを読んでやります。

[code]

    class ModelB
    {
        internal ModelA Owner { get; set; }
        private int number;
        public int Number
        {
            get
            {
                return number;
            }
            set
            {
                if (value != null)
                {
                    number = value;
                    OnPropertyChanged("Number");
    
                    Owner.NotifySumChanged();
                }
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2017-08-02T03:08:42.030
36866
2017-08-03T04:37:56.530
2017-08-03T04:37:56.530
5750
5750
36864
answer