DependencyProperty 未在 NotifyCollectionChanged 事件上通知 UI [英] DependencyProperty not notifying UI on NotifyCollectionChanged event

查看:43
本文介绍了DependencyProperty 未在 NotifyCollectionChanged 事件上通知 UI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一个自定义控件,该控件具有选定的项目依赖项属性,我已将该属性连接到集合更改事件,但未通知 UI,并且 PropertyChanged 事件始终为空.通常我会说这是一个数据上下文问题.但我无法更改控件上的数据上下文,因为不会显示任何数据.

I am working with a custom control which has a selected items dependency property which I have wired up the collection changed event but the UI is not notified and the PropertyChanged event is always null. Normally I would say this is a datacontext issue. but I cannot change the data context on the control as no data will display.

    public ObservableCollection<object> SelectedItems
    {
        get { return (ObservableCollection<object>)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectedItems.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.RegisterAttached("SelectedItems", typeof(ObservableCollection<object>), 
        typeof(MultiSelectComboBox), 
        new System.Windows.PropertyMetadata(new ObservableCollection<object>(), new PropertyChangedCallback(SelectedItemsPropertyChanged)));

    private static void SelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MultiSelectComboBox relay = d as MultiSelectComboBox;
        if (e.OldValue != null)
        {
            var coll = (INotifyCollectionChanged)e.OldValue;
            coll.CollectionChanged -= relay.SelectedItemsCollectionChanged;
        }
        if (e.NewValue != null)
        {
            var coll = (INotifyCollectionChanged)e.NewValue;
            coll.CollectionChanged += relay.SelectedItemsCollectionChanged;
        }
    }

上面是它在 xaml 中绑定到 ViewModel 上的 ObservableCollection 的属性声明.我错过了什么.该控件实现了 INotifyPropertyChanged.

Above is the property declaration it is bound in xaml to an ObservableCollection on the ViewModel. What am I missing. The control implements INotifyPropertyChanged.

我在下面添加了更多代码.这是原始代码,我想将相同的属性更改为依赖属性以绑定到集合目的.

I have added more code below. This is the original code and I would like to change the same property to a dependency property for binding to collection puposes.

    namespace Kepler.SilverlightControls.MultiSelectComboBox
    {
/// <summary>
/// MultiSelect ComboBox
/// </summary>
public class MultiSelectComboBox : Telerik.Windows.Controls.RadComboBox,    INotifyPropertyChanged
{
    #region Events

    /// <summary>
    /// Est appelé quand une propriété change
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of MultiSelectComboBox
    /// </summary>
    public MultiSelectComboBox()
    {
        ClearSelectionButtonVisibility = Visibility.Collapsed;

        string xaml = @"<DataTemplate 
            xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
            xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
            xmlns:local=""clr-namespace:Kepler.SilverlightControls.MultiSelectComboBox;assembly=Kepler.SilverlightControls"">
            <TextBlock TextWrapping=""Wrap"" local:MultiSelectComboBoxService.SelectionBoxLoaded=""True"" />
            </DataTemplate>";

        var selectionBoxTemplate = (DataTemplate)XamlReader.Load(xaml);
        SelectionBoxTemplate = selectionBoxTemplate;
        EmptySelectionBoxTemplate = selectionBoxTemplate;

        xaml = @"<DataTemplate 
            xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
            xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""    
            xmlns:local=""clr-namespace:Kepler.SilverlightControls.MultiSelectComboBox;assembly=Kepler.SilverlightControls"">
            <CheckBox local:MultiSelectComboBoxService.ComboBoxItemLoaded=""True"" 
                IsChecked=""{Binding Path=(local:MultiSelectComboBoxService.IsChecked), Mode=TwoWay, RelativeSource={RelativeSource Self}}"" />
            </DataTemplate>";
        ItemTemplate = (DataTemplate)XamlReader.Load(xaml);
    }

    #endregion

    #region Propriétés

    /// <summary>
    /// IsCheckedBindingPath Property
    /// </summary>
    public string IsCheckedBindingPath
    {
        get { return (string)GetValue(IsCheckedBindingPathProperty); }
        set { SetValue(IsCheckedBindingPathProperty, value); }
    }

    // Using a DependencyProperty as the backing store for IsCheckedBindingPath.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsCheckedBindingPathProperty =
        DependencyProperty.Register("IsCheckedBindingPath", typeof(string), typeof(MultiSelectComboBox), new PropertyMetadata(null, (obj, e) =>
        {
            Telerik.Windows.Controls.TextSearch.SetTextPath(obj, e.NewValue as string);
        }));



    /// <summary>
    /// DisplayBindingPath Property
    /// </summary>
    public static readonly DependencyProperty DisplayBindingPathProperty = DependencyProperty.Register(
       "DisplayBindingPath", typeof(string), typeof(MultiSelectComboBox), new PropertyMetadata(null, (obj, e) =>
       {
           Telerik.Windows.Controls.TextSearch.SetTextPath(obj, e.NewValue as string);
       }));
    /// <summary>
    /// Gets or sets the display member path (we can't reuse DisplayMemberPath property)
    /// </summary>
    public string DisplayBindingPath
    {
        get { return GetValue(DisplayBindingPathProperty) as string; }
        set { SetValue(DisplayBindingPathProperty, value); }
    }

    private ObservableCollection<object> _selectedItems;
    /// <summary>
    /// Gets the selected items
    /// </summary>
    public ObservableCollection<object> SelectedItems
    {
        get
        {
            if (_selectedItems == null)
            {
                _selectedItems = new ObservableCollection<object>();
                _selectedItems.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedItemsCollectionChanged);
            }
            return _selectedItems;
        }
    }

    private ObservableCollection<object> _selectedValues;
    /// <summary>
    /// Gets the selected values
    /// </summary>
    public ObservableCollection<object> SelectedValues
    {
        get
        {
            if (_selectedValues == null)
            {
                _selectedValues = new ObservableCollection<object>();
                _selectedValues.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedValuesCollectionChanged);
            }
            return _selectedValues;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Called when the Items property changed
    /// </summary>
    /// <param name="e">change informations</param>
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);

        int idx;
        var selectedItems = SelectedItems;

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
            case NotifyCollectionChangedAction.Replace:
            case NotifyCollectionChangedAction.Reset:
                var items = e.NewItems;
                if (items == null)
                {
                    var selected = new List<object>();
                    foreach (var item in this.ItemsSource)
                    {
                        PropertyInfo isCheckedBindingPathProperty = this.IsCheckedBindingPath != null ? item.GetType().GetProperty(this.IsCheckedBindingPath) : null;
                        if (isCheckedBindingPathProperty != null
                            && (bool)isCheckedBindingPathProperty.GetValue(item,null) == true)
                        {
                            selected.Add(item);
                            SelectedValues.Add(item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
                        }
                    }
                    items = selected;
                }
                if (items != null)
                {
                    foreach (object value in SelectedValues)
                    {
                        foreach (object item in items)
                        {
                            if (GetSelectedValue(item).Equals(value) && !selectedItems.Contains(item))
                            {
                                selectedItems.Add(item);
                            }
                        }
                    } 
                }
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (object item in e.OldItems)
                {
                    idx = selectedItems.IndexOf(item);
                    if (idx >= 0)
                    {
                        selectedItems.RemoveAt(idx);
                    }
                }
                break;
        }
    }

    private void RemoveCollectionChangedEvents()
    {
        SelectedItems.CollectionChanged -= new NotifyCollectionChangedEventHandler(SelectedItemsCollectionChanged);
        SelectedValues.CollectionChanged -= new NotifyCollectionChangedEventHandler(SelectedValuesCollectionChanged);
    }

    private void AddCollectionChangedEvents()
    {
        SelectedItems.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedItemsCollectionChanged);
        SelectedValues.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedValuesCollectionChanged);
    }

    private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (SelectedValuePath != null)
        {
            RemoveCollectionChangedEvents();
            try
            {
                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        AddSelectedValues(e.NewItems);
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        RemoveSelectedValues(e.OldItems);
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        RemoveSelectedValues(e.OldItems);
                        AddSelectedValues(e.NewItems);
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        SelectedValues.Clear();
                        foreach (object item in Items)
                        {
                            UpdateSelectedItem(item, false);
                        }
                        AddSelectedValues(e.NewItems);
                        break;
                }
            }
            finally
            {
                AddCollectionChangedEvents();
            }
        }
        RaiseSelectedItemsPropertyChanged();
    }

    private void RemoveSelectedValues(IList items)
    {
        foreach (var item in items)
        {
            SelectedValues.Remove(GetSelectedValue(item));
            UpdateSelectedItem(item, false);
        }
    }

    private void AddSelectedValues(IList items)
    {
        if (items != null)
        {
            object selectedValue;
            foreach (var item in items)
            {
                selectedValue = GetSelectedValue(item);
                if (!SelectedValues.Contains(selectedValue))
                {
                    SelectedValues.Add(selectedValue);
                }
                UpdateSelectedItem(item, true);
            }
        }
    }

    private object GetSelectedValue(object item)
    {
        return DataControlHelper.GetPropertyInfo(item.GetType(), SelectedValuePath).GetValue(item, null);
    }

    private void SelectedValuesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        RemoveCollectionChangedEvents();
        try
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    AddSelectedItems(e.NewItems);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    RemoveSelectedItems(e.OldItems);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    RemoveSelectedItems(e.OldItems);
                    AddSelectedItems(e.NewItems);
                    break;
                case NotifyCollectionChangedAction.Reset:
                    var selectedItems = SelectedItems.ToList();
                    SelectedItems.Clear();
                    foreach (object item in selectedItems)
                    {
                        UpdateSelectedItem(item, false);
                    }
                    AddSelectedItems(e.NewItems);
                    break;
            }
        }
        finally
        {
            AddCollectionChangedEvents();
        }
        RaiseSelectedItemsPropertyChanged();
    }

    private void RaiseSelectedItemsPropertyChanged()
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedItems"));
            // To update the selection box
        }
    }

    private void RemoveSelectedItems(IList values)
    {
        object item;
        foreach (var value in values)
        {
            item = SelectedItems.FirstOrDefault(e => GetSelectedValue(e).Equals(value));
            if (item != null)
            {
                SelectedItems.Remove(item);
                UpdateSelectedItem(item, false);
            }
        }
    }

    private void AddSelectedItems(IList values)
    {
        if (values != null)
        {
            object item;
            foreach (var value in values)
            {
                item = Items.FirstOrDefault(e => GetSelectedValue(e).Equals(value));
                if (item != null)
                {
                    SelectedItems.Add(item);
                    UpdateSelectedItem(item, true);
                }
            }
        }
    }

    private void UpdateSelectedItem(object item, bool select)
    {
        var obj = ItemContainerGenerator.ContainerFromItem(item);
        if (obj != null)
        {
            var cb = obj.FindChildByType<CheckBox>();
            if (cb != null && cb.IsChecked != select)
            {
                cb.IsChecked = select;
            }
        }
    }

    /// <summary>
    /// Create a new ComboBox item
    /// </summary>
    /// <returns>a new ComboBox item</returns>
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new MultiSelectComboBoxItem(this);
    }

    protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
    {
        base.OnKeyDown(e);
        IsDropDownOpen = true;
    }

    #endregion
}

}

