如何从包含的对象中正确触发 CollectionChanged 通知?

发布于 2024-10-21 21:13:54 字数 3611 浏览 5 评论 0原文

这是对本质上一个简单问题的冗长解释。我正在使用 Telerilk RadDropDownButton,它显示带有复选框的列表项。

                    <Controls:RadDropDownButton AutoOpenDelay="0:0:0.0" x:Name="Urgency" VerticalAlignment="Center" Width="150" Content="{Binding Path=ItemsSource, ElementName=UrgencyList, Mode=TwoWay, Converter={StaticResource ButtonTextConverter}}" HorizontalContentAlignment="Left">
                    <Controls:RadDropDownButton.DropDownContent>
                        <ListBox x:Name="UrgencyList">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <CheckBox Content="{Binding Name}" ClickMode="Press" IsChecked="{Binding IsChecked, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
                                    </StackPanel>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </Controls:RadDropDownButton.DropDownContent>
                </Controls:RadDropDownButton>

如您所见,我将 Content 属性绑定到转换器。我想要的是,如果没有选择任何内容,则内容将显示为“全部”,如果选中了某些内容,则显示所选(选中)项目的列表。

    public class ButtonTextConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Debug.WriteLine("I'm Binding");
        int numChecked = 0;
        if (value != null)
            numChecked = ((ObservableCollection<UrgencyItem>) value).Count(urgencyItem => urgencyItem.IsChecked);
        return numChecked > 0 ? string.Format("{0} Items Selected", numChecked) : "All";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

我绑定的类根据需要实现 INotifyPropertyChanged。此处部分列出:

    public class UrgencyItem : INotifyPropertyChanged
{
    private int _id;
    private bool _isChecked;
    private string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            NotifyPropertyChanged("Name");
        }
    }

我将 ListBox 绑定到代码隐藏中的数据,如下所示:

        private void SearchParamsVertical_Loaded(object sender, RoutedEventArgs e)
    {
        urgencyList = new ObservableCollection<UrgencyItem>
                          {
                              new UrgencyItem {ID = 1, IsChecked = false, Name = "Non Emergent"},
                              new UrgencyItem {ID = 2, IsChecked = false, Name = "Emergent"},
                              new UrgencyItem {ID = 3, IsChecked = false, Name = "Stat Emergent"},
                              new UrgencyItem {ID = 4, IsChecked = false, Name = "Stroke Protocol"}
                          };

        urgencyList.CollectionChanged += urgencyList_CollectionChanged;
        UrgencyList.ItemsSource = urgencyList;
    }

所以这是问题...

当选中复选框时,Content 的值应该更新。它不是。

之所以不是这样,是因为虽然 IsChecked 已更改的通知正在触发,但该通知基本上没有任何进展。 UrgencyItem 对象不知道它是 ObservableCollection 的一部分。 ObservableCollection 的特点是,它仅在向集合添加/删除项目时才向其绑定发送通知。换句话说,更改集合中项目的属性不会触发 CollectionChanged 事件,因为没有添加/删除对象。

我需要做的是当我修改集合的属性时触发 collectionChanged 事件。我曾经知道如何做到这一点,但由于远离 Silverlight 太久,我已经忘记了如何做到这一点。

有人吗?

Here is the long-winded explanation to what is essentially a simple problem. I am using a Telerilk RadDropDownButton, which displays a list Items with checkboxes.

                    <Controls:RadDropDownButton AutoOpenDelay="0:0:0.0" x:Name="Urgency" VerticalAlignment="Center" Width="150" Content="{Binding Path=ItemsSource, ElementName=UrgencyList, Mode=TwoWay, Converter={StaticResource ButtonTextConverter}}" HorizontalContentAlignment="Left">
                    <Controls:RadDropDownButton.DropDownContent>
                        <ListBox x:Name="UrgencyList">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <CheckBox Content="{Binding Name}" ClickMode="Press" IsChecked="{Binding IsChecked, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
                                    </StackPanel>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </Controls:RadDropDownButton.DropDownContent>
                </Controls:RadDropDownButton>

As you can see, I bound the Content property to a Converter. What I want is, if nothing is selected, for the content to read "All", and if something is checked, to show a list of the # of selected (Checked) items.

    public class ButtonTextConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Debug.WriteLine("I'm Binding");
        int numChecked = 0;
        if (value != null)
            numChecked = ((ObservableCollection<UrgencyItem>) value).Count(urgencyItem => urgencyItem.IsChecked);
        return numChecked > 0 ? string.Format("{0} Items Selected", numChecked) : "All";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

