WPF 自定义控件继承体系全解析(附圆形进度条示例)

195 阅读6分钟

前言

WPF 是一个强大且灵活的 UI 框架,它不仅提供了丰富的内置控件,还支持高度定制化的控件开发。对于想要深入掌握 WPF 的开发者来说,理解控件的继承体系是迈出自定义控件开发的第一步。

本文将带你梳理 WPF 中用于开发自定义控件的核心继承类,并通过一个完整的实战案例,实现如何从零开始开发一个自绘圆形进度条控件

WPF 控件继承体系详解

WPF 的控件体系庞大而有序,每一层都有其独特的职责和定位。

为了帮助大家快速选择合适的基类来实现你的控件目标,整理了下面这份控件继承类汇总表,并附上每个类的用途说明和典型应用场景。

控件继承类汇总表

继承类继承自用途/定位是否参与布局可否包含子元素典型用途示例
VisualDependencyObject最底层绘图元素,无布局、无交互图形渲染、构建低级绘图控件
DrawingVisualVisual可绘制内容的轻量级元素,适合自绘型 UI图形编辑器、缩略图、图层渲染控件
UIElementVisual增加了布局、输入、焦点、事件支持自定义输入处理控件,如手势控件
FrameworkElementUIElement提供布局、样式、数据绑定等完整基础自定义容器、自绘控件、数据绑定控件
DecoratorFrameworkElement只能包含一个子元素,用于包裹和增强子元素功能✅(单个)Border、自定义阴影或边框控件
PanelFrameworkElement容器控件,可布局多个子元素✅(多个)GridStackPanel、自定义布局容器
ControlFrameworkElement基础控件类,支持样式和模板✅(可定义)创建样式化可重用控件,如开关、进度条等
ContentControlControl支持一个内容的控件,内容可为任意对象✅(一个)自定义按钮、卡片、标签等
ItemsControlControl支持多个内容项的控件,通常用于数据列表✅(多个)列表、标签组、自定义列表控件
ShapeFrameworkElement用于矢量图形绘制(线、圆、矩形等)自定义图形控件(如频谱图、路径动画等)
AdornerFrameworkElement用于视觉装饰其他控件的叠加层拖拽提示框、元素边框装饰、拖放指示等
UserControlContentControl组合控件的容器,适合快速搭建封装控件(不是最底层)快速开发组合控件,如登录框、自定义弹窗等

继承建议速查

目标推荐继承类理由
需要完全自绘和极致性能DrawingVisual / UIElement控制精细,性能高,适合复杂绘图控件(但功能需全手动实现)
自定义布局容器Panel完全控制子元素的布局规则
做一个轻量但样式可变的控件Control支持样式、模板、绑定,是自定义控件的标准方式
做带有子内容的容器控件ContentControl / ItemsControl继承 Control,支持模板化和内容绑定
做快速组合控件(例如封装控件树)UserControl简洁方便,但不适合深度定制的可重用控件
做装饰或增强其他控件Decorator / Adorner无需重新绘制,可复用现有控件外观

实战案例

自绘圆形进度条 RingProgressBar

以一个实际项目中常用的控件——圆形进度条为例,演示如何从头创建一个完全自绘的控件。

这个控件不会依赖任何现成的 UI 元素(比如 Border、TextBlock),而是通过重写 OnRender 方法,使用 DrawingContext 手动绘制出一个漂亮的进度环。

控件实现

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;

        double radius = Math.Min(width, height) / 2 - 5;
        Point center = new(width / 2, height / 2);

        // 绘制背景圆环(灰色)
        dc.DrawEllipse(
            null,
            new Pen(Brushes.LightGray, 10),
            center,
            radius, radius);

        // 计算角度
        double angle = Progress / 100 * 360;
        double radians = (angle - 90) * Math.PI / 180;

        // 圆环起点
        Point startPoint = new(center.X, center.Y - radius);
        Point endPoint = new(
            center.X + radius * Math.Cos(radians),
            center.Y + radius * Math.Sin(radians));

        bool isLargeArc = angle > 180;

        PathFigure figure = new(
            startPoint,
            new[]
            {
                new ArcSegment(
                    endPoint,
                    new Size(radius, radius),
                    0,
                    isLargeArc,
                    SweepDirection.Clockwise,
                    true)
            },
            false);

        PathGeometry geometry = new();
        geometry.Figures.Add(figure);

        // 绘制进度圆环(蓝色)
        dc.DrawGeometry(
            null,
            new Pen(Brushes.SteelBlue, 10),
            geometry);
    }

    protected override Size MeasureOverride(Size availableSize) => new Size(100, 100);
    protected override Size ArrangeOverride(Size finalSize) => finalSize;
}

使用方式

首先在 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"
    Title="RingProgressBar 自定义控件预览效果"
    Width="800"
    Height="450"
    WindowStartupLocation="CenterScreen">
    <Grid>
        <!-- 自定义控件 -->
        <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 属性的变化,动态绘制一段彩色弧线作为进度条。整个控件没有使用任何现有控件元素,完全基于 OnRender 方法完成自定义绘制。

总结

WPF 的自定义控件开发并不神秘,关键在于理解控件的继承层级以及每种基类的适用场景。

  • 如果你追求极致性能和完全控制,可以从 DrawingVisualUIElement 开始;

  • 如果你需要布局支持和样式扩展性,Control 是最佳起点;

  • 如果你只是想快速封装一组控件,UserControl 是最便捷的选择;

  • 而如果想开发一个完全自绘的控件,像本文中的 RingProgressBar 这样继承 FrameworkElement 并重写 OnRender,就是一条可行之路。

通过不断实践和探索,会发现自定义控件其实并不难,它就像是一把钥匙,打开了一扇通往更高层次 UI 开发的大门。

关键词

WPF、自定义控件、FrameworkElement、DrawingContext、OnRender、RingProgressBar、UIElement、Control、UserControl、Panel

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:听风睡好觉

出处:blog.csdn.net/yzm3411/article/details/147893442

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!