最初的问题是不知道为什么PropertyChangedCallback不能触发,这是由某些代码输入错误(对于SO而言过于本地化)引起的。但是,我已经修改了该问题,以解决在添加/删除了子项目或子项目的属性已更改时如何重新呈现自定义控件的问题。请visit my answer深入了解如何触发重新渲染。


标题可能不是对问题的最好描述,但是无论如何,问题本身都不是很清楚。我认为发布此问题可能有一定用处,因为PropertyChangedCallback触发得异常。

我要编写的自定义控件具有以下结构:


MyControl : FrameworkElement


MyControl.Items


MyItem : FrameworkContentElement


MyItem.SubItems


MySubItem : FrameworkContentElement
MySubItem
....


MyItem
MyItem
....




因此,基本上,我的控件有一个DependencyProperty,其中存储了一个ObservableCollection<MyItem>
DependencyProperty有一个PropertyChangedCallback,用于检测集合的设置/取消设置时间。此回调函数用于预订CollectionChanged事件,以便在从集合中添加/删除MyItem时可以引起控件的重新呈现。

当集合项的属性发生更改时,我的控件也应重新呈现。因此,MyItem也实现了INotifyPropertyChanged,并且当PropertyChangedEventHandler对象被添加/删除到集合中时,我订阅了MyItem

到目前为止,一切都很好...

MyItem类定义了更多的DependencyProperies,每个都有相同的PropertyChangedCallback函数。但令我惊讶的是,当我修改XAML中的MyItem属性之一时,不会触发此回调函数。

我希望学习的是为什么会发生这种情况,为什么PropertyChangedCallback没有触发。另外,我想知道什么情况会导致回调触发。

我的目标是在以下情况下重新呈现控件:
a)其属性已更改
b)添加/删除其子级
c)其子代的属性已更改
d)添加/删除其孩子的孩子
e)它的孩子的孩子的属性被更改。



代码样本

这是我注册MyControl.Items属性的方法。此DependencyProperty成功触发属性更改事件。

public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
    "Items",
    typeof(ObservableCollection<MyItem>),
    typeof(MyControl),
    new FrameworkPropertyMetadata(
        null, //Default to null.  Instance-scope value is set in constructor.
        FrameworkPropertyMetadataOptions.AffectsRender,
        OnItemsPropertyChanged));


这是我对MyItems集合的设置/取消设置的响应:

private static void OnItemsPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{   //This callback is called when ObservableCollection<MyItem> is set/unset.
    MyControl ctrl = (MyControl)obj;

    INotifyCollectionChanged oldList = args.OldValue as INotifyCollectionChanged;
    INotifyCollectionChanged newList = args.NewValue as INotifyCollectionChanged;

    //If the old list implements the INotifyCollectionChanged interface, then unsubscribe to CollectionChanged events.
    if (oldList != null)
        oldList.CollectionChanged -= ctrl .OnItemsCollectionChanged;
    //If the new list implements the INotifyCollectionChanged interface, then subscribe to CollectionChanged events.
    if (newList != null)
        newList.CollectionChanged += ctrl .OnItemsCollectionChanged;
}


ObservableCollection<MyItem>添加或删除项目时,将调用以下函数

private void OnItemsCollectionChanged(object source, NotifyCollectionChangedEventArgs args)
{   //Invaliate the visual, causing it to re-layout and re-render.
    InvalidateVisual();

    //The contents of the Items collection was modified.
    //Subscribe/Unsubcribe to the PropertyChanged event as necessary.
    switch (args.Action)
    {
        case NotifyCollectionChangedAction.Add:
            foreach (MyItem mi in args.NewItems)
                mi.PropertyChanged += OnMyItemPropertyChanged;
            break;
        case NotifyCollectionChangedAction.Remove:
            foreach (MyItem mi in args.OldItems)
                mi.PropertyChanged -= OnMyItemPropertyChanged;
            break;
        case NotifyCollectionChangedAction.Replace:
            foreach (MyItem  mi in args.NewItems)
                mi.PropertyChanged += OnMyItemPropertyChanged;
            foreach (MyItem mi in args.OldItems)
                mi.PropertyChanged -= OnMyItemPropertyChanged;
                break;
        case NotifyCollectionChangedAction.Reset:
            foreach (MyItem mi in (source as IEnumerable<MyItem >))
                mi.PropertyChanged += OnMyItemPropertyChanged;
            break;
    }
}