The class I am binding to implements INotifyPropertyChanged, as required. Partial listing here:

    public class UrgencyItem : INotifyPropertyChanged
{
    private int _id;
    private bool _isChecked;
    private string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            NotifyPropertyChanged("Name");
        }
    }

And I bind the ListBox to the Data in the code-behind, like this:

        private void SearchParamsVertical_Loaded(object sender, RoutedEventArgs e)
    {
        urgencyList = new ObservableCollection<UrgencyItem>
                          {
                              new UrgencyItem {ID = 1, IsChecked = false, Name = "Non Emergent"},
                              new UrgencyItem {ID = 2, IsChecked = false, Name = "Emergent"},
                              new UrgencyItem {ID = 3, IsChecked = false, Name = "Stat Emergent"},
                              new UrgencyItem {ID = 4, IsChecked = false, Name = "Stroke Protocol"}
                          };

        urgencyList.CollectionChanged += urgencyList_CollectionChanged;
        UrgencyList.ItemsSource = urgencyList;
    }

SO HERE'S THE PROBLEM...

When a checkbox is checked, the value of Content should update. It is not.

The reason it's not is because, although the notification is firing that the IsChecked was changed, that notification is basically going nowhere. The UrgencyItem object has no idea that it is part of an ObservableCollection. And the thing about an ObservableCollection is that it only sends notifications to it's binding when items are ADDED/REMOVED to/from the collection. In other words, changing the property of an item in the collection does not fire the CollectionChanged event, because no objects were added/removed.

What I need to do is have the collectionChanged event fire when I modify a property of the collection. I used to know how to do this, but too much time away from Silverlight and I've forgotten how.

Anyone?

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

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

发布评论

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

