最初的问题是不知道为什么 PropertyChangedCallback
没有触发,这是由一些代码拼写错误引起的(对于SO来说太本地化了)。但是,我修改了问题,以解决当自定义控件具有添加/删除的子项或子项具有更改的属性时如何重新呈现自定义控件。 请visit my answer深入了解如何触发重新渲染。
我正在编写的自定义控件具有以下结构:
-
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
功能。 然而令我惊讶的是,当我修改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)。 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
类(class)。但是,当子子项具有 PropertyChanged
时,不会直接导致重新渲染。事件请调用RaisePropertyChanged
方法将通知传递回最父控件。
我希望这可以帮助任何控件作者管理控件的重新渲染! :)
关于c# - 当自定义控件的子项及其属性发生更改时,如何重新呈现自定义控件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26094035/