这篇文章呢,整理了 WPF 中用于自定义控件的核心继承类,按层级结构和用途从底层到底层进行了分类,适合控件开发者快速查阅和选择合适的继承基类。
🧩 控件继承类汇总表
| 继承类 | 继承自 | 用途/定位 | 是否参与布局 | 可否包含子元素 | 典型用途示例 |
|---|---|---|---|---|---|
Visual | DependencyObject | 最底层绘图元素,无布局、无交互 | ❌ | ❌ | 图形渲染、构建低级绘图控件 |
DrawingVisual | Visual | 可绘制内容的轻量级元素,适合自绘型 UI | ❌ | ❌ | 图形编辑器、缩略图、图层渲染控件 |
UIElement | Visual | 增加了布局、输入、焦点、事件支持 | ✅ | ❌ | 自定义输入处理控件,如手势控件 |
FrameworkElement | UIElement | 提供布局、样式、数据绑定等完整基础 | ✅ | ✅ | 自定义容器、自绘控件、数据绑定控件 |
Decorator | FrameworkElement | 只能包含一个子元素,用于包裹和增强子元素功能 | ✅ | ✅(单个) | Border、自定义阴影或边框控件 |
Panel | FrameworkElement | 容器控件,可布局多个子元素 | ✅ | ✅(多个) | Grid、StackPanel、自定义布局容器 |
Control | FrameworkElement | 基础控件类,支持样式和模板 | ✅ | ✅(可定义) | 创建样式化可重用控件,如开关、进度条等 |
ContentControl | Control | 支持一个内容的控件,内容可为任意对象 | ✅ | ✅(一个) | 自定义按钮、卡片、标签等 |
ItemsControl | Control | 支持多个内容项的控件,通常用于数据列表 | ✅ | ✅(多个) | 列表、标签组、自定义列表控件 |
Shape | FrameworkElement | 用于矢量图形绘制(线、圆、矩形等) | ✅ | ❌ | 自定义图形控件(如频谱图、路径动画等) |
Adorner | FrameworkElement | 用于视觉装饰其他控件的叠加层 | ✅ | ✅ | 拖拽提示框、元素边框装饰、拖放指示等 |
UserControl | ContentControl | 组合控件的容器,适合快速搭建封装控件(不是最底层) | ✅ | ✅ | 快速开发组合控件,如登录框、自定义弹窗等 |
🎯 继承建议速查
| 目标 | 推荐继承类 | 理由 |
|---|---|---|
| 需要完全自绘和极致性能 | DrawingVisual / UIElement | 控制精细,性能高,适合复杂绘图控件(但功能需全手动实现) |
| 自定义布局容器 | Panel | 完全控制子元素的布局规则 |
| 做一个轻量但样式可变的控件 | Control | 支持样式、模板、绑定,是自定义控件的标准方式 |
| 做带有子内容的容器控件 | ContentControl / ItemsControl | 继承 Control,支持模板化和内容绑定 |
| 做快速组合控件(例如封装控件树) | UserControl | 简洁方便,但不适合深度定制的可重用控件 |
| 做装饰或增强其他控件 | Decorator / Adorner | 无需重新绘制,可复用现有控件外观 |
🎯 案例:自绘圆环进度控件 RingProgressBar
🛠 控件实现(C# + WPF)
🎉 你想打造什么样的控件?
- 如果你不打算使用
TextBlock或Border等现成控件,只想自己“画”控件,我们可以从FrameworkElement或更底层的UIElement继承,重写OnRender,使用DrawingContext手动绘制内容。 - 如果希望控件具有样式扩展性、模板支持、状态逻辑复用等功能,我们可以从
Control继承并创建默认ControlTemplate来实现控件的视觉结构。 当然,如果你想掌握或深入了解
DrawingVisual的用法,可进一步深入DrawingContext与Visual的使用,这种新式在性能优化型控件中比较常见。💡 这个案例我们不继承
Control,而是直接从 FrameworkElement 继承,手动去重写渲染逻辑,创建一个简洁、可绑定进度值的圆形进度条控件。🥳// 首先呢,我们得引入命名空间吧😂 using System; using System.Windows; using System.Windows.Media; using System.Windows.Controls; /// <summary> /// 自定义圆环进度条控件,继承自 FrameworkElement,重写 OnRender 方法进行绘制。 /// </summary> public class RingProgressBar : FrameworkElement { /// <summary> /// 声明并注册一个依赖属性 Progress,用于控制进度条显示的百分比(0 ~ 100)。 /// </summary> public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register( nameof(Progress), // 属性名称 typeof(double), // 属性类型 typeof(RingProgressBar), // 所属类型 new FrameworkPropertyMetadata( 0.0, // 默认值 FrameworkPropertyMetadataOptions.AffectsRender // 当值变化时自动触发重绘 )); /// <summary> /// Progress 属性包装器,提供进度值的获取和设置。 /// 设置时自动裁剪到 0~100 范围。 /// </summary> public double Progress { get => (double)GetValue(ProgressProperty); set => SetValue(ProgressProperty, Math.Max(0, Math.Min(100, value))); } /// <summary> /// 重写 OnRender 方法,使用 DrawingContext 绘制控件外观。 /// </summary> protected override void OnRender(DrawingContext dc) { double width = ActualWidth; double height = ActualHeight; // 根据控件尺寸计算圆环半径,保留 5 像素边距 double radius = Math.Min(width, height) / 2 - 5; // 圆心位置 Point center = new(width / 2, height / 2); // 1. 绘制背景圆环(灰色圆环) dc.DrawEllipse( null, // 无填充 new Pen(Brushes.LightGray, 10), // 使用浅灰色画笔,宽度10 center, // 圆心 radius, radius // 水平半径 & 垂直半径 ); // 2. 计算进度角度(转换为 0~360) double angle = Progress / 100 * 360; // 3. 将角度转换为弧度,-90 是为了从正上方开始绘制 double radians = (angle - 90) * Math.PI / 180; // 4. 圆环起点:圆顶部 Point startPoint = new(center.X, center.Y - radius); // 5. 计算终点:根据弧度计算 X/Y Point endPoint = new( center.X + radius * Math.Cos(radians), center.Y + radius * Math.Sin(radians) ); // 6. 判断是否需要绘制大角度弧(超过180度) bool isLargeArc = angle > 180; // 7. 创建一个弧线段,从 startPoint 到 endPoint,构成圆环的一部分 PathFigure figure = new( startPoint, // 弧线起点 new[] { new ArcSegment( endPoint, // 弧线终点 new Size(radius, radius), // 弧线的X/Y半径 0, // 旋转角度 isLargeArc, // 是否为大角度弧线 SweepDirection.Clockwise, // 顺时针方向 true // 弧线是否可见(用于裁剪,一般设为 true) ) }, false // 图形是否闭合(环形不闭合) ); // 8. 创建 PathGeometry 对象用于绘制 PathGeometry geometry = new(); geometry.Figures.Add(figure); // 9. 绘制进度圆环(蓝色) dc.DrawGeometry( null, // 无填充 new Pen(Brushes.SteelBlue, 10), // 钢蓝色画笔,宽度10 geometry // 绘制路径 ); } /// <summary> /// Measure 阶段:建议大小为 100x100。 /// </summary> protected override Size MeasureOverride(Size availableSize) => new Size(100, 100); /// <summary> /// Arrange 阶段:直接接受布局系统给予的最终大小。 /// </summary> protected override Size ArrangeOverride(Size finalSize) => finalSize; }🧪 使用方式(XAML 中使用)
接着我们需要在 XAML 文件中使用该控件,首先要在窗体或页面里引入命名空间,然后像这样使用:
<Window
x:Class="Custom.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ct="clr-namespace:Custom.Controls.Controls;assembly=Custom.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Custom"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="RingProgressBar 自定义控件预览效果"
Width="800"
Height="450"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<!-- // 自定义控件 - 预览主体部分 // -->
<ct:RingProgressBar
x:Name="ringProgress"
Width="200"
Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Progress="0" />
<TextBlock
x:Name="ShowProgressValue"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Foreground="#d3d3d3" />
<Button
Width="100"
Height="30"
Margin="0,50"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Click="Button_Click"
Content="🚀 执行" />
</Grid>
</Window>🧩 效果预览
这个控件会在一个 200x200 区域内绘制一个灰色圆环,然后根据 Progress 属性(0 到 100)绘制一段彩色弧线作为进度条,没有使用 Border、TextBlock 或其它任何控件元素,完全基于 OnRender 自绘。
🚀 写在最后:自定义控件,其实没你想的那么玄
我也是刚开始学习这个自定义控件方面,没开始之前感觉自定义控件比较难,但是只有自己上手之后,才发现自定义控件并不神秘,只是你少了一把钥匙。现在,门已经打开了,剩下的,就看你如何发散设计思维、去动手实现那些你想要的功能、交互和体验。每一个小控件,都是你对技术世界的一次“雕刻”。
希望这篇文章能为你点亮一盏灯。如果对你有启发,欢迎点个赞、留个言、顺手收藏一下——这是对我继续创作最好的支持。
写给愿意在细节里打磨作品的你,咱们下篇文章见 。🥳