WPF 路径动画完全指南:自绘制控件实战

0 阅读19分钟

一、核心概念

1.1 什么是路径动画?

路径动画 = PathGeometry 驱动 UI 元素运动

区别于普通线性动画(From/To/By)和关键帧动画,路径动画以 PathGeometry 为核心,让 UI 元素沿任意复杂轨迹(直线、曲线、弧线、复合图形)运动,还能同步控制旋转、缩放。

自绘制控件 是通过 PathPolygonEllipseRectangle 等基础几何元素手动绘制 UI,优势:

  • 样式完全可定制
  • 交互灵活控制
  • 与路径动画天然适配
  • 摆脱默认控件样式束缚

1.2 三大核心路径动画类

动画类驱动类型核心用途关键特性
DoubleAnimationUsingPathDouble 数值平移、角度、透明度控制需绑定 X/Y/Angle,灵活控制单一属性
PointAnimationUsingPathPoint 点直接定位(Canvas.Left/Top)无需拆分 X/Y,简洁高效
MatrixAnimationUsingPathMatrix 矩阵复合变换(平移+旋转)DoesRotateWithTangent 自动随路径旋转

1.3 PathGeometry 路径语法

路径动画的"轨迹蓝图",XAML 中用 Figures 属性描述:

指令含义语法示例
M移动到起点M 50,120
L画直线L 550,120
C三次贝塞尔曲线C 150,30 400,220 550,120
A圆弧A 50,50 0 1,1 200,120
Z闭合路径Z

常用路径示例:

<!-- 1. 直线路径 -->
<!-- M 50,50:画笔移动到坐标(50,50),不绘图 -->
<!-- L 550,50:从当前点直线连接到坐标(550,50) -->
<PathGeometry x:Key="LinePath" Figures="M 50,50 L 550,50"/>

<!-- 2. 三次贝塞尔曲线路径 -->
<!-- M 50,120:起点坐标(50,120) -->
<!-- C 150,30 400,220 550,120:三次贝塞尔曲线 -->
<!-- 150,30=第一个控制点  400,220=第二个控制点  550,120=曲线终点 -->
<PathGeometry x:Key="CurvePath" Figures="M 50,120 C 150,30 400,220 550,120"/>

<!-- 3. 圆弧路径 -->
<!-- M 100,220:圆弧起点坐标(100,220) -->
<!-- A 50,50 0 1,1 200,220:画圆弧 -->
<!-- 50,50=横向半径、纵向半径  0=圆弧旋转角度 -->
<!-- 1=大弧模式  1=顺时针绘制  200,220=圆弧终点坐标 -->
<!-- Z=闭合路径,终点连回起点形成封闭图形 -->
<PathGeometry x:Key="ArcPath" Figures="M 100,220 A 50,50 0 1,1 200,220 Z"/>

<!-- 4. 复合组合路径 -->
<!-- M 50,300:起始坐标(50,300) -->
<!-- L 150,300:画直线到(150,300) -->
<!-- C 200,230 300,370 400,300:接一段三次贝塞尔曲线 -->
<!-- L 550,300:曲线结束后再画直线到(550,300) -->
<PathGeometry x:Key="CompositePath" Figures="M 50,300 L 150,300 C 200,230 300,370 400,300 L 550,300"/>

image

1.4 DoubleAnimation 基础动画

常与路径动画搭配实现复合动效:

核心属性作用说明
From动画起始值(可选)
To动画结束值(必选)
Duration动画时长,格式 时:分:秒
RepeatBehavior重复行为,Forever 无限循环
AutoReverse是否反向播放

1.5 自绘制控件基础

Ellipse(椭圆/圆形)

<!-- Ellipse 圆形控件 -->
<!-- Canvas.Left="50":距离画布左侧50像素(X轴坐标) -->
<!-- Canvas.Top="40":距离画布顶部40像素(Y轴坐标) -->
<!-- Width/Height="30":圆形宽度和高度均为30像素 -->
<!-- Fill="#E53935":内部填充颜色为红色 -->
<!-- Stroke="Black":边框颜色为黑色 -->
<!-- StrokeThickness="1":边框粗细为1像素 -->
<Ellipse Canvas.Left="50" Canvas.Top="40" Width="30" Height="30" Fill="#E53935" Stroke="Black" StrokeThickness="1"/>

Polygon(多边形)

<!-- Polygon 多边形控件(三角形) -->
<!-- Canvas.Left="50":与圆形X轴对齐,距离左侧50像素 -->
<!-- Canvas.Top="120":距离顶部120像素,位于圆形下方 -->
<!-- Points="0,0 20,10 0,20":定义三个顶点坐标,连接成三角形 -->
<!-- Fill="#4CAF50":内部填充颜色为绿色 -->
<Polygon Canvas.Left="50" Canvas.Top="120"  Points="0,0 20,10 0,20" Fill="#4CAF50" Stroke="Black" StrokeThickness="1"/>

Path(路径)

 <!-- Path 路径控件(矩形) -->
 <!-- Canvas.Left="50":与圆形、三角形X轴对齐,距离左侧50像素 -->
 <!-- Canvas.Top="200":距离顶部200像素,位于三角形下方 -->
 <!-- Data 路径语法:M=起点 L=画线 Z=闭合图形 -->
 <!-- Fill="#2196F3":内部填充颜色为蓝色 -->
 <Path Canvas.Left="50" Canvas.Top="200" Data="M 0,0 L 120,0 L 120,40 L 0,40 Z" Fill="#2196F3" Stroke="#1976D2" StrokeThickness="1"/>

image


二、实战案例

项目结构

00025.WPF 路径动画完全指南:自绘制实战/
├── 1_常用路径示例/
├── 2_自绘制控件基础/
├── 3_实战1:直线 + 圆形动画/
├── 4_实战2:曲线 + 箭头动画/
├── 5_效果A:淡入 + 缩放/
├── 6_路径动画 + 淡入 + 缩放/
├── 7_场景1:自绘制按钮(悬浮动效)/
├── 8_自绘制进度条(流动效果)/
├── 9_自绘制窗口控制按钮/

实战1:直线 + 圆形动画(DoubleAnimationUsingPath)

目标: 红色圆形沿直线往返移动,无限循环。

MainWindow.xaml
<Window x:Class="_3_实战1_直线___圆形动画.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_3_实战1_直线___圆形动画"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <!-- 定义运动轨迹资源 -->
    <Window.Resources>
        <!-- 路径几何图形:用于动画的直线路径 -->
        <PathGeometry x:Key="LinePath" Figures="M 0,0 L 500,0"/>
    </Window.Resources>

    <!-- 画布布局容器 -->
    <Canvas Background="#F8F9FA">

        <!-- 路径轨迹线 -->
        <!-- Data:绘制直线路径坐标 -->
        <!-- Stroke:线条颜色蓝色 -->
        <!-- StrokeThickness:线条粗细2 -->
        <!-- StrokeDashArray:虚线样式 -->
        <Path Data="M 50,120 L 550,120"
              Stroke="Blue" 
              StrokeThickness="2" 
              StrokeDashArray="2,2"/>

        <!-- 圆形动画控件 -->
        <!-- Width:圆形宽度30 -->
        <!-- Height:圆形高度30 -->
        <!-- Canvas.Left:水平初始位置50 -->
        <!-- Canvas.Top:垂直居中对齐直线 -->
        <!-- Fill:填充红色 -->
        <!-- Stroke:边框黑色 -->
        <!-- StrokeThickness:边框粗细1 -->
        <Ellipse Width="30" Height="30" 
                 Canvas.Left="35"
                 Canvas.Top="105"
                 Fill="#E53935"
                 Stroke="Black"
                 StrokeThickness="1">

            <!-- 渲染变换:设置位移变换 -->
            <Ellipse.RenderTransform>
                <TranslateTransform x:Name="BallTranslate"/>
            </Ellipse.RenderTransform>

            <!-- 动画触发器集合 -->
            <Ellipse.Triggers>
                <!-- 路由事件:窗体加载完成触发 -->
                <EventTrigger RoutedEvent="Loaded">
                    <BeginStoryboard>
                        <!-- 故事板:动画容器 -->
                        <!-- RepeatBehavior:永久循环 -->
                        <!-- AutoReverse:自动往返 -->
                        <Storyboard RepeatBehavior="Forever" AutoReverse="True">

                            <!-- X轴路径动画 -->
                            <!-- Storyboard.TargetName:目标元素 -->
                            <!-- Storyboard.TargetProperty:动画属性X -->
                            <!-- Source:数据源为X坐标 -->
                            <!-- PathGeometry:路径资源 -->
                            <!-- Duration:时长3秒 -->
                            <DoubleAnimationUsingPath 
                                Storyboard.TargetName="BallTranslate"
                                Storyboard.TargetProperty="X"
                                Source="X"
                                PathGeometry="{StaticResource LinePath}"
                                Duration="0:0:3"/>

                            <!-- Y轴路径动画 -->
                            <!-- Storyboard.TargetName:目标元素 -->
                            <!-- Storyboard.TargetProperty:动画属性Y -->
                            <!-- Source:数据源为Y坐标 -->
                            <!-- PathGeometry:路径资源 -->
                            <!-- Duration:时长3秒 -->
                            <DoubleAnimationUsingPath 
                                Storyboard.TargetName="BallTranslate"
                                Storyboard.TargetProperty="Y"
                                Source="Y"
                                PathGeometry="{StaticResource LinePath}"
                                Duration="0:0:3"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>
</Window>
MainWindow.xaml.cs
using System.Windows;

namespace WpfPathAnimationDemo.Views
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

效果: 红色圆形沿蓝色虚线直线往返移动,3秒/轮,无限循环。 output

视频转gif tool.bugcome.com/media-tools…


实战2:曲线 + 箭头动画(MatrixAnimationUsingPath)

目标: 绿色箭头沿贝塞尔曲线移动,自动随切线旋转。

MainWindow.xaml
<Window x:Class="_4_实战2_曲线___箭头动画.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_4_实战2_曲线___箭头动画"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <!-- 定义运动轨迹资源 -->
    <Window.Resources>
        <!-- 路径几何图形:用于动画的曲线路径(相对坐标) -->
        <PathGeometry x:Key="CurvePath" Figures="M 0,0 Q 350,-90 650,80"/>
    </Window.Resources>

    <!-- 画布布局容器 -->
    <Canvas Background="#F8F9FA">

        <!-- 路径轨迹线 -->
        <!-- Data:绘制曲线路径坐标 -->
        <!-- Stroke:线条颜色蓝色 -->
        <!-- StrokeThickness:线条粗细2 -->
        <!-- StrokeDashArray:虚线样式 -->
        <Path Data="M 50,120 Q 400,30 700,200"
              Stroke="Blue" 
              StrokeThickness="2" 
              StrokeDashArray="2,2"/>

        <!-- 箭头动画控件 -->
        <!-- Points:三角形箭头顶点坐标 -->
        <!-- Fill:填充绿色 -->
        <!-- Stroke:边框黑色 -->
        <!-- StrokeThickness:边框粗细1 -->
        <!-- Canvas.Left:水平初始位置50 -->
        <!-- Canvas.Top:垂直初始位置120 -->
        <!-- RenderTransformOrigin:旋转中心点(箭头左侧中心) -->
        <Polygon Points="0 0 20 10 0 20"
                 Fill="#4CAF50"
                 Stroke="Black"
                 StrokeThickness="1"
                 Canvas.Left="50"
                 Canvas.Top="120"
                 RenderTransformOrigin="0,0.5">

            <!-- 渲染变换:矩阵变换控制位置与旋转 -->
            <Polygon.RenderTransform>
                <MatrixTransform x:Name="ArrowMatrix"/>
            </Polygon.RenderTransform>

            <!-- 动画触发器集合 -->
            <Polygon.Triggers>
                <!-- 路由事件:窗体加载完成触发 -->
                <EventTrigger RoutedEvent="Loaded">
                    <BeginStoryboard>
                        <!-- 故事板:动画容器 -->
                        <!-- RepeatBehavior:永久循环 -->
                        <!-- AutoReverse:自动往返 -->
                        <!-- Duration:单次动画时长3秒 -->
                        <Storyboard RepeatBehavior="Forever" AutoReverse="True" Duration="0:0:3">

                            <!-- 曲线路径矩阵动画 -->
                            <!-- Storyboard.TargetName:目标元素 -->
                            <!-- Storyboard.TargetProperty:动画矩阵属性 -->
                            <!-- PathGeometry:路径资源 -->
                            <!-- DoesRotateWithTangent:随路径切线自动旋转 -->
                            <MatrixAnimationUsingPath
                                Storyboard.TargetName="ArrowMatrix"
                                Storyboard.TargetProperty="Matrix"
                                PathGeometry="{StaticResource CurvePath}"
                                DoesRotateWithTangent="True"/>

                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Polygon.Triggers>
        </Polygon>

    </Canvas>