评论(3

守不住的情 2024-10-28 21:13:54

简而言之,我认为您的诊断是正确的:如果 ObservableCollection 中的对象发生更改,即使该对象实现了 INotifyPropertyChanged,您通常也不会收到 CollectionChanged 通知。据我所知,没有一种直接的方法可以通过内置的 Silverlight 类获得您想要的行为。

据我所知,有三种可能的方法来解决这个问题:

(1)一种选择是为 UrgencyList 创建自己的集合,继承自 ObservableCollection,实现此行为,即它订阅每个对象的 INPC 通知添加到集合中,并在发生这种情况时触发 CollectionChanged 事件。

(2) 第二种选择是使用类似 ReactiveUI 框架 的东西,它有自己的 ReactiveCollection 来实现这个行为。

(3) 第三种选择是通过 Obtics 或 Continuous Linq 之类的方式创建 UrgencyList 。它们返回的集合自动实现此行为。

In brief, I think your diagnosis is correct: you don't normally get a CollectionChanged notification if an object in the ObservableCollection changes, even if that object implements INotifyPropertyChanged. So far as I'm aware, there isn't a straightforward way to get the behavior you're wanting through the built-in Silverlight classes.

There are three possible ways to address this that I'm aware of:

(1) One option would be to create your own collection for urgencyList, inheriting from ObservableCollection, that implements this behavior, i.e., it subscribes to the INPC notification of each object that is added to the collection, and fires a CollectionChanged event when that happens.

(2) A second alternative is to use something like the ReactiveUI framework, which has its own ReactiveCollection that implements this behavior.

(3) A third option is to create your urgencyList via something like Obtics or Continuous Linq. The collections they return implement this behavior automatically.

辞慾 2024-10-28 21:13:54

这就是我正在使用的。 Ken 在 nr (1) 中建议的内容我想:

public class Person: INotifyPropertyChanged
{
private string _name;
public string Name
    {
        get { return _name; }
        set { 
            _name = value;
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Name"));
        }
    }
public event PropertyChangedEventHandler PropertyChanged;
}

我在 PLObservableNotifyList 中有这些对象,我将其设置为 ItemsControl 上的 ItemsSource 。一旦我更新值(使用设置器),绑定就会自动更新。

public class PLObservableNotifyList<T> :
            ObservableCollection<T> where T : INotifyPropertyChanged
{
    public ItemPropertyChangedEventHandler ItemPropertyChanged;
    public EventHandler CollectionCleared;

    protected override void OnCollectionChanged(
                                NotifyCollectionChangedEventArgs args)
    {
        base.OnCollectionChanged(args);

        if (args.NewItems != null)
            foreach (INotifyPropertyChanged item in args.NewItems)
                item.PropertyChanged += OnItemPropertyChanged;

        if (args.OldItems != null)
            foreach (INotifyPropertyChanged item in args.OldItems)
                item.PropertyChanged -= OnItemPropertyChanged;
    }

    void OnItemPropertyChanged(object sender,
                               PropertyChangedEventArgs args)
    {
        if (ItemPropertyChanged != null)
            ItemPropertyChanged(this,
                new PLItemPropertyChangedEventArgs(sender,
                                                 args.PropertyName));
    }

    protected override void ClearItems()
    {
        foreach (INotifyPropertyChanged item in Items)
            item.PropertyChanged -= OnItemPropertyChanged;

        if (CollectionCleared != null)
            CollectionCleared(this, EventArgs.Empty);

        base.ClearItems();
    }
}

This is what I am using. What Ken suggested in nr (1) I suppose:

public class Person: INotifyPropertyChanged
{
private string _name;
public string Name
    {
        get { return _name; }
        set { 
            _name = value;
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Name"));
        }
    }
public event PropertyChangedEventHandler PropertyChanged;
}

I have theese objects in a PLObservableNotifyList<Person> which I set as ItemsSource on the ItemsControl. A soon as I update values (use the setter) the binding is updated automaticly.

public class PLObservableNotifyList<T> :
            ObservableCollection<T> where T : INotifyPropertyChanged
{
    public ItemPropertyChangedEventHandler ItemPropertyChanged;
    public EventHandler CollectionCleared;

    protected override void OnCollectionChanged(
                                NotifyCollectionChangedEventArgs args)
    {
        base.OnCollectionChanged(args);

        if (args.NewItems != null)
            foreach (INotifyPropertyChanged item in args.NewItems)
                item.PropertyChanged += OnItemPropertyChanged;

        if (args.OldItems != null)
            foreach (INotifyPropertyChanged item in args.OldItems)
                item.PropertyChanged -= OnItemPropertyChanged;
    }

    void OnItemPropertyChanged(object sender,
                               PropertyChangedEventArgs args)
    {
        if (ItemPropertyChanged != null)
            ItemPropertyChanged(this,
                new PLItemPropertyChangedEventArgs(sender,
                                                 args.PropertyName));
    }

    protected override void ClearItems()
    {
        foreach (INotifyPropertyChanged item in Items)
            item.PropertyChanged -= OnItemPropertyChanged;

        if (CollectionCleared != null)
            CollectionCleared(this, EventArgs.Empty);

        base.ClearItems();
    }
}
似狗非友 2024-10-28 21:13:54

您需要我的 ObservableComputations 库。使用该库您可以编写代码:

            private Computing<string> _checkedUrgencyItemsText;
            public Computing<string> CheckedUrgencyItemsText = _selectedUrgencyItemsText ?? 
                 Expr.Is(() => UrgencyItems.Filtering(urgencyItem => urgencyItem.IsChecked)
                   .Using(checkedUrgencyItems => 
                        checkedUrgencyItems.Count > 0 
                        ?  string.Format("{0} Items Selected", checkedUrgencyItems.Count) 
                        : "All")).Computing();
                    <Controls:RadDropDownButton Content="{Binding Path=CheckedUrgencyItemsText.Value}" AutoOpenDelay="0:0:0.0" x:Name="Urgency" VerticalAlignment="Center" Width="150"  HorizontalContentAlignment="Left">
                    <Controls:RadDropDownButton.DropDownContent>
                        <ListBox x:Name="UrgencyList">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <CheckBox Content="{Binding Name}" ClickMode="Press" IsChecked="{Binding IsChecked, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
                                    </StackPanel>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </Controls:RadDropDownButton.DropDownContent>
                </Controls:RadDropDownButton>

You need my ObservableComputations library. Using that library you can code:

            private Computing<string> _checkedUrgencyItemsText;
            public Computing<string> CheckedUrgencyItemsText = _selectedUrgencyItemsText ?? 
                 Expr.Is(() => UrgencyItems.Filtering(urgencyItem => urgencyItem.IsChecked)
                   .Using(checkedUrgencyItems => 
                        checkedUrgencyItems.Count > 0 
                        ?  string.Format("{0} Items Selected", checkedUrgencyItems.Count) 
                        : "All")).Computing();
                    <Controls:RadDropDownButton Content="{Binding Path=CheckedUrgencyItemsText.Value}" AutoOpenDelay="0:0:0.0" x:Name="Urgency" VerticalAlignment="Center" Width="150"  HorizontalContentAlignment="Left">
                    <Controls:RadDropDownButton.DropDownContent>
                        <ListBox x:Name="UrgencyList">
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <CheckBox Content="{Binding Name}" ClickMode="Press" IsChecked="{Binding IsChecked, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
                                    </StackPanel>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </Controls:RadDropDownButton.DropDownContent>
                </Controls:RadDropDownButton>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文