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.8a 4 4 0 0 1 4 -4h 18a 4 4 0 0 1 4 4v 32.2a 4 4 0 0 1 -4 4h -18a 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 -4h {width - 8}a 4 4 0 0 1 4 4v {height - 8}a 4 4 0 0 1 -4 4h -{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[1]
码云|ThermometerExample[2]
参考资料
[1]
Github|ThermometerExample: https://github.com/WPFDevelopersOrg/WPFDevelopers/blob/master/src/WPFDevelopers.Samples/ExampleViews/ThermometerExample.xaml
[2]码云|ThermometerExample: https://gitee.com/WPFDevelopersOrg/WPFDevelopers/blob/master/src/WPFDevelopers.Samples/ExampleViews/ThermometerExample.xaml