在WPF中创建自定义控件需要遵循特定的开发模式,以下是分步骤的详细指南:
一、创建自定义控件基础结构
新建类库项目
dotnet new classlib -n WpfCustomControls
引用必要程序集
<PackageReference Include="Microsoft.Windows.Compatibility" Version="6.0.0" />
二、实现控件核心逻辑
// CircularProgressBar.cs
[TemplatePart(Name = "PART_Indicator", Type = typeof(Shape))]
public class CircularProgressBar : Control
{
static CircularProgressBar()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(CircularProgressBar),
new FrameworkPropertyMetadata(typeof(CircularProgressBar)));
}
// 依赖属性注册
public static readonly DependencyProperty ProgressProperty =
DependencyProperty.Register("Progress", typeof(double), typeof(CircularProgressBar),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.AffectsRender,
OnProgressChanged));
private static void OnProgressChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CircularProgressBar cpb)
cpb.UpdateVisualState();
}
// CLR包装器
public double Progress
{
get => (double)GetValue(ProgressProperty);
set => SetValue(ProgressProperty, value);
}
// 获取模板部件
private Shape _indicator;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_indicator = GetTemplateChild("PART_Indicator") as Shape;
UpdateVisualState();
}
// 视觉状态更新
private void UpdateVisualState()
{
if (_indicator != null)
{
// 计算旋转角度
var angle = Progress * 3.6; // 转换为角度
var transform = new RotateTransform(angle);
_indicator.RenderTransform = transform;
}
}
}
三、定义控件模板(Generic.xaml)
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControls">
<Style TargetType="{x:Type local:CircularProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CircularProgressBar}">
<Grid>
<!-- 背景轨道 -->
<Ellipse Stroke="{TemplateBinding Background}"
StrokeThickness="{TemplateBinding BorderThickness}"
Width="100" Height="100"/>
<!-- 进度指示器 -->
<Path x:Name="PART_Indicator"
Stroke="{TemplateBinding Foreground}"
StrokeThickness="{TemplateBinding BorderThickness}"
StrokeStartLineCap="Round"
StrokeEndLineCap="Round">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="50,0">
<ArcSegment Size="50,50"
SweepDirection="Clockwise"
Point="50,0"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<!-- 进度文本 -->
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="{Binding Progress, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0}%}"
FontSize="16"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
四、控件使用示例
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControls;assembly=WpfCustomControls"
Title="Custom Control Demo" Height="450" Width="800">
<Grid>
<local:CircularProgressBar
Width="200" Height="200"
Background="#FFE0E0E0"
Foreground="#FF2196F3"
BorderThickness="8"
Progress="{Binding CurrentProgress}"/>
</Grid>
五、高级功能扩展
添加视觉状态管理
// 在控件类中添加
private static readonly VisualStateGroup ProgressStates;
static CircularProgressBar()
{
DefaultStyleKeyProperty.OverrideMetadata(...);
// 注册视觉状态
VisualStateManager.RegisterVisualStateGroupsProperty.OverrideMetadata(
typeof(CircularProgressBar),
new FrameworkPropertyMetadata(GetDefaultStateGroups()));
}
private static IEnumerable<VisualStateGroup> GetDefaultStateGroups()
{
return new VisualStateGroup[]
{
new VisualStateGroup
{
Name = "ProgressStates",
States =
{
new VisualState { Name = "Normal" },
new VisualState { Name = "Completed",
Storyboard = new Storyboard()
.AddDoubleAnimation(0, 1, "Foreground.(SolidColorBrush.Opacity)") }
}
}
};
}
实现动画支持
<VisualStateGroup x:Name="ProgressStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Completed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PART_Indicator"
Storyboard.TargetProperty="StrokeThickness"
To="12" Duration="0:0:0.3"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
六、调试与优化技巧
调试模板绑定 在控件模板中使用调试转换器:
<TextBlock Text="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource DebugConverter}}"/>
// 调试转换器实现
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine($"Value received: {value}");
return value;
}
}
性能优化方案
// 使用Freezable对象优化资源管理
public class ProgressGeometry : PathGeometry
{
protected override Freezable CreateInstanceCore() => new ProgressGeometry();
}
// 在模板中使用
// 实现单例模式
public static class ProgressGeometry
{
private static readonly Lazy<Geometry> _instance = new Lazy<Geometry>(() =>
{
var geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
ctx.BeginFigure(new Point(50,0), false, false);
ctx.ArcTo(new Point(50,0), new Size(50,50), 0, true, SweepDirection.Clockwise, true, true);
}
geometry.Freeze();
return geometry;
});
public static Geometry Instance => _instance.Value;
}
七、质量控制要点
单元测试规范
[TestClass]
public class CircularProgressBarTests
{
[TestMethod]
public void Progress_Clamping_Test()
{
var bar = new CircularProgressBar();
bar.Progress = 150;
Assert.AreEqual(100, bar.Progress);
bar.Progress = -50;
Assert.AreEqual(0, bar.Progress);
}
[TestMethod]
public void Template_Parts_Existence_Test()
{
var bar = new CircularProgressBar();
bar.ApplyTemplate();
Assert.IsNotNull(indicator);
}
}
跨主题兼容性验证
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
通过以上步骤创建的控件具有以下特性:
完整的数据绑定支持(DependencyProperty)
可扩展的视觉模板(ControlTemplate)
动画状态管理(VisualStateManager)
性能优化(Freezable对象)
严格的单元测试覆盖
多主题兼容性验证
实际开发时应根据具体需求选择继承层次:
Control:基础自定义控件
RangeBase:需要范围值的控件(如进度条)
ItemsControl:需要展示集合数据的控件
ContentControl:包含单个子元素的容器控件
最终控件应打包为独立NuGet包,包含:
/WpfCustomControls
/lib
net6.0-windows
/themes
Generic.xaml
package.nuspec
领取专属 10元无门槛券
私享最新 技术干货