</Window>

效果: 绿色箭头沿曲线平滑移动,方向始终与路径切线一致,4秒/轮。 output (1)

视频转gif tool.bugcome.com/media-tools…


实战3:DoubleAnimation 复合动效

效果A:按钮 + 淡入 + 缩放
<Window x:Class="WpfPathAnimationDemo.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DoubleAnimation 基础实战" Height="400" Width="600">
    <Canvas Background="#F8F9FA">
        <Ellipse x:Name="AnimateBall" 
                 Width="30" Height="30" 
                 Fill="#2196F3" 
                 Stroke="Black" 
                 StrokeThickness="1"
                 Opacity="0"
                 Canvas.Left="300" 
                 Canvas.Top="180">
            <Ellipse.RenderTransform>
                <ScaleTransform x:Name="BallScale" CenterX="15" CenterY="15"/>
            </Ellipse.RenderTransform>

            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <!-- 淡入 -->
                            <DoubleAnimation 
                                Storyboard.TargetName="AnimateBall"
                                Storyboard.TargetProperty="Opacity"
                                From="0" To="1"
                                Duration="0:0:0.5"/>

                            <!-- 缩放 -->
                            <DoubleAnimation 
                                Storyboard.TargetName="BallScale"
                                Storyboard.TargetProperty="ScaleX"
                                From="0.5" To="1.2"
                                Duration="0:0:0.5"/>
                            <DoubleAnimation 
                                Storyboard.TargetName="BallScale"
                                Storyboard.TargetProperty="ScaleY"
                                From="0.5" To="1.2"
                                Duration="0:0:0.5"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>
</Window>

output (2)

视频转gif tool.bugcome.com/media-tools…

效果B:路径动画 + 淡入 + 缩放 + 位置对齐 + 完整注释 + 按钮控制
<Window x:Class="_6_路径动画___淡入___缩放.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_6_路径动画___淡入___缩放"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <!-- 定义运动轨迹资源 -->
    <Window.Resources>
        <!-- 路径几何图形:相对路径(从0,0开始,确保位置不偏移) -->
        <PathGeometry x:Key="LinePath" Figures="M 0,0 L 400,0"/>

        <!-- 定义复合动画故事板 -->
        <Storyboard x:Key="CombineAnimation">
            <!-- 路径动画:沿直线X轴移动 -->
            <DoubleAnimationUsingPath 
                Storyboard.TargetName="CombineTranslate"
                Storyboard.TargetProperty="X"
                Source="X"
                PathGeometry="{StaticResource LinePath}"
                Duration="0:0:3"
                RepeatBehavior="Forever"
                AutoReverse="True"/>

            <!-- 淡入动画:透明度从0到1 -->
            <DoubleAnimation 
                Storyboard.TargetName="CombineBall"
                Storyboard.TargetProperty="Opacity"
                From="0" To="1"
                Duration="0:0:3"/>

            <!-- X轴缩放动画:从0.8倍到1.2倍 -->
            <DoubleAnimation 
                Storyboard.TargetName="CombineScale"
                Storyboard.TargetProperty="ScaleX"
                From="0.8" To="1.2"
                Duration="0:0:3"
                RepeatBehavior="Forever"
                AutoReverse="True"/>

            <!-- Y轴缩放动画:从0.8倍到1.2倍 -->
            <DoubleAnimation 
                Storyboard.TargetName="CombineScale"
                Storyboard.TargetProperty="ScaleY"
                From="0.8" To="1.2"
                Duration="0:0:3"
                RepeatBehavior="Forever"
                AutoReverse="True"/>
        </Storyboard>
    </Window.Resources>

    <!-- 画布布局容器 -->
    <Canvas Background="#F8F9FA">

        <!-- 控制按钮 -->
        <!-- Content:按钮显示文字 -->
        <!-- Width:按钮宽度120 -->
        <!-- Height:按钮高度40 -->
        <!-- Canvas.Left:按钮水平位置100 -->
        <!-- Canvas.Top:按钮垂直位置30 -->
        <Button x:Name="ToggleBtn"
                Content="启动动画"
                Width="120"
                Height="40"
                Canvas.Left="100"
                Canvas.Top="30"
                Click="ToggleBtn_Click"/>

        <!-- 路径轨迹线 -->
        <!-- Data:绘制直线路径坐标 -->
        <!-- Stroke:线条颜色蓝色 -->
        <!-- StrokeThickness:线条粗细2 -->
        <!-- StrokeDashArray:虚线样式 -->
        <Path Data="M 50,180 L 550,180"
              Stroke="Blue" 
              StrokeThickness="2" 
              StrokeDashArray="2,2"/>

        <!-- 复合动画圆形 -->
        <!-- Width:圆形宽度30 -->
        <!-- Height:圆形高度30 -->
        <!-- Fill:填充橙色 -->
        <!-- Stroke:边框黑色 -->
        <!-- StrokeThickness:边框粗细1 -->
        <!-- Opacity:初始透明度0 -->
        <Ellipse x:Name="CombineBall" 
                 Width="30" Height="30" 
                 Fill="#FF9800" 
                 Stroke="Black" 
                 StrokeThickness="1"
                 Opacity="0"
                 Canvas.Left="50"
                 Canvas.Top="180">

            <!-- 渲染变换:组合变换(位移+缩放) -->
            <Ellipse.RenderTransform>
                <TransformGroup>
                    <!-- 位移变换:控制水平移动 -->
                    <TranslateTransform x:Name="CombineTranslate"/>
                    <!-- 缩放变换:控制大小缩放 -->
                    <ScaleTransform x:Name="CombineScale" CenterX="15" CenterY="15"/>
                </TransformGroup>
            </Ellipse.RenderTransform>
        </Ellipse>
    </Canvas>
