c# - 当自定义控件的子项及其属性发生更改时,如何重新呈现自定义控件?

标签 c# wpf xaml wpf-controls dependency-properties

最初的问题是不知道为什么 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 ,我订阅了PropertyChangedEventHandlerMyItem对象被添加到集合中/从集合中删除。

到目前为止,一切顺利...

MyItem类定义了更多DependencyProperies ,每个都有相同的 PropertyChangedCallback功能。 然而令我惊讶的是,当我修改MyItem之一时,这个回调函数没有被触发。 XAML 中的属性。

我希望了解的是为什么会发生这种情况以及为什么PropertyChangedCallback没有开火。另外,我想知道什么情况下会导致回调触发。

我的目标是拥有一个在以下情况下重新呈现的控件:
a) 其属性已更改
b) 添加/删除其子项
c) 其子级属性发生更改
d) 添加/删除其子级的子级
e) 其子级的子级属性已更改。

<小时/>

代码示例

以下是我注册 MyControl.Items 的方法属性(property)。这个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 使用react。有一个会改变的属性。 因此,我建立了一个回调函数,当 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永远不要触发属性更改事件。下面说明了我如何设置 DependencyProperties我尝试在 XAML 中修改:

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> 时,就会触发重新渲染。已设置和未设置。 实例化控件时会自动设置此集合,这会导致第一次重新渲染。

接下来,应该监视集合以检测何时添加和删除项目。为此,我们必须使用PropertyChangedCallback提供给 DependencyProperty 的函数。 此函数只是订阅/取消订阅 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 内的属性时可以通知自定义控件。类(class)已更改。这允许控件在其子项的属性发生更改时重新呈现。

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

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

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

  • ObservableCollection<MyItem>已设置/取消设置。
  • ObservableCollection<MyItem>添加/删除了项目,或者集合被重置。
  • ObservableCollection<MyItem>有一个项目的属性已更改。

完成此说明的最后一步是实现 INotifyPropertyChanged MyItem内的界面类(class)。 PropertyChangedDependencyProperties 中的任何一个时,都会简单地调用事件被改变了。请参阅下面的代码:

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类(class)。但是,当子子项具有 PropertyChanged 时,不会直接导致重新渲染。事件请调用RaisePropertyChanged方法将通知传递回最父控件。

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

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

相关文章:

C# 获取上个月的最后一天给定月份和年份

c# - 如何使用 C# 驱动程序更新 cassandra 中的时间戳字段?

c# - WPF DispatcherTimer 不触发

c# - 当页面方向更改时, slider 控件无法正确调整大小

c# - 如何将 RichTextBox 滚动到底部?

c# - 如何正确重试 HttpRequestMessage?

c# - WPF Canvas Children Bind ObservableCollection 可以包含不同形状和 TextBlocks 的 ViewModel 吗?

wpf - 在不同 DataTemplates 中创建的两个 ScrollViewer 之间共享 Horizo​​ntalOffset

c# - 将我的应用程序升级到 Windows Phone 8.1。 TextBox/PasswordBox 上仍然没有 PlaceholderText

c# - 带有边界框缩放点框的用户控件