如何最好地向上传播更改通知以进行绑定的分层结构?

发布于 2024-10-11 07:34:36 字数 4102 浏览 5 评论 0原文

如果我有一个类似文件夹的结构,使用 复合设计模式 并且我绑定根文件夹到 TreeView。如果我可以显示从文件夹内容中积累的某些属性,那将非常有用。问题是,如何最好地通知文件夹子元素中发生的更改,以便更新累积属性?

我需要这个的上下文是我正在尝试制作的一个小型 RSS-FeedReader。这是我的模型中最重要的对象和方面:

复合接口:

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();
        }
    }

Feed:

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;
        }

好的,这就是问题,如果我绑定一个 TextBlock.Text< /code> 到 UnreadFeedItemsCount 当项目被标记为未读时,不会有简单的通知,因此我的方法之一是处理每个 PropertyChanged 事件>FeedItem 并且如果 IsUnread-Property 发生更改,我会让我的 Feed 发出属性 UnreadFeedItemsCount 已更改的通知。通过这种方法,我还需要处理 Children 中所有 FeedsFeedComposites 的所有 PropertyChanged 事件FeedComposite,从它的声音来看,很明显这不是一个很好的主意,您需要非常小心,在没有附加 的情况下,永远不要将项目添加到任何集合或从任何集合中删除首先是 PropertyChanged 事件处理程序。

另外:我该如何处理 CollectionChanged - 必然也会导致未读项目计数总和发生变化的事件?听起来事件处理更有趣。

真是一团糟;如果有人对此有一个优雅的解决方案,那就太好了,因为我不希望提要阅读器最终像我几年前的第一次尝试一样糟糕,当时我什至不知道数据绑定......

If i have a folder-like structure that uses the composite design pattern and i bind the root folder to a TreeView. It would be quite useful if i can display certain properties that are being accumulated from the folder's contents. The question is, how do i best inform the folder that changes occurred in a child-element so that the accumulative properties get updated?

The context in which i need this is a small RSS-FeedReader i am trying to make. This are the most important objects and aspects of my model:

Composite interface:

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

    int UnreadFeedItemsCount { get; }

    ObservableCollection<FeedItem> FeedItems { get; }
}

FeedComposite (aka Folder)

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();
        }
    }

Feed:

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;
        }

Ok, so here is the thing, if i bind a TextBlock.Text to the UnreadFeedItemsCount there won't be simple notifications when an item is marked unread, so one of my approaches has been to handle the PropertyChanged event of every FeedItem and if the IsUnread-Property is changed i have my Feed make a notification that the property UnreadFeedItemsCount has been changed. With this approach i also need to handle all PropertyChanged events of all Feeds and FeedComposites in Children of FeedComposite, from the sound of it, it should be obvious that this is not such a very good idea, you need to be very careful that items never get added to or removed from any collection without having attached the PropertyChanged event handler first.

Also: What do i do with the CollectionChanged-Events which necessarily also cause a change in the sum of the unread items count? Sounds like more event handling fun.

It is such a mess; it would be great if anyone has an elegant solution to this since i do not want the feed-reader to end up as awful as my first attempt years ago when i did not even know about DataBinding...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

冷月断魂刀 2024-10-18 07:34:36

好吧,我想我应该尝试一下你的问题,看看我会想出什么。它未经测试,并且与您已经拥有的有点相同。我所做的主要区别是添加了处理提要的添加和删除的方法,这些提要处理其工作所需的事件绑定。有一些代码,所以这里,

我的所有代码都在一个文件中,如果您想在单独的文件中,则需要稍微修改。

首先是 PropertyChangedEventHandler 的常规扩展方法
你不需要使用它,但我非常喜欢它。

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));
            }
        }
    }

其次,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);
            }
        }
    }
}

现在,我的界面略有不同,因为我的文件夹可以包含其他文件夹以及提要。

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 方法会为您挂钩属性更改事件,如果它被标记为未读,则会引发计数的属性更改事件。您可以重载此方法,以接受项目列表,然后将它们添加到列表后(如果有未读的地方),为所有项目引发一个属性更改事件。

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 类与 feed 类非常相似,但是这个类可以保存 feed 列表和 feed 文件夹列表(保存它们自己的 feed)。如果需要,您可以轻松添加方法或属性来返回源和源文件夹中的所有源项目。同样,各种检查仅在需要时引发更改事件。

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;

Well I thought I'd give your question a go, to see what I would come up with. Its untested and its is kinda along the same lines as what you already had. The major difference I made is added methods to handle the add and removal of feeds which handle the event binding needed for it to work. Theres a bit of code so here goes,

I all my code is in a single file, youll need to modify slightly if you want in in separate files.

First the groovy extension method for the PropertyChangedEventHandler
You dont need to use it, but I like it alot.

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));
            }
        }
    }

Second the FeedItem minus the feed stuff :) I have a check to raise a change event only when the value actually changes. You can see the Raise extension method in use here, no strings lovely.

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);
            }
        }
    }
}

Now the Interfaces, I made mine differ slightly, in that my folders can contain other folders as well as feeds.

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);
}

Now the Feed Class, the AddFeedItem method hooks the property changed event for you, and if it is marked as unread, raises the property changed event for the count. You could overload this method, to accept a list of items, then once they have been added to the list, if any where unread, raise a single property changed event for them all.

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);
        }
    }
}

Now the FeedFolder class, much the same as the feed class but this one can hold a list of feeds and a list of feed folders (which hold their own feeds). You can easily add a method or property to return all feeditems from feeds and feedfolders if you need. Again, various checks to only raise change events if needed.

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);
        }
    }
}

Now for usage, remember I havent tested this, but it should be mostly right.

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文