V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yanjinhua
V2EX  ›  .NET

WPF 使用 DrawingContext 绘制温度计

  •  
  •   yanjinhua · 2022-09-29 17:28:05 +08:00 · 1253 次点击
    这是一个创建于 546 天前的主题,其中的信息可能已经有所发展或是发生改变。

    WPF 使用 DrawingContext 绘制温度计

    控件名:Thermometer

    作者:WPFDevelopersOrg

    原文链接: https://github.com/WPFDevelopersOrg/WPFDevelopers

    • 框架使用大于等于.NET40
    • Visual Studio 2022;
    • 项目使用 MIT 开源许可协议;
    • 定义Interval步长、MaxValue最大温度值、MinValue最小温度值。
    • CurrentGeometry 重新绘制当前刻度的Path值。
    • CurrentValue 当前值如果发生变化时则去重新CurrentGeometry
    • OnRender 绘制如下
      • RoundedRectangle温度计的外边框。
      • 使用方法DrawText 单字绘制 华氏温度文本Y轴变化。
      • 使用方法DrawText 单字绘制 摄氏温度文本Y轴变化。
      • 使用方法DrawText 绘制温度计两侧的刻度数值。
      • 使用方法DrawLine 绘制温度计两侧的刻度线。

    1 ) 准备Thermometer.cs如下:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace WPFDevelopers.Controls
    {
        public class Thermometer : Control
        {
            public static readonly DependencyProperty MaxValueProperty =
                DependencyProperty.Register("MaxValue", typeof(double), typeof(Thermometer), new UIPropertyMetadata(40.0));
    
            public static readonly DependencyProperty MinValueProperty =
                DependencyProperty.Register("MinValue", typeof(double), typeof(Thermometer), new UIPropertyMetadata(-10.0));
    
            /// <summary>
            ///     当前值
            /// </summary>
            public static readonly DependencyProperty CurrentValueProperty =
                DependencyProperty.Register("CurrentValue", typeof(double), typeof(Thermometer),
                    new UIPropertyMetadata(OnCurrentValueChanged));
    
            /// <summary>
            ///     步长
            /// </summary>
            public static readonly DependencyProperty IntervalProperty =
                DependencyProperty.Register("Interval", typeof(double), typeof(Thermometer), new UIPropertyMetadata(10.0));
    
            /// <summary>
            ///     当前值的图形坐标点
            /// </summary>
            public static readonly DependencyProperty CurrentGeometryProperty =
                DependencyProperty.Register("CurrentGeometry", typeof(Geometry), typeof(Thermometer), new PropertyMetadata(
                    Geometry.Parse(@"M 2 132.8
                                  a 4 4 0 0 1 4 -4
                                  h 18
                                  a 4 4 0 0 1 4 4
                                  v 32.2
                                  a 4 4 0 0 1 -4 4
                                  h -18
                                  a 4 4 0 0 1 -4 -4 z")));
    
            /// <summary>
            ///     构造函数
            /// </summary>
            static Thermometer()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(Thermometer),
                    new FrameworkPropertyMetadata(typeof(Thermometer)));
            }
    
            public double MaxValue
            {
                get => (double)GetValue(MaxValueProperty);
    
                set => SetValue(MaxValueProperty, value);
            }
    
            public double MinValue
            {
                get => (double)GetValue(MinValueProperty);
    
                set => SetValue(MinValueProperty, value);
            }
    
            public double CurrentValue
            {
                get => (double)GetValue(CurrentValueProperty);
    
                set
                {
                    SetValue(CurrentValueProperty, value);
    
                    PaintPath();
                }
            }
    
            public double Interval
            {
                get => (double)GetValue(IntervalProperty);
    
                set => SetValue(IntervalProperty, value);
            }
    
            public Geometry CurrentGeometry
            {
                get => (Geometry)GetValue(CurrentGeometryProperty);
    
                set => SetValue(CurrentGeometryProperty, value);
            }
    
            private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var thermometer = d as Thermometer;
                thermometer.CurrentValue = Convert.ToDouble(e.NewValue);
            }
    
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    
                PaintPath();
            }
    
            protected override void OnRender(DrawingContext drawingContext)
            {
                var brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#82848A"));
                var rect = new Rect();
                rect.Width = 30;
                rect.Height = 169;
                drawingContext.DrawRoundedRectangle(Brushes.Transparent,
                    new Pen(brush, 2d),
                    rect, 8d, 8d);
    
                #region 华氏温度
    
                drawingContext.DrawText(
                    DrawingContextHelper.GetFormattedText("华",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), textSize: 14D),
                    new Point(-49, 115));
    
    
                drawingContext.DrawText(
                    DrawingContextHelper.GetFormattedText("氏",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), textSize: 14D),
                    new Point(-49, 115 + 14));
    
    
                drawingContext.DrawText(
                    DrawingContextHelper.GetFormattedText("温",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), textSize: 14D),
                    new Point(-49, 115 + 28));
    
    
                drawingContext.DrawText(
                    DrawingContextHelper.GetFormattedText("度",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), textSize: 14D),
                    new Point(-49, 115 + 42));
    
                #endregion
    
                #region 摄氏温度
    
                drawingContext.DrawText(
                    DrawingContextHelper.GetFormattedText("摄",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), FlowDirection.LeftToRight,
                        14D), new Point(75, 115));
    
    
                drawingContext.DrawText(
                    DrawingContextHelper.GetFormattedText("氏",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), FlowDirection.LeftToRight,
                        14D), new Point(75, 115 + 14));
    
    
                drawingContext.DrawText(
                    DrawingContextHelper.GetFormattedText("温",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), FlowDirection.LeftToRight,
                        14D), new Point(75, 115 + 28));
    
    
                drawingContext.DrawText(
                    DrawingContextHelper.GetFormattedText("度",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), FlowDirection.LeftToRight,
                        14D), new Point(75, 115 + 42));
    
                #endregion
    
                #region 画刻度
    
                var total_Value = MaxValue - MinValue;
    
                var cnt = total_Value / Interval;
    
                var one_value = 161d / cnt;
    
                for (var i = 0; i <= cnt; i++)
                {
                    var formattedText = DrawingContextHelper.GetFormattedText($"{MaxValue - i * Interval}",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), FlowDirection.LeftToRight,
                        14D);
    
                    drawingContext.DrawText(formattedText,
                        new Point(43, i * one_value - formattedText.Height / 2d)); //减去字体高度的一半
    
                    formattedText = DrawingContextHelper.GetFormattedText($"{(MaxValue - i * Interval) * 1.8d + 32d}",
                        (Brush)DrawingContextHelper.BrushConverter.ConvertFromString("#82848A"), textSize: 14D);
    
                    drawingContext.DrawText(formattedText, new Point(-13, i * one_value - formattedText.Height / 2d));
    
                    if (i != 0 && i != 5)
                    {
                        drawingContext.DrawLine(new Pen(Brushes.Black, 1d),
                            new Point(4, i * one_value), new Point(6, i * one_value));
    
                        drawingContext.DrawLine(new Pen(Brushes.Black, 1d),
                            new Point(24, i * one_value), new Point(26, i * one_value));
                    }
                }
    
                #endregion
            }
    
            /// <summary>
            ///     动态计算当前值图形坐标点
            /// </summary>
            private void PaintPath()
            {
                var one_value = 161d / ((MaxValue - MinValue) / Interval);
    
                var width = 26d;
    
                var height = 169d - (MaxValue - CurrentValue) * (one_value / Interval);
    
                var x = 2d;
    
                var y = 169d - (169d - (MaxValue - CurrentValue) * (one_value / Interval));
    
    
                CurrentGeometry = Geometry.Parse($@"M 2 {y + 4}
                                  a 4 4 0 0 1 4 -4
                                  h {width - 8}
                                  a 4 4 0 0 1 4 4
                                  v {height - 8}
                                  a 4 4 0 0 1 -4 4
                                  h -{width - 8}
                                  a 4 4 0 0 1 -4 -4 z");
            }
        }
    }
    

    2 ) 使用ThermometerExample.xaml.cs如下:

    <UserControl x:Class="WPFDevelopers.Samples.ExampleViews.ThermometerExample"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"
                 xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <Grid>
    
            <Border Background="{DynamicResource BackgroundSolidColorBrush}" 
                    CornerRadius="12"
                    Width="400" Height="400"
                    Effect="{StaticResource NormalShadowDepth}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <Slider x:Name="PART_Slider" IsSnapToTickEnabled="True"
                    Value="10"
                    Minimum="-10"
                    Maximum="40" 
                    Orientation="Vertical"
                    Height="300"/>
                    <Grid VerticalAlignment="Center"
                          Margin="160,0,0,0">
                        <Path Fill="{StaticResource PrimaryMouseOverSolidColorBrush}" 
                              Stroke="{StaticResource PrimaryMouseOverSolidColorBrush}"
                              StrokeThickness="1" Opacity=".6"
                              Data="{Binding ElementName=PART_Thermometer, Path=CurrentGeometry,Mode=TwoWay}"/>
                        <wpfdev:Thermometer x:Name="PART_Thermometer"
                                            CurrentValue="{Binding ElementName=PART_Slider,Path=Value,Mode=TwoWay}"/>
                    </Grid>
                    <TextBlock Text="{Binding ElementName=PART_Thermometer,Path=CurrentValue,StringFormat={}{0}℃}" 
                               FontSize="24" Grid.Column="1"
                               Foreground="{StaticResource PrimaryPressedSolidColorBrush}" FontFamily="Bahnschrift"
                               HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Grid>
            </Border>
        </Grid>
    </UserControl>
    
    

    鸣谢 - 帅嘉欣

    Github|ThermometerExample
    码云|ThermometerExample

    2 条回复    2022-10-13 15:51:55 +08:00
    sinnosong1
        1
    sinnosong1  
       2022-09-29 19:35:44 +08:00
    我说大佬怎么一直在发 WPF 的东西,原来是 WPF 方向的 MVP
    yanjinhua
        2
    yanjinhua  
    OP
       2022-10-13 15:51:55 +08:00
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3285 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 14:13 · PVG 22:13 · LAX 07:13 · JFK 10:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.