现在,我需要能够对MyItem具有更改的属性做出反应。
因此,当MyItem具有PropertyChanged事件时,我建立了一个回调函数:

private void OnMyItemPropertyChanged(object source, PropertyChangedEventArgs args)
{   //One of the MyItems had a property that was changed,
    //invalidate the visual and re-render.
    InvalidateVisual();
}


永远不会调用前一个函数,因为MyItem's DependencyProperties永远不会触发属性更改事件。下面说明了如何设置在XAML中尝试修改的DependencyProperties

public static readonly DependencyProperty MyIntProperty = DependencyProperty.Register(
    "MyInt",
    typeof(int),
    typeof(MyItem),
    new PropertyMetadata(0, DependencyPropertyChanged));

public static readonly DependencyProperty MyDoubleProperty = DependencyProperty.Register(
    "MyDouble",
    typeof(double),
    typeof(MyItem),
    new PropertyMetadata(0d, DependencyPropertyChanged));

public static readonly DependencyProperty MyStringProperty = DependencyProperty.Register(
    "MyString",
    typeof(string),
    typeof(MyItem),
    new PropertyMetadata("", DependencyPropertyChanged));


以下函数是这些DependencyProperties的回调。如果被触发,它应该提高INotifyPropertyChanged.PropertyChangedEventHandler

private static void DependencyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    MyItem item= (MyItem )obj;
    item.RaisePropertyChanged(args.Property.Name);
}

protected void RaisePropertyChanged(string name)
{   //Notify listeners (such as the parent control) when a property changes.
    if(PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(name));;
}


好吧...我有一组类似的事件/处理程序,用于MyItem.SubItems DependencyProperty,但是在这一点上没有任何用处。

如果您能够对PropertyChangedCallback的工作原理有任何见解,我将不胜感激。感谢您阅读这篇冗长的文章。

最佳答案

我很抱歉,但是原始问题太局限了(小的错别字和复制粘贴错误)。但是,为了使此页面有用,我准备了有关如何创建内部包含子项或子子项的自定义控件的完整说明。此页面还说明了如何配置每个项目,以便属性更改和集合更改导致在原始控件上重新呈现。

首先,自定义控件必须包含用于项目集合的DependencyProperty(和CLR支持的属性)。此集合应实现INotifyCollectionChanged,这使ObservableCollection成为不错的选择。应该将此集合参数化以保存控件的子项。使用原始帖子中的名称,这需要类似于以下内容的代码:

[ContentProperty("Items")] //This allows the "Items" property to be implicitly used in XAML.
public class MyControl : Control
{
    public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
        "Items",
        typeof(ObservableCollection<MyItem>),
        typeof(MyControl),
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, OnItemsChangedProperty));

    //CLR-property.
    [Category("MyControl")]
    public ObservableCollection<MyItem> Items
    {
        get { return (ObservableCollection<MyItem>)GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }

    public MyControl() : base()
    {   //Set a new collection per control, but don't destroy binding.
        SetCurrentValue(ItemsProperty, new ObservableCollection<MyItem>());
    }

    protected override void OnRender(DrawingContext dc)
    {
        //Draw stuff here.
    }

    //More methods defined later...
}


此时,当设置和取消设置ObservabledCollection<MyItem>时,将触发重新渲染。
实例化控件时,将自动设置此集合,这将导致第一次重新呈现。

接下来,应该监视集合以检测何时添加和删除项目。为此,我们必须使用提供给PropertyChangedCallbackDependencyProperty函数。
此函数仅根据项集合是已设置还是未设置来订阅/取消订阅CollectionChanged事件:

