这篇文章主要为大家展示了“XAML自定义控件中事件处理的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“XAML自定义控件中事件处理的示例分析”这篇文章吧。
在开发 XAML(WPF/UWP) 应用程序中,有时候,我们需要创建自定义控件 (Custom Control) 来满足实际需求。而在自定义控件中,我们一般会用到一些原生的控件(如 Button、TextBox 等)来辅助以完成自定义控件的功能。
自定义控件并不像用户控件 (User Control) 一样,使用 Code-Behind(UI 与逻辑在一起)技术。相反,它通过把 UI 与逻辑分离而将两者解耦。因此,创建一个自定义控件会产生两个文件,一个是 Generic.xaml,在它里面定义其模板与样式;另一个是 <ControlName>.cs,这里面存放其逻辑,如下图:
在这种情况下,要想在代码中获取到模板里定义的控件,就不像 Code-Behind 中那么容易,而要借助于 OnApplyTemplate 和 GetTemplateChild 这两个方法。它们的意义分别如下:
OnApplyTemplate: 在自定义控件中,通常要重写这个方法,当基类调用 ApplyTemplate() 方法以构造可视化树时,会调用它;
GetTemplateChild: 获取 ControlTemplate 中所定义的可视化树上指定名称的元素;
所以,如果我们在模板中定义了一个名为 PART_ViewButton 的按钮,那么,我们可以这样获取它,并为它注册响应事件:
public override void OnApplyTemplate() { base.OnApplyTemplate(); Button btnView = GetTemplateChild("PART_ViewButton") as Button; if (btnView != null) { btnView.Click += BtnView_Click; } } private void BtnView_Click(object sender, RoutedEventArgs e) { // 这里写响应逻辑 }
当我们(或者其他人)要用这个控件时,通过给它设置了模板(一般都是默认模板)后, OnApplyTemplate 方法就会被执行。这样做看起来没什么问题。不过,其实这里有可能会引起一个听起来很严重的问题:内存泄露 (Memory Leak)。
何为内存泄露
内存泄露有多种类型,一般来说,它是指某种类型的资源不再使用,但却仍然占用内存。换句话说,它从受管理的内存区域中“泄漏”出去了。如果在程序中有多处内存泄露,将会占有很多内存,并最终导到内存被耗尽。
在 C# 中,常见的内存泄露有:
• 没有移除事件监听;
• 没有销毁非托管资源(如数据库、文件流等);
对于上面两种情况,它们的解决办法也非常简单,分别是:要反注册事件(即移除事件监听)与调用 Dispose 方法(如果没有,则要实现 IDisposable 接口,并在其中销毁非托管资源)。
对于第二种情况,比较好理解;而对于第一种情况,问题是,为什么没有移除事件监听,会导致内存泄露呢?这是因为事件源比事件监听者的生命周期更长。来看代码:
ObjectA objA = new ObjectA(); ObjectB objB = new ObjectB(); objA.Event += objB.EventHanlder;
ObjectA 中定义了 Event 事件,我们为它注册了一个事件处理器(对象 objB 中的 EventHanlder 方法);因此,事件源 objA 对事件监听对象 objB 存在一个引用。
如果 objB 不再使用,我们要销毁它,但由于 objA 引用了它,所以它不会被销毁、回收;它要等到 objA 销毁时,才能被销毁。所以本来需要被销毁的对象,却因有其它对象对它的引用,结果造成了内存泄露。
如何解决
再回到自定义控件的问题上,因为我们的自定义控件,可能会被重写样式或者重写模板,这会使 OnApplyTemplate 方法在这个自定义控件的生命周期内被执行多次。所以,我们需要为那些通过 GetTemplateChild 方法得到并且又添加了事件处理的控件(如上述代码中的 btnView 控件)进行事件反注册。因为这些都是前一个模板中的控件(元素),当反注册后,原来的控件与事件监听者(自定义控件本身)就不存在引用关系,从而避免了内存泄露的问题。
根据我们的解决思路,对之前的代码重构如下:
private Button btnView = null; public override void OnApplyTemplate() { base.OnApplyTemplate(); // 先反注册事件 if (btnView != null) { btnView.Click -= BtnView_Click; } btnView = GetTemplateChild("PART_ViewButton") as Button; if (btnView != null) { btnView.Click += BtnView_Click; } } private void BtnView_Click(object sender, RoutedEventArgs e) { // 这里写响应逻辑 }
这样,就解决了本文开头所说的问题。不过,接下来,我们还需要做一点调整。
进一步重构
试想,如果我们的自定义控件中,有多个类似像前述 btnView 这样的控件,我们就要将上面的代码在 OnApplyTemplate 方法中复制若干次,从而导致 OnApplyTemplate 方法的复杂度增加,以及代码的可读性变差 。
为了改善这一点,我们将每个控件以及它的事件注册与反注册封装一下。
重构后,代码如下:
protected const string PART_ViewButton = nameof(PART_ViewButton); private Button btnView = null; public Button ViewButton { get { return btnView; } set { // 先反注册事件 if (btnView != null) { btnView.Click -= BtnView_Click; } btnView = value; if (btnView != null) { btnView.Click += BtnView_Click; } } } public override void OnApplyTemplate() { base.OnApplyTemplate(); ViewButton = GetTemplateChild(PART_ViewButton) as Button; } private void BtnView_Click(object sender, RoutedEventArgs e) { // 这里写响应逻辑 }
针对最终的代码,这里再提几点:
1. 在 OnApplyTemplate 方法中,建议一开始要先调用 base.OnApplyTemplate();
2. 无论在为控件反注册事件,还是注册事件时,都要对控件是否为空进行判断,这是因为有可能用户重写模板时没有遵循 TemplatePart 属性中所指定的控件名称;
3. 将控件的名称声明为常量,可以避免字符串拼写错误;
以上是“XAML自定义控件中事件处理的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。