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事件,只要我不反注冊該事件,那么主窗口會一直引用該對象,直到主窗口被關閉,該對象才會被回收。所以,每當我們注冊某個對象的事件時,都有可能在不經意間埋下內存泄露的隱患。
解決這個問題的根本方法是,在必要的時候進行事件的反注冊。但是,在某些情況下,我們可能很難判定這個“必要的時候”。另外,當我們作為類庫的提供者時,我們也很難保證類庫的使用者都記得要反注冊事件。因此,另一個解決方案就是使用弱事件。
弱事件的實現原理很簡單,就是對事件進行一層封裝。不讓事件發布者直接引用監聽者,而是讓他們保留一個監聽者的弱引用。當事件觸發時,發布者會先檢查監聽者是否還存在于內存中,如果存在才通知它。如此一來,監聽者的生命周期就不會依賴于發布者了。
-------------------------------------------------------------------------------------------------
此番論述,未盡其詳。乞盼來日,續有心得,再行補益。