WPF 使用 DrawingContext 绘制温度计
框架使用大于等于.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); | |
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)); | |
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)); | |
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)); | |
} | |
} | |
} | |
/// <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> |
实现效果