此服务类如下:

    namespace Kepler.SilverlightControls.MultiSelectComboBox
    {
/// <summary>
/// Service for the MultiSelect comboBox
/// </summary>
public static class MultiSelectComboBoxService
{
    /// <summary>
    /// IsChecked property
    /// </summary>
    public static DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached("IsChecked",
        typeof(bool), typeof(MultiSelectComboBoxService), new PropertyMetadata(false, (obj, e) =>
        {
            MultiSelectComboBoxItem comboBoxItem = obj.GetVisualParent<MultiSelectComboBoxItem>();
            if (comboBoxItem != null)
            {
                MultiSelectComboBox comboBox = comboBoxItem.ParentComboBox;
                var selectedItems = (IList)comboBox.SelectedItems;
                object item = comboBoxItem.DataContext;
                PropertyInfo isCheckedBindingPathProperty = item.GetType().GetProperty(comboBox.IsCheckedBindingPath);
                isCheckedBindingPathProperty.SetValue(item, e.NewValue,null);
                if ((bool)e.NewValue)
                {
                    if (!selectedItems.Contains(item))
                    {
                        selectedItems.Add(item);
                    }
                }
                else
                {
                    selectedItems.Remove(item);
                }
            }
        }));

    /// <summary>
    /// Gets a value indicating if the object is checked or not
    /// </summary>
    /// <param name="obj">DependencyObject</param>
    /// <returns>a value indicating if the object is checked or not</returns>
    public static bool GetIsChecked(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsCheckedProperty);
    }

    /// <summary>
    /// Sets a value indicating if the object is checked or not
    /// </summary>
    /// <param name="obj">DependencyObject</param>
    /// <param name="value">the value indicating if the object is checked or not</param>
    public static void SetIsChecked(DependencyObject obj, bool value)
    {
        obj.SetValue(IsCheckedProperty, value);
    }

    /// <summary>
    /// SelectionBoxLoaded property called on SelectionBox load
    /// </summary>
    public static DependencyProperty SelectionBoxLoadedProperty = DependencyProperty.RegisterAttached("SelectionBoxLoaded",
        typeof(bool), typeof(MultiSelectComboBoxService), new PropertyMetadata(false, (obj, e) =>
        {
            TextBlock targetElement = obj as TextBlock;
            if (targetElement != null)
            {
                targetElement.Loaded += new RoutedEventHandler(targetElement_Loaded);
            }
        }));

    private static void targetElement_Loaded(object sender, RoutedEventArgs e)
    {
        TextBlock targetElement = (TextBlock)sender;
        targetElement.Loaded -= new RoutedEventHandler(targetElement_Loaded);
        MultiSelectComboBox comboBox = targetElement.GetVisualParent<MultiSelectComboBox>();
        if (comboBox != null)
        {
            targetElement.SetBinding(TextBlock.TextProperty, new Binding("SelectedItems")
            {
                Converter = new MultiSelectComboxConverter(),
                Source = comboBox,
                ConverterParameter = comboBox.DisplayBindingPath
            });
        }
    }

    /// <summary>
    /// Gets the value indicating if the object is loaded or not
    /// </summary>
    /// <param name="obj">DependencyObject</param>
    /// <returns>the value indicating if the object is loaded or not</returns>
    public static bool GetSelectionBoxLoaded(DependencyObject obj)
    {
        return (bool)obj.GetValue(SelectionBoxLoadedProperty);
    }

    /// <summary>
    /// Sets the value indicating if the object is loaded or not
    /// </summary>
    /// <param name="obj">DependencyObject</param>
    /// <param name="value">the value indicating if the object is loaded or not</param>
    public static void SetSelectionBoxLoaded(DependencyObject obj, bool value)
    {
        obj.SetValue(SelectionBoxLoadedProperty, value);
    }

    /// <summary>
    /// ComboBoxItemLoaded called on ComboBoxItem load
    /// </summary>
    public static DependencyProperty ComboBoxItemLoadedProperty = DependencyProperty.RegisterAttached("ComboBoxItemLoaded",
        typeof(bool), typeof(MultiSelectComboBoxService), new PropertyMetadata(false, (obj, e) =>
        {
            CheckBox targetElement = obj as CheckBox;
            if (targetElement != null)
            {
                targetElement.Loaded += new RoutedEventHandler(comboBoxItem_Loaded);
                targetElement.SetBinding(MultiSelectComboBoxService.DataContextProperty, new Binding());
            }
        }));

    private static void comboBoxItem_Loaded(object sender, RoutedEventArgs e)
    {
        FrameworkElement element = (FrameworkElement)sender;
        MultiSelectComboBox comboBox = GetComboBox(element);
        if (comboBox != null)
        {
            element.SetBinding(CheckBox.ContentProperty, new Binding(comboBox.DisplayBindingPath));
            //Binding binding = new Binding(comboBox.IsCheckedBindingPath);
            //binding.Mode = BindingMode.TwoWay;
            //element.SetBinding(CheckBox.IsCheckedProperty, binding);
        }
    }

    /// <summary>
    ///Gets the value indicating if the item is loaded or not
    /// </summary>
    /// <param name="obj">DependencyObject</param>
    /// <returns>the value indicating if the item is loaded or not</returns>
    public static bool GetComboBoxItemLoaded(DependencyObject obj)
    {
        return (bool)obj.GetValue(ComboBoxItemLoadedProperty);
    }

    /// <summary>
    /// Sets the value indicating if the item is loaded or not
    /// </summary>
    /// <param name="obj">DependencyObject</param>
    /// <param name="value">the value indicating if the item is loaded or not</param>
    public static void SetComboBoxItemLoaded(DependencyObject obj, bool value)
    {
        obj.SetValue(ComboBoxItemLoadedProperty, value);
    }

    private static MultiSelectComboBox GetComboBox(DependencyObject targetElement)
    {
        MultiSelectComboBoxItem item = targetElement.GetVisualParent<MultiSelectComboBoxItem>();
        if (item != null)
        {
            return item.ParentComboBox;
        }
        return null;
    }

    private static DependencyProperty DataContextProperty = DependencyProperty.RegisterAttached("DataContext",
        typeof(object), typeof(MultiSelectComboBoxService), new PropertyMetadata(null, (obj, e) =>
        {
            CheckBox checkBox = (CheckBox)obj;
            MultiSelectComboBox comboBox = GetComboBox(checkBox);
            if (comboBox != null)
            {
                checkBox.IsChecked = comboBox.SelectedItems.Contains(checkBox.DataContext);
            }
        }));
    private static object GetDataContext(DependencyObject obj)
    {
        return obj.GetValue(DataContextProperty);
    }
    private static void SetDataContext(DependencyObject obj, object value)
    {
        obj.SetValue(DataContextProperty, value);
    }
}

}