</Window>

output (3)

视频转gif tool.bugcome.com/media-tools…


三、进阶实战

场景1:自绘制按钮(悬浮动效)

目标: 鼠标悬浮时沿微小曲线微动 + 放大 + 颜色加深。

<Window x:Class="_7_场景1_自绘制按钮_悬浮动效_.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_7_场景1_自绘制按钮_悬浮动效_"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <!-- 画布布局容器 -->
    <Canvas Background="#F8F9FA">

        <!-- 按钮整体容器:文字 + 按钮一起缩放 -->
        <!-- Canvas.Left:容器水平位置 240 -->
        <!-- Canvas.Top:容器垂直位置 180 -->
        <!-- 绑定鼠标事件:移入、移出、点击 -->
        <Canvas x:Name="ButtonCanvas" 
                Canvas.Left="240" 
                Canvas.Top="180"
                MouseEnter="ButtonCanvas_MouseEnter"
                MouseLeave="ButtonCanvas_MouseLeave"
                MouseLeftButtonDown="ButtonCanvas_MouseLeftButtonDown"
                Cursor="Hand">

            <!-- 渲染变换:缩放控制 -->
            <Canvas.RenderTransform>
                <!-- 从正中心放大 -->
                <ScaleTransform x:Name="ButtonScale" 
                                ScaleX="1" 
                                ScaleY="1"/>
            </Canvas.RenderTransform>

            <!-- 变换中心点:0.5,0.5 为中心缩放 -->
            <Canvas.RenderTransformOrigin>
                <Point>0.5,0.5</Point>
            </Canvas.RenderTransformOrigin>

            <!-- 按钮形状:矩形按钮 -->
            <!-- Width:按钮宽度 120 -->
            <!-- Height:按钮高度 40 -->
            <!-- Fill:按钮填充颜色 -->
            <!-- Stroke:按钮边框颜色 -->
            <Path Width="120" Height="40" 
                  Data="M0,0 L120,0 L120,40 L0,40 Z" 
                  Fill="#2196F3" 
                  Stroke="#1976D2" 
                  StrokeThickness="1"/>

            <!-- 按钮文字 -->
            <!-- Canvas.Left:文字距离左侧 25 -->
            <!-- Canvas.Top:文字距离顶部 10 -->
            <!-- IsHitTestVisible:不接收鼠标事件 -->
            <TextBlock Text="自绘制按钮" 
                       Canvas.Left="25" 
                       Canvas.Top="10" 
                       FontSize="14" 
                       Foreground="White"
                       FontWeight="Medium"
                       IsHitTestVisible="False"/>
        </Canvas>
    </Canvas>
</Window>
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace _7_场景1_自绘制按钮_悬浮动效_
{
    /// <summary>
    /// 自绘制按钮 + 中心悬浮放大动效
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent(); // 初始化界面
        }

        // 鼠标进入:从中心放大 1.2 倍
        private void ButtonCanvas_MouseEnter(object sender, MouseEventArgs e)
        {
            // 创建缩放动画
            DoubleAnimation scale = new DoubleAnimation
            {
                To = 1.2,                      // 目标放大到 1.2 倍
                Duration = TimeSpan.FromSeconds(0.2) // 动画时长 0.2 秒
            };

            // 执行 X 轴缩放
            ButtonScale.BeginAnimation(ScaleTransform.ScaleXProperty, scale);
            // 执行 Y 轴缩放
            ButtonScale.BeginAnimation(ScaleTransform.ScaleYProperty, scale);
        }

        // 鼠标离开:恢复原始大小
        private void ButtonCanvas_MouseLeave(object sender, MouseEventArgs e)
        {
            // 创建复位动画
            DoubleAnimation scale = new DoubleAnimation
            {
                To = 1,                        // 恢复到 1 倍原始大小
                Duration = TimeSpan.FromSeconds(0.2)
            };

            // X 轴复位
            ButtonScale.BeginAnimation(ScaleTransform.ScaleXProperty, scale);
            // Y 轴复位
            ButtonScale.BeginAnimation(ScaleTransform.ScaleYProperty, scale);
        }

        // 鼠标点击事件:弹出提示
        private void ButtonCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("自绘制按钮被点击!", "提示");
        }
    }
}

output (4)

视频转gif tool.bugcome.com/media-tools…

场景2:自绘制进度条(流动效果)

