1.AI帮忙定义新用户控件
2.在属性上添加TelerikEditorAttribute特性
private ObservableCollection<string> _axisOrder;[Display(Description = "点位", GroupName = "通用", Name = "轴&顺序", Order = 1)][DataMember][TelerikEditorAttribute(typeof(DoubleListBoxEditor), "SelectedItems")]public ObservableCollection<string> AxisOrder{get => _axisOrder;set => this.RaiseAndSetIfChanged(ref _axisOrder, value);}
[TelerikEditorAttribute(typeof(DoubleListBoxEditor), "SelectedItems")]
SelectedItems属性可以根据实体情况进行更换;
3.效果
4.扩展
这个编辑控件包含了两个ListBox,我想根据实体的其他属性变更然后变更其中一个ListBox的数据源:
思路就是在DoubleListBoxEditor类里面监听这个属性变化,要挂载事件;
要想找到这个属性的事件就得找到对应的实体;
(要有控件的Parent属性, 视觉树这些概念)
使用自定义Editor控件的Parent属性,可以得到对应的在PropertyGrid中对应的条目包装:
在这个包装中找到DataContext属性得到对应的实体属性包装;
然后再找到Instance属性得到对应实体,再得到对应想拿到的属性;
挂载事件的时机要把控好,不能写在构造函数里,此时界面对象还未赋值;
代码:
public override void OnApplyTemplate(){base.OnApplyTemplate();if (!(Parent is PropertyGridField field)){return;}var def = (PropertyDefinition)field.DataContext;CustomPropertyDescriptor pd = null;if (def.Instance is MovePositionActionNode node){node.PropertyChanged -= PositionPropertyChanged;node.PropertyChanged += PositionPropertyChanged;}}
更新:
测试中发现会多次调用PositionPropertyChanged方法,和我预想的结果不同,因为我的写法是:
node.PropertyChanged -= PositionPropertyChanged;
node.PropertyChanged += PositionPropertyChanged;
本来想的是始终只会挂载一个方法上去,但是测试的时候发现每调用一次OnApplyTemplate(),就会多挂载一个方法上去。
原因:
在 C# 中,委托实际上是对象(继承自 System.MulticastDelegate
)。每次使用方法名创建委托时,都会在堆上创建一个新的委托对象实例。
写一段代码模拟上面出现的bug:
public class EventSource{public event EventHandler Event;public void RaiseEvent(){Event.Invoke(null,null);}}public class Example{public void MyMethod(object sender, EventArgs e) { Console.WriteLine("Hello, World!"); }}
然后这样调用:
EventSource obj = new EventSource();for (int i = 0; i < 3; i++){Example ex = new Example();obj.Event -= ex.MyMethod;obj.Event += ex.MyMethod;}obj.RaiseEvent();
结果会调用三次MyMethod,因为我的代码中,执行OnApplyTemplate时都是新创建对象的时候,所以效果类似这段测试代码;
查看IL:
node.PropertyChanged -= PositionPropertyChanged;
node.PropertyChanged += PositionPropertyChanged;
IL_0045: ldloc.2 // nodeIL_0046: ldarg.0 // thisIL_0047: ldftn instance void Lithography.Model.ActionAbout.UI.DoubleListBoxEditor::PositionPropertyChanged(object, class [System]System.ComponentModel.PropertyChangedEventArgs)IL_004d: newobj instance void [System]System.ComponentModel.PropertyChangedEventHandler::.ctor(object, native int)IL_0052: callvirt instance void [ReactiveUI]ReactiveUI.ReactiveObject::remove_PropertyChanged(class [System]System.ComponentModel.PropertyChangedEventHandler)IL_0057: nop// [82 17 - 82 65]IL_0058: ldloc.2 // nodeIL_0059: ldarg.0 // thisIL_005a: ldftn instance void Lithography.Model.ActionAbout.UI.DoubleListBoxEditor::PositionPropertyChanged(object, class [System]System.ComponentModel.PropertyChangedEventArgs)IL_0060: newobj instance void [System]System.ComponentModel.PropertyChangedEventHandler::.ctor(object, native int)IL_0065: callvirt instance void [ReactiveUI]ReactiveUI.ReactiveObject::add_PropertyChanged(class [System]System.ComponentModel.PropertyChangedEventHandler)IL_006a: nop
挂载对象前创建了一个PropertyChangedEventHandler对象,对应着上方:委托实际上是对象(继承自 System.MulticastDelegate
)。每次使用方法名创建委托时,都会在堆上创建一个新的委托对象实例。
也就是说+=操作符相当于执行了Add_TestEvent(new Action(memory.Run)),就是这个new Action包含了对memory指向的内存的引用。而这个引用在CLR看来是可达的,可以通过引发事件来调用该内存,所以这种情况会有内存泄漏的风险。
引用:https://blog.csdn.net/nodeathphoenix/article/details/84549399?fromshare=blogdetail&sharetype=blogdetail&sharerId=84549399&sharerefer=PC&sharesource=qq_59062726&sharefrom=from_link
防止内存泄漏解决办法:
1.在界面对象销毁前,主动取消订阅
新添加一个字段保存事件发布者的对象:
private MovePositionActionNode _currentNode;
然后在界面Loaded事件里订阅事件,Unloaded事件里取消订阅:
private void OnLoaded(object sender, RoutedEventArgs e){// 通过视觉树找到PropertyGridItemvar propertyGridItem = VisualTreeExtensions.FindParent<PropertyGridField>(this);var def = (PropertyDefinition)propertyGridItem.DataContext;if (def.Instance is MovePositionActionNode node){_currentNode = node;_currentNode.PropertyChanged += OnNodePropertyChanged;UpdateAvailableItemsBasedOnPositionType(node.EquipmentPositionType);}}private void OnUnloaded(object sender, RoutedEventArgs e){if (_currentNode != null){_currentNode.PropertyChanged -= OnNodePropertyChanged;_currentNode = null;}}
辅助方法:
// 辅助方法:在视觉树中查找父元素
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{var parent = VisualTreeHelper.GetParent(child);while (parent != null && !(parent is T)){parent = VisualTreeHelper.GetParent(parent);}return parent as T;
}
2.使用弱引用事件:WeakEventManager
// 保存委托引用private PropertyChangedEventHandler _positionPropertyChangedHandler;//发布者对象引用private MovePositionActionNode _currentNode;
public override void OnApplyTemplate(){base.OnApplyTemplate();if (!(Parent is PropertyGridField field)){return;}var def = (PropertyDefinition)field.DataContext;if (def.Instance is MovePositionActionNode node){// 移除旧节点的订阅if (_currentNode != null){WeakEventManager<MovePositionActionNode, PropertyChangedEventArgs>.RemoveHandler(_currentNode,nameof(INotifyPropertyChanged.PropertyChanged),OnNodePropertyChanged);}// 订阅新节点_currentNode = node;WeakEventManager<MovePositionActionNode, PropertyChangedEventArgs>.AddHandler(node,nameof(INotifyPropertyChanged.PropertyChanged),OnNodePropertyChanged);UpdateAvailableItemsBasedOnPositionType(node.EquipmentPositionType);}}
引用1:https://www.cnblogs.com/monkeyZhong/p/4596914.html
引用2:https://blog.csdn.net/weixin_30621959/article/details/97911511?fromshare=blogdetail&sharetype=blogdetail&sharerId=97911511&sharerefer=PC&sharesource=qq_59062726&sharefrom=from_link
.Net中的事件有时会引起内存泄露问题。例如,A注册了B的某个事件,此时B就会暗中保留A的一个强引用,导致A无法被内存回收,直到B被回收或A反注册了B的事件。例如,我有一个对象注册了主窗口的Loaded事件,只要我不反注册该事件,那么主窗口会一直引用该对象,直到主窗口被关闭,该对象才会被回收。所以,每当我们注册某个对象的事件时,都有可能在不经意间埋下内存泄露的隐患。
解决这个问题的根本方法是,在必要的时候进行事件的反注册。但是,在某些情况下,我们可能很难判定这个“必要的时候”。另外,当我们作为类库的提供者时,我们也很难保证类库的使用者都记得要反注册事件。因此,另一个解决方案就是使用弱事件。
弱事件的实现原理很简单,就是对事件进行一层封装。不让事件发布者直接引用监听者,而是让他们保留一个监听者的弱引用。当事件触发时,发布者会先检查监听者是否还存在于内存中,如果存在才通知它。如此一来,监听者的生命周期就不会依赖于发布者了。
-------------------------------------------------------------------------------------------------
此番论述,未尽其详。乞盼来日,续有心得,再行补益。