转换器如下:

    namespace Kepler.SilverlightControls.MultiSelectComboBox
    {
#region Méthodes
public class MultiSelectComboxConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string displayMemberPath = parameter as string;
        if (String.IsNullOrWhiteSpace(displayMemberPath))
        {
            return String.Empty;
        }

        PropertyInfo propertyInfo;
        return string.Join(", ", (value as IEnumerable<object>).Select(item =>
            {
                propertyInfo = DataControlHelper.GetPropertyInfo(item.GetType(), displayMemberPath);
                if (propertyInfo == null)
                {
                    return String.Empty;
                }
                return propertyInfo.GetValue(item, null);
            }).ToArray());
    }

    /// <summary>
    /// Not implemented
    /// </summary>
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

#endregion
}

}

推荐答案

您不得将依赖属性注册为附加属性,而应注册为常规依赖属性.此外,您不应使用 new ObservableCollection() 作为默认属性值,因为这会使用相同的集合实例作为 MultiSelectComboBox 的所有实例上的属性的默认值代码>.

You must not register the dependency property as an attached property, but as a regular dependency property instead. Moreover, you should not use new ObservableCollection<object>() as default property value, as that would use the same collection instance as default value for the property on all instances of your MultiSelectComboBox.

public static readonly DependencyProperty SelectedItemsProperty =
    DependencyProperty.Register( // Register instead of RegisterAttached
        "SelectedItems",
        typeof(ObservableCollection<object>), 
        typeof(MultiSelectComboBox), 
        new PropertyMetadata(SelectedItemsPropertyChanged)); // no default value

