首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

WPF中创建自定义控件(3)

在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

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OCsCyUVQy8uieeYjwYm7Qplg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券