🛠️WPF 自定义Halcon显示控件完整流程与 OnApplyTemplate
未触发的根本原因解析!
本片文章最后给出自定义alcon显示控件源码,可以实现图片绑定!
WPF 中封装控件是非常常见的需求,而“自定义控件”是一种高级的控件复用方式。很多人在第一次尝试自定义控件时会遇到一个常见问题:
✅ 控件已经显示到界面了,但
OnApplyTemplate()
却从未被调用!
本文将带你完整梳理 WPF 自定义控件的定义流程,并重点分析 OnApplyTemplate()
没有触发的真正原因(并不是大家常说的“忘记设置 DefaultStyleKey”!),最后解释为什么必须在 App.xaml 中引入样式资源。
🧱 什么是 WPF 自定义控件?
WPF 中有三种控件封装方式:
封装方式 | 特点 |
---|---|
UserControl |
最简单,直接嵌套已有控件组合 |
CustomControl (继承自 Control ) |
推荐方式,支持样式模板、主题切换 |
TemplatedControl (高级控件) |
在 Control 基础上进一步抽象和通用性封装 |
本文关注的是 自定义控件(即继承自 Control
的控件),其优势包括:
- 可复用性强
- 样式外置,界面逻辑和结构分离
- 支持模板定制和视觉状态管理
✍️ 自定义控件的完整定义流程
1️⃣ 创建控件类(继承 Control
)
public class ImageView : Control
{
static ImageView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageView),
new FrameworkPropertyMetadata(typeof(ImageView)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var part = GetTemplateChild("PART_Content") as Border;
// 可访问模板内部元素
}
}
OnApplyTemplate()
是你获取模板中元素的最佳时机。
2️⃣ 添加样式模板(例如 Views/ImageView.xaml
)
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sw="clr-namespace:MyControlLib.Views">
<Style TargetType="sw:ImageView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="sw:ImageView">
<Border x:Name="PART_Content" Background="LightGray">
<TextBlock Text="模板加载成功" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
控件模板中通过
PART_
前缀命名可供控件类通过GetTemplateChild
获取。
3️⃣ ✅ 最重要的一步:在 App.xaml 引入样式资源!
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Views/ImageView.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
❗OnApplyTemplate()
没有触发的原因!
唯一的原因是:
✅ 样式资源没有添加到
App.xaml
,导致 WPF 没有为控件应用样式!
❓为什么样式必须添加到 App.xaml?
WPF 控件模板来自于资源系统,而不是控件自己:
- WPF 会在控件创建后,到资源系统中查找匹配该控件类型的样式
- 如果找不到对应的样式(例如你没有在
App.xaml
添加),控件就不会被套用样式 - 没有样式 => 没有模板 => 不调用
OnApplyTemplate()
⚠️ 也就是说:
不添加样式 = 没有模板 = OnApplyTemplate()
永远不会执行!
🧪 如何验证样式是否加载成功?
你可以在模板中放一个明显控件,比如:
<TextBlock Text="模板已应用!" Foreground="Red" />
如果程序运行后能看到这个控件,那就说明模板加载成功,OnApplyTemplate()
也会被调用。
✅ 小结
步骤 | 是否必须 |
---|---|
继承 Control |
✅ 是 |
设置 DefaultStyleKey |
✅ 是 |
定义样式模板 | ✅ 是 |
将样式引入 App.xaml | ✅ 是!必须! |
实现 OnApplyTemplate() |
可选,但用于访问模板子元素很常见 |
📎 最后提醒
如果你写的样式是在控件类库项目中,而不是主程序项目,那么:
🔗 引用样式路径应使用 Pack URI 方式:
<ResourceDictionary Source="/MyControlLib;component/Views/ImageView.xaml" />
⚔️ OnApplyTemplate() 与 Loaded 事件的区别
特性 | OnApplyTemplate() |
Loaded |
---|---|---|
调用时机 | 模板刚刚被应用 | 控件已加载进可视树 |
控件类型 | 仅适用于自定义控件(Control ) |
所有控件 |
是否依赖模板 | ✅ 依赖 ControlTemplate |
❌ 不依赖 |
常用于 | 获取模板中的子元素 | 访问可视化控件属性、执行初始化逻辑 |
是否能重复调用 | ✔️ 可能多次(重设样式) | ❌ 通常仅一次(除非卸载重载) |
Halcon自定义显示控件源码
自定义样式
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:h="clr-namespace:HalconDotNet;assembly=halcondotnet"
xmlns:v="clr-namespace:ROIWindow.Views">
<Style TargetType="v:ImageView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="v:ImageView">
<Grid>
<h:HSmartWindowControlWPF
x:Name="PART_hSmart"
HDoubleClickToFitContent="True"
HDrawingObjectsModifier="None"
HKeepAspectRatio="True"
HMoveContent="False"
HZoomContent="WheelForwardZoomsIn" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
自定义控件代码
using HalconDotNet;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace ROIWindow.Views;
public class ImageView : Control
{
private HWindow window;
private HSmartWindowControlWPF hSmart;
static ImageView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageView), new FrameworkPropertyMetadata(typeof(ImageView)));
}
public HObject Image
{
get { return (HObject)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(HObject), typeof(ImageView), new PropertyMetadata(ImageChangedCallBack));
public static void ImageChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageView view)
view.Display();
}
public void Display()
{
if (window == null) return;
if (Image != null && Image.IsInitialized())
{
window.ClearWindow();
window.DispObj(Image);
}
}
public override void OnApplyTemplate()
{
hSmart = (HSmartWindowControlWPF)GetTemplateChild("PART_hSmart");
hSmart.Loaded += Hsmart_Loaded;
base.OnApplyTemplate();
}
private void Hsmart_Loaded(object sender, RoutedEventArgs e)
{
window = hSmart.HalconWindow;
}
}
使用方法
<v:ImageView Image="{Binding Image}" />
最后别忘记在,App.xaml, 添加样式!!!!
<ResourceDictionary Source="pack://application:,,,/CameraXXXXX;component/Controls/ImageView.xaml" />
✍️ 作者:code_bean
📅 发布于:2025年7月
🔗 原创文章,转载请注明出处!