<小时>

我还建议不要使用 ObservableCollection 作为属性类型,而应使用 ICollectionIEnumerable 作为属性类型.这将允许在具体集合类型中实现 INotifyCollectionChanged 的其他实现.


I'd also recommend not to use ObservableCollection<object> as the property type, but simply ICollection or IEnumerable instead. This would allow for other implementations of INotifyCollectionChanged in the concrete collection type.

public static readonly DependencyProperty SelectedItemsProperty =
    DependencyProperty.Register(
        "SelectedItems",
        typeof(ICollection),
        typeof(MultiSelectComboBox),
        new PropertyMetadata(SelectedItemsPropertyChanged));

public ICollection SelectedItems
{
    get { return (ICollection)GetValue(SelectedItemsProperty); }
    set { SetValue(SelectedItemsProperty, value); }
}

private static void SelectedItemsPropertyChanged(
    DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    var comboBox = (MultiSelectComboBox)obj;
    var oldCollection = e.OldValue as INotifyCollectionChanged;
    var newCollection = e.NewValue as INotifyCollectionChanged;

    if (oldCollection != null)
    {
        oldCollection.CollectionChanged -= SelectedItemsCollectionChanged;
    }

    if (newCollection != null)
    {
        newCollection.CollectionChanged += SelectedItemsCollectionChanged;
    }
}

private static void SelectedItemsCollectionChanged(
    object sender, NotifyCollectionChangedEventArgs e)
{
    switch (e.Action)
    {
        ...
    }
}

<小时>

另请注意,MultiSelectComboBox 不需要实现 INotifyPropertyChanged.这仅用于通知非依赖属性的属性更改.


Please also note that the MultiSelectComboBox does not need to implement INotifyPropertyChanged. This would only be necessary for notifying changes of properties that are no dependency properties.

这篇关于DependencyProperty 未在 NotifyCollectionChanged 事件上通知 UI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