<Window x:Class="_8_自绘制进度条_流动效果_.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_8_自绘制进度条_流动效果_"
        mc:Ignorable="d"
        Title="进度条演示" Height="450" Width="800">

    <!-- 窗口资源:定义进度条背景路径 -->
    <Window.Resources>
        <PathGeometry x:Key="ProgressBgPath" 
                      Figures="M 50,180 L 550,180 L 550,210 L 50,210 Z"/>
    </Window.Resources>

    <!-- 主画布:页面布局容器 -->
    <Canvas Background="#F8F9FA">

        <!-- 进度条背景 -->
        <!-- Data:背景形状路径(引用Window.Resources中的ProgressBgPath) -->
        <!-- Fill:背景填充色 -->
        <!-- Stroke:边框颜色 -->
        <!-- StrokeThickness:边框粗细 -->
        <Path Data="{StaticResource ProgressBgPath}"
              Fill="#E9ECEF"
              Stroke="#DEE2E6"
              StrokeThickness="1"/>

        <!-- 流动进度条(蓝色加载条) -->
        <!-- x:Name:后台代码通过此名称访问该元素 -->
        <!-- Fill:填充色(蓝色) -->
        <!-- Stroke:边框颜色(深蓝色) -->
        <!-- StrokeThickness:边框粗细 -->
        <!-- 初始不设置Data,默认完全隐藏,无任何蓝色 -->
        <Path x:Name="FlowPath"
              Fill="#2196F3"
              Stroke="#1976D2"
              StrokeThickness="1"/>

        <!-- 进度显示文本 -->
        <!-- x:Name:后台代码通过此名称更新文字 -->
        <!-- Text:初始显示文字 -->
        <!-- Canvas.Left:水平位置 -->
        <!-- Canvas.Top:垂直位置 -->
        <!-- FontSize:文字大小 -->
        <!-- Foreground:文字颜色 -->
        <TextBlock x:Name="ProgressText" 
                   Text="加载未开始"
                   Canvas.Left="256"
                   Canvas.Top="148"
                   FontSize="14"
                   Foreground="#333"/>

        <!-- 按钮整体容器 -->
        <!-- Canvas.Left:容器水平位置 -->
        <!-- Canvas.Top:容器垂直位置 -->
        <Canvas Canvas.Left="240" Canvas.Top="230">

            <!-- 按钮形状 -->
            <!-- x:Name:后台代码通过此名称绑定点击事件 -->
            <!-- Data:按钮绘制路径(120x40的矩形) -->
            <!-- Fill:填充色(绿色) -->
            <!-- Stroke:边框色(深绿色) -->
            <!-- StrokeThickness:边框粗细 -->
            <!-- Cursor:鼠标悬浮样式(手型) -->
            <Path x:Name="ControlBtn"
                  Data="M 0,0 L 120,0 L 120,40 L 0,40 Z"
                  Fill="#4CAF50"
                  Stroke="#388E3C"
                  StrokeThickness="1"
                  Cursor="Hand"/>

            <!-- 按钮文字 -->
            <!-- Text:文字内容 -->
            <!-- Canvas.Left:文字左侧偏移 -->
            <!-- Canvas.Top:文字顶部偏移 -->
            <!-- FontSize:文字字号 -->
            <!-- Foreground:文字颜色 -->
            <!-- FontWeight:文字粗细 -->
            <!-- IsHitTestVisible:不接收鼠标事件(让点击穿透到下方的Path按钮) -->
            <TextBlock Text="开始加载"
                       Canvas.Left="22"
                       Canvas.Top="10"
                       FontSize="14"
                       Foreground="White"
                       FontWeight="Medium"
                       IsHitTestVisible="False"/>
        </Canvas>
    </Canvas>
</Window>

using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;

namespace _8_自绘制进度条_流动效果_
{
    /// <summary>
    /// 自绘制进度条 - 3秒从0%到100%
    /// </summary>
    public partial class MainWindow : Window
    {
        // ==================== 常量配置 ====================
        private const double TotalLength = 500;     // 进度条总长度(像素)
        private const double TotalSeconds = 3.0;    // 总时长(秒)
        private const double IntervalMs = 16;       // 定时器间隔 ~60fps

        // ==================== 状态变量 ====================
        private DispatcherTimer _timer;     // 动画定时器
        private DateTime _startTime;        // 开始时间,用于精确计算进度
        private bool _isRunning = false;    // 防止重复点击

        public MainWindow()
        {
            InitializeComponent();

            // 初始状态:隐藏进度条
            FlowPath.Visibility = Visibility.Hidden;

            // 创建定时器
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromMilliseconds(IntervalMs);
            _timer.Tick += Timer_Tick;

            // 绑定按钮点击事件
            ControlBtn.MouseLeftButtonDown += (s, e) => StartLoad();
        }

        /// <summary>
        /// 开始加载动画
        /// </summary>
        private void StartLoad()
        {
            // 防止动画过程中重复点击
            if (_isRunning) return;

            _isRunning = true;
            _startTime = DateTime.Now;      // 记录开始时间

            // 显示进度条并重置为0
            FlowPath.Visibility = Visibility.Visible;
            UpdatePath(0);

            // 启动定时器
            _timer.Start();
        }

        /// <summary>
        /// 定时器回调 - 每16ms执行一次
        /// </summary>
        private void Timer_Tick(object sender, EventArgs e)
        {
            // 计算实际经过的秒数(基于真实时间,避免累积误差)
            double elapsed = (DateTime.Now - _startTime).TotalSeconds;

            // 计算进度比例 (0.0 ~ 1.0)
            double progress = elapsed / TotalSeconds;

            if (progress >= 1.0)
            {
                // ========== 动画完成 ==========
                _timer.Stop();
                _isRunning = false;

                ProgressText.Text = "加载完成!100%";
                UpdatePath(TotalLength);    // 确保填满500像素
            }
            else
            {
                // ========== 动画进行中 ==========
                double currentLength = progress * TotalLength;

                // 显示百分比,保留1位小数
                ProgressText.Text = $"加载中... {progress * 100:F1}%";

                UpdatePath(currentLength);
            }
        }

        /// <summary>
        /// 更新进度条路径
        /// </summary>
        /// <param name="length">当前进度长度(像素)</param>
        private void UpdatePath(double length)
        {
            // M:移动到起点
            // L:画直线到终点
            // Z:闭合路径
            double endX = 50 + length;
            string data = $"M 50,180 L {endX},180 L {endX},210 L 50,210 Z";
            FlowPath.Data = Geometry.Parse(data);
        }
    }
}

output (5)

场景3:自绘制窗口控制按钮

