2011-01-10 8 views
2

私はcomposite design patternを使用するフォルダのような構造を持っていて、私はTreeViewにルートフォルダをバインドします。私はフォルダの内容から蓄積されている特定のプロパティを表示することができれば非常に便利です。問題は、子要素に変更が発生して累積プロパティが更新されるようにフォルダに通知するにはどうすればよいですか?変更通知をバインディングの階層構造の上位に伝播するにはどうすればよいですか?

私が必要とする文脈は、私が作ろうとしている小さなRSSフィードリーダーです。これは私のモデルの中で最も重要なオブジェクトおよび側面です:

コンポジットインターフェース:

public interface IFeedComposite : INotifyPropertyChanged 
{ 
    string Title { get; set; } 

    int UnreadFeedItemsCount { get; } 

    ObservableCollection<FeedItem> FeedItems { get; } 
} 

FeedComposite(別名フォルダ)

public class FeedComposite : BindableObject, IFeedComposite 
    { 
     private string title = ""; 
     public string Title 
     { 
      get { return title; } 
      set 
      { 
       title = value; 
       NotifyPropertyChanged("Title"); 
      } 
     } 

     private ObservableCollection<IFeedComposite> children = new ObservableCollection<IFeedComposite>(); 
     public ObservableCollection<IFeedComposite> Children 
     { 
      get { return children; } 
      set 
      { 
       children.Clear(); 
       foreach (IFeedComposite item in value) 
       { 
        children.Add(item); 
       } 
       NotifyPropertyChanged("Children"); 
      } 
     } 

     public FeedComposite() { } 

     public FeedComposite(string title) 
     { 
      Title = title; 
     } 

     public ObservableCollection<FeedItem> FeedItems 
     { 
      get 
      { 
       ObservableCollection<FeedItem> feedItems = new ObservableCollection<FeedItem>(); 
       foreach (IFeedComposite child in Children) 
       { 
        foreach (FeedItem item in child.FeedItems) 
        { 
         feedItems.Add(item); 
        } 
       } 
       return feedItems; 
      } 
     } 


    public int UnreadFeedItemsCount 
    { 
     get 
     { 
      return (from i in FeedItems 
        where i.IsUnread 
        select i).Count(); 
     } 
    } 

フィード:

public class Feed : BindableObject, IFeedComposite 
    { 
     private string url = ""; 
     public string Url 
     { 
      get { return url; } 
      set 
      { 
       url = value; 
       NotifyPropertyChanged("Url"); 
      } 
     } 

     ... 


     private ObservableCollection<FeedItem> feedItems = new ObservableCollection<FeedItem>(); 
     public ObservableCollection<FeedItem> FeedItems 
     { 
      get { return feedItems; } 
      set 
      { 
       feedItems.Clear(); 
       foreach (FeedItem item in value) 
       { 
        AddFeedItem(item); 
       } 
       NotifyPropertyChanged("Items"); 
      } 
     } 

     public int UnreadFeedItemsCount 
     { 
      get 
      { 
       return (from i in FeedItems 
         where i.IsUnread 
         select i).Count(); 
      } 
     } 

     public Feed() { } 

     public Feed(string url) 
     { 
      Url = url; 
     } 

私がを縛るならば、ここに事がありますからUnreadFeedItemsCountにアイテムが未読とマークされている場合、簡単な通知はありません。私のアプローチの1つはFeedItemPropertyChangedイベントを処理することでした。IsUnread - プロパティが変更された場合、私はFeedプロパティUnreadFeedItemsCountが変更されました。このアプローチでは、すべてのイベントをすべてFeedsFeedCompositesChildrenFeedCompositeというイベントで処理する必要があります。そのサウンドから、これはあまり良いアイデアではないことが明らかです。最初にPropertyChangedイベントハンドラをアタッチしなくても、任意のコレクションに追加または削除できます。

また、私はCollectionChangedで何をしていますか?未読アイテム数の合計にも変化が生じる必要がありますか?より多くのイベント処理の楽しみのように聞こえる。

このような混乱です。何年も前に私がデータバインディングについて知りませんでしたが、フィードリーダーが私の最初の試みほどひどくなることを望んでいないので、誰かがこれに優雅な解決策を持っているなら、素晴らしいでしょう...

答えて

1

まあ私は思った私はあなたの質問に行くと、私は思い付くものを見ています。そのテストされていないと、あなたがすでに持っていたものと同じ行にちょっとです。私が作った主な違いは、それが動作するのに必要なイベントバインディングを処理するフィードの追加と削除を処理するメソッドが追加されていることです。ここで少しのコードがあります。

私のすべてのコードは1つのファイルになっています。別々のファイルに入れたい場合は、少し修正する必要があります。

最初にPropertyChangedEventHandlerのGroovy拡張メソッド あなたはそれを使用する必要はありませんが、私はそれが大好きです。

public static class NotifyPropertyChangedExtention 
    { 
     public static void Raise<T, TP>(this PropertyChangedEventHandler pc, T source, Expression<Func<T, TP>> pe) 
     { 
      if (pc != null) 
      { 
       pc.Invoke(source, new PropertyChangedEventArgs(((MemberExpression)pe.Body).Member.Name)); 
      } 
     } 
    } 

2番目のFeedItemマイナスフィードの要素:)値が実際に変更されたときにのみ変更イベントを発生させるチェックがあります。 Raise拡張メソッドがここで使用されていますが、素敵な文字列はありません。

class FeedItem : INotifyPropertyChanged 
{ 
    private bool _isUnread; 
    public event PropertyChangedEventHandler PropertyChanged; 

    public bool IsUnread 
    { 
     get { return _isUnread; } 
     set 
     { 
      if (_isUnread != value) 
      { 
       _isUnread = value; 
       PropertyChanged.Raise(this, x => x.IsUnread); 
      } 
     } 
    } 
} 

これで、Interfacesは、私の鉱山がわずかに異なりました。私のフォルダには、フィードと同様に他のフォルダも含めることができます。

internal interface IFeedComposite : INotifyPropertyChanged 
{ 
    string Title { get; set; } 
    int UnreadFeedItemsCount { get; } 
} 

internal interface IFeedFolder : IFeedComposite 
{ 
    ObservableCollection<IFeedFolder> FeedFolders { get; } 
    ObservableCollection<IFeed> Feeds { get; } 
    void AddFeed(IFeed newFeed); 
    void RemoveFeed(IFeed feedToRemove); 
    void AddFeedFolder(IFeedFolder newFeedFolder); 
    void RemoveFeedFolder(IFeedFolder feedFolderToRemove); 
} 

internal interface IFeed : IFeedComposite 
{ 
    ObservableCollection<FeedItem> FeedItems { get; } 
    void AddFeedItem(FeedItem newFeedItem); 
    void RemoveFeedItem(FeedItem feedItemToRemove); 
} 

Feedクラス、AddFeedItem方法は、あなたのためのプロパティ変更イベントをフックし、そしてそれは未読としてマークされている場合、カウントにプロパティ変更イベントを発生させます。このメソッドをオーバーロードしてアイテムのリストを受け入れることができます。リストにアイテムが追加されると、未読のアイテムがあれば、そのアイテムのプロパティ変更イベントが1つだけ発生します。

class Feed : IFeed 
{ 
    private readonly ObservableCollection<FeedItem> _feedItems = new ObservableCollection<FeedItem>(); 

    public event PropertyChangedEventHandler PropertyChanged; 
    public string Title { get; set; } 

    public int UnreadFeedItemsCount 
    { 
     get 
     { 
      return (from i in FeedItems 
        where i.IsUnread 
        select i).Count(); 
     } 
    } 

    public ObservableCollection<FeedItem> FeedItems 
    { 
     get { return _feedItems; } 
    } 

    public void AddFeedItem(FeedItem newFeed) 
    { 
     newFeed.PropertyChanged += NewFeedPropertyChanged; 
     _feedItems.Add(newFeed); 
     PropertyChanged.Raise(this, x => x.FeedItems); 
     if (newFeed.IsUnread) 
     { 
      PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount); 
     } 
    } 

    public void RemoveFeedItem(FeedItem feedToRemove) 
    { 
     _feedItems.Remove(feedToRemove); 
     PropertyChanged.Raise(this, x => x.FeedItems); 
     if (feedToRemove.IsUnread) 
     { 
      PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount); 
     } 
    } 

    void NewFeedPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName == "IsUnread") 
     { 
      PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount); 
     } 
    } 
} 