private static void OnItemsChangedProperty(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    MyControl ctrl = (MyControl)obj;

    INotifyCollectionChanged oldList = args.OldValue as INotifyCollectionChanged;
    INotifyCollectionChanged newList = args.NewValue as INotifyCollectionChanged;

    //If the old list implements the INotifyCollectionChanged interface, then unsubscribe to CollectionChanged events.
    if (oldList != null)
        oldList.CollectionChanged -= ctrl.OnItemsCollectionChanged;
    //If the new list implements the INotifyCollectionChanged interface, then subscribe to CollectionChanged events.
    if (newList != null)
        newList.CollectionChanged += ctrl.OnItemsCollectionChanged;
}


下面是处理添加/删除项目时的回调函数。同样在此处触发重新渲染:

private void OnItemsCollectionChanged(object source, NotifyCollectionChangedEventArgs args)
{
    InvalidateVisual(); //Re-render MyControl

    switch (args.Action)
    {
        case NotifyCollectionChangedAction.Add:
            foreach (MyItem item in args.NewItems)
                item.PropertyChanged += OnItemPropertyChanged;
            break;
        case NotifyCollectionChangedAction.Remove:
            foreach (MyItem item in args.OldItems)
                item.PropertyChanged -= OnItemPropertyChanged;
            break;
        case NotifyCollectionChangedAction.Replace:
            foreach (MyItem item in args.NewItems)
                item.PropertyChanged += OnItemPropertyChanged;
            foreach (MyItem item in args.OldItems)
                item.PropertyChanged -= OnItemPropertyChanged;
                break;
        case NotifyCollectionChangedAction.Reset:
            foreach (MyItem item in (source as IEnumerable<MyItem>))
                item.PropertyChanged += OnItemPropertyChanged;
            break;
    }
}


如您在上面的函数中看到的那样,根据需要订阅/取消订阅在PropertyChanged中定义的MyItem事件,以便在更改MyItem类中的属性时可以通知自定义控件。这允许控件在子项的属性更改时重新呈现。

这是子项属性更改时的处理程序:

private void OnItemPropertyChanged(object source, PropertyChangedEventArgs args)
{
    InvalidateVisual(); //Just re-render.
}


此时,由于以下情况,自定义控件将重新呈现:


ObservableCollection<MyItem>已设置/未设置。
ObservableCollection<MyItem>具有添加/删除的项目,或者如果重置了集合。
ObservableCollection<MyItem>的一项具有更改的属性。


完成此解释的最后一步是在INotifyPropertyChanged类中实现MyItem接口。当更改任何PropertyChanged时,仅调用DependencyProperties事件。请参见下面的代码:

public class MyItem : FrameworkContentElement, INotifyPropertyChanged
{
    //INotifyPropertyChanged members:
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }

    //DependencyProperties
    public static readonly DependencyProperty MyIntProperty = DependencyProperty.Register(
        "MyInt",
        typeof(int),
        typeof(MyItem),
        new PropertyMetadata(0, DependencyPropertyChanged));
    public static readonly DependencyProperty MyStringProperty = DependencyProperty.Register(
        "MyString",
        typeof(string),
        typeof(MyItem),
        new PropertyMetadata("", DependencyPropertyChanged));

    //Callback that invokes the INotifyPropertyChanged.PropertyChangedEventHandler
    private static void DependencyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        MyItem item = (MyItem)obj;
        item.RaisePropertyChanged(args.Property.Name);
    }
}


如果需要,可以重复此模式以允许子子项引起自定义控件的重新呈现。只需在包含另一个DependencyProperty的第一个子项目中建立一个ObservableCollection,就像MyControl类一样。但是,不是在子子项具有PropertyChanged事件时直接导致重新渲染,而是调用RaisePropertyChanged方法将通知传递回最父级控件。

我希望这可以帮助任何控件作者管理其控件的重新呈现! :)

关于c# - 当子项目及其属性更改时,如何重新呈现自定义控件?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26094035/

10-10 05:53