<Window x:Class="_9_自绘制窗口控制按钮.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="登录" Height="360" Width="320"
        FontFamily="宋体" FontWeight="ExtraLight"
        WindowStartupLocation="CenterScreen" 
        WindowStyle="None" 
        Icon="/Assets/Images/logo.png"
        ShowInTaskbar="False"
        AllowsTransparency="True" Background="{x:Null}"
        Name="loginView">

    <Window.Resources>
        <!-- 最小化按钮 -->
        <ControlTemplate x:Key="MinimizedButtonTemplate" TargetType="Button">
            <Border Background="Transparent" Name="backMinimized" Width="30" Height="30">
                <Path Data="M0 0 12 0" Stroke="White" StrokeThickness="2" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="backMinimized" Property="Background" Value="#22FFFFFF"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="backMinimized" Property="Background" Value="#44FFFFFF"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- 关闭按钮 -->
        <ControlTemplate x:Key="CloseButtonTemplate" TargetType="Button">
            <Border Background="Transparent" Name="backClose" Width="30" Height="30">
                <Path Data="M0 0 12 12M0 12 12 0" Stroke="White" StrokeThickness="2" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="backClose" Property="Background" Value="#EE4863"/>
                    <Setter TargetName="backClose" Property="CornerRadius" Value="0,8,0,0"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="backClose" Property="Background" Value="#EF1A48"/>
                    <Setter TargetName="backClose" Property="CornerRadius" Value="0,8,0,0"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- 找回密码 -->
        <ControlTemplate x:Key="ForgotPasswordButtonTemplate" TargetType="Button">
            <Border Background="Transparent">
                <TextBlock Text="找回密码" FontSize="12" Foreground="#CCC" Name="backForgotPassword"/>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="backForgotPassword" Property="Foreground" Value="Black"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="backForgotPassword" Property="Foreground" Value="#00B3F6"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- 注册账号 -->
        <ControlTemplate x:Key="RegisterButtonTemplate" TargetType="Button">
            <Border Background="Transparent">
                <TextBlock Text="注册账号" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="8" Foreground="#CCC" Name="backRegister"/>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="backRegister" Property="Foreground" Value="Black"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="backRegister" Property="Foreground" Value="#00B3F6"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- 二维码 -->
        <ControlTemplate x:Key="QRcodeButtonTemplate" TargetType="Button">
            <Border Background="Transparent">
                <TextBlock Text="&#xe642;" Foreground="#CCC" FontSize="30" FontFamily="/Assets/Fonts/#iconfont" Margin="0,0,6,-2" Name="backQRcode"/>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="backQRcode" Property="Foreground" Value="Black"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="backQRcode" Property="Foreground" Value="#00B3F6"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- 登录按钮 -->
        <ControlTemplate x:Key="LoginButtonTemplate" TargetType="Button">
            <Border Background="#007DFA" CornerRadius="5" Height="40">
                <Grid>
                    <Border CornerRadius="4" Background="#22FFFFFF" x:Name="backLogin" Visibility="Hidden"/>
                    <ContentControl Content="{TemplateBinding Content}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="backLogin" Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- 账号输入框样式(ComboBox 修复下拉没内容) -->
        <ControlTemplate x:Key="AccountComboBoxTemplate" TargetType="ComboBox">
            <Grid>
                <Border Background="White" BorderBrush="#E7E7E7" BorderThickness="1" CornerRadius="5" Height="40">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="40"/>
                            <ColumnDefinition/>
                            <ColumnDefinition Width="30"/>
                        </Grid.ColumnDefinitions>
                        <!-- 图标 -->
                        <TextBlock Text="&#xe601;" FontFamily="/Assets/Fonts/#iconfont" FontSize="20" 
                                   Foreground="#CCC" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        <!-- 输入区域 -->
                        <TextBox x:Name="PART_EditableTextBox" Grid.Column="1" BorderThickness="0" 
                                 Background="Transparent" VerticalAlignment="Center" FontSize="14"
                                 Padding="0" Margin="0" CaretBrush="#555"/>
                        <!-- 下拉箭头 -->
                        <ToggleButton Grid.Column="2" Width="30" Height="30" 
                                      IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                                      ClickMode="Press" Focusable="False">
                            <ToggleButton.Template>
                                <ControlTemplate TargetType="ToggleButton">
                                    <Border Background="Transparent">
                                        <Path Data="M0 0 4 4 8 0" Stroke="#999" StrokeThickness="1.5" 
                                              VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0,2,0,0"/>
                                    </Border>
                                </ControlTemplate>
                            </ToggleButton.Template>
                        </ToggleButton>
                    </Grid>
                </Border>
                <!-- 下拉面板 修复空白无内容 -->
                <Popup IsOpen="{TemplateBinding IsDropDownOpen}" Placement="Bottom" 
                       Width="{TemplateBinding ActualWidth}" StaysOpen="False">
                    <Border Background="White" BorderBrush="#E7E7E7" BorderThickness="1" CornerRadius="0,0,5,5" 
                            MaxHeight="150" Margin="0,-1,0,0">
                        <ScrollViewer VerticalScrollBarVisibility="Auto">
                            <!-- 关键:用ItemsPresenter自带默认项模板,不会空白 -->
                            <ItemsPresenter />
                        </ScrollViewer>
                    </Border>
                </Popup>
            </Grid>
        </ControlTemplate>

        <!-- PasswordBox 样式 -->
        <SolidColorBrush x:Key="TextBox.Focus.Border1" Color="#FF569DE5"/>
        <Style x:Key="PasswordBoxStyle" TargetType="{x:Type PasswordBox}">
            <Setter Property="PasswordChar" Value="●"/>
            <Setter Property="Background" Value="White"/>
            <Setter Property="BorderBrush" Value="#E7E7E7"/>
            <Setter Property="Foreground" Value="#555"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="FontSize" Value="14"/>
            <Setter Property="Height" Value="40"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type PasswordBox}">
                        <Border x:Name="border" Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                SnapsToDevicePixels="True" CornerRadius="5" Height="40">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="40"/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition Width="30"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Text="&#xe74e;" Foreground="#CCC" FontSize="20" 
                                           FontFamily="/Assets/Fonts/#iconfont" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                                <ScrollViewer x:Name="PART_ContentHost" Grid.Column="1"
                                              Focusable="false" 
                                              HorizontalScrollBarVisibility="Hidden" 
                                              VerticalScrollBarVisibility="Hidden"
                                              VerticalAlignment="Center"/>
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="true">
                                <Setter Property="BorderBrush" TargetName="border" Value="#959595"/>
                            </Trigger>
                            <Trigger Property="IsKeyboardFocused" Value="true">
                                <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.Focus.Border1}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- 复选框样式 -->
        <SolidColorBrush x:Key="OptionMark.Static.Glyph" Color="#00B5FC"/>
        <Style x:Key="CheckBoxStyle" TargetType="{x:Type CheckBox}">
            <Setter Property="Foreground" Value="#CCC"/>
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type CheckBox}">
                        <Grid Background="Transparent" SnapsToDevicePixels="True">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Border x:Name="checkBoxBorder" Background="White" BorderBrush="#CCC" BorderThickness="1" 
                                    HorizontalAlignment="Center" Margin="0,0,4,0" VerticalAlignment="Center" Width="14" Height="14">
                                <Grid>
                                    <Path x:Name="optionMark" Data="M 2 7 L 5 10 L 10 3" 
                                          Stroke="{StaticResource OptionMark.Static.Glyph}" StrokeThickness="2" 
                                          Margin="1" Opacity="0" Stretch="None" StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
                                </Grid>
                            </Border>
                            <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" 
                                              VerticalAlignment="Center"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="true">
                                <Setter TargetName="checkBoxBorder" Property="BorderBrush" Value="#FF5593FF"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="true">
                                <Setter TargetName="optionMark" Property="Opacity" Value="1"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <Border Margin="5" Background="White" CornerRadius="8">
        <Border.Effect>
            <DropShadowEffect Color="Gray" ShadowDepth="0" BlurRadius="8" Opacity="0.5" Direction="0"/>
        </Border.Effect>

        <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">
            <Grid.RowDefinitions>
                <RowDefinition Height="1.2*"/>
                <RowDefinition Height="3*"/>
                <RowDefinition Height="45"/>
            </Grid.RowDefinitions>

            <!-- 顶部蓝色栏 -->
            <Border Background="#007DFA" CornerRadius="8,8,0,0"/>

            <!-- 窗口控制按钮 绑定点击事件 -->
            <Button Click="BtnMinimize_Click" VerticalAlignment="Top" HorizontalAlignment="Right" Width="30" Height="30" 
                    Margin="0,0,35,0" Template="{StaticResource MinimizedButtonTemplate}"/>
            <Button Click="BtnClose_Click" VerticalAlignment="Top" HorizontalAlignment="Right" Width="30" Height="30" 
                    Margin="0,0,5,0" Template="{StaticResource CloseButtonTemplate}"/>

            <!-- Logo与标题 原样保留 -->
            <StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
                <Border Width="30" Height="30" Margin="10,10,6,10">
                    <Border.Effect>
                        <DropShadowEffect Color="Gray" ShadowDepth="0" BlurRadius="5" Opacity="0.3" Direction="0"/>
                    </Border.Effect>
                    <Border.Background>
                        <ImageBrush ImageSource="/Assets/Images/logo.png"/>
                    </Border.Background>
                </Border>
                <TextBlock Text="Bugcome" HorizontalAlignment="Center" Foreground="White" 
                           VerticalAlignment="Center" FontSize="12" FontWeight="Black"/>
            </StackPanel>

            <!-- 表单区域 -->
            <Grid Grid.Row="1" Margin="20,15,20,0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="40"/>
                    <RowDefinition Height="15"/>
                    <RowDefinition Height="40"/>
                    <RowDefinition Height="20"/>
                    <RowDefinition Height="26"/>
                    <RowDefinition Height="20"/>
                    <RowDefinition Height="40"/>
                    <RowDefinition Height="22"/>
                </Grid.RowDefinitions>

                <!-- 账号输入框 -->
                <ComboBox x:Name="cboAccount" Template="{StaticResource AccountComboBoxTemplate}" 
                          IsEditable="True" Height="40" VerticalAlignment="Center"
                          FontSize="14" Foreground="#555"/>

                <!-- 密码输入框 -->
                <PasswordBox Style="{StaticResource PasswordBoxStyle}" Grid.Row="2" 
                             VerticalAlignment="Center" HorizontalAlignment="Stretch"/>

                <!-- 选项栏 -->
                <StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                    <CheckBox Style="{StaticResource CheckBoxStyle}" Content="自动登录" Width="95"/>
                    <CheckBox Style="{StaticResource CheckBoxStyle}" Content="记住密码" Width="95" Margin="10,0"/>
                    <Button Template="{StaticResource ForgotPasswordButtonTemplate}" VerticalAlignment="Center"/>
                </StackPanel>

                <!-- 登录按钮 -->
                <Button Content="登录" Grid.Row="6" Height="40" 
                        Template="{StaticResource LoginButtonTemplate}" 
                        Foreground="White" FontSize="16" IsDefault="True"/>

                <!-- 错误提示 -->
                <TextBlock Foreground="Red" LineHeight="22" Grid.Row="7" 
                           VerticalAlignment="Center" HorizontalAlignment="Center" 
                           TextWrapping="Wrap" Text=""/>
            </Grid>

            <!-- 底部栏 -->
            <Grid Grid.Row="2">
                <UniformGrid Columns="2">
                    <Button VerticalAlignment="Bottom" HorizontalAlignment="Left" Height="32" 
                            Template="{StaticResource RegisterButtonTemplate}" Name="register"/>
                    <Button VerticalAlignment="Bottom" HorizontalAlignment="Right" Height="32" 
                            Template="{StaticResource QRcodeButtonTemplate}" Name="QRcode"/>
                </UniformGrid>
            </Grid>
        </Grid>
    </Border>