今FeedFolderクラス、多くのフィードクラスと同じですが、この1つはフィードのリストと(自分のフィードを保持する)フィードフォルダのリストを保持することができます。必要に応じて、フィードやフィードフォルダーからすべてのフィードアイテムを返すメソッドやプロパティを簡単に追加できます。また、必要に応じて変更イベントのみを発生させるさまざまなチェックが行われます。

class FeedFolder : IFeedFolder 
{ 
    private readonly ObservableCollection<IFeedFolder> _feedFolders = new ObservableCollection<IFeedFolder>(); 
    private readonly ObservableCollection<IFeed> _feeds = new ObservableCollection<IFeed>(); 

    public event PropertyChangedEventHandler PropertyChanged; 
    public string Title { get; set; } 

    public int UnreadFeedItemsCount 
    { 
     get { return Feeds.Sum(x => x.UnreadFeedItemsCount) + FeedFolders.Sum(x => x.UnreadFeedItemsCount); } 
    } 

    public ObservableCollection<IFeedFolder> FeedFolders 
    { 
     get { return _feedFolders; } 
    } 

    public ObservableCollection<IFeed> Feeds 
    { 
     get { return _feeds; } 
    } 

    public void AddFeed(IFeed newFeed) 
    { 
     newFeed.PropertyChanged += NewFeedPropertyChanged; 
     _feeds.Add(newFeed); 
     PropertyChanged.Raise(this, x => x.Feeds); 
     if (newFeed.UnreadFeedItemsCount > 0) 
     { 
      PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount); 
     } 
    } 

    void NewFeedPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName == "UnreadFeedItemsCount") 
     { 
      PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount); 
     } 
    } 

    public void RemoveFeed(IFeed feedToRemove) 
    { 
     _feeds.Remove(feedToRemove); 
     PropertyChanged.Raise(this, x => x.Feeds); 
     if (feedToRemove.UnreadFeedItemsCount > 0) 
     { 
      PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount); 
     } 
    } 

    public void AddFeedFolder(IFeedFolder newFeedFolder) 
    { 
     newFeedFolder.PropertyChanged += NewFeedPropertyChanged; 
     _feedFolders.Add(newFeedFolder); 
     PropertyChanged.Raise(this, x => x.FeedFolders); 
     if (newFeedFolder.UnreadFeedItemsCount > 0) 
     { 
      PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount); 
     } 
    } 

    public void RemoveFeedFolder(IFeedFolder feedFolderToRemove) 
    { 
     _feedFolders.Remove(feedFolderToRemove); 
     PropertyChanged.Raise(this, x => x.FeedFolders); 
     if (feedFolderToRemove.UnreadFeedItemsCount > 0) 
     { 
      PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount); 
     } 
    } 
} 

