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

时间:2011-03-14 15:22:43

标签: silverlight observablecollection

以下是对本质上是一个简单问题的冗长解释。我正在使用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属性绑定到Converter。我想要的是,如果没有选择任何内容,内容将被读取为“全部”,如果选中了某些内容,则显示所选(已选中)项目的列表。

    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绑定到代码隐藏中的Data,如下所示:

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

这就是问题......

选中复选框后,内容的值应更新。事实并非如此。

之所以不是因为,虽然通知发出IsChecked已被更改,但该通知基本无处可去。 UrgencyItem对象不知道它是ObservableCollection的一部分。关于ObservableCollection的事情是它只在向集合添加/从集合中添加项目时向其绑定发送通知。换句话说,更改集合中项目的属性不会触发CollectionChanged事件,因为没有添加/删除任何对象。

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

任何?

3 个答案:

答案 0 :(得分:1)

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

我知道有三种可能的解决方法:

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

(2)第二种方法是使用类似ReactiveUI framework的东西,它有自己的ReactiveCollection来实现这种行为。

(3)第三种选择是通过Obtics or Continuous Linq之类的东西创建您的urgencyList。他们返回的集合会自动实现此行为。

答案 1 :(得分:0)

这就是我正在使用的。肯在Ken(n)(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<Person>中设置了ItemsSource的对象,我在ItemsControl上设置为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(); } } 。一旦我更新值(使用setter),绑定就会自动更新。

{{1}}

答案 2 :(得分:0)

您需要我的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>