</Window>
using System;
using System.Windows;
using System.Windows.Input;

namespace _9_自绘制窗口控制按钮
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // 给下拉框加测试数据,立马能看到下拉有内容
            cboAccount.Items.Add("admin");
            cboAccount.Items.Add("user123");
            cboAccount.Items.Add("test001");
        }

        private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                this.DragMove();
            }
        }

        private void BtnMinimize_Click(object sender, RoutedEventArgs e)
        {
            this.WindowState = WindowState.Minimized;
        }

        private void BtnClose_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }
    }
}

image


四、避坑指南

问题原因解决方案
动画不生效,提示找不到目标x:Name 未设置或拼写错误检查 TargetNamex:Name 完全一致(区分大小写)
路径动画偏移、不贴合轨迹起点与元素初始位置不匹配确保元素位置与 PathGeometry 起点一致
自绘制元素无填充/边框未设置 Fill/Stroke明确设置 FillStrokeStrokeThickness
FindName 返回 null调用时机过早或元素未命名Loaded 事件中调用,确保元素已加载
动画卡顿时长设置不合理或路径过于复杂微动效 0.2-0.3 秒,简化路径节点

五、总结

  1. 核心逻辑: 自绘制控件(Path/Polygon/Ellipse)是基础,PathGeometry 是轨迹蓝图,三大路径动画类是核心工具。

  2. 实战重点: 掌握"自绘制元素绘制 → 动画绑定 → 功能联动"的完整流程。

  3. 关键技巧:

    • 熟记路径语法(M、L、C、A、Z)
    • 动画绑定注意 x:Name 正确性
    • 复合动效协调动画时长和变换中心
    • 自绘制样式完全自由定制

