WPF 使用 DrawingContext 绘制温度计

2022-09-29 17:28:05 +08:00
 yanjinhua

WPF 使用 DrawingContext 绘制温度计

控件名:Thermometer

作者:WPFDevelopersOrg

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

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

1569 次点击
所在节点    .NET
2 条回复
sinnosong1
2022-09-29 19:35:44 +08:00
我说大佬怎么一直在发 WPF 的东西,原来是 WPF 方向的 MVP
yanjinhua
2022-10-13 15:51:55 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/883871

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX