WPF Telerik.Windows.Controls.Data.PropertyGrid 自定义属性编辑器

1.AI帮忙定义新用户控件

2.在属性上添加TelerikEditorAttribute特性

cs 复制代码
 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);
 }
cs 复制代码
[TelerikEditorAttribute(typeof(DoubleListBoxEditor), "SelectedItems")]

SelectedItems属性可以根据实体情况进行更换;

3.效果

4.扩展

这个编辑控件包含了两个ListBox,我想根据实体的其他属性变更然后变更其中一个ListBox的数据源:

思路就是在DoubleListBoxEditor类里面监听这个属性变化,要挂载事件;

要想找到这个属性的事件就得找到对应的实体;

(要有控件的Parent属性, 视觉树这些概念)

使用自定义Editor控件的Parent属性,可以得到对应的在PropertyGrid中对应的条目包装:

在这个包装中找到DataContext属性得到对应的实体属性包装;

然后再找到Instance属性得到对应实体,再得到对应想拿到的属性;

挂载事件的时机要把控好,不能写在构造函数里,此时界面对象还未赋值;

代码:

cs 复制代码
  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:

cs 复制代码
 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!"); }

          
        }

然后这样调用:

cs 复制代码
 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;

cs 复制代码
  IL_0045: ldloc.2      // node
  IL_0046: ldarg.0      // this
  IL_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      // node
  IL_0059: ldarg.0      // this
  IL_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.在界面对象销毁前,主动取消订阅

新添加一个字段保存事件发布者的对象:

cs 复制代码
private MovePositionActionNode _currentNode;

然后在界面Loaded事件里订阅事件,Unloaded事件里取消订阅:

cs 复制代码
 private void OnLoaded(object sender, RoutedEventArgs e)
 {
     // 通过视觉树找到PropertyGridItem
     var 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;
     }
 }

辅助方法:

cs 复制代码
// 辅助方法:在视觉树中查找父元素
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
cs 复制代码
 // 保存委托引用
 private PropertyChangedEventHandler _positionPropertyChangedHandler;
 //发布者对象引用
 private MovePositionActionNode _currentNode;
cs 复制代码
 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事件,只要我不反注册该事件,那么主窗口会一直引用该对象,直到主窗口被关闭,该对象才会被回收。所以,每当我们注册某个对象的事件时,都有可能在不经意间埋下内存泄露的隐患。

解决这个问题的根本方法是,在必要的时候进行事件的反注册。但是,在某些情况下,我们可能很难判定这个"必要的时候"。另外,当我们作为类库的提供者时,我们也很难保证类库的使用者都记得要反注册事件。因此,另一个解决方案就是使用弱事件。

弱事件的实现原理很简单,就是对事件进行一层封装。不让事件发布者直接引用监听者,而是让他们保留一个监听者的弱引用。当事件触发时,发布者会先检查监听者是否还存在于内存中,如果存在才通知它。如此一来,监听者的生命周期就不会依赖于发布者了。


此番论述,未尽其详。乞盼来日,续有心得,再行补益。

相关推荐
FuckPatience2 天前
WPF 具有跨线程功能的UI元素
wpf
诗仙&李白2 天前
HEFrame.WpfUI :一个现代化的 开源 WPF UI库
ui·开源·wpf
He BianGu2 天前
【笔记】在WPF中Binding里的详细功能介绍
笔记·wpf
He BianGu2 天前
【笔记】在WPF中 BulletDecorator 的功能、使用方式并对比 HeaderedContentControl 与常见 Panel 布局的区别
笔记·wpf
123梦野3 天前
WPF——效果和可视化对象
wpf
He BianGu3 天前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf
时光追逐者4 天前
一款专门为 WPF 打造的开源 Office 风格用户界面控件库
ui·开源·c#·.net·wpf
He BianGu4 天前
【笔记】介绍 WPF XAML 中 Binding 的 StringFormat详细功能
笔记·wpf
Rotion_深5 天前
C# WPF使用线程池运行Action方法
c#·wpf·线程池
攻城狮CSU5 天前
WPF 深入系列.2.布局系统.尺寸属性
wpf