走进WPF之自定义温度计

  • A+
所属分类:.NET技术
摘要

WPF中的控件不再具有固定的形象,仅仅是算法内容或数据内容的载体,你可以把控件看成为一组操作穿上了一套衣服,换一个控件就相当于换另外一套衣服。本文主要用WPF的控件模板自定义一个温度计,简述WPF的控件模板的用法,仅供学习分享使用,如有不足之处,还请指正。

WPF中的控件不再具有固定的形象,仅仅是算法内容或数据内容的载体,你可以把控件看成为一组操作穿上了一套衣服,换一个控件就相当于换另外一套衣服。本文主要用WPF的控件模板自定义一个温度计,简述WPF的控件模板的用法,仅供学习分享使用,如有不足之处,还请指正。

模板分类

WPF引入模板,将数据和算法的内容与形式解耦,模板主要分为两大类:

  • 控件模板:是算法内容的体现形式,一个控件怎样组织内部结构才能更符合业务逻辑,让用户操作起来更舒服,控件模板决定了控件“长成什么样子”。
  • 数据模板:是数据内容的表现形式,一条数据显示成什么样子,是简单的文本,还是直观的图表,由数据模板决定。

示例截图

本例主要用过ProgressBar控件,设计成温度计的样式,如下所示:

走进WPF之自定义温度计

示例步骤

  1. 创建控件模板副本,生成样式资源
  2. 修改默认资源样式,添加对应内容。
  3. 绑定数据到文本框

关于如何创建模板副本,可参考前面相关文章。

示例源码

1. 窗口布局

本例使用了两个进度条,分别展示不同样式的温度计,UI代码如下所示:

 1    <Grid>  2         <Grid.RowDefinitions>  3             <RowDefinition Height="*"></RowDefinition>  4             <RowDefinition Height="Auto"></RowDefinition>  5         </Grid.RowDefinitions>  6         <Grid.ColumnDefinitions>  7             <ColumnDefinition Width="*"></ColumnDefinition>  8             <ColumnDefinition Width="Auto" MinWidth="80"></ColumnDefinition>  9             <ColumnDefinition Width="*"></ColumnDefinition> 10             <ColumnDefinition Width="Auto" MinWidth="80"></ColumnDefinition> 11         </Grid.ColumnDefinitions> 12         <ProgressBar x:Name="temp" HorizontalAlignment="Right" Grid.Column="0"  IsIndeterminate="False" Orientation="Vertical" Background="LightGreen" Foreground="LightPink"  Margin="5" VerticalAlignment="Bottom" Maximum="350" Minimum="0" Value="150" Width="100" Height="350" Style="{DynamicResource ProgressBarStyle1}" /> 13         <StackPanel Grid.Column="1" Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Center"> 14             <TextBlock Text="最小值" Margin="3" Padding="3"></TextBlock> 15             <TextBox Text="{Binding ElementName=temp ,Path= Minimum,StringFormat={}{0}°C}" Width="50"  Margin="3" Padding="3"></TextBox> 16             <TextBlock Text="最大值"  Margin="3" Padding="3"></TextBlock> 17             <TextBox Text="{Binding ElementName=temp ,Path= Maximum,StringFormat={}{0}°C}" Width="50"  Margin="3" Padding="3"></TextBox> 18             <TextBlock Text="当前值" Margin="3" Padding="3"></TextBlock> 19             <TextBox Text="{Binding ElementName=temp ,Path= Value,StringFormat={}{0}°C}" Width="50"  Margin="3" Padding="3"></TextBox> 20         </StackPanel> 21  22         <ProgressBar Grid.Row="0" Grid.Column="2"  x:Name="temp1" HorizontalAlignment="Right"  IsIndeterminate="False" Orientation="Vertical" Background="LightCoral" Foreground="Goldenrod"  Margin="5" VerticalAlignment="Bottom" Maximum="350" Minimum="0" Value="180" Width="100" Height="350" Style="{DynamicResource ProgressBarStyle1}" /> 23         <StackPanel Grid.Row="0" Grid.Column="3" Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Center"> 24             <TextBlock Text="最小值" Margin="3" Padding="3"></TextBlock> 25             <TextBox Text="{Binding ElementName=temp1 ,Path= Minimum,StringFormat={}{0}°C}" Width="50"  Margin="3" Padding="3"></TextBox> 26             <TextBlock Text="最大值"  Margin="3" Padding="3"></TextBlock> 27             <TextBox Text="{Binding ElementName=temp1 ,Path= Maximum,StringFormat={}{0}°C}" Width="50"  Margin="3" Padding="3"></TextBox> 28             <TextBlock Text="当前值" Margin="3" Padding="3"></TextBlock> 29             <TextBox Text="{Binding ElementName=temp1 ,Path= Value,StringFormat={}{0}°C}" Width="50"  Margin="3" Padding="3"></TextBox> 30         </StackPanel> 31         <TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Text="WPF自定义温度计" HorizontalAlignment="Center" FontSize="30" Foreground="LightBlue"></TextBlock> 32     </Grid>