ここで使用するために、私はこれをテストしていないことを覚えておいてください。

var myFolder = new FeedFolder(); 
var myFeed = new Feed(); 
var myFeedItem = new FeedItem(); 
myFeedItem.IsUnread = true; 
myFeed.AddFeedItem(myFeedItem); 
myFolder.AddFeed(myFeed); 

var mySecondFeedItem = new FeedItem(); 

//add a second feeditem to feed, but it is marked as read, so no notifications raised for unread count. 
myFeed.AddFeedItem(mySecondFeedItem); 

//this should fire off change events all the way up to the folder 
mySecondFeedItem.IsUnread = true; 
+0

回答ありがとうございました。ここでかなりの長さになりました。今すぐ私はあなたに長い応答を与えることはできません、私はできるだけ早くそうしようとします... –

+0

私はあなたの答えが好きではないいくつかのことがあります。既存のもの以外の他のオブジェクトがそれらを実装する可能性も非常に低くなります。また、FeedCompositeのChildrenをFoldersのフィードに分割してIFeedCompositeインターフェイスを使用せず、実質的に同一で、1つに置き換えることのできる多くのメソッドを持たせることで、コンポジットパターンを削除しました。より面倒で、フィードとフォルダの混在した順序を持つことはできません。 –

+0

私のオリジナルのソリューションには、オブジェクトをコレクションに追加するときにハンドラーを追加するヘル​​パーメソッドと同じものがありましたが、この質問で指摘したように、他の提案が良いかもしれません。あなたの答えにはいくつかのものがありますが、これは便利なような拡張メソッドのように役立ちますので、努力していただきありがとうございます。 –

関連する問題