WPF 中实现自定义单数码管控件

231 阅读4分钟

前言

软件开发中,UI组件的可定制性和视觉表现力越来越受到重视。WPF(Windows Presentation Foundation)作为一款强大的界面开发框架,提供了丰富的样式与模板机制,可以轻松创建自定义控件。

本文将带你一步步实现一个自定义的单数码管控件,通过继承 Control 类并结合 XAML 样式与后台逻辑代码,完成一个支持数字显示、外观定制、数据绑定等功能的数码管控件。

该控件适用于仪表盘、计数器、工业控制等需要数字展示的场景,具有良好的扩展性与复用价值。

项目目标

创建一个基于 WPF 的自定义控件:SingleDigitalTube

支持显示数字 0~9,超出范围显示错误标识 "E"

可自定义背景色、边框颜色、边框粗细

支持数据绑定,便于集成到 MVVM 架构中

使用 Path 路径绘制七段数码管结构,保证缩放不失真

绘制数码管图形

使用矢量绘图工具(如 Inkscape)绘制一个标准的七段数码管图形,并导出为 SVG 或直接提取其 Path 数据用于 WPF 控件。

以下是七段数码管的 Path 定义:

<Path x:Name="tube1" Data="M 1.1889374,0.49999738 51.351967,51.757227 H 150.79113 L 202.04941,0.49999738 Z"/>
<Path x:Name="tube2" Data="m 203.23948,25.604947 -51.25723,50.16303 v 99.439163 l 51.25723,51.25828 z"/>
<Path x:Name="tube3" Data="m 202.43163,275.21452 -51.25723,50.16303 v 99.43916 l 51.25723,51.25828 z"/>
<Path x:Name="tube4" Data="M 202.06759,501.59746 151.90456,450.34023 H 52.465396 L 1.2071159,501.59746 Z"/>
<Path x:Name="tube5" Data="M 1.9025759,476.49251 53.159806,426.32948 V 326.89032 L 1.9025759,275.63204 Z"/>
<Path x:Name="tube6" Data="M 2.7104259,226.88294 53.967656,176.71991 V 77.280747 L 2.7104259,26.022467 Z"/>
<Path x:Name="tube7" Data="M 53.140896,201.36022 1.8826159,251.42644 53.140896,300.89664 H 152.0813 l 51.8543,-49.4702 -51.8543,-50.06622 z"/>

这些 Path 元素分别代表数码管的七段亮起部分,我们将在 ControlTemplate 中引用它们,并根据当前数值动态设置其填充颜色。

代码实现

1、创建控件类:SingleDigitalTube.cs

该类继承自 Control,定义了一个 Value 属性,并监听其变化以更新数码管显示状态。

public class SingleDigitalTube : Control
{
    private Path? tuble1, tuble2, tuble3, tuble4, tuble5, tuble6, tuble7;
    private Brush TransparentBrush { get; } = new SolidColorBrush(Colors.Transparent);

    public int Value
    {
        get => (int)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(int), typeof(SingleDigitalTube),
            new PropertyMetadata(0, OnValueChanged));

    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is SingleDigitalTube control && control.IsLoaded)
        {
            control.SetDigitalTube((int)e.NewValue);
        }
    }

    private void SetDigitalTube(int num)
    {
        if (num >= 0 && num <= 9)
        {
            tuble1!.Fill = (num != 1 && num != 4) ? Background : TransparentBrush;
            tuble2!.Fill = (num != 5 && num != 6) ? Background : TransparentBrush;
            tuble3!.Fill = num != 2 ? Background : TransparentBrush;
            tuble4!.Fill = (num != 1 && num != 4 && num != 7) ? Background : TransparentBrush;
            tuble5!.Fill = (new[] { 0, 2, 6, 8 }.Contains(num)) ? Background : TransparentBrush;
            tuble6!.Fill = !(new[] { 1, 2, 3, 7 }.Contains(num)) ? Background : TransparentBrush;
            tuble7!.Fill = (num != 0 && num != 1 && num != 7) ? Background : TransparentBrush;
        }
        else
        {
            // 显示 E
            tuble1!.Fill = Background;
            tuble2!.Fill = Background;
            tuble3!.Fill = TransparentBrush;
            tuble4!.Fill = Background;
            tuble5!.Fill = Background;
            tuble6!.Fill = Background;
            tuble7!.Fill = Background;
        }
    }

    private void SetStrokeThickness()
    {
        var thickness = (BorderThickness.Left + BorderThickness.Right + BorderThickness.Top + BorderThickness.Bottom) / 4;
        tuble1!.StrokeThickness = thickness;
        tuble2!.StrokeThickness = thickness;
        tuble3!.StrokeThickness = thickness;
        tuble4!.StrokeThickness = thickness;
        tuble5!.StrokeThickness = thickness;
        tuble6!.StrokeThickness = thickness;
        tuble7!.StrokeThickness = thickness;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        tuble1 = GetTemplateChild("tube1") as Path;
        tuble2 = GetTemplateChild("tube2") as Path;
        tuble3 = GetTemplateChild("tube3") as Path;
        tuble4 = GetTemplateChild("tube4") as Path;
        tuble5 = GetTemplateChild("tube5") as Path;
        tuble6 = GetTemplateChild("tube6") as Path;
        tuble7 = GetTemplateChild("tube7") as Path;

        SetStrokeThickness();
        SetDigitalTube(Value);
    }

    static SingleDigitalTube()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(SingleDigitalTube),
            new FrameworkPropertyMetadata(typeof(SingleDigitalTube)));
    }
}

2、定义控件样式:Generic.xaml

Themes/Generic.xaml 中定义控件的默认样式和模板:

<Style TargetType="{x:Type local:SingleDigitalTube}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Viewbox Stretch="Uniform">
                    <Grid>
                        <Path x:Name="tube1" ... />
                        <Path x:Name="tube2" ... />
                        <Path x:Name="tube3" ... />
                        <Path x:Name="tube4" ... />
                        <Path x:Name="tube5" ... />
                        <Path x:Name="tube6" ... />
                        <Path x:Name="tube7" ... />
                    </Grid>
                </Viewbox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

使用示例

在 XAML 页面中引入命名空间后即可使用:

<local:SingleDigitalTube 
    Height="50"
    Background="Red"
    BorderBrush="Black"
    BorderThickness="5"
    Value="{Binding ElementName=setNumber, Path=Value}" />

其中 setNumber 是一个滑块或输入控件,用于动态修改数码管显示值。

运行效果

控件会根据传入的 Value 值实时刷新七段数码管的状态,支持从 0 到 9 的数字显示,超出范围则显示 “E”。

总结

本文详细讲解了如何使用 WPF 实现一个自定义的单数码管控件,包括:

  • 使用 Path 绘制矢量图形

  • 自定义控件继承 Control 并添加依赖属性

  • 在模板中绑定路径元素

  • 动态控制各段点亮状态

  • 支持多种外观定制和数据绑定

该控件不仅可用于实际项目中的数字展示,也可以作为学习 WPF 自定义控件开发的一个良好示例。后续你可以进一步拓展功能,如支持小数点、多数码管联动、动画效果等,以满足更复杂的应用需求。

最后

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

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

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

作者:奇点旅行者

出处:mp.weixin.qq.com/s/2tk7jfD2-06sF-RzzAaEXQ

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