👋 关注我!持续分享 C# 实战技巧、代码示例 & 技术干货

  • 获取示例代码,轻松上手!

  • 私信输入数字: g843

  • 获取代码下载链接

    image


六、英文单词汉译对照表

文章有很多陌生单词,我先列举出来,可以分页对照!

英文中文翻译英文中文翻译英文中文翻译
PathGeometry路径几何UI用户界面DoubleAnimation双精度动画
Path路径Polygon多边形Ellipse椭圆
Rectangle矩形DoubleAnimationUsingPath路径双精度动画PointAnimationUsingPath路径点动画
MatrixAnimationUsingPath路径矩阵动画DoesRotateWithTangent随切线旋转From起始值
To目标值Duration持续时间RepeatBehavior重复行为
Forever永远AutoReverse自动反向Figures图形集
StartPoint起点LineSegment线段ArcPath圆弧路径
CompositePath复合路径CurvePath曲线路径LinePath直线路径
Background背景Canvas画布RenderTransform渲染变换
TranslateTransform平移变换transXX轴平移transYY轴平移
Storyboard故事板BeginStoryboard开始故事板EventTrigger事件触发器
Loaded已加载Animation动画MatrixTransform矩阵变换
ArrowMatrix箭头矩阵ScaleTransform缩放变换BallScale球缩放变换
BallTranslate球平移变换CenterX中心X坐标CenterY中心Y坐标
TransformGroup变换组CombineBall复合球CombineScale复合缩放
CombineTranslate复合平移Opacity不透明度SmallCurvePath微小曲线路径
SelfDrawBtn自绘制按钮SelfDrawButtonStyle自绘制按钮样式Style样式
Setter设置器TargetType目标类型Fill填充
Stroke描边StrokeThickness描边粗细RenderTransformOrigin渲染变换原点
Cursor光标Hand手型光标MouseEnter鼠标进入
MouseLeave鼠标离开MouseLeftButtonDown鼠标左键按下IsHitTestVisible命中测试可见
Text文本TextBlock文本块FontSize字体大小
Foreground前景色FontWeight字体粗细SetTarget设置目标
SetTargetProperty设置目标属性TargetName目标名称TargetProperty目标属性
PropertyPath属性路径ScaleXX轴缩放ScaleXPropertyX轴缩放属性
ScaleYY轴缩放ScaleYPropertyY轴缩放属性Source
PathAnimationSource路径动画源StaticResource静态资源FindResource查找资源
Geometry几何Parse解析FlowGeometry流动几何
FlowPath流动路径flowWidth流动宽度ProgressBgPath进度背景路径
ProgressText进度文本progressTimer进度定时器progressValue进度值
StartProgress开始进度ControlBtn控制按钮DispatcherTimer调度定时器
Interval间隔Tick定时触发TimeSpan时间跨度
FromMilliseconds从毫秒FromSeconds从秒FromArgb从ARGB
WindowControlBtnStyle窗口控制按钮样式Title标题TitleBar标题栏
SizeAll四向调整光标MinimizeBtn最小化按钮MaximizeBtn最大化按钮
CloseBtn关闭按钮isMaximized是否最大化DragMove拖动移动
WindowState窗口状态Minimized已最小化Maximized已最大化
Normal正常NoneTransparent透明
SolidColorBrush纯色画刷System系统Input输入
InitializeComponent初始化组件Helpers辅助工具EmptyHelper空辅助类
Models模型层EmptyModel空模型ViewModels视图模型层
MainViewModel主视图模型Views视图层MainWindow主窗口
AssemblyInfo程序集信息WpfPathAnimationDemoWPF路径动画演示WPFWindows呈现基础
winfxWindows框架扩展schemas架构xmlnsXML命名空间
XAML可扩展应用程序标记语言XX轴XPropertyX属性
YY轴YPropertyY属性Z闭合指令
Click点击Close关闭Color颜色
Controls控件集Children子元素集ResizeMode调整大小模式
WindowStyle窗口样式Windows窗口系统MessageBox消息框
Media媒体Medium中等EventArgs事件参数
RoutedEvent路由事件RoutedEventArgs路由事件参数MouseEventArgs鼠标事件参数
MouseButtonEventArgs鼠标按钮事件参数Threading线程FindName查找名称
Resources资源StrokeDashArray描边虚线Brushes画刷集
BtnScale按钮缩放BtnTranslate按钮平移Segments线段集
Points点集PointPathFigure路径图形
PathFigureCollection路径图形集Property属性presentation表示层