注意:默认情况下,进度条是水平方向的,本例为了模拟温度计,需要设置成垂直方向。

2. 模板样式

ControlTemplate,主要用于设置模板,其中默认生成模板中定义x:Name的内容不可以删除,否则会报错【如:x:Name="TemplateRoot",x:Name="PART_Indicator"等】。具体如下所示:

 1     <Window.Resources>  2         <local:WidthToMarginConvert x:Key="WidthToMargin" rate="3"/>  3         <local:TempToStringConvert x:Key="tempformat"></local:TempToStringConvert>  4         <SolidColorBrush x:Key="ProgressBar.Progress" Color="#FF06B025"/>  5         <SolidColorBrush x:Key="ProgressBar.Background" Color="#FFE6E6E6"/>  6         <SolidColorBrush x:Key="ProgressBar.Border" Color="#FFBCBCBC"/>  7         <Style x:Key="ProgressBarStyle1" TargetType="{x:Type ProgressBar}">  8             <Setter Property="Foreground" Value="{StaticResource ProgressBar.Progress}"/>  9             <Setter Property="Background" Value="{StaticResource ProgressBar.Background}"/> 10             <Setter Property="BorderBrush" Value="{StaticResource ProgressBar.Border}"/> 11             <Setter Property="BorderThickness" Value="1"/> 12             <Setter Property="Template"> 13                 <Setter.Value> 14                     <ControlTemplate TargetType="{x:Type ProgressBar}"> 15                         <Grid x:Name="TemplateRoot"> 16                              17                             <VisualStateManager.VisualStateGroups> 18                                 <VisualStateGroup x:Name="CommonStates"> 19                                     <VisualState x:Name="Determinate"/> 20                                     <VisualState x:Name="Indeterminate"> 21                                         <Storyboard RepeatBehavior="Forever"> 22                                             <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="Animation"> 23                                                 <EasingDoubleKeyFrame KeyTime="0" Value="0.25"/> 24                                                 <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0.25"/> 25                                                 <EasingDoubleKeyFrame KeyTime="0:0:2" Value="0.25"/> 26                                             </DoubleAnimationUsingKeyFrames> 27                                             <PointAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)" Storyboard.TargetName="Animation"> 28                                                 <EasingPointKeyFrame KeyTime="0" Value="-0.5,0.5"/> 29                                                 <EasingPointKeyFrame KeyTime="0:0:1" Value="0.5,0.5"/> 30                                                 <EasingPointKeyFrame KeyTime="0:0:2" Value="1.5,0.5"/> 31                                             </PointAnimationUsingKeyFrames> 32                                         </Storyboard> 33                                     </VisualState> 34                                 </VisualStateGroup> 35                             </VisualStateManager.VisualStateGroups> 36                             <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0 50 50 0"/> 37                             <Border Background="AliceBlue" Width="280" Height="30" CornerRadius="0 15 15 0"></Border> 38                             <Rectangle x:Name="PART_Track"/> 39                             <Grid x:Name="PART_Indicator" ClipToBounds="true" HorizontalAlignment="Left" Margin="38 0 0 0" Height="6"> 40                                  41                                 <Rectangle x:Name="Indicator" Fill="Black" Opacity="0.8" /> 42                                 <Rectangle x:Name="Animation" Fill="{TemplateBinding Foreground}" RenderTransformOrigin="0.5,0.5"> 43                                     <Rectangle.RenderTransform> 44                                         <TransformGroup> 45                                             <ScaleTransform/> 46                                             <SkewTransform/> 47                                             <RotateTransform/> 48                                             <TranslateTransform/> 49                                         </TransformGroup> 50                                     </Rectangle.RenderTransform> 51                                 </Rectangle> 52                                  53                             </Grid> 54  55                              56                             <Ellipse Width="40" HorizontalAlignment="Left" Margin="0 0 0 0" Height="40"  Fill="{TemplateBinding Foreground}" ></Ellipse> 57                             <Ellipse Width="10" HorizontalAlignment="Left" Margin="15 0 0 0" Height="10"  Fill="White" ></Ellipse> 58                             <TextBlock Text="{TemplateBinding Value, Converter={StaticResource tempformat}}" Foreground="Black"  Width="40" Height="20" Padding="3"  Margin="{TemplateBinding Value, Converter={StaticResource WidthToMargin}}" > 59                          60                                 <TextBlock.RenderTransform> 61                                     <RotateTransform Angle="90"></RotateTransform> 62                                 </TextBlock.RenderTransform> 63                             </TextBlock> 64                             <Canvas x:Name="tick" > 65                                 <Path Canvas.Left="0" Canvas.Top="0" Stroke="Black" Data="M40 0 L40 5 M50 0 L50 5 M60 0 L60 5 M70 0 L70 5  M80 0 L80 5 M90 0 L90 5 M100 0 L100 5"></Path> 66                                 <Path Canvas.Left="0" Canvas.Top="0" Stroke="Black" Data="M110 0 L110 5 M120 0 L120 5 M130 0 L130 5 M140 0 L140 5 M150 0 L150 5 M160 0 L160 5 M170 0 L170 5 M180 0 L180 5  M190 0 L190 5 M200 0 L200 5"></Path> 67                                 <Path Canvas.Left="0" Canvas.Top="0" Stroke="Black" Data="M210 0 L210 5 M220 0 L220 5 M230 0 L230 5 M240 0 L240 5 M250 0 L250 5 M260 0 L260 5 M270 0 L270 5 M280 0 L280 5 M290 0 L290 5 M300 0 L300 5 "></Path> 68                                  69  70                                 <Path Canvas.Left="0" Canvas.Bottom="0" Stroke="Black" Data="M40 0 L40 5 M50 0 L50 5 M60 0 L60 5 M70 0 L70 5  M80 0 L80 5 M90 0 L90 5 M100 0 L100 5"></Path> 71                                 <Path Canvas.Left="0" Canvas.Bottom="0" Stroke="Black" Data="M110 0 L110 5 M120 0 L120 5 M130 0 L130 5 M140 0 L140 5 M150 0 L150 5 M160 0 L160 5 M170 0 L170 5 M180 0 L180 5  M190 0 L190 5 M200 0 L200 5"></Path> 72                                 <Path Canvas.Left="0" Canvas.Bottom="0" Stroke="Black" Data="M210 0 L210 5 M220 0 L220 5 M230 0 L230 5 M240 0 L240 5 M250 0 L250 5 M260 0 L260 5 M270 0 L270 5 M280 0 L280 5 M290 0 L290 5 M300 0 L300 5 "></Path> 73  74  75                             </Canvas> 76                         </Grid> 77                         <ControlTemplate.Triggers> 78                             <Trigger Property="Orientation" Value="Vertical"> 79                                 <Setter Property="LayoutTransform" TargetName="TemplateRoot"> 80                                     <Setter.Value> 81                                         <RotateTransform Angle="-90"/> 82                                     </Setter.Value> 83                                 </Setter> 84                             </Trigger> 85                             <Trigger Property="IsIndeterminate" Value="true"> 86                                 <Setter Property="Visibility" TargetName="Indicator" Value="Collapsed"/> 87                             </Trigger> 88                         </ControlTemplate.Triggers> 89                     </ControlTemplate> 90                 </Setter.Value> 91             </Setter> 92         </Style> 93     </Window.Resources>

3. 数据转换

在模板数据绑定的过程中,有些数据需要进行格式化【如:数字转换成带单位的字符串,数值转换成Margin类型等】,所以需要定义数据转换类,如下所示:

 1     public class WidthToMarginConvert : IValueConverter  2     {  3         public Double rate { get; set; }  4   5         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)  6         {  7             Double width = (Double)value;  8             Double pwidth = width * rate;  9             pwidth = pwidth < 300 ? pwidth : 300; 10             return new Thickness(pwidth, 0, 0, 0); 11         } 12  13         //没有用到 14         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 15         { 16             throw new NotImplementedException(); 17         } 18     } 19  20     public class TempToStringConvert : IValueConverter 21     { 22  23         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 24         { 25             return string.Format("{0}°C", value.ToString()); 26         } 27  28         //没有用到 29         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 30         { 31             throw new NotImplementedException(); 32         } 33     }

备注

以上就是自定义温度计的全部内容,旨在抛砖引玉,共同学习,一起进步。

走进WPF之自定义温度计