WPF学习笔记4:控件自定义
2022-08-29 23:03:02一、概述
1. 个性化处理的方式
- 控件模板
- UserControl 用户控件
- CustomControl 自定义控件
2. UserControl与CustomControl
开发方式与表现结构
CustomControl 模板与关键特性
区别:
1、自定义控件:注重控件对象的功能,必须遵守WPF的控件规则
- 完全自己实现一个控件 继承现有控件进行功能扩展,并且添加新功能 WPF的控件要求
- 后台代码(控制逻辑)和Generic.Xaml(样式 模板)进行组合
- 支持模板重写
- 继承Control
2、用户控件:注重复合控件组合使用,非常灵活,可以根据控件开发人员自己的意愿进行功能处理
- 多个现有控件的集合,组成一个可复用的控件组
- XAML和后台代码组成 绑定非常紧密
- 不支持模板重写、样式
- 继承UserControl
二、日期时间选择器
<UserControl x:Class="Zhaoxi.CustomLesson.Controls.DateTimePicker"
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:local="clr-namespace:Controls"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="200">
<UserControl.Resources>
<Style TargetType="RepeatButton" x:Key="ButtonUpStyle">
<Setter Property="Height" Value="18"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<Border Background="Transparent">
<Path Data="M838.116 732.779 877.7 693.195 511.979 327.549 146.3 693.195 185.883 732.779 512.003 406.652Z"
Fill="#999" Stretch="Uniform" Margin="6"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="RepeatButton" x:Key="ButtonDownStyle">
<Setter Property="Height" Value="18"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<Border Background="Transparent">
<Path Data="M185.884 327.55 146.3 367.133 512.021 732.779 877.7 367.133 838.117 327.55 511.997 653.676Z"
Fill="#999" Stretch="Uniform" Margin="6"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button" x:Key="ButtonCancelStyle">
<Setter Property="Width" Value="24"/>
<Setter Property="Height" Value="24"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="#EEE">
<Path Data="M557.3 512l329.3-329.4a32 32 0 1 0-45.2-45.2L512 466.7 182.6 137.4a32 32 0 1 0-45.2 45.2L466.7 512 137.4 841.4a31.9 31.9 0 0 0 0 45.2 31.9 31.9 0 0 0 45.2 0L512 557.3l329.4 329.3a31.9 31.9 0 0 0 45.2 0 31.9 31.9 0 0 0 0-45.2z"
Fill="#999" Stretch="Uniform" HorizontalAlignment="Center" Margin="5"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button" x:Key="ButtonAcceptStyle">
<Setter Property="Width" Value="24"/>
<Setter Property="Height" Value="24"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="#EEE">
<Path Data="M892.064 261.888a31.936 31.936 0 0 0-45.216 1.472L421.664 717.248l-220.448-185.216a32 32 0 1 0-41.152 48.992l243.648 204.704a31.872 31.872 0 0 0 20.576 7.488 31.808 31.808 0 0 0 23.36-10.112L893.536 307.136a32 32 0 0 0-1.472-45.248z"
Fill="#999" Stretch="Uniform" HorizontalAlignment="Center" Margin="5"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CalendarCalendarDayButtonStyle1" TargetType="{x:Type CalendarDayButton}">
<Setter Property="MinWidth" Value="10"/>
<Setter Property="MinHeight" Value="10"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CalendarDayButton}">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.1"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="HighlightBackground" To="0.5" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="HighlightBackground" To="0.5" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="HighlightBackground" To="0" Storyboard.TargetProperty="Opacity"/>
<DoubleAnimation Duration="0" Storyboard.TargetName="NormalText" To=".35" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Unselected"/>
<VisualState x:Name="Selected">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="SelectedBackground" To=".75" Storyboard.TargetProperty="Opacity"/>
<ColorAnimation Duration="0" Storyboard.TargetName="NormalText" To="#FFFFFFFF" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CalendarButtonFocusStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="CalendarButtonFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="DayButtonFocusVisual" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="CalendarButtonUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="DayButtonFocusVisual" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ActiveStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Active"/>
<VisualState x:Name="Inactive">
<Storyboard>
<ColorAnimation Duration="0" Storyboard.TargetName="NormalText" To="#FF777777" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DayStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="RegularDay"/>
<VisualState x:Name="Today">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="TodayBackground" To="1" Storyboard.TargetProperty="Opacity"/>
<ColorAnimation Duration="0" Storyboard.TargetName="NormalText" To="#FFFFFFFF" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="BlackoutDayStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="NormalDay"/>
<VisualState x:Name="BlackoutDay">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="Blackout" To=".2" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="TodayBackground" Fill="Gray" Opacity="0" RadiusX="1" RadiusY="1"/>
<Rectangle x:Name="SelectedBackground" Fill="#409EFE" Opacity="0" RadiusX="1" RadiusY="1"/>
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"/>
<Rectangle x:Name="HighlightBackground" Fill="#FFBADDE9" Opacity="0" RadiusX="1" RadiusY="1"/>
<ContentPresenter x:Name="NormalText" TextElement.Foreground="#FF333333" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="5,1,5,1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Path x:Name="Blackout" Data="M8.1772461,11.029181 L10.433105,11.029181 L11.700684,12.801641 L12.973633,11.029181 L15.191895,11.029181 L12.844727,13.999395 L15.21875,17.060919 L12.962891,17.060919 L11.673828,15.256231 L10.352539,17.060919 L8.1396484,17.060919 L10.519043,14.042364 z" Fill="#FF000000" HorizontalAlignment="Stretch" Margin="3" Opacity="0" RenderTransformOrigin="0.5,0.5" Stretch="Fill" VerticalAlignment="Stretch"/>
<Rectangle x:Name="DayButtonFocusVisual" IsHitTestVisible="false" RadiusX="1" RadiusY="1" Stroke="#FF45D6FA" Visibility="Collapsed"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CalendarCalendarItemStyle1" TargetType="{x:Type CalendarItem}">
<Setter Property="Margin" Value="0,3,0,3"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CalendarItem}">
<ControlTemplate.Resources>
<DataTemplate x:Key="{x:Static CalendarItem.DayTitleTemplateResourceKey}">
<TextBlock Foreground="#FF333333" FontFamily="Verdana" FontWeight="Bold" FontSize="9.5" HorizontalAlignment="Center" Margin="0,6,0,6" Text="{Binding}" VerticalAlignment="Center"/>
</DataTemplate>
</ControlTemplate.Resources>
<Grid x:Name="PART_Root">
<Grid.Resources>
<SolidColorBrush x:Key="DisabledColor" Color="#A5FFFFFF"/>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="PART_DisabledVisual" To="1" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="1">
<Border BorderBrush="#FFFFFFFF" BorderThickness="0" CornerRadius="1">
<Grid>
<Grid.Resources>
<ControlTemplate x:Key="PreviousButtonTemplate" TargetType="{x:Type Button}">
<Grid Cursor="Hand">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0" Storyboard.TargetName="path" To="#FF73A9D8" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="path" To=".5" Storyboard.TargetProperty="(Shape.Fill).(Brush.Opacity)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Background="Transparent">
<Path x:Name="path" Data="M671.968176 911.99957c-12.287381 0-24.576482-4.67206-33.951566-14.047144L286.048434 545.984249c-18.751888-18.719204-18.751888-49.12028 0-67.872168L638.016611 126.111222c18.751888-18.751888 49.12028-18.751888 67.872168 0 18.751888 18.719204 18.751888 49.12028 0 67.872168l-318.016611 318.047574L705.888778 830.047574c18.751888 18.751888 18.751888 49.12028 0 67.872168C696.544658 907.32751 684.255557 911.99957 671.968176 911.99957z"
Fill="#FF333333" HorizontalAlignment="Center" VerticalAlignment="Center"
Height="10" Width="6" Stretch="Fill" Margin="10,0"/>
</Grid>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="NextButtonTemplate" TargetType="{x:Type Button}">
<Grid Cursor="Hand">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0" Storyboard.TargetName="path" To="#FF73A9D8" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="path" To=".5" Storyboard.TargetProperty="(Shape.Fill).(Brush.Opacity)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Background="Transparent">
<Path x:Name="path" Data="M761.055557 532.128047c0.512619-0.992555 1.343475-1.823411 1.792447-2.848649 8.800538-18.304636 5.919204-40.703346-9.664077-55.424808L399.935923 139.743798c-19.264507-18.208305-49.631179-17.344765-67.872168 1.888778-18.208305 19.264507-17.375729 49.631179 1.888778 67.872168l316.960409 299.839269L335.199677 813.631716c-19.071845 18.399247-19.648112 48.767639-1.247144 67.872168 9.407768 9.791372 21.984142 14.688778 34.560516 14.688778 12.000108 0 24.000215-4.479398 33.311652-13.439914l350.048434-337.375729c0.672598-0.672598 0.927187-1.599785 1.599785-2.303346 0.512619-0.479935 1.056202-0.832576 1.567101-1.343475C757.759656 538.879828 759.199462 535.391265 761.055557 532.128047z"
Fill="#FF333333" HorizontalAlignment="Right" VerticalAlignment="Center"
Height="10" Stretch="Fill" Width="6" Margin="10,0"/>
</Grid>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="HeaderButtonTemplate" TargetType="{x:Type Button}">
<Grid Cursor="Hand" Background="Transparent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0" Storyboard.TargetName="buttonContent" To="#FF73A9D8" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="buttonContent" To=".5" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="buttonContent" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" TextElement.Foreground="#FF333333"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="1,9" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
</ControlTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="PART_HeaderButton" Grid.Column="0" Focusable="False" FontWeight="Bold" FontSize="14" HorizontalAlignment="Left"
VerticalContentAlignment="Center"
Grid.Row="0" Template="{StaticResource HeaderButtonTemplate}" VerticalAlignment="Center" Margin="10,5"/>
<Button x:Name="PART_PreviousButton" Grid.Column="1" Focusable="False" HorizontalAlignment="Center"
Template="{StaticResource PreviousButtonTemplate}" Margin="0,5"/>
<Button x:Name="PART_NextButton" Grid.Column="2" Focusable="False" HorizontalAlignment="Right"
Template="{StaticResource NextButtonTemplate}" Margin="0,5"/>
<Grid x:Name="PART_MonthView" Grid.ColumnSpan="3" HorizontalAlignment="Center" Margin="6,-1,6,6" Grid.Row="1" Visibility="Visible">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
</Grid>
<Grid x:Name="PART_YearView" Grid.ColumnSpan="3" HorizontalAlignment="Center" Margin="6,-3,7,6" Grid.Row="1" Visibility="Hidden">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
</Grid>
</Grid>
</Border>
</Border>
<Rectangle x:Name="PART_DisabledVisual" Fill="{StaticResource DisabledColor}" Opacity="0" RadiusX="2" RadiusY="2" Stroke="{StaticResource DisabledColor}" Stretch="Fill" StrokeThickness="1" Visibility="Collapsed"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" TargetName="PART_DisabledVisual" Value="Visible"/>
</Trigger>
<DataTrigger Binding="{Binding DisplayMode, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Calendar}}}" Value="Year">
<Setter Property="Visibility" TargetName="PART_MonthView" Value="Hidden"/>
<Setter Property="Visibility" TargetName="PART_YearView" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding DisplayMode, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Calendar}}}" Value="Decade">
<Setter Property="Visibility" TargetName="PART_MonthView" Value="Hidden"/>
<Setter Property="Visibility" TargetName="PART_YearView" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<TextBox IsReadOnly="True" Name="textBox" VerticalContentAlignment="Center" BorderBrush="#DDD"
Text="{Binding CurrentDateTime,RelativeSource={RelativeSource AncestorType=UserControl},StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}"/>
<ToggleButton Grid.Column="1" Name="toggleButton" Background="#DDD" BorderThickness="0">
<Path Data="M453.228 781.672H170.319V409.18h650.457c11.686 0 21.16-9.473 21.16-21.16V206.29c0-11.686-9.474-21.16-21.16-21.16H660.945v-35.972c0-11.686-9.474-21.16-21.16-21.16-11.688 0-21.16 9.474-21.16 21.16v35.972H356.247v-35.972c0-11.686-9.473-21.16-21.16-21.16s-21.16 9.474-21.16 21.16v35.972H149.16c-11.687 0-21.16 9.474-21.16 21.16V790.44c0 18.5 14.822 33.55 33.04 33.55H453.23c11.686 0 21.16-9.472 21.16-21.16 0-11.686-9.475-21.16-21.161-21.16zM170.319 227.45h143.609v35.971c0 11.687 9.473 21.16 21.16 21.16s21.159-9.472 21.159-21.16V227.45h262.377v35.971c0 11.687 9.472 21.16 21.16 21.16 11.686 0 21.16-9.472 21.16-21.16V227.45h138.672v139.412H170.319V227.45zM690.5 483.1c-113.837 0-206.45 92.613-206.45 206.453C484.05 803.388 576.663 896 690.5 896s206.45-92.613 206.45-206.447c0-113.84-92.611-206.453-206.45-206.453z m0 370.58c-90.5 0-164.13-73.628-164.13-164.131s73.628-164.132 164.13-164.132c90.503 0 164.133 73.628 164.133 164.132S781.002 853.68 690.5 853.68zM390.104 640.816H246.218c-11.687 0-21.16 9.472-21.16 21.16 0 11.685 9.473 21.158 21.16 21.158h143.885c11.686 0 21.16-9.473 21.16-21.158-0.002-11.688-9.475-21.16-21.16-21.16z m0-119.489H246.218c-11.687 0-21.16 9.473-21.16 21.158 0 11.688 9.473 21.16 21.16 21.16h143.885c11.686 0 21.16-9.472 21.16-21.16-0.002-11.683-9.475-21.158-21.16-21.158z m413.071 147.067H711.66V617.61c0-11.687-9.472-21.16-21.159-21.16s-21.16 9.473-21.16 21.16v71.943c0 11.685 9.474 21.158 21.16 21.158h112.673c11.687 0 21.16-9.473 21.16-21.158 0.001-11.688-9.473-21.16-21.16-21.16z"
Fill="Orange" Stretch="Fill" Margin="5"/>
</ToggleButton>
<Popup StaysOpen="False" IsOpen="{Binding ElementName=toggleButton,Path=IsChecked}"
PlacementTarget="{Binding ElementName=textBox}" AllowsTransparency="True"
HorizontalOffset="2" Name="popup">
<Border Background="#F7F9FA" Width="auto" Height="auto" Margin="3">
<Border.Effect>
<DropShadowEffect BlurRadius="5" Color="Gray" ShadowDepth="0" Opacity="1"/>
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Calendar Name="calendar" CalendarItemStyle="{StaticResource CalendarCalendarItemStyle1}"
CalendarDayButtonStyle="{StaticResource CalendarCalendarDayButtonStyle1}"
BorderThickness="0"
Background="Transparent"/>
<Grid Grid.Row="1" Margin="10,5,10,10">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<RepeatButton Content="上" Style="{StaticResource ButtonUpStyle}" Click="BtnHourUp_Click"/>
<RepeatButton Content="上" Style="{StaticResource ButtonUpStyle}" Grid.Column="2" Click="BtnMinuteUp_Click" Tag="{Binding ElementName=tb_minute}"/>
<RepeatButton Content="上" Style="{StaticResource ButtonUpStyle}" Grid.Column="4" Click="BtnMinuteUp_Click" Tag="{Binding ElementName=tb_second}"/>
<RepeatButton Content="下" Style="{StaticResource ButtonDownStyle}" Grid.Row="2" Click="BtnHourDown_Click"/>
<RepeatButton Content="下" Style="{StaticResource ButtonDownStyle}" Grid.Row="2" Grid.Column="2" Click="BtnMinuteDown_Click" Tag="{Binding ElementName=tb_minute}"/>
<RepeatButton Content="下" Style="{StaticResource ButtonDownStyle}" Grid.Row="2" Grid.Column="4" Click="BtnMinuteDown_Click" Tag="{Binding ElementName=tb_second}"/>
<TextBox Grid.Row="1" Name="tb_Hour" TextChanged="tb_Hour_TextChanged" Height="24"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<TextBlock Text=":" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Margin="3,0"/>
<TextBox Grid.Row="1" Grid.Column="2" Name="tb_minute" TextChanged="tb_minute_TextChanged" Height="24"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<TextBlock Text=":" Grid.Row="1" Grid.Column="3" VerticalAlignment="Center" Margin="3,0"/>
<TextBox Grid.Row="1" Grid.Column="4" Name="tb_second" TextChanged="tb_minute_TextChanged" Height="24"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<Button Content="X" Grid.Column="5" Grid.RowSpan="3" Style="{StaticResource ButtonCancelStyle}"
Click="BtnCancel_Click"/>
<Button Content="确" Grid.Column="6" Grid.RowSpan="3" Style="{StaticResource ButtonAcceptStyle}"
Click="BtnAccept_Click"/>
</Grid>
</Grid>
</Border>
</Popup>
</Grid>
</UserControl>
public partial class DateTimePicker : UserControl
{
public event EventHandler<DateTime> SelectedChanged;
public DateTime CurrentDateTime
{
get { return (DateTime)GetValue(CurrentDateTimeProperty); }
set { SetValue(CurrentDateTimeProperty, value); }
}
public static readonly DependencyProperty CurrentDateTimeProperty =
DependencyProperty.Register("CurrentDateTime",
typeof(DateTime),
typeof(DateTimePicker),
new PropertyMetadata(DateTime.Now, new PropertyChangedCallback(OnCurrentDateTimeChanged)));
private static void OnCurrentDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as DateTimePicker).Refresh();
}
private void Refresh()
{
this.calendar.SelectedDate = this.CurrentDateTime;
this.tb_Hour.Text = this.CurrentDateTime.Hour.ToString();
this.tb_minute.Text = this.CurrentDateTime.Minute.ToString();
this.tb_second.Text = this.CurrentDateTime.Second.ToString();
}
public DateTimePicker()
{
InitializeComponent();
}
private void BtnAccept_Click(object sender, RoutedEventArgs e)
{
//日期时间合并
int year = this.calendar.SelectedDate.Value.Year;
int month = this.calendar.SelectedDate.Value.Month;
int day = this.calendar.SelectedDate.Value.Day;
int.TryParse(this.tb_Hour.Text.Trim(), out int hour);
int.TryParse(this.tb_minute.Text.Trim(), out int minute);
int.TryParse(this.tb_second.Text.Trim(), out int second);
CurrentDateTime = new DateTime(year, month, day, hour, minute, second);
this.popup.IsOpen = false;
SelectedChanged?.Invoke(this, CurrentDateTime);
}
private void BtnHourUp_Click(object sender, RoutedEventArgs e)
{
int.TryParse(this.tb_Hour.Text.Trim(), out int hour);
hour++;
hour %= 24;
this.tb_Hour.Text = hour.ToString();
}
private void BtnMinuteUp_Click(object sender, RoutedEventArgs e)
{
RepeatButton btn = sender as RepeatButton;
TextBox tb = btn.Tag as TextBox;
int.TryParse(tb.Text.Trim(), out int value);
value++;
value %= 60;
tb.Text = value.ToString();
}
private void BtnHourDown_Click(object sender, RoutedEventArgs e)
{
int.TryParse(this.tb_Hour.Text.Trim(), out int hour);
hour--;
if (hour < 0) hour = 23;
this.tb_Hour.Text = hour.ToString();
}
private void BtnMinuteDown_Click(object sender, RoutedEventArgs e)
{
RepeatButton btn = sender as RepeatButton;
TextBox tb = btn.Tag as TextBox;
int.TryParse(tb.Text.Trim(), out int value);
value--;
if (value < 0) value = 59;
tb.Text = value.ToString();
}
private void tb_Hour_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = sender as TextBox;
if (!string.IsNullOrEmpty(tb.Text.Trim()))
{
int.TryParse(tb.Text.Trim(), out int hour);
if (hour < 0)
{
hour *= -1;
tb.TextChanged -= tb_Hour_TextChanged;
tb.Text = tb.Text.Substring(1);
tb.TextChanged += tb_Hour_TextChanged;
}
if (hour > 23)
{
tb.TextChanged -= tb_Hour_TextChanged;
tb.Text = tb.Text.Substring(0, tb.Text.Length - 1);
tb.TextChanged += tb_Hour_TextChanged;
}
}
}
private void tb_minute_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = sender as TextBox;
if (!string.IsNullOrEmpty(tb.Text.Trim()))
{
int.TryParse(tb.Text.Trim(), out int minute);
if (minute < 0)
{
minute *= -1;
tb.TextChanged -= tb_Hour_TextChanged;
tb.Text = tb.Text.Substring(1);
tb.TextChanged += tb_Hour_TextChanged;
}
if (minute > 59)
{
tb.TextChanged -= tb_minute_TextChanged;
tb.Text = tb.Text.Substring(0, tb.Text.Length - 1);
tb.TextChanged += tb_minute_TextChanged;
}
}
}
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
this.popup.IsOpen = false;
}
}
三、数字框
[TemplatePart(Name = "PART_Value", Type = typeof(TextBox))]
public class NumericBox : Control
{
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(NumericBox),
new PropertyMetadata(0));
static NumericBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericBox), new FrameworkPropertyMetadata(typeof(NumericBox)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// 获取模板里的控件
var txtValue = base.GetTemplateChild("PART_Value") as TextBox;
var btnIncrease = base.GetTemplateChild("PART_IncreaseButton") as RepeatButton;
var btnDecrease = base.GetTemplateChild("PART_DecreaseButton") as RepeatButton;
if (txtValue != null)
{
// 建立对象中的Value属性与模板中的TextBox控件Text属性的绑定
Binding binding = new Binding("Value");
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
binding.RelativeSource = new RelativeSource() { AncestorType = typeof(NumericBox), Mode = RelativeSourceMode.FindAncestor };
txtValue.SetBinding(TextBox.TextProperty, binding);
}
if (btnIncrease != null)
btnIncrease.Click += BtnIncrease_Click;
if (btnDecrease != null)
btnDecrease.Click += BtnDecrease_Click;
}
private void BtnDecrease_Click(object sender, RoutedEventArgs e)
{
this.Value--;
}
private void BtnIncrease_Click(object sender, RoutedEventArgs e)
{
this.Value++;
}
}
<!--Themes/Aero.NormalColor.xaml-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:Controls">
<Style TargetType="{x:Type c:NumericBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:NumericBox}">
<Border Background="Green"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Height="30">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<!--Themes/Generic.xaml-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:Controls">
<Style TargetType="{x:Type c:NumericBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:NumericBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<TextBox Name="PART_Value" VerticalAlignment="Center" Margin="3,5" BorderThickness="0"
Foreground="{TemplateBinding Foreground}"/>
<UniformGrid Rows="2" Grid.Column="1">
<RepeatButton Content="+" Background="Transparent"
BorderThickness="1,0,0,0" Name="PART_IncreaseButton"
BorderBrush="#DDD"/>
<RepeatButton Content="-" Background="Transparent"
BorderThickness="1,1,0,0" Name="PART_DecreaseButton"
BorderBrush="#DDD"/>
</UniformGrid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
四、工业仪表
<UserControl x:Class="Controls.Instrument"
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:local="clr-namespace:Controls"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="400">
<Grid>
<Border Name="border" Background="#FF030A28">
<Border.Effect>
<DropShadowEffect BlurRadius="20" Opacity="0.5" ShadowDepth="0" Direction="0" Color="#FF3CAFFF"/>
</Border.Effect>
<Grid>
<Path Stroke="#333CAFFF" StrokeThickness="10" Name="circle" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<RotateTransform Angle="-45"/>
</Path.RenderTransform>
</Path>
<Canvas x:Name="canvasPlate" RenderTransformOrigin="0.5,0.5" Margin="0">
<Canvas.RenderTransform>
<RotateTransform Angle="-45"/>
</Canvas.RenderTransform>
</Canvas>
<Path Data="" Name="plateBorder" Stroke="#FF3CAFFF" StrokeThickness="3" RenderTransformOrigin="0.5,0.5"
Width="{Binding ElementName=border,Path=Width}"
Height="{Binding ElementName=border,Path=Height}">
<Path.RenderTransform>
<RotateTransform Angle="-45"/>
</Path.RenderTransform>
</Path>
<Path Data="M200 205,360 200,200 195,195 200 200 205" Fill="Red" RenderTransformOrigin="0.5,0.5" Name="pointer">
<Path.RenderTransform>
<RotateTransform Angle="135" x:Name="rtPointer"/>
</Path.RenderTransform>
</Path>
<Border Width="60" Height="60" CornerRadius="30" Background="#FF030A28">
<Border.Effect>
<DropShadowEffect BlurRadius="20" Opacity="0.3" ShadowDepth="0" Direction="0" Color="#FF3CAFFF"/>
</Border.Effect>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center">
<Run Text="{Binding Value,RelativeSource={RelativeSource AncestorType=UserControl}}"
FontSize="20"/>
<Run Text="m³/H" FontSize="8"/>
</TextBlock>
<TextBlock Text="NATURAL GAS" Foreground="#FF8CBEF0" VerticalAlignment="Center" HorizontalAlignment="Center"
FontSize="6"/>
</StackPanel>
</Border>
</Grid>
</Border>
</Grid>
</UserControl>
public partial class Instrument : UserControl
{
// 当前值
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(Instrument),
new PropertyMetadata(default(double), new PropertyChangedCallback(OnPropertyChanged)));
// 最小刻度
public double Minimum
{
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(Instrument),
new PropertyMetadata(default(double), new PropertyChangedCallback(OnPropertyChanged)));
// 最大刻度
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(Instrument),
new PropertyMetadata(default(double), new PropertyChangedCallback(OnPropertyChanged)));
//// 间隔
//public double Interval
//{
// get { return (double)GetValue(IntervalProperty); }
// set { SetValue(IntervalProperty, value); }
//}
//public static readonly DependencyProperty IntervalProperty =
// DependencyProperty.Register("Interval", typeof(double), typeof(Instrument), new PropertyMetadata(default(double), new PropertyChangedCallback(OnPropertyChanged)));
// 大刻度的个数
public int ScaleCount
{
get { return (int)GetValue(ScaleCountProperty); }
set { SetValue(ScaleCountProperty, value); }
}
public static readonly DependencyProperty ScaleCountProperty =
DependencyProperty.Register("ScaleCount", typeof(int), typeof(Instrument),
new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropertyChanged)));
// 刻度的厚度
public double ScaleThickness
{
get { return (double)GetValue(ScaleThicknessProperty); }
set { SetValue(ScaleThicknessProperty, value); }
}
public static readonly DependencyProperty ScaleThicknessProperty =
DependencyProperty.Register("ScaleThickness", typeof(double), typeof(Instrument),
new PropertyMetadata(default(double), new PropertyChangedCallback(OnPropertyChanged)));
// 刻度的颜色
public Brush ScaleBrush
{
get { return (Brush)GetValue(ScaleBrushProperty); }
set { SetValue(ScaleBrushProperty, value); }
}
public static readonly DependencyProperty ScaleBrushProperty =
DependencyProperty.Register("ScaleBrush", typeof(Brush), typeof(Instrument),
new PropertyMetadata(default(Brush), new PropertyChangedCallback(OnPropertyChanged)));
// 指针的颜色
public Brush PointerBrush
{
get { return (Brush)GetValue(PointerBrushProperty); }
set { SetValue(PointerBrushProperty, value); }
}
public static readonly DependencyProperty PointerBrushProperty =
DependencyProperty.Register("PointerBrush", typeof(Brush), typeof(Instrument),
new PropertyMetadata(default(Brush), new PropertyChangedCallback(OnPropertyChanged)));
// 刻度字体大小
public new double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
public static new readonly DependencyProperty FontSizeProperty =
DependencyProperty.Register("FontSize", typeof(double), typeof(Instrument),
new PropertyMetadata(9.0, new PropertyChangedCallback(OnPropertyChanged)));
public Instrument()
{
InitializeComponent();
SetCurrentValue(MinimumProperty, 0d);
SetCurrentValue(MaximumProperty, 100d);
//SetCurrentValue(IntervalProperty, 10d);
SizeChanged += (se, ev) => { Refresh(); };
}
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as Instrument).Refresh();
private void Refresh()
{
// 圆
this.border.Width = Math.Min(RenderSize.Width, RenderSize.Height);
this.border.Height = Math.Min(RenderSize.Width, RenderSize.Height);
this.border.CornerRadius = new CornerRadius(this.border.Width / 2);
// 半径
double radius = this.border.Width / 2;
this.canvasPlate.Children.Clear();
if (ScaleCount <= 0 || radius <= 0) return;
// 画边
string borderPathData = $"M4,{radius}A{radius - 4} {radius - 4} 0 1 1 {radius} {this.border.Height - 4}";
// 将字符串转换成Geometry
var converter = TypeDescriptor.GetConverter(typeof(Geometry));
//this.plateBorder.Data = (Geometry)converter.ConvertFrom(borderPathData);
this.plateBorder.Data = PathGeometry.Parse(borderPathData);
// 计算刻度
double label = this.Minimum;
double interval = 0;
double step = 270.0 / (this.Maximum - this.Minimum);
// 计算小刻度
for (int i = 0; i < this.Maximum - this.Minimum; i++)
{
//添加刻度线
Line lineScale = new Line();
// 角度需要转换弧度
lineScale.X1 = radius - (radius - 13) * Math.Cos(step * i * Math.PI / 180);
lineScale.Y1 = radius - (radius - 13) * Math.Sin(step * i * Math.PI / 180);
lineScale.X2 = radius - (radius - 8) * Math.Cos(step * i * Math.PI / 180);
lineScale.Y2 = radius - (radius - 8) * Math.Sin(step * i * Math.PI / 180);
lineScale.Stroke = this.ScaleBrush;
lineScale.StrokeThickness = this.ScaleThickness;
this.canvasPlate.Children.Add(lineScale);
}
// 计算大刻度
do
{
//添加刻度线
Line lineScale = new Line();
lineScale.X1 = radius - (radius - 20) * Math.Cos(interval * step * Math.PI / 180);
lineScale.Y1 = radius - (radius - 20) * Math.Sin(interval * step * Math.PI / 180);
lineScale.X2 = radius - (radius - 8) * Math.Cos(interval * step * Math.PI / 180);
lineScale.Y2 = radius - (radius - 8) * Math.Sin(interval * step * Math.PI / 180);
lineScale.Stroke = this.ScaleBrush;
lineScale.StrokeThickness = this.ScaleThickness;
this.canvasPlate.Children.Add(lineScale);
TextBlock txtScale = new TextBlock();
txtScale.Text = label.ToString("0");
txtScale.Width = 34;
txtScale.TextAlignment = TextAlignment.Center;
txtScale.Foreground = new SolidColorBrush(Colors.White);
txtScale.RenderTransform = new RotateTransform() { Angle = 45, CenterX = 17, CenterY = 8 };
txtScale.FontSize = this.FontSize;
Canvas.SetLeft(txtScale, radius - (radius - 34) * Math.Cos(interval * step * Math.PI / 180) - 17);
Canvas.SetTop(txtScale, radius - (radius - 34) * Math.Sin(interval * step * Math.PI / 180) - 8);
this.canvasPlate.Children.Add(txtScale);
interval += (this.Maximum - this.Minimum) / this.ScaleCount;
label += (this.Maximum - this.Minimum) / this.ScaleCount;
} while (interval <= this.Maximum - this.Minimum);
// 修改指针
string sData = "M{0} {1},{2} {0},{0} {3},{3} {0},{0} {1}";
sData = string.Format(sData, radius, radius + 2, this.border.Width - radius / 10, radius - 4);
converter = TypeDescriptor.GetConverter(typeof(Geometry));
this.pointer.Data = (Geometry)converter.ConvertFrom(sData);
this.pointer.Fill = this.PointerBrush;
//DoubleAnimation da = new DoubleAnimation((Value - Minimum) * step + 135, new Duration(TimeSpan.FromMilliseconds(200)));
//this.rtPointer.BeginAnimation(RotateTransform.AngleProperty, da);
this.rtPointer.Angle = (Value - Minimum) * step + 135;
// 修改圆 M100 200 A100 100 0 1 1 200 300
// 厚度
double thickness = radius / 2;
this.circle.StrokeThickness = thickness;
double startX = radius - thickness / 2;
double startY = radius;
double endX = radius - (radius - thickness / 2) * Math.Cos((Value - Minimum) * step * Math.PI / 180);
double endY = radius - (radius - thickness / 2) * Math.Sin((Value - Minimum) * step * Math.PI / 180);
int isLarge = 1;
if ((Value - Minimum) * step < 180)
isLarge = 0;
sData = $"M{startX},{startY}A{radius / 2} {radius / 2} 0 1 1 {endX} {endY}";
sData = $"M{thickness / 2},{radius}A{radius - thickness / 2} {radius - thickness / 2} 0 {isLarge} 1 {endX} {endY}";
//sData = string.Format(sData, radius * 0.5, radius, radius * 1.5);
this.circle.Data = (Geometry)converter.ConvertFrom(sData);
this.circle.Visibility = Visibility.Visible;
if (this.border.Width < 200)
this.circle.Visibility = Visibility.Collapsed;
}
}
五、各种常见开关
public class Switch : ToggleButton
{
static Switch()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Switch), new FrameworkPropertyMetadata(typeof(Switch)));
}
}
<!--Themes/Generic.xaml-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:Controls">
<Style TargetType="{x:Type c:Switch}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:Switch}">
<Grid>
<Border Width="{Binding RelativeSource={RelativeSource Self},Path=ActualHeight}"
CornerRadius="{Binding RelativeSource={RelativeSource Self},Path=ActualHeight}"
Visibility="Collapsed" Name="dropdown" Margin="-23">
<Border.Background>
<RadialGradientBrush>
<GradientStop Color="Transparent" Offset="1"/>
<GradientStop Color="#5500D787" Offset="0.7"/>
<GradientStop Color="Transparent" Offset="0.59"/>
</RadialGradientBrush>
</Border.Background>
</Border>
<Border Width="{Binding RelativeSource={RelativeSource Self},Path=ActualHeight}"
BorderBrush="DarkGreen" BorderThickness="5"
CornerRadius="{Binding RelativeSource={RelativeSource Self},Path=ActualHeight}"
Background="Gray" Name="bor">
<Border Background="#FF00C88C" Margin="2" Name="bor1"
Width="{Binding RelativeSource={RelativeSource Self},Path=ActualHeight}"
CornerRadius="{Binding RelativeSource={RelativeSource Self},Path=ActualHeight}"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard >
<ColorAnimation To="White" Duration="0:0:0.5"
Storyboard.TargetName="bor1"
Storyboard.TargetProperty="Background.Color"/>
<ColorAnimation To="#FF32FAC8" Duration="0:0:0.5"
Storyboard.TargetName="bor"
Storyboard.TargetProperty="BorderBrush.Color"/>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="dropdown"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard >
<ColorAnimation Storyboard.TargetName="bor1"
Storyboard.TargetProperty="Background.Color"/>
<ColorAnimation Storyboard.TargetName="bor"
Storyboard.TargetProperty="BorderBrush.Color"/>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="dropdown"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
六、第三方控件库
1. 简单介绍
- HandyControl:https://handyorg.github.io/
- 图表控件库
- LiveCharts:LiveCharts2预览版、内存管理不是很好,长时间持续更新的情况下,内存溢出,慎用!数据加载量不能太大(1000点左右 开始卡)
- ScottPlot:考虑性能, 建议这个
- OxyPlot
- LightingChart
2. ScottPlot使用MVVM绑定数据
问题:ScottPlot不支持数据绑定,所以没办法直接把ViewModel中的模型数据提供给ScottPlot。
思路:遇到这种问题,我们通常可以使用附加依赖属性来实现。
- 添加一个ScottPlotExtension,这个属性里面可以知道绑定的属性值,以及被附加的对象
public class ScottPlotExtension
{
// 这个属性里面可以知道绑定的属性值,以及被附加的对象
public static ObservableCollection<double> GetValues(DependencyObject obj)
{
return (ObservableCollection<double>)obj.GetValue(ValuesProperty);
}
public static void SetValues(DependencyObject obj, ObservableCollection<double> value)
{
obj.SetValue(ValuesProperty, value);
}
public static readonly DependencyProperty ValuesProperty =
DependencyProperty.RegisterAttached(
"Values",
typeof(ObservableCollection<double>),
typeof(ScottPlotExtension),
new PropertyMetadata(null, new PropertyChangedCallback(OnValuesChanged)));
// 当给ScottPlotExtension的Values属性赋的时候 才触发
// new List<double>() 这种情况针对触发
private static void OnValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var plt = (d as WpfPlot);
var new_list = (ObservableCollection<double>)e.NewValue;
var signal = plt.Plot.AddSignal(new_list.ToArray());
// 对集合实例进行子项培养的时候,进行回调
new_list.CollectionChanged += (s, ee) =>
{
if (ee.Action == NotifyCollectionChangedAction.Add)
{
signal.Update((s as ObservableCollection<double>).ToArray());
plt.Refresh();
}
};
plt.Refresh();
}
}
- 创建一个ScottPlot专用的ViewModel
public class ScottPlotViewModel : INotifyPropertyChanged
{
public ObservableCollection<double> Datas { get; set; }
public ScottPlotViewModel()
{
Datas = new ObservableCollection<double>(DataGen.RandomWalk(new Random(), 1_000_000));
// 实时监控:持续获取数据 变更图表的数据源
Task.Run(async () =>
{
while (true)
{
await Task.Delay(500);
Application.Current.Dispatcher.Invoke(() =>
{
Datas.RemoveAt(0);
Datas.Add(new Random().NextDouble());
});
}
});
}
public event PropertyChangedEventHandler? PropertyChanged;
}
- 界面绑定
<Window x:Class="Views.ScottPlotWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Zhaoxi.MvvmPattern.Views"
xmlns:vm="clr-namespace:ViewModels"
xmlns:b="clr-namespace:Base"
mc:Ignorable="d"
Title="ScottPlot" Height="450" Width="800">
<Window.DataContext>
<vm:ScottPlotViewModel/>
</Window.DataContext>
<Grid>
<WpfPlot Name="wpf_plot" b:ScottPlotExtension.Values="{Binding Datas}"/>
</Grid>
</Window>
