Wpf常用样式与自定义控件(仪表盘,管道,分页器等)

前文

由于Wpf默认提供的控件只能满足通用的需求,且其默认的样式是基本样式,缺乏一定的现代UI美观性,故本文介绍一些实际开发中常用的自定义扩展控件 ,以及一些自定义呈现扁平化效果的样式,提高开发效率。

Wpf项目常使用MVVM架构进行前后端分离开发,如此便不能使用WinForm中的UI后端耦合的方式进行事件订阅。而是需要通过命令绑定事件,达到前后端分离情况下的事件订阅。本文将详细介绍如何通过UIElement.InputBindingsBehavior两种途径实现事件与命令的绑定。

效果

**注意事项:**在.net Framework框架下引用另外程序集的资源(例如字体文件),可能出现设计模式下引入的资源无效,但运行模式下资源作用又有效的现象。为了避免这种现象尽量使用.net core框架。

例如:

xaml 复制代码
 <TextBlock VerticalAlignment="Center" FontSize="20" FontFamily="{StaticResource DigitalFont}" Text="{Binding}" Margin="4,1"></TextBlock>

.net Framework框架设计模式显示:

引入的字体未呈现效果

.net Framework框架运行模式显示:

引入的字体呈现效果

1,自定义样式

1.1,ButtonStyles
1.1.1,ControlButtonStyle
xaml 复制代码
    <Style TargetType="Button" x:Key="ControlButtonStyle">
        <Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Height" Value="30"/>
        <Setter Property="FontSize" Value="11"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid Background="Transparent">
                        <Border Background="Transparent" Name="border">
                            <ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="#3000" TargetName="border"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter Property="Background" Value="#5000" TargetName="border"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
xaml 复制代码
<Button Content="&#xe7e6;" FontFamily="{StaticResource Icons}"
                        Style="{StaticResource ControlButtonStyle}"
                        Click="ButtonMin_Click"/>

显示效果

1.1.2,NormalButtonStyle
xaml 复制代码
<Style TargetType="Button" x:Key="NormalButtonStyle">
        <Setter Property="Foreground" Value="#666"/>
        <Setter Property="Background" Value="#FFF"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <Border Background="{TemplateBinding Background}" CornerRadius="4">
                            <Border.Effect>
                                <DropShadowEffect BlurRadius="5" Color="Gray" ShadowDepth="0" Opacity="0.3"/>
                            </Border.Effect>
                        </Border>
                        <Border Background="Transparent" Name="back" CornerRadius="4"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              Margin="{TemplateBinding Padding}"/>
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="#11000000" TargetName="back"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
xaml 复制代码
 <Button Content="打开出入监控" Style="{StaticResource NormalButtonStyle}"
                                Foreground="White" Height="33"
                                Margin="20,10" FontWeight="Bold"
                                Command="{Binding ShowMonitorCommand}">
                                <Button.Background>
                                    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                                        <GradientStop Color="#2096F0" Offset="-0.2"/>
                                        <GradientStop Color="#A723F0" Offset="1"/>
                                    </LinearGradientBrush>
                                </Button.Background>
                            </Button>

显示效果:

1.1.3,IconWithContentButtonStyle
xaml 复制代码
 <Style TargetType="Button" x:Key="IconWithContentButtonStyle">
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Height" Value="30"/>
        <Setter Property="Background" Value="#FF0ABEFF"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <Border Background="{TemplateBinding Background}" CornerRadius="4">
                            <Border.Effect>
                                <DropShadowEffect BlurRadius="5" Color="Gray" ShadowDepth="0" Opacity="0.3"/>
                            </Border.Effect>
                        </Border>
                        <Border Background="Transparent" Name="back" CornerRadius="4">
                            <Grid HorizontalAlignment="Center">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="auto"/>
                                    <ColumnDefinition/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Text="{TemplateBinding Tag}" FontFamily="{DynamicResource Icons}" 
                                       VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5,0"/>
                                <ContentPresenter Grid.Column="1" Margin="0,0,10,0"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                            </Grid>
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="#11000000" TargetName="back"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
xaml 复制代码
 <Button Content="新建" Style="{StaticResource IconWithContentButtonStyle}"
                        Command="{Binding ModifyCommand}"
                        Tag="&#xe603;" Width="80">
                    <Button.Background>
                        <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                            <GradientStop Color="#FE582D" Offset="0"/>
                            <GradientStop Color="#F1961A" Offset="1"/>
                        </LinearGradientBrush>
                    </Button.Background>
                </Button>

显示效果:

1.1.4,IconButtonStyle
xaml 复制代码
<Style TargetType="Button" x:Key="IconButtonStyle">
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Height" Value="30"/>
        <Setter Property="Background" Value="#FF0ABEFF"/>
        <Setter Property="FontFamily" Value="{DynamicResource Icons}"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border Background="{TemplateBinding Background}" CornerRadius="4">
                        <Border Background="Transparent" Name="back" CornerRadius="4">
                            <ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                        </Border>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="#11000000" TargetName="back"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

显示效果:

1.2,ComboBoxStyle
xaml 复制代码
<SolidColorBrush x:Key="TextBox.Static.Background" Color="#FFFFFFFF"/>
    <SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
    <SolidColorBrush x:Key="ComboBox.Static.Glyph" Color="#FF606060"/>
    <SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
    <SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
    <LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
        <GradientStop Color="#FFEBF4FC" Offset="0.0"/>
        <GradientStop Color="#FFDCECFC" Offset="1.0"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
    <LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
        <GradientStop Color="#FFDAEBFC" Offset="0.0"/>
        <GradientStop Color="#FFC4E0FC" Offset="1.0"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
    <SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
    <SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>


    <Style x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="IsTabStop" Value="false"/>
        <Setter Property="Focusable" Value="false"/>
        <Setter Property="ClickMode" Value="Press"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Border x:Name="templateRoot"
                                Background="{TemplateBinding Background}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                BorderBrush="{StaticResource TextBox.Static.Border}"
                                CornerRadius="5">
                        <Border x:Name="splitBorder" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" 
                                    SnapsToDevicePixels="true" Margin="0" HorizontalAlignment="Right" 
                                    BorderThickness="1" BorderBrush="Transparent">
                            <TextBlock Text="&#xe6df;" FontFamily="{DynamicResource Icons}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Border>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource TextBox.MouseOver.Border}"/>
                        </Trigger>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource TextBox.Focus.Border}"/>
                        </Trigger>
                        <MultiDataTrigger>
                            <MultiDataTrigger.Conditions>
                                <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
                                <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="false"/>
                                <Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="false"/>
                                <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="true"/>
                            </MultiDataTrigger.Conditions>
                            <Setter Property="Background" TargetName="splitBorder" Value="{StaticResource ComboBox.Static.Editable.Button.Background}"/>
                            <Setter Property="BorderBrush" TargetName="splitBorder" Value="{StaticResource ComboBox.Static.Editable.Button.Border}"/>
                        </MultiDataTrigger>
                        <MultiDataTrigger>
                            <MultiDataTrigger.Conditions>
                                <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
                                <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
                            </MultiDataTrigger.Conditions>
                            <Setter Property="Background" TargetName="splitBorder" Value="{StaticResource ComboBox.MouseOver.Editable.Button.Background}"/>
                            <Setter Property="BorderBrush" TargetName="splitBorder" Value="{StaticResource ComboBox.MouseOver.Editable.Button.Border}"/>
                        </MultiDataTrigger>
                        <MultiDataTrigger>
                            <MultiDataTrigger.Conditions>
                                <Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="true"/>
                                <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
                            </MultiDataTrigger.Conditions>
                            <Setter Property="Background" TargetName="splitBorder" Value="{StaticResource ComboBox.Pressed.Editable.Button.Background}"/>
                            <Setter Property="BorderBrush" TargetName="splitBorder" Value="{StaticResource ComboBox.Pressed.Editable.Button.Border}"/>
                        </MultiDataTrigger>
                        <MultiDataTrigger>
                            <MultiDataTrigger.Conditions>
                                <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
                                <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
                            </MultiDataTrigger.Conditions>
                            <Setter Property="Background" TargetName="splitBorder" Value="{StaticResource ComboBox.Disabled.Editable.Button.Background}"/>
                            <Setter Property="BorderBrush" TargetName="splitBorder" Value="{StaticResource ComboBox.Disabled.Editable.Button.Border}"/>
                        </MultiDataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="ComboBoxEditableTextBox" TargetType="{x:Type TextBox}">
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="AllowDrop" Value="true"/>
        <Setter Property="MinWidth" Value="0"/>
        <Setter Property="MinHeight" Value="0"/>
        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
        <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <ScrollViewer x:Name="PART_ContentHost" Background="Transparent" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ComboBox}">
        <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
            </Grid.ColumnDefinitions>
            <Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2"
                       IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" 
                       Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
                <!--这个对象由编辑模板自动带出来的-->
                <Themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent"
                                                   MaxHeight="{TemplateBinding MaxDropDownHeight}" 
                                                   MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
                    <Border x:Name="dropDownBorder" 
                                BorderBrush="{DynamicResource ComboBox.Static.Border}" 
                                BorderThickness="1" 
                                Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
                        <ScrollViewer x:Name="DropDownScrollViewer">
                            <Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
                                <Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                    <Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
                                </Canvas>
                                <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            </Grid>
                        </ScrollViewer>
                    </Border>
                </Themes:SystemDropShadowChrome>
            </Popup>
            <ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" 
                              BorderThickness="{TemplateBinding BorderThickness}" 
                              Background="White" Grid.ColumnSpan="2" 
                              IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                              Style="{StaticResource ComboBoxToggleButton}"/>
            <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" 
                                  ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" 
                                  Content="{TemplateBinding SelectionBoxItem}"
                                  ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                  IsHitTestVisible="false" Margin="{TemplateBinding Padding}" 
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
                <Setter Property="Margin" TargetName="shadow" Value="0,0,5,5"/>
                <Setter Property="Color" TargetName="shadow" Value="#71000000"/>
            </Trigger>
            <Trigger Property="HasItems" Value="false">
                <Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
            </Trigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsGrouping" Value="true"/>
                    <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
                </MultiTrigger.Conditions>
                <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
            </MultiTrigger>
            <Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
                <Setter Property="Canvas.Top" TargetName="opaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
                <Setter Property="Canvas.Left" TargetName="opaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <ControlTemplate x:Key="ComboBoxToggleButton2" TargetType="{x:Type ToggleButton}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="40"/>
            </Grid.ColumnDefinitions>
            <Border Grid.ColumnSpan="2" CornerRadius="3" BorderThickness="1" Background="White"/>
            <Border BorderBrush="#EEE" BorderThickness="1,0,0,0" Grid.Column="1" Margin="0,10"/>
            <TextBlock Text="更改" Foreground="#0066CC" Cursor="Hand" Grid.Column="1"
                                       VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="12"
                                       SnapsToDevicePixels="True">
            </TextBlock>
        </Grid>
    </ControlTemplate>
    <ControlTemplate x:Key="ComboBoxEditableTemplate" TargetType="{x:Type ComboBox}">
        <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
            </Grid.ColumnDefinitions>
            <Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
                <Border x:Name="shadow" Background="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
                    <Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
                        <ScrollViewer x:Name="DropDownScrollViewer">
                            <Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
                                <Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                    <Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
                                </Canvas>
                                <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            </Grid>
                        </ScrollViewer>
                    </Border>
                </Border>
            </Popup>
            <ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ComboBoxToggleButton2}"/>
            <ContentPresenter x:Name="ContentSite"
                                IsHitTestVisible="False"
                                Content="{TemplateBinding SelectionBoxItem}"
                                ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
                                ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
                                              Margin="10,0,0,0"
                                VerticalAlignment="Center"
                                HorizontalAlignment="Left">
            </ContentPresenter>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsKeyboardFocusWithin" Value="true">
                <Setter Property="Foreground" Value="Black"/>
            </Trigger>
            <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
                <Setter Property="Margin" TargetName="shadow" Value="0,0,5,5"/>
                <Setter Property="Background" TargetName="shadow" Value="#71000000"/>
            </Trigger>
            <Trigger Property="HasItems" Value="false">
                <Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
            </Trigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsGrouping" Value="true"/>
                    <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
                </MultiTrigger.Conditions>
                <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
            </MultiTrigger>
            <Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
                <Setter Property="Canvas.Top" TargetName="opaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
                <Setter Property="Canvas.Left" TargetName="opaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <Style TargetType="{x:Type ComboBox}">
        <Setter Property="Background" Value="White"/>
        <Setter Property="Foreground" Value="#333"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
        <Setter Property="Padding" Value="6,3,5,3"/>
        <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
        <Setter Property="ScrollViewer.PanningMode" Value="Both"/>
        <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Template" Value="{StaticResource ComboBoxTemplate}"/>
        
        <Style.Triggers>
            <Trigger Property="IsEditable" Value="true">
                <Setter Property="IsTabStop" Value="false"/>
                <Setter Property="Padding" Value="2"/>
                <Setter Property="Template" Value="{StaticResource ComboBoxEditableTemplate}"/>
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style TargetType="ComboBoxItem">
        <Setter Property="Height" Value="28"/>
    </Style>

显示效果:

1.3,DataGridStyle
xaml 复制代码
    <Style TargetType="DataGridCell" x:Key="DataGridCellStyle">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="DataGridCell">
                    <Grid Background="Transparent">
                        <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="FontWeight" Value="Normal"/>
                <Setter Property="Foreground" Value="#444"/>
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style TargetType="DataGridColumnHeader" x:Key="DataGridColumnHeaderfStyle">
        <Setter Property="Background" Value="White"/>
        <Setter Property="Height" Value="30"/>
        <Setter Property="BorderBrush" Value="#DDD"/>
        <Setter Property="BorderThickness" Value="0,0,0,1"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="DataGridColumnHeader">
                    <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                        <TextBlock Text="{TemplateBinding Content}" VerticalAlignment="Center" HorizontalAlignment="Center"
                                   Foreground="#565656" FontWeight="Bold"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style TargetType="DataGridRow" x:Key="DataGridRowStyle">
        <Setter Property="Height" Value="40"/>
       
        <!--行头样式-->
        <Setter Property="HeaderStyle">
            <Setter.Value>
                <Style TargetType="DataGridRowHeader">
                    <Setter Property="Width" Value="20"/>
                    <Setter Property="Background" Value="Transparent"/>
                    
                </Style>
            </Setter.Value>
        </Setter>
        <!--行头模版-->
        <Setter Property="HeaderTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid Margin="5,0">
                        <TextBlock Text="{Binding Item.Index,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGridRow}}"
                                   Foreground="#888"/>
                    </Grid>
                </DataTemplate>
            </Setter.Value>
        </Setter>
        <!--行右键菜单-->
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu>
                    <MenuItem Header="复制" Command="{Binding MenuItemCommand}" CommandParameter="{Binding}"/>
                    <MenuItem Header="剪切" Command="{Binding MenuItemCommand}" CommandParameter="{Binding}"/>
                    <MenuItem Header="粘贴" Command="{Binding MenuItemCommand}" CommandParameter="{Binding}"/>
                </ContextMenu>
            </Setter.Value>
        </Setter>


        <Style.Triggers>
            <!--条纹间隔,需要配合DataGrid.AlternationCount属性-->
            <Trigger Property="AlternationIndex" Value="-1" >
                <Setter Property="Background" Value="#EEE" />
            </Trigger>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#F7F9FA"/>
            </Trigger>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="#F7F9FA"/>
            </Trigger>
        </Style.Triggers>
    </Style>

    <!--DataGrid样式-->
    <Style TargetType="{x:Type DataGrid}">
        <!--网格线颜色-->
        <Setter Property="CanUserResizeColumns" Value="True"/>
        <Setter Property="Foreground" Value="#444" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="BorderBrush" Value="Transparent" />
        <Setter Property="HorizontalGridLinesBrush" Value="#EEE"/>
        <Setter Property="VerticalGridLinesBrush" Value="Transparent"/>
        <Setter Property="CanUserAddRows" Value="False"/>
        <Setter Property="AutoGenerateColumns" Value="False"/>
        

        <Setter Property="CellStyle" Value="{StaticResource DataGridCellStyle}"/>
        <Setter Property="ColumnHeaderStyle" Value="{StaticResource DataGridColumnHeaderfStyle}"/>
        <Setter Property="RowStyle" Value="{StaticResource DataGridRowStyle}"/>
    </Style>

显示效果:

1.4,ScrollViewerStyle
xaml 复制代码
<SolidColorBrush x:Key="ScrollBar.Static.Background" Color="#F0F0F0"/>
    <SolidColorBrush x:Key="ScrollBar.Static.Border" Color="#F0F0F0"/>
    <SolidColorBrush x:Key="ScrollBar.Static.Thumb" Color="#CDCDCD"/>
    <SolidColorBrush x:Key="ScrollBar.MouseOver.Thumb" Color="#A6A6A6"/>
    <SolidColorBrush x:Key="ScrollBar.Pressed.Thumb" Color="#606060"/>

    <Style TargetType="ScrollViewer">
        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ScrollViewer}">
                    <Grid x:Name="Grid" Background="{TemplateBinding Background}">
                        <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" 
                                        CanHorizontallyScroll="False" CanVerticallyScroll="False" 
                                        ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" 
                                        Margin="{TemplateBinding Padding}"/>
                        <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" 
                           Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" 
                           Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" 
                           Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
                           ViewportSize="{TemplateBinding ViewportHeight}"
                           HorizontalAlignment="Right" Style="{DynamicResource ScrollBarStyle}"
                           Opacity="0"/>
                        <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" 
                           Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal"
                           Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                           Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" 
                           ViewportSize="{TemplateBinding ViewportWidth}"
                           VerticalAlignment="Bottom"
                           Opacity="0" Style="{DynamicResource ScrollBarStyle}"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0.3" To="0.7" 
                                                 Storyboard.TargetName="PART_VerticalScrollBar"
                                                 Storyboard.TargetProperty="Opacity"/>
                                        <DoubleAnimation Duration="0:0:0.3" To="0.7" 
                                                 Storyboard.TargetName="PART_HorizontalScrollBar"
                                                 Storyboard.TargetProperty="Opacity"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0.2"
                                                 Storyboard.TargetName="PART_VerticalScrollBar"
                                                 Storyboard.TargetProperty="Opacity"/>
                                        <DoubleAnimation Duration="0:0:0.2"
                                                 Storyboard.TargetName="PART_HorizontalScrollBar"
                                                 Storyboard.TargetProperty="Opacity"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="ScrollBarStyle" TargetType="{x:Type ScrollBar}">
        <Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
        <Setter Property="Stylus.IsFlicksEnabled" Value="false"/>
        <Setter Property="Background" Value="{StaticResource ScrollBar.Static.Background}"/>
        <Setter Property="BorderBrush" Value="{StaticResource ScrollBar.Static.Border}"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        <Setter Property="BorderThickness" Value="1,0"/>
        <Setter Property="Width" Value="12"/>
        <Setter Property="MinWidth" Value="12"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ScrollBar}">
                    <Grid x:Name="Bg" SnapsToDevicePixels="true">
                        <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0"
                                    Background="Transparent" Grid.Row="1"/>
                        <Track x:Name="PART_Track" IsDirectionReversed="true" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="1">
                            <Track.DecreaseRepeatButton>
                                <RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Style="{DynamicResource RepeatButtonTransparent}"/>
                            </Track.DecreaseRepeatButton>
                            <Track.IncreaseRepeatButton>
                                <RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Style="{DynamicResource RepeatButtonTransparent}"/>
                            </Track.IncreaseRepeatButton>
                            <Track.Thumb>
                                <Thumb Style="{DynamicResource ScrollBarThumbVertical}"/>
                            </Track.Thumb>
                        </Track>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="Orientation" Value="Horizontal">
                <Setter Property="Width" Value="Auto"/>
                <Setter Property="MinWidth" Value="0"/>
                <Setter Property="Height" Value="12"/>
                <Setter Property="MinHeight" Value="12"/>
                <Setter Property="BorderThickness" Value="0,1"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ScrollBar}">
                            <Grid x:Name="Bg" SnapsToDevicePixels="true">
                                <Border BorderBrush="{TemplateBinding BorderBrush}" 
                                            BorderThickness="0" 
                                            Background="Transparent" Grid.Column="1"/>
                                <Track x:Name="PART_Track" Grid.Column="1" IsEnabled="{TemplateBinding IsMouseOver}">
                                    <Track.DecreaseRepeatButton>
                                        <RepeatButton Command="{x:Static ScrollBar.PageLeftCommand}" Style="{DynamicResource RepeatButtonTransparent}"/>
                                    </Track.DecreaseRepeatButton>
                                    <Track.IncreaseRepeatButton>
                                        <RepeatButton Command="{x:Static ScrollBar.PageRightCommand}" Style="{DynamicResource RepeatButtonTransparent}"/>
                                    </Track.IncreaseRepeatButton>
                                    <Track.Thumb>
                                        <Thumb Style="{DynamicResource ScrollBarThumbHorizontal}"/>
                                    </Track.Thumb>
                                </Track>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}">
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Focusable" Value="false"/>
        <Setter Property="IsTabStop" Value="false"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type RepeatButton}">
                    <Rectangle Fill="#11FFFFFF" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="ScrollBarThumbVertical" TargetType="{x:Type Thumb}">
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="IsTabStop" Value="false"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Thumb}">
                    <Rectangle x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" Height="{TemplateBinding Height}" 
                                   SnapsToDevicePixels="True" Width="12" RadiusX="6" RadiusY="6"/>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.MouseOver.Thumb}"/>
                        </Trigger>
                        <Trigger Property="IsDragging" Value="true">
                            <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.Pressed.Thumb}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="ScrollBarThumbHorizontal" TargetType="{x:Type Thumb}">
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="IsTabStop" Value="false"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Thumb}">
                    <Rectangle x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" 
                                   Height="{TemplateBinding Height}" SnapsToDevicePixels="True" 
                                   Width="{TemplateBinding Width}"
                                   RadiusX="6" RadiusY="6"/>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.MouseOver.Thumb}"/>
                        </Trigger>
                        <Trigger Property="IsDragging" Value="true">
                            <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.Pressed.Thumb}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

显示效果:

1.5,TextBoxStyles
1.5.1,SearchTextBoxStyle
xaml 复制代码
  <SolidColorBrush x:Key="TextBox.Static.Border" Color="#FFABAdB3"/>
    <SolidColorBrush x:Key="TextBox.MouseOver.Border" Color="#FF7EB4EA"/>
    <SolidColorBrush x:Key="TextBox.Focus.Border" Color="#FF569DE5"/>


    <Style x:Key="SearchTextBoxStyle" TargetType="{x:Type TextBox}">
        <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
        <Setter Property="BorderBrush" Value="#DDD"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="AllowDrop" Value="true"/>
        <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
        <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <Grid>
                        <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Background="White" 
                                CornerRadius="5" Height="30">
                            <Border.Effect>
                                <DropShadowEffect BlurRadius="5" Color="Gray" ShadowDepth="0" Opacity="0.1"/>
                            </Border.Effect>
                        </Border>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="30"/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Text="&#xe601;" FontFamily="{DynamicResource Icons}"
                                           VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#999" FontSize="14"/>
                            <TextBlock Text="输入搜索关键词" VerticalAlignment="Center" Margin="3,0" FontSize="11"
                                           Foreground="#CCC" Grid.Column="1" Visibility="Collapsed" Name="mask"
                                       Effect="{x:Null}"/>
                            <ScrollViewer x:Name="PART_ContentHost"  Grid.Column="1" Focusable="false" 
                                              VerticalAlignment="Center"
                                              HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                        </Grid>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Opacity" TargetName="border" Value="0.56"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.MouseOver.Border}"/>
                        </Trigger>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.Focus.Border}"/>
                        </Trigger>
                        <DataTrigger Binding="{Binding Text,RelativeSource={RelativeSource Self}}" Value="">
                            <Setter Property="Visibility" Value="Visible" TargetName="mask"/>
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsInactiveSelectionHighlightEnabled" Value="true"/>
                    <Condition Property="IsSelectionActive" Value="false"/>
                </MultiTrigger.Conditions>
                <Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
            </MultiTrigger>
        </Style.Triggers>
    </Style>
xaml 复制代码
 <!--搜索输入框-->
            <TextBox VerticalAlignment="Center"  Width="300" HorizontalAlignment="Left" 
                     Text="{Binding SearchKey,UpdateSourceTrigger=PropertyChanged}"
                     Style="{StaticResource SearchTextBoxStyle}" Name="tb_search">
                <TextBox.InputBindings>
                    <KeyBinding Key="Return" Command="{Binding RefreshCommand}"/>
                    
                </TextBox.InputBindings>
                
            </TextBox>

显示效果:

1.5.2,PasswordBoxStyle
xaml 复制代码
<Style TargetType="PasswordBox">
            <Setter Property="FontSize" Value="14"/>
            <Setter Property="Height" Value="30"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="PasswordBox">
                        <Border BorderBrush="#DDD" BorderThickness="0,0,0,1"
                                Name="border" Background="White">
                            <Grid>
                                <TextBlock Text="请输入用户密码"
                                           VerticalAlignment="Center" 
                                           Foreground="#DDD"
                                           Name="markText" Visibility="Collapsed"
                                           FontSize="12" Margin="3,0"/>
                                <ScrollViewer x:Name="PART_ContentHost" Focusable="false"
                                              HorizontalScrollBarVisibility="Hidden" 
                                              VerticalScrollBarVisibility="Hidden"
                                              VerticalAlignment="Center" MinHeight="20"/>
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="BorderBrush" Value="#0b3d90" TargetName="border"/>
                            </Trigger>
                            <Trigger Property="IsKeyboardFocused" Value="True">
                                <Setter Property="BorderBrush" Value="#0b3d90" TargetName="border"/>
                            </Trigger>
                            <!--<DataTrigger Binding="{Binding Path=Password,RelativeSource={RelativeSource Self}}"-->
                            <DataTrigger Binding="{Binding Path=Password}" Value="">
                                <Setter TargetName="markText" Property="Visibility" Value="Visible"/>
                            </DataTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
xaml 复制代码
<PasswordBox Grid.Row="4" b:PasswordEx.Password="{Binding Password,Mode=TwoWay}"/>

PasswordBox.Password属性为普通属性非依赖属性,故不支持绑定。为了实现与后端绑定,这里扩展定义一个附加属性,需要注意的是依赖属性需要定义在依赖对象中,但附加属性不要求定义在依赖对象中。

c# 复制代码
 public class PasswordEx
    {
        public static readonly DependencyProperty PasswordProperty =
            DependencyProperty.RegisterAttached(
                "Password",
                typeof(string),
                typeof(PasswordEx),
                new PropertyMetadata(new PropertyChangedCallback(OnPropertyChanged))
                );

        public static string GetPassword(DependencyObject d)
        {
            return (string)d.GetValue(PasswordProperty);
        }
        public static void SetPassword(DependencyObject d, string value)
        {
            d.SetValue(PasswordProperty, value);
        }

        static bool _isUpdating = false;
        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            PasswordBox pb = d as PasswordBox;
            pb.PasswordChanged -= Pb_PasswordChanged;
            if (!_isUpdating)
                pb.Password = e.NewValue == null ? "" : e.NewValue.ToString();
            pb.PasswordChanged += Pb_PasswordChanged;
        }

        private static void Pb_PasswordChanged(object sender, RoutedEventArgs e)
        {
            PasswordBox pb = sender as PasswordBox;
            _isUpdating = true;
            SetPassword(pb, pb.Password);
            _isUpdating = false;
        }
    }

显示效果:

1.6,DatePickerStyle
xaml 复制代码
<SolidColorBrush x:Key="DisabledBrush" Color="#A5FFFFFF"/>

    <ControlTemplate x:Key="DropDownButtonTemplate" TargetType="{x:Type Button}">
        <Border Background="Transparent" Name="back">
            <TextBlock Text="&#xe635;" FontFamily="{DynamicResource Icons}" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="16"/>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsMouseOver" Value="true">
                <Setter Property="BorderBrush" TargetName="back" Value="{StaticResource TextBox.MouseOver.Border}"/>
            </Trigger>
            <Trigger Property="IsKeyboardFocused" Value="true">
                <Setter Property="BorderBrush" TargetName="back" Value="{StaticResource TextBox.Focus.Border}"/>
            </Trigger>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter TargetName="back" Property="Background" Value="#EEE"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    
    
    <Style TargetType="{x:Type DatePicker}">
        <Setter Property="Foreground" Value="#FF333333"/>
        <Setter Property="IsTodayHighlighted" Value="True"/>
        <Setter Property="SelectedDateFormat" Value="Short"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border}"/>
        <Setter Property="Padding" Value="2"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DatePicker}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" 
                            BorderThickness="{TemplateBinding BorderThickness}" 
                            Background="White" CornerRadius="5"
                            Padding="{TemplateBinding Padding}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PART_DisabledVisual"/>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid x:Name="PART_Root" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <Button x:Name="PART_Button" Grid.Column="1" Foreground="{TemplateBinding Foreground}" Focusable="False" 
                                    Template="{StaticResource DropDownButtonTemplate}" Width="20"
                                    />
                            <DatePickerTextBox x:Name="PART_TextBox" Grid.Column="0" Focusable="{TemplateBinding Focusable}" HorizontalContentAlignment="Stretch" Grid.Row="0" VerticalContentAlignment="Stretch"
                                             />
                    
                            <Grid x:Name="PART_DisabledVisual" Grid.ColumnSpan="2" Grid.Column="0" IsHitTestVisible="False" Opacity="0" Grid.Row="0">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Rectangle Grid.Column="0" Fill="#A5FFFFFF" RadiusY="1" Grid.Row="0" RadiusX="1"/>
                                <Rectangle Grid.Column="1" Fill="#A5FFFFFF" Height="18" Margin="3,0,3,0" RadiusY="1" Grid.Row="0" RadiusX="1" Width="19"/>
                                <Popup x:Name="PART_Popup" AllowsTransparency="True" Placement="Bottom" PlacementTarget="{Binding ElementName=PART_TextBox}" StaysOpen="False"/>
                            </Grid>
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding Source={x:Static SystemParameters.HighContrast}}" Value="false">
                            <Setter Property="Foreground" TargetName="PART_TextBox" Value="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

显示效果:

1.7,TabControlStyle

注意: 该示例需要安装Prism.Unity

xaml 复制代码
 <TabControl   Grid.Row="1" Background="Transparent"
                        BorderThickness="0" p:RegionManager.RegionName="MainRegion">
                    <TabControl.Resources>

                        <Style TargetType="TabControl">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="TabControl">
                                        <Grid>
                                            <Grid.RowDefinitions>
                                                <RowDefinition Height="auto"/>
                                                <RowDefinition/>
                                            </Grid.RowDefinitions>
                                            <Border Background="#FAFCFF" BorderBrush="#EEE" BorderThickness="0,1"/>
                                            <TabPanel x:Name="HeaderPanel"
                                                    Panel.ZIndex="1"
                                                    Margin="0,3"
                                                    IsItemsHost="True"
                                                    KeyboardNavigation.TabIndex="1"
                                                    Background="Transparent" />

                                            <ContentPresenter x:Name="PART_SelectedContentHost"
                                                          Grid.Row="1"
                                                          ContentSource="SelectedContent" />
                                        </Grid>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>


                        <Style TargetType="Button" x:Key="TabCloseButtonStyle">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="Button">
                                        <Border VerticalAlignment="Center" HorizontalAlignment="Center" 
                                            CornerRadius="3" Background="Transparent"
                                            Name="back">
                                            <Path Data="M0 0 8 8M0 8 8 0" Margin="5"
                                              Stroke="{TemplateBinding Foreground}" StrokeThickness="1"/>
                                        </Border>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="IsMouseOver" Value="True">
                                                <Setter Property="Background" Value="#19000000" TargetName="back"/>
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>


                        <Style TargetType="TabItem">
                            <Setter Property="Header" Value="{Binding DataContext.PageTitle}"/>
                            <Setter Property="Background" Value="Transparent"/>
                            <Setter Property="Foreground" Value="#444"/>
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="TabItem">
                                        <Border Background="{TemplateBinding Background}" Height="28"
                                            CornerRadius="5" Margin="2,0">
                                            <Grid Margin="5,0,3,0">
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition/>
                                                    <ColumnDefinition Width="auto" MaxWidth="30" MinWidth="10"/>
                                                </Grid.ColumnDefinitions>
                                                <TextBlock Text="{TemplateBinding Header}" VerticalAlignment="Center" 
                                                       Margin="10,5,0,5"
                                                        FontSize="12"/>
                                                <Grid Grid.Column="1" Name="close_grid" Visibility="Collapsed"
                                                  Width="30" Margin="0,0,-3,0">
                                                    <Button Grid.Column="1" Style="{StaticResource TabCloseButtonStyle}" 
                                                        Foreground="{TemplateBinding Foreground}"
                                                        Visibility="Collapsed" Name="close_btn"
                                                        Command="{Binding DataContext.CloseCommand}"/>
                                                </Grid>
                                            </Grid>
                                        </Border>

                                        <ControlTemplate.Triggers>
                                            <DataTrigger Binding="{Binding DataContext.IsCanClose}" Value="True">
                                                <Setter TargetName="close_grid" Property="Visibility" Value="Visible"/>
                                            </DataTrigger>
                                            <Trigger Property="IsMouseOver" Value="True">
                                                <Setter Property="Visibility" Value="Visible" TargetName="close_btn"/>
                                            </Trigger>
                                            <Trigger Property="IsSelected" Value="True">
                                                <Setter Property="Visibility" Value="Visible" TargetName="close_btn"/>
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                            <Style.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter Property="Background" Value="#EEE"/>
                                </Trigger>
                                <Trigger Property="IsSelected" Value="True">
                                    <Setter Property="Background">
                                        <Setter.Value>
                                            <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                                                <GradientStop Color="#FE582D" Offset="0"/>
                                                <GradientStop Color="#F1961A" Offset="1"/>
                                            </LinearGradientBrush>
                                        </Setter.Value>
                                    </Setter>
                                    <Setter Property="Foreground" Value="White"/>
                                </Trigger>
                            </Style.Triggers>
                        </Style>

                    </TabControl.Resources>
                </TabControl>

显示效果:

1.8,ViewTreeStyle
xaml 复制代码
 <TreeView Grid.Row="1" Background="Transparent" BorderThickness="0"
                      ItemsSource="{Binding Menus}">
                        <TreeView.Resources>
                            <ControlTemplate TargetType="ToggleButton" x:Key="ArrowButtonTemplate">
                                <Grid Background="Transparent" Name="back">
                                    <TextBlock Text="&#xe81d;" FontFamily="{StaticResource Icons}" 
                                           VerticalAlignment="Center" HorizontalAlignment="Center"
                                           RenderTransformOrigin="0.5,0.5" Name="arrow"
                                           Foreground="{TemplateBinding Foreground}"
                                           FontSize="9">
                                    </TextBlock>
                                </Grid>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsChecked" Value="True">
                                        <Setter TargetName="arrow" Property="RenderTransform">
                                            <Setter.Value>
                                                <RotateTransform Angle="90"/>
                                            </Setter.Value>
                                        </Setter>
                                    </Trigger>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter TargetName="back" Property="Background" Value="#11000000"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>

                            <Style TargetType="TreeViewItem">
                                <Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="TreeViewItem">
                                            <Grid Background="Transparent" Name="root">
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition/>
                                                    <ColumnDefinition MaxWidth="30"/>
                                                </Grid.ColumnDefinitions>
                                                <Grid.RowDefinitions>
                                                    <RowDefinition Height="Auto" MinHeight="33"/>
                                                    <RowDefinition  />
                                                </Grid.RowDefinitions>
                                                <Grid.InputBindings>
                                                    <MouseBinding MouseAction="LeftDoubleClick"
                                                              Command="{Binding DataContext.OpenViewCommand,RelativeSource={RelativeSource AncestorType=Window}}"
                                                              CommandParameter="{Binding}"/>
                                                </Grid.InputBindings>

                                                <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
                                                    BorderThickness="{TemplateBinding BorderThickness}"
                                                    Background="{TemplateBinding Background}"
                                                    CornerRadius="5" Grid.ColumnSpan="2" Padding="{TemplateBinding Padding}"
                                                    SnapsToDevicePixels="true"
                                                    Margin="2,1">
                                                    <ContentPresenter x:Name="PART_Header" ContentSource="Header"
                                                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                                                    </ContentPresenter>
                                                </Border>
                                                <ToggleButton x:Name="Expander" Grid.Column="1" ClickMode="Press"
                                                          IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                                                          Template="{StaticResource ArrowButtonTemplate}"
                                                          Foreground="#4000"/>
                                                <ItemsPresenter x:Name="ItemsHost" Margin="18,0,0,0" Grid.ColumnSpan="2" Grid.Row="1" />
                                            </Grid>



                                            <ControlTemplate.Triggers>
                                                <Trigger Property="IsExpanded" Value="false">
                                                    <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
                                                </Trigger>
                                                <Trigger Property="HasItems" Value="false">
                                                    <Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
                                                </Trigger>
                                                <Trigger Property="IsSelected" Value="true">
                                                    <Setter Property="Background" TargetName="Bd">
                                                        <Setter.Value>
                                                            <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                                                                <GradientStop Color="#FE582D" Offset="0"/>
                                                                <GradientStop Color="#F1961A" Offset="1"/>
                                                            </LinearGradientBrush>
                                                        </Setter.Value>
                                                    </Setter>
                                                    <Setter Property="Foreground" Value="White"/>
                                                    <Setter Property="Foreground" Value="white" TargetName="Expander"/>
                                                </Trigger>
                                                <!--被选中后失去焦点-->
                                                <DataTrigger Binding="{Binding MenuIcon}" Value="{x:Null}">
                                                    <Setter Property="Margin" Value="10,0,0,0" TargetName="root"/>
                                                </DataTrigger>
                                            </ControlTemplate.Triggers>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </TreeView.Resources>
                        <TreeView.ItemTemplate>
                            <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                                <Grid Background="Transparent">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="30" Name="c1"/>
                                        <ColumnDefinition/>
                                    </Grid.ColumnDefinitions>
                                    <TextBlock Text="{Binding MenuIcon}" 
                                           FontFamily="{StaticResource Icons}" 
                                           VerticalAlignment="Center" HorizontalAlignment="Center"
                                           FontSize="15"/>
                                    <TextBlock Text="{Binding MenuHeader}" Grid.Column="1" Margin="5,0,0,0" FontSize="12"
                                            VerticalAlignment="Center"/>
                                </Grid>
                                <HierarchicalDataTemplate.Triggers>
                                    <DataTrigger Binding="{Binding MenuIcon}" Value="{x:Null}">
                                        <Setter TargetName="c1" Property="Width" Value="0"/>
                                    </DataTrigger>
                                </HierarchicalDataTemplate.Triggers>
                            </HierarchicalDataTemplate>
                        </TreeView.ItemTemplate>
                    </TreeView>
1.9,边框定义阴影效果(DropShadowEffect)
xaml 复制代码
<Border CornerRadius="5" Background="White" Grid.Row="1" Margin="0,5,0,10">
                <Border.Effect>
                    <DropShadowEffect BlurRadius="5" Color="Gray" ShadowDepth="0" Opacity="0.3"/>
                </Border.Effect>
            </Border>

2,通过INotifyDataErrorInfo接口实现输入值的验证错误通知

派生自ValidationRule的验证规则类虽然也可以实现输入值验证,但是在多个值进行提交时需要逐一检测各个值是否已变更,是否满足要求,而实现INotifyDataErrorInfo接口的对象,在提交其需要验证的属性值否已变更,是否满足要求只需要判断HasErrors属性值即可。

C# 复制代码
 //
    // 摘要:
    //     定义数据实体类可实现以提供自定义同步和异步验证支持的成员。
    public interface INotifyDataErrorInfo
    {
        //
        // 摘要:
        //     获取一个值,该值指示实体是否包含验证错误。
        //
        // 返回结果:
        //     如果实体当前具有验证错误,则为 true;否则为 false。
        bool HasErrors { get; }

        //
        // 摘要:
        //     当验证错误针对属性或整个实体更改时发生。
        event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;

        //
        // 摘要:
        //     获取针对指定属性或整个实体的验证错误。
        //
        // 参数:
        //   propertyName:
        //     要检索验证错误的属性的名称;如果检索实体级别错误,则为 null 或 System.String.Empty。
        //
        // 返回结果:
        //     针对属性或实体的验证错误。
        IEnumerable GetErrors(string? propertyName);
    }

应用

C# 复制代码
 public class DialogViewModelBase : INotifyDataErrorInfo
    {

        #region INotifyDataErrorInfo接口实现
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        public bool HasErrors => ErrorList.Count > 0;
        public IEnumerable GetErrors(string propertyName)
        {
            if (ErrorList.ContainsKey(propertyName))
                return ErrorList[propertyName];
            return null;
        }
        public Dictionary<string, IList<string>> ErrorList = new Dictionary<string, IList<string>>();
        public void RaiseErrorsChanged([CallerMemberName] string propName = "")
        {
            ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propName));
        }
        #endregion
 }
C# 复制代码
public class ModifyAutoViewModel : DialogViewModelBase
{
     private string _autoLisence;

        // 必输项、不重复
        public string AutoLicense
        {
            get { return _autoLisence; }
            set
            {
                _autoLisence = value;

                ErrorList.Clear();
                if (string.IsNullOrEmpty(value))
                {
                    ErrorList.Add("AutoLicense", new List<string> { "车牌号不能为空" });
                }
                else if (_autoService.Query<AutoRegister>(a => a.AutoLicense == value &&
                                                               a.AutoId != Auto.AutoId).Count() > 0)
                {
                    ErrorList.Add("AutoLicense", new List<string> { "车牌号不能重复" });
                }
                RaiseErrorsChanged();
            }
        }

}
xaml 复制代码
<TextBox Grid.Column="1" Text="{Binding AutoLicense,ValidatesOnNotifyDataErrors=True}" 
                 Height="30"
                 Style="{StaticResource ValidationTextBoxStyle}" 
                 IsReadOnly="{Binding IsAutoLicenseReadOnly}" Grid.ColumnSpan="2"/>

3,事件绑定命令

3.1,默认的Mouse,Key事件绑定命令。
xaml 复制代码
 <Grid.InputBindings>
            <MouseBinding MouseAction="LeftClick" Command=""></MouseBinding>
            <KeyBinding Key="A" Modifiers="Shift" Command=""></KeyBinding>
        </Grid.InputBindings>
3.2,自定义事件绑定命令。
3.2.1,安装Nuget包:Microsoft.Xaml.Behaviors.Wpf
3.2.2,引入命名空间。
xaml 复制代码
 xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
3.2.3,应用示例
  • 定义自定义路由事件。

    C# 复制代码
    public partial class Pagination : UserControl, System.ComponentModel.INotifyPropertyChanged
        {
         ....
            public static readonly RoutedEvent PageIndexChangedEvent;
            public Pagination()
            {
                InitializeComponent();
                // this.DataContext = this;
               
            }
            static Pagination()
            {
    ...
                PageIndexChangedEvent = EventManager.RegisterRoutedEvent("PageIndexChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<int>), typeof(Pagination));
            }
    
    ...
            /// <summary>
            /// 页面索引值变化时触发该事件
            /// </summary>
            public event RoutedPropertyChangedEventHandler<int> PageIndexChanged
            {
                add
                {
                    this.AddHandler(PageIndexChangedEvent, value);
                }
                remove
                {
                    this.RemoveHandler(PageIndexChangedEvent, value);
                }
            }
    ...
        //发布事件
            private void Grid_Click(object sender, RoutedEventArgs e)
            {
                if(e.OriginalSource is System.Windows.Controls.Primitives.ButtonBase btn)
                {
                    string tag = btn.Tag?.ToString();
                    if (tag != null)
                    {
                        if(int.TryParse(tag,out int index))
                        {
                            int oldIndex = PageIndex;
                            PageIndex = index;
                            int newIndex = PageIndex;
                            RoutedPropertyChangedEventArgs<int> args = new RoutedPropertyChangedEventArgs<int>(oldIndex, newIndex);
                            args.RoutedEvent = PageIndexChangedEvent;
                            args.Source = this;
                            
                            this.RaiseEvent(args);
                           
                            
                        }
                    }
                }
            }
  • 定义命令

    C# 复制代码
      public ICommand SelectedPageCommand { get; set; }
            public MainWindowViewModel()
            {
                for (int i = 0; i < 100; i++)
                {
                    testData.Add(i);
                }
                TotalCount = testData.Count;
                SelectedPageCommand = new DelegateCommand<int>(index =>
                  {
                      Data = testData.Skip((index - 1) * PageSize).Take(PageSize).ToList();
                  });
                SelectedPageCommand?.Execute(1);
            }
  • 事件绑定命令

xaml 复制代码
                    <controls:Pagination  Grid.Row="1" PageSize="{Binding PageSize}"   HorizontalAlignment="Center"  TotalCount="{Binding TotalCount}" >
                        <behaviors:Interaction.Triggers>
                            <behaviors:EventTrigger EventName="PageIndexChanged" >
                                <behaviors:InvokeCommandAction Command="{Binding SelectedPageCommand}" CommandParameter="{Binding RelativeSource={RelativeSource  AncestorType=controls:Pagination,Mode=FindAncestor}, Path=PageIndex}"></behaviors:InvokeCommandAction>
                            </behaviors:EventTrigger>
                        </behaviors:Interaction.Triggers>                                          
                    </controls:Pagination>

注意:该方法可以绑定任何事件(路由事件+普通事件,不限制事件类型),不仅仅是自定义的路由事件

4,自定义控件。

4.1,加载动画控件(Loading)
4.1.1 效果
xaml 复制代码
 <controls:Loading></controls:Loading>
4.1.2 Xaml代码
xaml 复制代码
<UserControl x:Class="WpfAssets.Assets.Controls.Loading"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="100" d:DesignWidth="100">
    <UserControl.Resources>
        <KeySpline x:Key="keySpline">0.1,0.6,0.9,0.4</KeySpline>

        <Storyboard x:Key="keyFrameStoryboard1">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="e1" Storyboard.TargetProperty="Opacity" RepeatBehavior="Forever">
                <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt1" Storyboard.TargetProperty="Angle" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0.3" Value="0" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:2.5" Value="360" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:5" Value="720" KeySpline="{StaticResource keySpline}"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="720"/>
            </DoubleAnimationUsingKeyFrames>

            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="e2" Storyboard.TargetProperty="Opacity" BeginTime="0:0:0.2" RepeatBehavior="Forever">
                <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt2" Storyboard.TargetProperty="Angle" BeginTime="0:0:0.2" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0.3" Value="0" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:2.5" Value="360" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:5" Value="720" KeySpline="{StaticResource keySpline}"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="720"/>
            </DoubleAnimationUsingKeyFrames>

            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="e3" Storyboard.TargetProperty="Opacity" BeginTime="0:0:0.4" RepeatBehavior="Forever">
                <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt3" Storyboard.TargetProperty="Angle" BeginTime="0:0:0.4" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0.3" Value="0" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:2.5" Value="360" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:5" Value="720" KeySpline="{StaticResource keySpline}"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="720"/>
            </DoubleAnimationUsingKeyFrames>

            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="e4" Storyboard.TargetProperty="Opacity" BeginTime="0:0:0.6" RepeatBehavior="Forever">
                <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt4" Storyboard.TargetProperty="Angle" BeginTime="0:0:0.6" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0.3" Value="0" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:2.5" Value="360" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:5" Value="720" KeySpline="{StaticResource keySpline}"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="720"/>
            </DoubleAnimationUsingKeyFrames>

            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="e5" Storyboard.TargetProperty="Opacity" BeginTime="0:0:0.8" RepeatBehavior="Forever">
                <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt5" Storyboard.TargetProperty="Angle" BeginTime="0:0:0.8" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0.3" Value="0" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:2.5" Value="360" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:5" Value="720" KeySpline="{StaticResource keySpline}"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="720"/>
            </DoubleAnimationUsingKeyFrames>

            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="e6" Storyboard.TargetProperty="Opacity" BeginTime="0:0:1" RepeatBehavior="Forever">
                <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt6" Storyboard.TargetProperty="Angle" BeginTime="0:0:1" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0.3" Value="0" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:2.5" Value="360" KeySpline="{StaticResource keySpline}"/>
                <SplineDoubleKeyFrame KeyTime="0:0:5" Value="720" KeySpline="{StaticResource keySpline}"/>
                <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="720"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>

    <UserControl.Triggers>
        <EventTrigger RoutedEvent="UserControl.Loaded">
            <BeginStoryboard Storyboard="{StaticResource keyFrameStoryboard1}"/>
        </EventTrigger>
    </UserControl.Triggers>


    <Viewbox>
        <Grid Height="140">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>
            <Grid Height="70" Width="70">
                <Border RenderTransformOrigin="0.5,0.5">
                    <Border.RenderTransform>
                        <RotateTransform Angle="0" x:Name="rt1"/>
                    </Border.RenderTransform>
                    <Ellipse Fill="Orange" Width="8" Height="8" VerticalAlignment="Bottom" Name="e1"/>
                </Border>
                <Border RenderTransformOrigin="0.5,0.5">
                    <Border.RenderTransform>
                        <RotateTransform Angle="0" x:Name="rt2"/>
                    </Border.RenderTransform>
                    <Ellipse Fill="Orange" Width="8" Height="8" VerticalAlignment="Bottom" Name="e2"/>
                </Border>
                <Border RenderTransformOrigin="0.5,0.5">
                    <Border.RenderTransform>
                        <RotateTransform Angle="0" x:Name="rt3"/>
                    </Border.RenderTransform>
                    <Ellipse Fill="Orange" Width="8" Height="8" VerticalAlignment="Bottom" Name="e3"/>
                </Border>
                <Border RenderTransformOrigin="0.5,0.5">
                    <Border.RenderTransform>
                        <RotateTransform Angle="0" x:Name="rt4"/>
                    </Border.RenderTransform>
                    <Ellipse Fill="Orange" Width="8" Height="8" VerticalAlignment="Bottom" Name="e4"/>
                </Border>
                <Border RenderTransformOrigin="0.5,0.5">
                    <Border.RenderTransform>
                        <RotateTransform Angle="0" x:Name="rt5"/>
                    </Border.RenderTransform>
                    <Ellipse Fill="Orange" Width="8" Height="8" VerticalAlignment="Bottom" Name="e5"/>
                </Border>
                <Border RenderTransformOrigin="0.5,0.5">
                    <Border.RenderTransform>
                        <RotateTransform Angle="0" x:Name="rt6"/>
                    </Border.RenderTransform>
                    <Ellipse Fill="Orange" Width="8" Height="8" VerticalAlignment="Bottom" Name="e6"/>
                </Border>
            </Grid>

            <TextBlock Text="{Binding Message,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor}}"
                   Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFF"
                   Margin="0,10" FontSize="13">
                <TextBlock.Effect>
                    <DropShadowEffect BlurRadius="10" ShadowDepth="0"
                                      Opacity="0.4"/>
                </TextBlock.Effect>
            </TextBlock>
        </Grid>
    </Viewbox>
</UserControl>
4.1.3 C#代码
C# 复制代码
public partial class Loading : UserControl
    {
        [Browsable(true),Category("自定义属性"),Description("提示文字")]
        public string Message
        {
            get { return (string)GetValue(MessageProperty); }
            set { SetValue(MessageProperty, value); }
        }
        public static readonly DependencyProperty MessageProperty =
            DependencyProperty.Register("Message", typeof(string), typeof(Loading), new PropertyMetadata("正在处理"));

        public Loading()
        {
            InitializeComponent();
        }
    }
4.2,进度环控件(CircularProgressBar
4.2.1,效果
xaml 复制代码
<controls:CircularProgressBar Grid.Column="1" Value="{Binding ElementName=slider01, Path=Value}" StrokeThickness="15"></controls:CircularProgressBar>
4.2.2 ,Xaml代码
xaml 复制代码
<Style TargetType="local:CircularProgressBar">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:CircularProgressBar">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"></RowDefinition>
                            <RowDefinition></RowDefinition>
                        </Grid.RowDefinitions>
                        <TextBlock  x:Name="txt_Title" Margin="1"  FontSize="{TemplateBinding FontSize}" FontWeight="{TemplateBinding FontWeight}" FontFamily="{TemplateBinding FontFamily}" Text="{TemplateBinding Title}" HorizontalAlignment="Center" VerticalAlignment="Center" ></TextBlock>
                        <extend:Arc Grid.Row="1" Opacity="0.7" StartAngle="0" EndAngle="360" OffsetAngle="0" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="{TemplateBinding  StrokeThickness}"></extend:Arc>
                        <extend:Arc Grid.Row="1" x:Name="Part_arcProgress" StrokeStartLineCap="{TemplateBinding StrokeStartLineCap}" StrokeEndLineCap="{TemplateBinding StrokeEndLineCap}" StartAngle="{TemplateBinding StartAngle}" OffsetAngle="{TemplateBinding OffsetAngle}" Stroke="{TemplateBinding CircularProgressBrush}" StrokeThickness="{TemplateBinding StrokeThickness}"></extend:Arc>
                        <TextBlock Grid.Row="1" x:Name="Part_txtDisplay" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="{TemplateBinding FontWeight}" FontSize="{TemplateBinding FontSize}" Foreground="{TemplateBinding Foreground}" ></TextBlock>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="Title" Value="" >
                            <Setter TargetName="txt_Title" Property="Visibility" Value="Collapsed"></Setter>
                        </Trigger>
                        <Trigger Property="Title" Value="{x:Null}" >
                            <Setter TargetName="txt_Title" Property="Visibility" Value="Collapsed"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
4.2.3,C#代码
C# 复制代码
/// <summary>
    ///圆弧
    /// </summary>
    class Arc : Shape
    {
        Rect _rec = Rect.Empty;
        public static readonly DependencyProperty StartAngleProperty;
        public static readonly DependencyProperty EndAngleProperty;
        public static readonly DependencyProperty OffsetAngleProperty;
        static Arc()
        {
            StartAngleProperty = DependencyProperty.Register("StartAngle", typeof(float), typeof(Arc), new FrameworkPropertyMetadata(-30f, FrameworkPropertyMetadataOptions.AffectsRender));
            EndAngleProperty = DependencyProperty.Register("EndAngle", typeof(float), typeof(Arc), new FrameworkPropertyMetadata(210f, FrameworkPropertyMetadataOptions.AffectsRender));
            OffsetAngleProperty = DependencyProperty.Register("OffsetAngle", typeof(float), typeof(Arc), new FrameworkPropertyMetadata(180f, FrameworkPropertyMetadataOptions.AffectsRender));
            Arc.StretchProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(Stretch.Uniform));
            Arc.StrokeStartLineCapProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(PenLineCap.Round));
            Arc.StrokeEndLineCapProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(PenLineCap.Round));
        }
        [Browsable(true), Category("自定义属性"), Description("圆弧开始角度")]
        public float StartAngle
        {
            get => (float)GetValue(StartAngleProperty);
            set => SetValue(StartAngleProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("圆弧停止角度")]
        public float EndAngle
        {
            get => (float)GetValue(EndAngleProperty);
            set => SetValue(EndAngleProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("圆弧偏移角度")]
        public float OffsetAngle
        {
            get => (float)GetValue(OffsetAngleProperty);
            set => SetValue(OffsetAngleProperty, value);
        }
        public override Geometry RenderedGeometry => DefiningGeometry;
        public override Transform GeometryTransform => Transform.Identity;
        protected override Geometry DefiningGeometry
        {
            get
            {
                // _rec = new Rect(StrokeThickness / 2, StrokeThickness / 2, this.ActualWidth-StrokeThickness, this.ActualWidth-StrokeThickness);
                if (_rec == Rect.Empty)
                {
                    return Geometry.Empty;
                }
				if (EndAngle - StartAngle == 360)
                {
                    EllipseGeometry ellipseGeometry = new EllipseGeometry(_rec);
                    return ellipseGeometry;
                }
                PathGeometry pathGeometry = new PathGeometry();
                PathFigure figure = new PathFigure();
                Point center = new Point(_rec.X + _rec.Width / 2, _rec.Y + _rec.Height / 2);
                Point startPoint = new Point();
                startPoint.X = center.X + _rec.Width * Math.Cos((StartAngle + OffsetAngle) / 180 * Math.PI) / 2;
                startPoint.Y = center.Y + _rec.Height * Math.Sin((StartAngle + OffsetAngle) / 180 * Math.PI) / 2;
                figure.IsClosed = false;
                figure.StartPoint = startPoint;
                ArcSegment segment = new ArcSegment();
                Point endPoint = new Point();
                endPoint.X = center.X + _rec.Width * Math.Cos((EndAngle + OffsetAngle) / 180 * Math.PI) / 2;
                endPoint.Y = center.Y + _rec.Height * Math.Sin((EndAngle + OffsetAngle) / 180 * Math.PI) / 2;
                segment.Point = endPoint;
                if (EndAngle - StartAngle > 180)
                {
                    segment.IsLargeArc = true;
                }
                else
                {
                    segment.IsLargeArc = false;
                }
                segment.SweepDirection = SweepDirection.Clockwise;
                segment.RotationAngle = EndAngle - StartAngle;
                segment.Size = new Size(_rec.Width / 2, _rec.Height / 2);
                figure.Segments.Add(segment);
                pathGeometry.Figures.Add(figure);
                return pathGeometry;
            }
        }
        protected override Size MeasureOverride(Size constraint)
        {
            if (double.IsPositiveInfinity(constraint.Width) || double.IsNaN(constraint.Width))
            {
                _rec = Rect.Empty;
                return new Size(0,0);
            }
            double len = Math.Min(constraint.Width, constraint.Height);
            return new Size(len, len);
        }
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (double.IsPositiveInfinity(finalSize.Width) || double.IsNaN(finalSize.Width))
            {
                _rec = Rect.Empty;
                return new Size(0,0);
            }
            double len = Math.Min(finalSize.Width, finalSize.Height);
            if (len - StrokeThickness < 0)
            {
                _rec = Rect.Empty;
                return finalSize;
            }
            _rec = new Rect(StrokeThickness / 2, StrokeThickness / 2, len - StrokeThickness, len - StrokeThickness);
            return new Size(len, len);
        }
        protected override void OnRender(DrawingContext drawingContext)
        {
            Pen p = new Pen(Stroke, StrokeThickness) { StartLineCap = this.StrokeStartLineCap, EndLineCap = this.StrokeEndLineCap };
            drawingContext.DrawGeometry(Fill, p, RenderedGeometry);
        }
    }
/// <summary>
    /// 环形进度块
    /// </summary>
    public class CircularProgressBar : Control
    {
        public static readonly DependencyProperty StartAngleProperty;
        public static readonly DependencyProperty EndAngleProperty;
        public static readonly DependencyProperty OffsetAngleProperty;
        public static readonly DependencyProperty StrokeStartLineCapProperty;
        public static readonly DependencyProperty StrokeEndLineCapProperty;
        public static readonly DependencyProperty RangeProperty;
        public static readonly DependencyProperty ValueProperty;
        public static readonly DependencyProperty CircularProgressBrushProperty;
        public static readonly DependencyProperty StrokeThicknessProperty;
        public static readonly DependencyProperty TitleProperty;
        Arc arc;
        TextBlock txtDisplay;
       
        static CircularProgressBar()
        {
            StartAngleProperty = DependencyProperty.Register("StartAngle", typeof(float), typeof(CircularProgressBar), new FrameworkPropertyMetadata(0f, FrameworkPropertyMetadataOptions.AffectsRender));
            EndAngleProperty = DependencyProperty.Register("EndAngle", typeof(float), typeof(CircularProgressBar), new FrameworkPropertyMetadata(60f, FrameworkPropertyMetadataOptions.AffectsRender));
            OffsetAngleProperty = DependencyProperty.Register("OffsetAngle", typeof(float), typeof(CircularProgressBar), new FrameworkPropertyMetadata(270f, FrameworkPropertyMetadataOptions.AffectsRender));
            StrokeStartLineCapProperty = DependencyProperty.Register("StrokeStartLineCap", typeof(PenLineCap), typeof(CircularProgressBar), new FrameworkPropertyMetadata(PenLineCap.Round, FrameworkPropertyMetadataOptions.AffectsRender));
            StrokeEndLineCapProperty = DependencyProperty.Register("StrokeEndLineCap", typeof(PenLineCap), typeof(CircularProgressBar), new FrameworkPropertyMetadata(PenLineCap.Round, FrameworkPropertyMetadataOptions.AffectsRender));
            RangeProperty = DependencyProperty.Register("Range", typeof(float), typeof(CircularProgressBar), new FrameworkPropertyMetadata(100f, FrameworkPropertyMetadataOptions.AffectsRender));
            ValueProperty = DependencyProperty.Register("Value", typeof(float), typeof(CircularProgressBar), new FrameworkPropertyMetadata(30f, FrameworkPropertyMetadataOptions.AffectsRender));
            CircularProgressBrushProperty = DependencyProperty.Register("CircularProgressBrush", typeof(Brush), typeof(CircularProgressBar), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromRgb(0x2b, 0xb6, 0xfe)), FrameworkPropertyMetadataOptions.AffectsRender));
            StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(double), typeof(CircularProgressBar), new FrameworkPropertyMetadata(8d, FrameworkPropertyMetadataOptions.AffectsRender));
            TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(CircularProgressBar), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender));
            BorderBrushProperty.OverrideMetadata(typeof(CircularProgressBar), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromArgb(0xaa, 0x99, 0x99, 0x99))));
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CircularProgressBar), new FrameworkPropertyMetadata(typeof(CircularProgressBar)));
        }
        [Browsable(true), Category("自定义属性"), Description("圆弧开始角度")]
        public float StartAngle
        {
            get => (float)GetValue(StartAngleProperty);
            set => SetValue(StartAngleProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("圆弧偏移角度(默认值为:270°)")]
        public float OffsetAngle
        {
            get => (float)GetValue(OffsetAngleProperty);
            set => SetValue(OffsetAngleProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("线开始端形状")]
        public PenLineCap StrokeStartLineCap
        {
            get => (PenLineCap)GetValue(StrokeStartLineCapProperty);
            set => SetValue(StrokeStartLineCapProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("线结束端形状")]
        public PenLineCap StrokeEndLineCap
        {
            get => (PenLineCap)GetValue(StrokeEndLineCapProperty);
            set => SetValue(StrokeEndLineCapProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("量程")]
        public float Range
        {
            get => (float)GetValue(RangeProperty);
            set => SetValue(RangeProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("当前值")]
        public float Value
        {
            get => (float)GetValue(ValueProperty);
            set => SetValue(ValueProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("圆环进度条画刷")]
        public Brush CircularProgressBrush
        {
            get => (Brush)GetValue(CircularProgressBrushProperty);
            set => SetValue(CircularProgressBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("触笔宽度")]
        public double StrokeThickness
        {
            get => (double)GetValue(StrokeThicknessProperty);
            set => SetValue(StrokeThicknessProperty, value);
        }
        private Func<CircularProgressBar, string> _displayText = e => (e.Value / e.Range).ToString("p");
        /// <summary>
        /// 设置中间文本的显示表达式
        /// </summary>
        [Browsable(true), Category("自定义属性"), Description("文本显示表达式")]
        [TypeConverter(typeof(Base.NullTypeConverter))]
        public Func<CircularProgressBar, string> DisplayText
        {
            get
            {
                return _displayText;
            }
            set
            {
                _displayText = value;
                if (_displayText?.GetInvocationList().Length > 1)
                {
                    Delegate.Remove(_displayText, value);
                }
            }
        }
        /// <summary>
        /// 设置中间文本的显示表达式
        /// </summary>
        [Browsable(true), Category("自定义属性"), Description("Title")]
        public string Title
        {
            get => (string)GetValue(TitleProperty);
            set => SetValue(TitleProperty, value);
        }
        protected override Size MeasureOverride(Size constraint)
        {
            return base.MeasureOverride(constraint);
        }
        protected override Size ArrangeOverride(Size arrangeBounds)
        {
            return base.ArrangeOverride(arrangeBounds);
        }
        protected override void OnRender(DrawingContext drawingContext)
        {
            if (arc != null)
            {
                float desDeg;
                if (this.Value < 0)
                {
                    desDeg = 0;
                }
                else if (this.Value > Range)
                {
                    desDeg = 360;
                }
                else
                {
                    desDeg = Value / Range * 360;
                }
                System.Windows.Media.Animation.SingleAnimation doubleAnimation = new System.Windows.Media.Animation.SingleAnimation(desDeg, TimeSpan.FromMilliseconds(200));
                arc.BeginAnimation(Arc.EndAngleProperty, doubleAnimation);
            }
            if (txtDisplay != null && DisplayText != null)
            {
                txtDisplay.Text = DisplayText(this);
            }
            base.OnRender(drawingContext);
        }
        public override void OnApplyTemplate()
        {
            arc = GetTemplateChild("Part_arcProgress") as Arc;
            txtDisplay = GetTemplateChild("Part_txtDisplay") as TextBlock;
            base.OnApplyTemplate();
        }
    }
4.3,仪表盘控件(MeterPlate
4.3.1,效果
xaml 复制代码
 <controls:MeterPlate Grid.Column="2" MeterValue="{Binding ElementName=slider01, Path=Value}"></controls:MeterPlate>
4.3.2,Xaml代码
xaml 复制代码
<UserControl x:Class="WpfAssets.Assets.Controls.MeterPlate"
             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:WpfAssets.Assets.Controls"
             xmlns:extend="clr-namespace:WpfAssets.Assets.Controls.Extend"
             mc:Ignorable="d" d:DesignHeight="400" d:DesignWidth="400"  >
    <Viewbox HorizontalAlignment="{Binding HorizontalContentAlignment}" VerticalAlignment="{Binding VerticalContentAlignment}">
        <Grid  >
            <Border Width="400" Height="400" CornerRadius="200" Background="{Binding MeterBackgroundBrush}" ClipToBounds="True" ></Border>
            <extend:Arc x:Name="arcRange" Opacity="0.3" StrokeThickness="{Binding MeterArcStrokeThickness}"  Stroke="{Binding MeterRangeBrush}"   ></extend:Arc>
            <extend:Arc x:Name="arcProgress" Opacity="0.8" StrokeThickness="{Binding MeterArcStrokeThickness}"  Stroke="{Binding MeterProgressBrush}" ></extend:Arc>
            <Canvas  x:Name="cavs" ></Canvas>
            <Border Width="200" Height="200" CornerRadius="100" Opacity="0.5" Background="{Binding MeterBackgrondCenterBrush}" ></Border>
            <Border Panel.ZIndex="9" Width="100" Height="100" CornerRadius="50" Background="{Binding MeterCenterBrush}">
            </Border>
            <Path x:Name="pointer" RenderTransformOrigin="0.5,0.5" Data="M200,200 L200,205 L350,200 L200,195 Z" Fill="{Binding MeterPointerBrush}"></Path>
            <StackPanel Panel.ZIndex="999" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
                <TextBlock Text="{Binding MeterValue}" HorizontalAlignment="Center" MinHeight="30" FontWeight="Bold" FontSize="25" Foreground="{Binding  MeterValueForeground}"></TextBlock>
                <TextBlock HorizontalAlignment="Center" Text="{Binding MeterUnit}" Foreground="White"></TextBlock>
            </StackPanel>
        </Grid>
    </Viewbox>
</UserControl>
4.3.3,C#代码
C# 复制代码
/// <summary>
    /// MeterPlate.xaml 的交互逻辑
    /// </summary>
    public partial class MeterPlate : UserControl
    {
        public static readonly DependencyProperty MeterRangeBrushProperty;
        public static readonly DependencyProperty MeterProgressBrushProperty;
        public static readonly DependencyProperty MeterBackgroundBrushProperty;
        public static readonly DependencyProperty MeterBackgrondCenterBrushProperty;
        public static readonly DependencyProperty MeterCenterBrushProperty;
        public static readonly DependencyProperty MeterPointerBrushProperty;
        public static readonly DependencyProperty MeterLowAlarmBrushProperty;
        public static readonly DependencyProperty MeterHightAlarmBrushProperty;
        public static readonly DependencyProperty MeterNormalBrushProperty;
        public static readonly DependencyProperty MeterValueProperty;
        public static readonly DependencyProperty MeterMinValueProperty;
        public static readonly DependencyProperty MeterMaxValueProperty;
        public static readonly DependencyProperty MeterAngleRangeProperty;
        public static readonly DependencyProperty MeterValueForegroundProperty;
        public static readonly DependencyProperty MeterUnitProperty;
        public static readonly DependencyProperty MeterLargeMarkIntervalProperty;
        public static readonly DependencyProperty MeterSmallMarkIntervalProperty;
        public static readonly DependencyProperty MeterArcStrokeThicknessProperty;
        public static readonly DependencyProperty MeterLowAlarmValueProperty;
        public static readonly DependencyProperty MeterHightAlarmValueProperty;
        RotateTransform rotateTransform = new RotateTransform();
        double preDeg = 0;
        public MeterPlate()
        {
            InitializeComponent();
             this.DataContext = this;
        }
        static MeterPlate()
        {
            MeterRangeBrushProperty = DependencyProperty.Register("MeterRangeBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromRgb(0x23, 0x0e, 0x1c)), FrameworkPropertyMetadataOptions.AffectsRender));
            MeterProgressBrushProperty = DependencyProperty.Register("MeterProgressBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromRgb(0x2b, 0x1a, 0x51)), FrameworkPropertyMetadataOptions.AffectsRender));
            MeterBackgroundBrushProperty = DependencyProperty.Register("MeterBackgroundBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.SlateBlue), FrameworkPropertyMetadataOptions.AffectsRender));
            MeterBackgrondCenterBrushProperty = DependencyProperty.Register("MeterBackgrondCenterBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromRgb(0x23, 0x0e, 0x1c)), FrameworkPropertyMetadataOptions.AffectsRender));
            RadialGradientBrush meterCenterBrush = new RadialGradientBrush();
            meterCenterBrush.RadiusX =1;
            meterCenterBrush.RadiusY = 1;
            meterCenterBrush.GradientStops.Add(new GradientStop(Color.FromRgb(0x23, 0x15, 0x3b), 1));
            meterCenterBrush.GradientStops.Add(new GradientStop(Color.FromRgb(0x23, 0x15, 0x3b), 0.4));
            meterCenterBrush.GradientStops.Add(new GradientStop(Color.FromRgb(0x45, 0x24, 0x6b), 0.3));
            MeterCenterBrushProperty = DependencyProperty.Register("MeterCenterBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(meterCenterBrush, FrameworkPropertyMetadataOptions.AffectsRender));
            RadialGradientBrush meterPinterBrush = new RadialGradientBrush();
            meterPinterBrush.RadiusX = 0.5;
            meterPinterBrush.RadiusY = 0.5;
            meterPinterBrush.GradientStops.Add(new GradientStop(Color.FromRgb(0x00, 0xc9, 0xff), 0.2));
            meterPinterBrush.GradientStops.Add(new GradientStop(Color.FromRgb(0x00, 0xc9, 0xff), 0.6));
            meterPinterBrush.GradientStops.Add(new GradientStop(Color.FromRgb(0x00, 0xc9, 0x6b), 0.8));
            MeterPointerBrushProperty = DependencyProperty.Register("MeterPointerBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(meterPinterBrush, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterLowAlarmBrushProperty = DependencyProperty.Register("MeterLowAlarmBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Orange), FrameworkPropertyMetadataOptions.AffectsRender));
            MeterHightAlarmBrushProperty = DependencyProperty.Register("MeterHightAlarmBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Red), FrameworkPropertyMetadataOptions.AffectsRender));
            MeterNormalBrushProperty = DependencyProperty.Register("MeterNormalBrush", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), FrameworkPropertyMetadataOptions.AffectsRender));
            MeterValueForegroundProperty = DependencyProperty.Register("MeterValueForeground", typeof(Brush), typeof(MeterPlate), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), FrameworkPropertyMetadataOptions.AffectsRender));
            MeterMinValueProperty = DependencyProperty.Register("MeterMinValue", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(0f, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterMaxValueProperty = DependencyProperty.Register("MeterMaxValue", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(120f, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterAngleRangeProperty = DependencyProperty.Register("MeterAngleRange", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(240f, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterValueProperty = DependencyProperty.Register("MeterValue", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(0f, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterUnitProperty = DependencyProperty.Register("MeterUnit", typeof(string), typeof(MeterPlate), new FrameworkPropertyMetadata("km/h", FrameworkPropertyMetadataOptions.AffectsRender));
            MeterLargeMarkIntervalProperty = DependencyProperty.Register("MeterLargeMarkInterval", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(10f, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterSmallMarkIntervalProperty = DependencyProperty.Register("MeterSmallMarkInterval", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(1f, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterArcStrokeThicknessProperty = DependencyProperty.Register("MeterArcStrokeThickness", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(30f, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterLowAlarmValueProperty = DependencyProperty.Register("MeterLowAlarmValue", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(20f, FrameworkPropertyMetadataOptions.AffectsRender));
            MeterHightAlarmValueProperty = DependencyProperty.Register(" MeterHightAlarmValue", typeof(float), typeof(MeterPlate), new FrameworkPropertyMetadata(100f, FrameworkPropertyMetadataOptions.AffectsRender));
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘量程画刷")]
        public Brush MeterRangeBrush
        {
            get => (Brush)GetValue(MeterRangeBrushProperty);
            set => SetValue(MeterRangeBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘当前进程画刷")]
        public Brush MeterProgressBrush
        {
            get => (Brush)GetValue(MeterProgressBrushProperty);
            set => SetValue(MeterProgressBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘背景画刷")]
        public Brush MeterBackgroundBrush
        {
            get => (Brush)GetValue(MeterBackgroundBrushProperty);
            set => SetValue(MeterBackgroundBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘中心背景画刷")]
        public Brush MeterBackgrondCenterBrush
        {
            get => (Brush)GetValue(MeterBackgrondCenterBrushProperty);
            set => SetValue(MeterBackgrondCenterBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘中心画刷")]
        public Brush MeterCenterBrush
        {
            get => (Brush)GetValue(MeterCenterBrushProperty);
            set => SetValue(MeterCenterBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘指针画刷")]
        public Brush MeterPointerBrush
        {
            get => (Brush)GetValue(MeterPointerBrushProperty);
            set => SetValue(MeterPointerBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘低值警戒画刷")]
        public Brush MeterLowAlarmBrush
        {
            get => (Brush)GetValue(MeterLowAlarmBrushProperty);
            set => SetValue(MeterLowAlarmBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘高值警戒画刷")]
        public Brush MeterHightAlarmBrush
        {
            get => (Brush)GetValue(MeterHightAlarmBrushProperty);
            set => SetValue(MeterHightAlarmBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘正常值画刷")]
        public Brush MeterNormalBrush
        {
            get => (Brush)GetValue(MeterNormalBrushProperty);
            set => SetValue(MeterNormalBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘当前值画刷")]
        public Brush MeterValueForeground
        {
            get => (Brush)GetValue(MeterValueForegroundProperty);
            set => SetValue(MeterValueForegroundProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘最小值")]
        public float MeterMinValue
        {
            get => (float)GetValue(MeterMinValueProperty);
            set => SetValue(MeterMinValueProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘最大值")]
        public float MeterMaxValue
        {
            get => (float)GetValue(MeterMaxValueProperty);
            set => SetValue(MeterMaxValueProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘量程角度")]
        public float MeterAngleRange
        {
            get => (float)GetValue(MeterAngleRangeProperty);
            set => SetValue(MeterAngleRangeProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘当前值")]
        public float MeterValue
        {
            get => (float)GetValue(MeterValueProperty);
            set => SetValue(MeterValueProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘大刻度间隔值")]
        public float MeterLargeMarkInterval
        {
            get => (float)GetValue(MeterLargeMarkIntervalProperty);
            set => SetValue(MeterLargeMarkIntervalProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘小刻度间隔值")]
        public float MeterSmallMarkInterval
        {
            get => (float)GetValue(MeterSmallMarkIntervalProperty);
            set => SetValue(MeterSmallMarkIntervalProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘进度条与范围条的宽度")]
        public float MeterArcStrokeThickness
        {
            get => (float)GetValue(MeterArcStrokeThicknessProperty);
            set => SetValue(MeterArcStrokeThicknessProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘低值警戒值")]
        public float MeterLowAlarmValue
        {
            get => (float)GetValue(MeterLowAlarmValueProperty);
            set => SetValue(MeterLowAlarmValueProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表盘高值警戒值")]
        public float MeterHightAlarmValue
        {
            get => (float)GetValue(MeterHightAlarmValueProperty);
            set => SetValue(MeterHightAlarmValueProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("仪表的单位")]
        public string MeterUnit
        {
            get => (string)GetValue(MeterUnitProperty);
            set => SetValue(MeterUnitProperty, value);
        }
        protected override void OnRender(DrawingContext drawingContext)
        {
            if (this.MeterAngleRange > 180)
            {
                arcRange.OffsetAngle = 180 - (this.MeterAngleRange - 180) / 2;
            }
            else
            {
                arcRange.OffsetAngle = 270 - this.MeterAngleRange / 2;
            }
            arcRange.StartAngle = 0;
            arcRange.EndAngle = arcRange.StartAngle + this.MeterAngleRange;
            arcProgress.StartAngle = 0;
            arcProgress.OffsetAngle = arcRange.OffsetAngle;
            //指针动作           
            float targetDeg = 0;
            if (MeterValue < MeterMinValue)
            {
                targetDeg = arcRange.StartAngle + arcRange.OffsetAngle;
            }
            else if (MeterValue > MeterMaxValue)
            {
                targetDeg = arcRange.EndAngle + arcRange.OffsetAngle;
            }
            else
            {
                targetDeg = arcProgress.StartAngle + (this.MeterValue - MeterMinValue) * 1.0f / (MeterMaxValue - MeterMinValue) * this.MeterAngleRange + arcRange.OffsetAngle;
            }
            double interval = Math.Abs(targetDeg - preDeg) / (arcRange.EndAngle - arcRange.StartAngle) * 500;
            preDeg = targetDeg;
            DoubleAnimation animation = new DoubleAnimation(targetDeg, TimeSpan.FromMilliseconds(interval));
            rotateTransform.BeginAnimation(RotateTransform.AngleProperty, animation);
            pointer.RenderTransform = rotateTransform;
            SingleAnimation animation2 = new SingleAnimation(targetDeg - arcRange.OffsetAngle, TimeSpan.FromMilliseconds(interval));
            arcProgress.BeginAnimation(Arc.EndAngleProperty, animation2);
            DrawScale();
            //确定当前值得前景色
            if (this.MeterValue <= this.MeterLowAlarmValue)
            {
                this.MeterValueForeground = this.MeterLowAlarmBrush;
            }
            else if (this.MeterValue >= this.MeterHightAlarmValue)
            {
                this.MeterValueForeground = this.MeterHightAlarmBrush;
            }
            else
            {
                this.MeterValueForeground = this.MeterNormalBrush;
            }
            base.OnRender(drawingContext);
        }
        //绘制刻度
        void DrawScale()
        {
            cavs.Children.Clear();
            Point center = new Point(cavs.ActualWidth / 2, cavs.ActualHeight / 2);
            double radius = center.X - this.MeterArcStrokeThickness;
            float unitDeg = this.MeterAngleRange / (this.MeterMaxValue - this.MeterMinValue);
            for (float i = 0; i <= Math.Ceiling(MeterMaxValue - MeterMinValue); i += 1)
            {
                Brush strokeBrush;
                if (i + MeterMinValue <= MeterLowAlarmValue)
                {
                    strokeBrush = MeterLowAlarmBrush;
                }
                else if (i + MeterMinValue >= MeterHightAlarmValue)
                {
                    strokeBrush = MeterHightAlarmBrush;
                }
                else
                {
                    strokeBrush = MeterNormalBrush;
                }
                float curDeg = i * unitDeg + arcRange.OffsetAngle;
                //绘制大刻度
                if (i % this.MeterLargeMarkInterval == 0)
                {
                    Line line = new Line();
                    line.Stroke = strokeBrush;
                    line.StrokeThickness = 3;
                    line.X1 = center.X + Math.Cos(curDeg / 180 * Math.PI) * radius;
                    line.Y1 = center.Y + Math.Sin(curDeg / 180 * Math.PI) * radius;
                    line.X2 = center.X + Math.Cos(curDeg / 180 * Math.PI) * (radius - 20);
                    line.Y2 = center.Y + Math.Sin(curDeg / 180 * Math.PI) * (radius - 20);
                    cavs.Children.Add(line);
                    //绘制标签文本
                    string text = (i + MeterMinValue).ToString();
                    FormattedText formattedText = new FormattedText(text, System.Globalization.CultureInfo.CurrentCulture,
                         FlowDirection.LeftToRight, new Typeface("宋体"), 14, Brushes.White);
                    double top = center.Y + Math.Sin(curDeg / 180 * Math.PI) * (radius - 35) - formattedText.Height / 2;
                    double left = center.X + Math.Cos(curDeg / 180 * Math.PI) * (radius - 35) - formattedText.Width / 2;
                    if (i > Math.Ceiling(MeterMaxValue - MeterMinValue) / 2)
                    {
                        left = center.X + Math.Cos(curDeg / 180 * Math.PI) * (radius - 35) - formattedText.Width;
                    }
                    TextBlock tb = new TextBlock();
                    tb.FontSize = 14;
                    tb.Text = text;
                    tb.Foreground = strokeBrush;
                    Canvas.SetLeft(tb, left);
                    Canvas.SetTop(tb, top);
                    cavs.Children.Add(tb);
                    continue;
                }
                //绘制小刻度
                if (i % MeterSmallMarkInterval == 0)
                {
                    Line line = new Line();
                    line.Stroke = strokeBrush;
                    line.StrokeThickness = 1;
                    line.X1 = center.X + Math.Cos(curDeg / 180 * Math.PI) * radius;
                    line.Y1 = center.Y + Math.Sin(curDeg / 180 * Math.PI) * radius;
                    line.X2 = center.X + Math.Cos(curDeg / 180 * Math.PI) * (radius - 10);
                    line.Y2 = center.Y + Math.Sin(curDeg / 180 * Math.PI) * (radius - 10);
                    cavs.Children.Add(line);
                }
            }
        }
    }
4.4,雷达图控件(RaderChart
4.4.1,效果
xaml 复制代码
 <controls:RaderChart Grid.Column="3" Margin="5">
            <x:Array Type="sys:Single">
                <sys:Single>12.4</sys:Single>
                <sys:Single>24.5</sys:Single>
                <sys:Single>56.9</sys:Single>
                <sys:Single>102</sys:Single>
                <sys:Single>45</sys:Single>
            </x:Array>
        </controls:RaderChart>
4.4.2,Xaml代码
xaml 复制代码
<UserControl x:Class="WpfAssets.Assets.Controls.RaderChart"
             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:WpfAssets.Assets.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Canvas x:Name="mainCanvas" ></Canvas>
    </Grid>
</UserControl>
4.4.3,C#代码
C# 复制代码
[ContentProperty("ItemsSource")]
    [DefaultProperty("ItemsSource")]
    /// <summary>
    /// 雷达图
    /// </summary>
    public partial class RaderChart : UserControl
    {
        public RaderChart()
        {
            InitializeComponent();
        }
        #region 依赖属性
        public static readonly DependencyProperty ItemsSourceProperty;
        public static readonly DependencyProperty MaxNumProperty;
        public static readonly DependencyProperty MeshStrokeProperty;
        public static readonly DependencyProperty RaderBorderBrushProperty;
        public static readonly DependencyProperty RaderFillProperty;
        public static readonly DependencyProperty MeshStrokeThicknessProperty;
        public static readonly DependencyProperty RaderBorderThicknessProperty;
        public static readonly DependencyProperty NumberOfPliesProperty;
        static RaderChart()
        {
            ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(RaderChart), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
            MaxNumProperty = DependencyProperty.Register("MaxNum", typeof(double), typeof(RaderChart), new FrameworkPropertyMetadata(100d, FrameworkPropertyMetadataOptions.AffectsRender));
            MeshStrokeProperty = DependencyProperty.Register("MeshStroke", typeof(Brush), typeof(RaderChart), new FrameworkPropertyMetadata(Brushes.DimGray, FrameworkPropertyMetadataOptions.AffectsRender));
            RaderBorderBrushProperty = DependencyProperty.Register("RaderBorderBrush", typeof(Brush), typeof(RaderChart), new FrameworkPropertyMetadata(Brushes.DeepSkyBlue, FrameworkPropertyMetadataOptions.AffectsRender));
            RaderFillProperty = DependencyProperty.Register("RaderFill", typeof(Brush), typeof(RaderChart), new FrameworkPropertyMetadata(Brushes.AliceBlue, FrameworkPropertyMetadataOptions.AffectsRender));
            MeshStrokeThicknessProperty = DependencyProperty.Register("MeshStrokeThickness", typeof(float), typeof(RaderChart), new FrameworkPropertyMetadata(1f, FrameworkPropertyMetadataOptions.AffectsRender));
            RaderBorderThicknessProperty = DependencyProperty.Register("RaderBorderThickness", typeof(float), typeof(RaderChart), new FrameworkPropertyMetadata(1f, FrameworkPropertyMetadataOptions.AffectsRender));
            NumberOfPliesProperty = DependencyProperty.Register("NumberOfPlies", typeof(int), typeof(RaderChart), new FrameworkPropertyMetadata(5, FrameworkPropertyMetadataOptions.AffectsRender));
        }
        [Browsable(true), Category("自定义属性"), Description("数据源")]
        public IEnumerable ItemsSource
        {
            get => (IEnumerable)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("最大数即量程")]
        public double MaxNum
        {
            get => (double)GetValue(MaxNumProperty);
            set => SetValue(MaxNumProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("网络线stroke")]
        public Brush MeshStroke
        {
            get => (Brush)GetValue(MeshStrokeProperty);
            set => SetValue(MeshStrokeProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("雷达线边框画刷")]
        public Brush RaderBorderBrush
        {
            get => (Brush)GetValue(RaderBorderBrushProperty);
            set => SetValue(RaderBorderBrushProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("雷达填充画刷")]
        public Brush RaderFill
        {
            get => (Brush)GetValue(RaderFillProperty);
            set => SetValue(RaderFillProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("网络线画刷宽度")]
        public float MeshStrokeThickness
        {
            get => (float)GetValue(MeshStrokeThicknessProperty);
            set => SetValue(MeshStrokeThicknessProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("雷达边界线宽度")]
        public float RaderBorderThickness
        {
            get => (float)GetValue(RaderBorderThicknessProperty);
            set => SetValue(RaderBorderThicknessProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("网络层数")]
        public int NumberOfPlies
        {
            get => (int)GetValue(NumberOfPliesProperty);
            set => SetValue(NumberOfPliesProperty, value);
        }
        #endregion
        protected override void OnRender(DrawingContext drawingContext)
        {
            if (ItemsSource != null)
            {
                mainCanvas.Children.Clear();
                double len = Math.Min(this.ActualWidth, this.ActualHeight);
                int count = 0;
                foreach (var item in ItemsSource)
                {
                    count++;
                }
                List<Point>[] points = new List<Point>[NumberOfPlies];
                if (len > 0)
                {
                    double radius = len / 2 - 10;
                    Point center = new Point(radius + 10, radius + 10);
                    for (int i = 1; i <= NumberOfPlies; i++)
                    {
                        Polyline polyline = new Polyline { Stroke = MeshStroke, StrokeThickness = MeshStrokeThickness };
                        points[i - 1] = new List<Point>();
                        for (int j = 0; j <= count; j++)
                        {
                            Point p = new Point();
                            p.X = center.X + (radius * i * 1.0 / NumberOfPlies) * Math.Cos((j * 360 * 1.0 / count - 90) / 180 * Math.PI);
                            p.Y = center.Y + (radius * i * 1.0 / NumberOfPlies) * Math.Sin((j * 360 * 1.0 / count - 90) / 180 * Math.PI);
                            polyline.Points.Add(p);
                            points[i - 1].Add(p);
                        }
                        mainCanvas.Children.Add(polyline);
                    }
                    //网格连接线
                    for (int i = 0; i < count; i++)
                    {
                        Polyline polyline = new Polyline { Stroke = MeshStroke, StrokeThickness = MeshStrokeThickness };
                        for (int j = 0; j < NumberOfPlies; j++)
                        {
                            polyline.Points.Add(points[j][i]);
                        }
                        mainCanvas.Children.Add(polyline);
                    }
                    //绘制雷达图
                    int index = 0;
                    Polygon polygon = new Polygon { Stroke = RaderBorderBrush, StrokeThickness = RaderBorderThickness, Fill = RaderFill };
                    foreach (var item in ItemsSource)
                    {
                        double rate = Convert.ToDouble(item) > MaxNum ? 1 : Convert.ToDouble(item) / MaxNum;
                        Point p = new Point();
                        p.X = center.X + (radius * rate) * Math.Cos((index * 360 * 1.0 / count - 90) / 180 * Math.PI);
                        p.Y = center.Y + (radius * rate) * Math.Sin((index * 360 * 1.0 / count - 90) / 180 * Math.PI);
                        TextBlock tb = new TextBlock();
                        tb.Text = item.ToString();
                        FormattedText formattedText = new FormattedText(item.ToString(), System.Threading.Thread.CurrentThread.CurrentCulture, FlowDirection.LeftToRight, new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), FontSize, Foreground);
                        double degree = index * 360 * 1.0 / count - 90;
                        double left, top;
                        Point wordPoint = new Point();
                        wordPoint.X = points[NumberOfPlies - 1][index].X;
                        wordPoint.Y = points[NumberOfPlies - 1][index].Y;
                        left = wordPoint.X;
                        top = wordPoint.Y;
                        if (degree == -90)
                        {
                            left = wordPoint.X - formattedText.Width / 2;
                            top = wordPoint.Y - formattedText.Height;
                        }
                        else if (degree < 0)
                        {
                            left = wordPoint.X;
                            top = wordPoint.Y - formattedText.Height;
                        }
                        else if (degree == 0)
                        {
                            left = wordPoint.X;
                            top = wordPoint.Y - formattedText.Height / 2;
                        }
                        else if (degree < 90)
                        {
                        }
                        else if (degree == 90)
                        {
                            left = wordPoint.X - formattedText.Width / 2;
                            top = wordPoint.Y;
                        }
                        else if (degree < 180)
                        {
                            left = wordPoint.X - formattedText.Width;
                            top = wordPoint.Y;
                        }
                        else if (degree == 180)
                        {
                            left = wordPoint.X - formattedText.Width;
                            top = wordPoint.Y - formattedText.Height / 2;
                        }
                        else
                        {
                            left = wordPoint.X - formattedText.Width;
                            top = wordPoint.Y - formattedText.Height;
                        }
                        Canvas.SetLeft(tb, left);
                        Canvas.SetTop(tb, top);
                        mainCanvas.Children.Add(tb);
                        index++;
                        polygon.Points.Add(p);
                    }
                    mainCanvas.Children.Add(polygon);
                    //添加标签
                    mainCanvas.Width = len;
                    mainCanvas.Height = len;
                }
            }
            base.OnRender(drawingContext);
        }
    }
4.4,水位进度控件(WaterProgress
4.4.1,效果
xaml 复制代码
<controls:WaterProgress Grid.Row="1" Value="{Binding ElementName=slider01, Path=Value}"></controls:WaterProgress>
4.4.2,Xaml代码
xaml 复制代码
<UserControl x:Class="WpfAssets.Assets.Controls.WaterProgress"
             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" 
             
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <Storyboard x:Key="sb">
            <!--外圈旋转-->
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt1" Storyboard.TargetProperty="Angle" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                <SplineDoubleKeyFrame KeyTime="0:0:2.5" Value="360" KeySpline="0.4,0.1,0.6,0.9"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt2" Storyboard.TargetProperty="Angle" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0" Value="90"/>
                <SplineDoubleKeyFrame KeyTime="0:0:3" Value="450" KeySpline="0.4,0.1,0.6,0.9"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt3" Storyboard.TargetProperty="Angle" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0" Value="180"/>
                <SplineDoubleKeyFrame KeyTime="0:0:3.5" Value="540" KeySpline="0.4,0.1,0.6,0.9"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="rt4" Storyboard.TargetProperty="Angle" RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="0:0:0" Value="270"/>
                <SplineDoubleKeyFrame KeyTime="0:0:3" Value="630" KeySpline="0.4,0.1,0.6,0.9"/>
            </DoubleAnimationUsingKeyFrames>

            <!--<DoubleAnimation Duration="0:0:2" From="0" To="360" Storyboard.TargetName="rt1" Storyboard.TargetProperty="Angle"
                             RepeatBehavior="Forever"/>
            <DoubleAnimation Duration="0:0:2.5" From="90" To="450" Storyboard.TargetName="rt2" Storyboard.TargetProperty="Angle"
                             RepeatBehavior="Forever"/>
            <DoubleAnimation Duration="0:0:3" From="180" To="540" Storyboard.TargetName="rt3" Storyboard.TargetProperty="Angle"
                             RepeatBehavior="Forever"/>
            <DoubleAnimation Duration="0:0:3.5" From="270" To="630" Storyboard.TargetName="rt4" Storyboard.TargetProperty="Angle"
                             RepeatBehavior="Forever"/>-->
            <!--波浪模拟位移-->
            <DoubleAnimation Duration="0:0:1.3" From="0" To="-166" Storyboard.TargetName="tt1" Storyboard.TargetProperty="X"
                             RepeatBehavior="Forever"/>
            <DoubleAnimation Duration="0:0:2.7" From="0" To="-166" Storyboard.TargetName="tt2" Storyboard.TargetProperty="X"
                             RepeatBehavior="Forever"/>
            <DoubleAnimation Duration="0:0:1.9" From="-166" To="0" Storyboard.TargetName="tt3" Storyboard.TargetProperty="X"
                             RepeatBehavior="Forever"/>
        </Storyboard>
    </UserControl.Resources>
    <UserControl.Triggers>
        <EventTrigger RoutedEvent="UserControl.Loaded">
            <BeginStoryboard Storyboard="{StaticResource sb }"/>
        </EventTrigger>
    </UserControl.Triggers>
    <Viewbox>
        <Grid Width="200" Height="200">
            <!--外圈-->
            <Path Data="M10.077735,47.685855 C31.078032,13.685855 25.578212,18.186285 60.078032,7.6858546 98.105732,-2.7958154 121.12748,-1.2938974 136.07773,4.6862026 151.02798,10.666293 171.52126,13.34086 186.07803,42.686 193.52198,65.84058 189.06226,75.02501 189.07803,107.686 185.62079,132.09885 181.48699,134.68936 171.078,146.686 144.0497,177.83681 162.57824,158.68624 139.078,178.686 102.41447,209.88832 87.194615,208.93377 69.194405,206.9341 51.194195,204.93442 38.008452,202.54643&#xD;&#xA;17.078032,160.686 -3.8523784,118.82557 0.57719474,123.68578 0.077734746,97.685855 -0.54939526,65.040225 10.077735,47.685855 10.077735,47.685855 z" VerticalAlignment="Center" HorizontalAlignment="Center"
              RenderTransformOrigin="0.5,0.5" Height="207.771"
              Width="190.388" Opacity="0.3">
                <Path.Fill>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#77FFFFFF" Offset="0"/>
                        <GradientStop Color="#5AFFFFFF" Offset="1"/>
                    </LinearGradientBrush>
                </Path.Fill>
                <Path.RenderTransform>
                    <TransformGroup>
                        <RotateTransform Angle="0" x:Name="rt1"/>
                    </TransformGroup>
                </Path.RenderTransform>
            </Path>
            <Path Data="M10.077735,47.685855 C31.078032,13.685855 25.578212,18.186285 60.078032,7.6858546 98.105732,-2.7958154 121.12748,-1.2938974 136.07773,4.6862026 151.02798,10.666293 171.52126,13.34086 186.07803,42.686 193.52198,65.84058 189.06226,75.02501 189.07803,107.686 185.62079,132.09885 181.48699,134.68936 171.078,146.686 144.0497,177.83681 162.57824,158.68624 139.078,178.686 102.41447,209.88832 87.194615,208.93377 69.194405,206.9341 51.194195,204.93442 38.008452,202.54643&#xD;&#xA;17.078032,160.686 -3.8523784,118.82557 0.57719474,123.68578 0.077734746,97.685855 -0.54939526,65.040225 10.077735,47.685855 10.077735,47.685855 z" VerticalAlignment="Center" HorizontalAlignment="Center"
              RenderTransformOrigin="0.5,0.5" Height="207.771"
              Width="190.388" Opacity="0.6">
                <Path.Fill>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#77FFFFFF" Offset="0"/>
                        <GradientStop Color="#5AFFFFFF" Offset="1"/>
                    </LinearGradientBrush>
                </Path.Fill>
                <Path.RenderTransform>
                    <TransformGroup>
                        <RotateTransform Angle="90" x:Name="rt2"/>
                    </TransformGroup>
                </Path.RenderTransform>
            </Path>
            <Path Data="M10.077735,47.685855 C31.078032,13.685855 25.578212,18.186285 60.078032,7.6858546 98.105732,-2.7958154 121.12748,-1.2938974 136.07773,4.6862026 151.02798,10.666293 171.52126,13.34086 186.07803,42.686 193.52198,65.84058 189.06226,75.02501 189.07803,107.686 185.62079,132.09885 181.48699,134.68936 171.078,146.686 144.0497,177.83681 162.57824,158.68624 139.078,178.686 102.41447,209.88832 87.194615,208.93377 69.194405,206.9341 51.194195,204.93442 38.008452,202.54643&#xD;&#xA;17.078032,160.686 -3.8523784,118.82557 0.57719474,123.68578 0.077734746,97.685855 -0.54939526,65.040225 10.077735,47.685855 10.077735,47.685855 z" VerticalAlignment="Center" HorizontalAlignment="Center"
              RenderTransformOrigin="0.5,0.5" Height="207.771"
              Width="190.388" Opacity="0.8">
                <Path.Fill>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#77FFFFFF" Offset="0"/>
                        <GradientStop Color="#5AFFFFFF" Offset="1"/>
                    </LinearGradientBrush>
                </Path.Fill>
                <Path.RenderTransform>
                    <TransformGroup>
                        <RotateTransform Angle="180" x:Name="rt3"/>
                    </TransformGroup>
                </Path.RenderTransform>
            </Path>
            <Path Data="M10.077735,47.685855 C31.078032,13.685855 25.578212,18.186285 60.078032,7.6858546 98.105732,-2.7958154 121.12748,-1.2938974 136.07773,4.6862026 151.02798,10.666293 171.52126,13.34086 186.07803,42.686 193.52198,65.84058 189.06226,75.02501 189.07803,107.686 185.62079,132.09885 181.48699,134.68936 171.078,146.686 144.0497,177.83681 162.57824,158.68624 139.078,178.686 102.41447,209.88832 87.194615,208.93377 69.194405,206.9341 51.194195,204.93442 38.008452,202.54643&#xD;&#xA;17.078032,160.686 -3.8523784,118.82557 0.57719474,123.68578 0.077734746,97.685855 -0.54939526,65.040225 10.077735,47.685855 10.077735,47.685855 z" VerticalAlignment="Center" HorizontalAlignment="Center"
              RenderTransformOrigin="0.5,0.5" Height="207.771"
              Width="190.388">
                <Path.Fill>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#77FFFFFF" Offset="0"/>
                        <GradientStop Color="#5AFFFFFF" Offset="1"/>
                    </LinearGradientBrush>
                </Path.Fill>
                <Path.RenderTransform>
                    <TransformGroup>
                        <RotateTransform Angle="270" x:Name="rt4"/>
                    </TransformGroup>
                </Path.RenderTransform>
            </Path>

            <!--内圈-->
            <Border Width="175" Height="175" CornerRadius="90" BorderThickness="5" BorderBrush="Orange">
                <Border.Background>
                    <RadialGradientBrush>
                        <GradientStop Color="#B6FFFFFF" Offset="1"/>
                        <GradientStop Color="White"/>
                        <GradientStop Color="White" Offset="0.672"/>
                    </RadialGradientBrush>
                </Border.Background>
            </Border>

            <!--波浪-->
            <Border  Width="166" Height="166" CornerRadius="90">
                <Border.Clip>
                    <EllipseGeometry RadiusX="83" RadiusY="83" Center="83,83"/>
                </Border.Clip>
                
                <Canvas>
                    <Grid>
                        <Grid.RenderTransform>
                            <TranslateTransform Y="160" x:Name="ttg"/>
                        </Grid.RenderTransform>
                        <Path Data="M0 5A80 40 0 0 1 83 5A80 40 0 0 0 166 5A80 40 0 0 1 249 5A80 40 0 0 0 332 5L332 182 0 182Z" 
                  Fill="#4C0b3d90">
                            <Path.RenderTransform>
                                <TranslateTransform x:Name="tt1"/>
                            </Path.RenderTransform>
                        </Path>
                        <Path Data="M0 5A80 40 0 0 1 83 5A80 40 0 0 0 166 5A80 40 0 0 1 249 5A80 40 0 0 0 332 5L332 182 0 182Z" 
                  Fill="#7F0b3d90">
                            <Path.RenderTransform>
                                <TranslateTransform x:Name="tt2"/>
                            </Path.RenderTransform>
                        </Path>
                        <Path Data="M0 5A80 40 0 0 1 83 5A80 40 0 0 0 166 5A80 40 0 0 1 249 5A80 40 0 0 0 332 5L332 182 0 182Z" 
                  Fill="#CC0b3d90">
                            <Path.RenderTransform>
                                <TranslateTransform X="-166" x:Name="tt3"/>
                            </Path.RenderTransform>
                        </Path>
                    </Grid>
                </Canvas>
            </Border>

            <Border Width="175" Height="175" CornerRadius="90" BorderThickness="5" BorderBrush="#0b3d90">
                <Border.Background>
                    <RadialGradientBrush>
                        <GradientStop Color="#B6FFFFFF" Offset="1"/>
                        <GradientStop Color="#E2FFFFFF"/>
                        <GradientStop Color="#D3FFFFFF" Offset="0.528"/>
                    </RadialGradientBrush>
                </Border.Background>
            </Border>

            <!--文字-->
            <Viewbox Width="160" Height="95">
                <TextBlock FontSize="50" FontWeight="ExtraLight" VerticalAlignment="Center" HorizontalAlignment="Center" 
                       Foreground="#888" FontFamily="Microsoft YaHei"><Run Text="{Binding Value,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor}}"/><Run FontSize="18" Text="%"/></TextBlock>
            </Viewbox>
        </Grid>
    </Viewbox>
</UserControl>
4.4.3,C#代码
C# 复制代码
/// <summary>
    /// WaterProgress.xaml 的交互逻辑
    /// </summary>
    public partial class WaterProgress : UserControl
    {
        [Browsable(true),Category("自定义属性"),Description("进度值:0-100之间")]
        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(WaterProgress), new PropertyMetadata(0.0,new PropertyChangedCallback(OnValueChanged)));

        private static void OnValueChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
        {
            // 获取当前Value的最新值
            double newValue = (double)e.NewValue;  // 0-100 以  160/100=1.6

            // 配置一个简单线性动画
            DoubleAnimation da = new DoubleAnimation(160 - newValue * 1.6, TimeSpan.FromMilliseconds(200));
            (d as WaterProgress).ttg.BeginAnimation(TranslateTransform.YProperty, da);
        }

        public WaterProgress()
        {
            InitializeComponent();
        }
    }
4.5,管道控件(PipeLine
4.5.1,效果
xaml 复制代码
<Viewbox Grid.Row="2">
            <Canvas Height="200" Width="200">
                <!--需要明确指定Width与Height,否则异常-->
                <controls:PipeLine Width="160" PipeSecondJointDirection="Down" Height="25" PipeFluidIsFlow="True"></controls:PipeLine>
                <controls:PipeLine Height="54" PipeDirection="Verticality" Canvas.Left="135" Canvas.Top="24" PipeFluidIsFlow="True" ></controls:PipeLine>
                <controls:PipeLine Canvas.Left="30" Height="25" PipeFirstJointDirection="Down" PipeSecondJointDirection="Up" Canvas.Top="78" Width="130" PipeFluidIsFlow="True" PipeFlowVelocity="-4"></controls:PipeLine>
                <controls:PipeLine PipeDirection="Verticality" PipeSecondJointDirection="Right" Width="25" Height="65" Canvas.Top="103" Canvas.Left="30" PipeFluidIsFlow="True" ></controls:PipeLine>
                <controls:PipeLine Width="120" Height="25" Canvas.Left="55" Canvas.Top="143" PipeFluidIsFlow="True"></controls:PipeLine>
                <controls:PipeLine Width="25" PipeFirstJointDirection="Right" PipeSecondJointDirection="Left" Height="159" PipeDirection="Verticality" Canvas.Left="174" Canvas.Top="9" PipeFluidIsFlow="True" PipeFlowVelocity="-4"></controls:PipeLine>
            </Canvas>
        </Viewbox>
4.5.2,Xaml代码

4.5.3,C#代码
C# 复制代码
 /// <summary>
    /// 管道
    /// </summary>
    public class PipeLine : Shape
    {
        PathGeometry fluidGeometry = new PathGeometry();
        double fillThickness;
        System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
        double offset = 0;
        #region 依赖属性
        public static readonly DependencyProperty PipeStrokeThicknessProperty;
        public static readonly DependencyProperty PipeStrokeProperty;
        public static readonly DependencyProperty PipeFlowVelocityProperty;
        public static readonly DependencyProperty PipeFirstJointDirectionProperty;
        public static readonly DependencyProperty PipeSecondJointDirectionProperty;
        public static readonly DependencyProperty PipeDirectionProperty;
        public static readonly DependencyProperty PipeFillProperty;
        public static readonly DependencyProperty PipeFluidStrokeDashArrayProperty;
        public static readonly DependencyProperty PipeFluidStrokeDashOffsetProperty;
        public static readonly DependencyProperty PipeFluidStrokeDashCapProperty;
        public static readonly DependencyProperty PipeFluidStrokeProperty;
        public static readonly DependencyProperty PipeFluidStrokeThicknessProperty;
        public static readonly DependencyProperty PipeFluidIsFlowProperty;
        static PipeLine()
        {
            PipeStrokeThicknessProperty = DependencyProperty.Register("PipeStrokeThickness", typeof(float), typeof(PipeLine), new FrameworkPropertyMetadata(3f, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeStrokeProperty = DependencyProperty.Register("PipeStroke", typeof(Brush), typeof(PipeLine), new FrameworkPropertyMetadata(Brushes.Gray, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeFlowVelocityProperty = DependencyProperty.Register(" PipeFlowVelocity", typeof(float), typeof(PipeLine), new FrameworkPropertyMetadata(4f, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeFirstJointDirectionProperty = DependencyProperty.Register("PipeFirstJointDirection", typeof(JointDirection), typeof(PipeLine), new FrameworkPropertyMetadata(JointDirection.None, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeSecondJointDirectionProperty = DependencyProperty.Register("PipeSecondJointDirection", typeof(JointDirection), typeof(PipeLine), new FrameworkPropertyMetadata(JointDirection.None, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeDirectionProperty = DependencyProperty.Register("PipeDirection", typeof(PipeDirection), typeof(PipeLine), new FrameworkPropertyMetadata(PipeDirection.Horizontality, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(PipeDirectionPropertyChanged)));
            PipeFillProperty = DependencyProperty.Register("PipeFill", typeof(Brush), typeof(PipeLine), new FrameworkPropertyMetadata(Brushes.LightGray, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeFluidStrokeDashArrayProperty = DependencyProperty.Register("PipeFluidStrokeDashArray", typeof(DoubleCollection), typeof(PipeLine), new FrameworkPropertyMetadata(new DoubleCollection { 3, 3 }, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeFluidStrokeDashOffsetProperty = DependencyProperty.Register("PipeFluidStrokeDashOffset", typeof(float), typeof(PipeLine), new FrameworkPropertyMetadata(0f, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeFluidStrokeDashCapProperty = DependencyProperty.Register("PipeFluidStrokeDashCap", typeof(PenLineCap), typeof(PipeLine), new FrameworkPropertyMetadata(PenLineCap.Flat, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeFluidStrokeProperty = DependencyProperty.Register("PipeFluidStroke", typeof(Brush), typeof(PipeLine), new FrameworkPropertyMetadata(Brushes.BlueViolet, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeFluidStrokeThicknessProperty = DependencyProperty.Register("PipeFluidStrokeThickness", typeof(float), typeof(PipeLine), new FrameworkPropertyMetadata(6f, FrameworkPropertyMetadataOptions.AffectsRender));
            PipeFluidIsFlowProperty = DependencyProperty.Register("PipeFluidIsFlow", typeof(bool), typeof(PipeLine), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(PropertyChaned)));
        }
        private static void PipeDirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is PipeLine pipe)
            {
                if ((PipeDirection)e.NewValue == PipeDirection.Horizontality && (PipeDirection)e.OldValue == PipeDirection.Verticality)
                {
                    if (pipe.ActualWidth > 0)
                    {
                        double temp = pipe.ActualWidth;
                        pipe.Width = pipe.ActualHeight;
                        pipe.Height = temp;
                    }
                }
                if ((PipeDirection)e.NewValue == PipeDirection.Verticality && (PipeDirection)e.OldValue == PipeDirection.Horizontality)
                {
                    if (pipe.ActualWidth > 0)
                    {
                        double temp = pipe.ActualWidth;
                        pipe.Width = pipe.ActualHeight;
                        pipe.Height = temp;
                    }
                }
            }
        }
        #endregion
        public PipeLine()
        {
            timer.Interval = TimeSpan.FromMilliseconds(500);
            timer.Tick += Timer_Tick;
            if (PipeFluidIsFlow)
            {
                timer.Start();
            }
        }
        private static void PropertyChaned(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is PipeLine pipe)
            {
                if ((bool)e.NewValue)
                {
                    pipe.timer.Start();
                }
                else
                {
                    pipe.timer.Stop();
                }
            }
        }
        private void Timer_Tick(object sender, EventArgs e)
        {
            offset += PipeFlowVelocity;
            double moveUnit = PipeFluidDashArray.Aggregate((a, b) => a + b);
            if (offset % moveUnit == 0)
            {
                offset = 0;
            }
            PipeFluidDashOffset = (float)offset;
        }
        [Browsable(true), Category("自定义属性"), Description("管道壁的厚度")]
        public float PipeStrokeThickness
        {
            get => (float)GetValue(PipeStrokeThicknessProperty);
            set => SetValue(PipeStrokeThicknessProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("管道壁的画刷")]
        public Brush PipeStroke
        {
            get => (Brush)GetValue(PipeStrokeProperty);
            set => SetValue(PipeStrokeProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("流体速度")]
        public float PipeFlowVelocity
        {
            get => (float)GetValue(PipeFlowVelocityProperty);
            set => SetValue(PipeFlowVelocityProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("第一个接头方向")]
        public JointDirection PipeFirstJointDirection
        {
            get => (JointDirection)GetValue(PipeFirstJointDirectionProperty);
            set => SetValue(PipeFirstJointDirectionProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("第二个接头方向")]
        public JointDirection PipeSecondJointDirection
        {
            get => (JointDirection)GetValue(PipeSecondJointDirectionProperty);
            set => SetValue(PipeSecondJointDirectionProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("管道方向")]
        public PipeDirection PipeDirection
        {
            get => (PipeDirection)GetValue(PipeDirectionProperty);
            set => SetValue(PipeDirectionProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("管道填充画刷")]
        public Brush PipeFill
        {
            get => (Brush)GetValue(PipeFillProperty);
            set => SetValue(PipeFillProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("流体虚线和间隙的样式的double值")]
        public DoubleCollection PipeFluidDashArray
        {
            get => (DoubleCollection)GetValue(PipeFluidStrokeDashArrayProperty);
            set => SetValue(PipeFluidStrokeDashArrayProperty, value);
        }
        [Browsable(false), Category("自定义属性"), Description("流体速度")]
        float PipeFluidDashOffset
        {
            get => (float)GetValue(PipeFluidStrokeDashOffsetProperty);
            set => SetValue(PipeFluidStrokeDashOffsetProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("流体虚线两端的形状")]
        public PenLineCap PipeFluidDashCap
        {
            get => (PenLineCap)GetValue(PipeFluidStrokeDashCapProperty);
            set => SetValue(PipeFluidStrokeDashCapProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("流体画刷")]
        public Brush PipeFluidStroke
        {
            get => (Brush)GetValue(PipeFluidStrokeProperty);
            set => SetValue(PipeFluidStrokeProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("流体宽度")]
        public float PipeFluidStrokeThickness
        {
            get => (float)GetValue(PipeFluidStrokeThicknessProperty);
            set => SetValue(PipeFluidStrokeThicknessProperty, value);
        }
        [Browsable(true), Category("自定义属性"), Description("流体是否流动")]
        public bool PipeFluidIsFlow
        {
            get => (bool)GetValue(PipeFluidIsFlowProperty);
            set => SetValue(PipeFluidIsFlowProperty, value);
        }
        public override Transform GeometryTransform => Transform.Identity;
        protected override Geometry DefiningGeometry
        {
            get
            {
                PathGeometry pipeGeometry = new PathGeometry();
                this.fluidGeometry.Clear();
                if (double.IsPositiveInfinity(this.ActualWidth) || double.IsNegativeInfinity(this.ActualHeight)
                    || this.ActualWidth == 0 || this.ActualHeight == 0
                    )
                    return pipeGeometry;
                double width = this.ActualWidth;
                double height = this.ActualHeight;
                if (PipeDirection == PipeDirection.Horizontality)
                {
                    fillThickness = height - 2 * PipeStrokeThickness;
                }
                else
                {
                    fillThickness = width - 2 * PipeStrokeThickness;
                }
                PathFigure pathFigure1 = new PathFigure();
                PathFigure pathFigure2 = new PathFigure();
                PathFigure fluidPath = new PathFigure();
                if (PipeDirection == PipeDirection.Horizontality)
                {
                    //管道水平方向
                    switch (PipeFirstJointDirection)
                    {
                        case JointDirection.Up:
                            pathFigure2.StartPoint = new Point(PipeStrokeThickness / 2, 0);
                            pathFigure2.Segments.Add(new ArcSegment(new Point(height, height - PipeStrokeThickness / 2), new Size(height - PipeStrokeThickness / 2, height - PipeStrokeThickness / 2), 90, false, SweepDirection.Counterclockwise, true));
                            pathFigure2.Segments.Add(new LineSegment(new Point(width - height, height - PipeStrokeThickness / 2), true));
                            pathFigure1.StartPoint = new Point(height - PipeStrokeThickness / 2, 0);
                            pathFigure1.Segments.Add(new ArcSegment(new Point(height, PipeStrokeThickness / 2), new Size(PipeStrokeThickness / 2, PipeStrokeThickness / 2), 90, false, SweepDirection.Counterclockwise, true));
                            pathFigure1.Segments.Add(new LineSegment(new Point(width - height, PipeStrokeThickness / 2), true));
                            //中心线
                            fluidPath.StartPoint = new Point(height / 2, 0);
                            fluidPath.Segments.Add(new ArcSegment(new Point(height, height / 2), new Size(height / 2, height / 2), 90, false, SweepDirection.Counterclockwise, true));
                            fluidPath.Segments.Add(new LineSegment(new Point(width - height, height / 2), true));
                            break;
                        case JointDirection.Down:
                            pathFigure1.StartPoint = new Point(PipeStrokeThickness / 2, height);
                            pathFigure1.Segments.Add(new ArcSegment(new Point(height, PipeStrokeThickness / 2), new Size(height - PipeStrokeThickness / 2, height - PipeStrokeThickness / 2), 90, false, SweepDirection.Clockwise, true));
                            pathFigure1.Segments.Add(new LineSegment(new Point(width - height, PipeStrokeThickness / 2), true));
                            pathFigure2.StartPoint = new Point(height - PipeStrokeThickness / 2, height);
                            pathFigure2.Segments.Add(new ArcSegment(new Point(height, height - PipeStrokeThickness / 2), new Size(PipeStrokeThickness / 2, PipeStrokeThickness / 2), 90, false, SweepDirection.Clockwise, true));
                            pathFigure2.Segments.Add(new LineSegment(new Point(width - height, height - PipeStrokeThickness / 2), true));
                            //中心线
                            fluidPath.StartPoint = new Point(height / 2, height);
                            fluidPath.Segments.Add(new ArcSegment(new Point(height, height / 2), new Size(height / 2, height / 2), 90, false, SweepDirection.Clockwise, true));
                            fluidPath.Segments.Add(new LineSegment(new Point(width - height, height / 2), true));
                            break;
                        case JointDirection.None:
                        case JointDirection.Left:
                        case JointDirection.Right:
                            pathFigure1.StartPoint = new Point(0, PipeStrokeThickness / 2);
                            pathFigure1.Segments.Add(new LineSegment(new Point(width - height, PipeStrokeThickness / 2), true));
                            pathFigure2.StartPoint = new Point(0, height - PipeStrokeThickness / 2);
                            pathFigure2.Segments.Add(new LineSegment(new Point(width - height, height - PipeStrokeThickness / 2), true));
                            fluidPath.StartPoint = new Point(0, height / 2);
                            fluidPath.Segments.Add(new LineSegment(new Point(width - height, height / 2), true));
                            break;
                        default:
                            break;
                    }
                    switch (PipeSecondJointDirection)
                    {
                        case JointDirection.Up:
                            pathFigure1.Segments.Add(new ArcSegment(new Point(width - height + PipeStrokeThickness / 2, 0), new Size(PipeStrokeThickness / 2, PipeStrokeThickness / 2), 90, false, SweepDirection.Counterclockwise, true));
                            pathFigure2.Segments.Add(new ArcSegment(new Point(width - PipeStrokeThickness / 2, 0), new Size(height - PipeStrokeThickness / 2, height - PipeStrokeThickness / 2), 90, false, SweepDirection.Counterclockwise, true));
                            fluidPath.Segments.Add(new ArcSegment(new Point(width - height / 2, 0), new Size(height / 2, height / 2), 90, false, SweepDirection.Counterclockwise, true));
                            break;
                        case JointDirection.Down:
                            pathFigure2.Segments.Add(new ArcSegment(new Point(width - height + PipeStrokeThickness / 2, height), new Size(PipeStrokeThickness / 2, PipeStrokeThickness / 2), 90, false, SweepDirection.Clockwise, true));
                            pathFigure1.Segments.Add(new ArcSegment(new Point(width - PipeStrokeThickness / 2, height), new Size(height - PipeStrokeThickness / 2, height - PipeStrokeThickness / 2), 90, false, SweepDirection.Clockwise, true));
                            fluidPath.Segments.Add(new ArcSegment(new Point(width - height / 2, height), new Size(height / 2, height / 2), 90, false, SweepDirection.Clockwise, true));
                            break;
                        case JointDirection.None:
                        case JointDirection.Left:
                        case JointDirection.Right:
                            pathFigure1.Segments.Add(new LineSegment(new Point(width, PipeStrokeThickness / 2), true));
                            pathFigure2.Segments.Add(new LineSegment(new Point(width, height - PipeStrokeThickness / 2), true));
                            fluidPath.Segments.Add(new LineSegment(new Point(width, height / 2), true));
                            break;
                        default:
                            break;
                    }
                }
                else
                {
                    //垂直方向
                    switch (PipeFirstJointDirection)
                    {
                        case JointDirection.Left:
                            pathFigure1.StartPoint = new Point(0, width - PipeStrokeThickness / 2);
                            pathFigure1.Segments.Add(new ArcSegment(new Point(PipeStrokeThickness / 2, width), new Size(PipeStrokeThickness / 2, PipeStrokeThickness / 2), 90, false, SweepDirection.Clockwise, true));
                            pathFigure1.Segments.Add(new LineSegment(new Point(PipeStrokeThickness / 2, height - width), true));
                            pathFigure2.StartPoint = new Point(0, PipeStrokeThickness / 2);
                            pathFigure2.Segments.Add(new ArcSegment(new Point(width - PipeStrokeThickness / 2, width), new Size(width - PipeStrokeThickness / 2, width - PipeStrokeThickness / 2), 90, false, SweepDirection.Clockwise, true));
                            pathFigure2.Segments.Add(new LineSegment(new Point(width - PipeStrokeThickness / 2, height - width), true));
                            //中心线
                            fluidPath.StartPoint = new Point(0, width / 2);
                            fluidPath.Segments.Add(new ArcSegment(new Point(width / 2, width), new Size(width / 2, width / 2), 90, false, SweepDirection.Clockwise, true));
                            fluidPath.Segments.Add(new LineSegment(new Point(width / 2, height - width), true));
                            break;
                        case JointDirection.Right:
                            pathFigure1.StartPoint = new Point(width, PipeStrokeThickness / 2);
                            pathFigure1.Segments.Add(new ArcSegment(new Point(PipeStrokeThickness / 2, width), new Size(width - PipeStrokeThickness / 2, width - PipeStrokeThickness / 2), 90, false, SweepDirection.Counterclockwise, true));
                            pathFigure1.Segments.Add(new LineSegment(new Point(PipeStrokeThickness / 2, height - width), true));
                            pathFigure2.StartPoint = new Point(width, width - PipeStrokeThickness / 2);
                            pathFigure2.Segments.Add(new ArcSegment(new Point(width - PipeStrokeThickness / 2, width), new Size(PipeStrokeThickness / 2, PipeStrokeThickness / 2), 90, false, SweepDirection.Counterclockwise, true));
                            pathFigure2.Segments.Add(new LineSegment(new Point(width - PipeStrokeThickness / 2, height - width), true));
                            //中心线
                            fluidPath.StartPoint = new Point(width, width / 2);
                            fluidPath.Segments.Add(new ArcSegment(new Point(width / 2, width), new Size(width / 2, width / 2), 90, false, SweepDirection.Counterclockwise, true));
                            fluidPath.Segments.Add(new LineSegment(new Point(width / 2, height - width), true));
                            break;
                        case JointDirection.None:
                        case JointDirection.Up:
                        case JointDirection.Down:
                            pathFigure1.StartPoint = new Point(PipeStrokeThickness / 2, 0);
                            pathFigure1.Segments.Add(new LineSegment(new Point(PipeStrokeThickness / 2, height - width), true));
                            pathFigure2.StartPoint = new Point(width - PipeStrokeThickness / 2, 0);
                            pathFigure2.Segments.Add(new LineSegment(new Point(width - PipeStrokeThickness / 2, height - width), true));
                            //中心线
                            fluidPath.StartPoint = new Point(width / 2, 0);
                            fluidPath.Segments.Add(new LineSegment(new Point(width / 2, height - width), true));
                            break;
                        default:
                            break;
                    }
                    switch (PipeSecondJointDirection)
                    {
                        case JointDirection.Left:
                            pathFigure1.Segments.Add(new ArcSegment(new Point(0, height - width + PipeStrokeThickness / 2), new Size(PipeStrokeThickness / 2, PipeStrokeThickness / 2), 90, false, SweepDirection.Clockwise, true));
                            pathFigure2.Segments.Add(new ArcSegment(new Point(0, height - PipeStrokeThickness / 2), new Size(width - PipeStrokeThickness / 2, width - PipeStrokeThickness / 2), 90, false, SweepDirection.Clockwise, true));
                            //中心线
                            fluidPath.Segments.Add(new ArcSegment(new Point(0, height - width / 2), new Size(width / 2, width / 2), 90, false, SweepDirection.Clockwise, true));
                            break;
                        case JointDirection.Right:
                            pathFigure1.Segments.Add(new ArcSegment(new Point(width, height - PipeStrokeThickness / 2), new Size(width - PipeStrokeThickness / 2, width - PipeStrokeThickness / 2), 90, false, SweepDirection.Counterclockwise, true));
                            pathFigure2.Segments.Add(new ArcSegment(new Point(width, height - width + PipeStrokeThickness / 2), new Size(PipeStrokeThickness / 2, PipeStrokeThickness / 2), 90, false, SweepDirection.Counterclockwise, true));
                            //中心线
                            fluidPath.Segments.Add(new ArcSegment(new Point(width, height - width / 2), new Size(width / 2, width / 2), 90, false, SweepDirection.Counterclockwise, true));
                            break;
                        case JointDirection.None:
                        case JointDirection.Up:
                        case JointDirection.Down:
                            pathFigure1.Segments.Add(new LineSegment(new Point(PipeStrokeThickness / 2, height), true));
                            pathFigure2.Segments.Add(new LineSegment(new Point(width - PipeStrokeThickness / 2, height), true));
                            //中心线
                            fluidPath.Segments.Add(new LineSegment(new Point(width / 2, height), true));
                            break;
                        default:
                            break;
                    }
                }
                pipeGeometry.Figures.Add(pathFigure1);
                pipeGeometry.Figures.Add(pathFigure2);
                fluidGeometry.Figures.Add(fluidPath);
                return pipeGeometry;
            }
        }
        protected override Size MeasureOverride(Size constraint)
        {
            
                if (double.IsPositiveInfinity(constraint.Width)||double.IsPositiveInfinity(constraint.Height))
                {
                    if(PipeDirection == PipeDirection.Horizontality)
                    {
                        return new Size(200, 25);
                    }
                    else
                    {
                        return new Size(25, 200);
                    }
                   
                }
                                              
            return constraint;
        }
        protected override Size ArrangeOverride(Size finalSize)
        {
       
            return finalSize;
        }
        protected override void OnRender(DrawingContext drawingContext)
        {
            //绘制管道壁
            drawingContext.DrawGeometry(null, new Pen(PipeStroke, PipeStrokeThickness), DefiningGeometry);
            //绘制管道填充
            if (fillThickness > 0)
            {
                drawingContext.DrawGeometry(null, new Pen(PipeFill, fillThickness), fluidGeometry);
            }
            //绘制流体
            if (fluidGeometry.Figures.Count > 0)
            {
                Pen p = new Pen()
                {
                    Brush = PipeFluidStroke,
                    Thickness = PipeFluidStrokeThickness,
                    DashStyle = new DashStyle(PipeFluidDashArray,
                    PipeFluidDashOffset),
                    DashCap = PipeFluidDashCap
                };
                drawingContext.DrawGeometry(null, p, fluidGeometry);
            }
            base.OnRender(drawingContext);
        }
    }
    /// <summary>
    /// 管道方向
    /// </summary>
    public enum PipeDirection
    {
        Horizontality,
        Verticality
    }
    public enum JointDirection
    {
        None,
        Up,
        Down,
        Left,
        Right
    }
4.6,分页器控件(Pagination
4.6.1,效果
xaml 复制代码
 <Border Margin="10" BorderThickness="1" BorderBrush="SkyBlue" Grid.Row="1">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition></RowDefinition>
                        <RowDefinition Height="auto"></RowDefinition>
                    </Grid.RowDefinitions>
                    <ListBox ItemsSource="{Binding Data}" >
                    </ListBox>
                    <controls:Pagination  Grid.Row="1" PageSize="{Binding PageSize}"   HorizontalAlignment="Center"  TotalCount="{Binding TotalCount}" >
                        <behaviors:Interaction.Triggers>
                            <behaviors:EventTrigger EventName="PageIndexChanged" >
                                <behaviors:InvokeCommandAction Command="{Binding SelectedPageCommand}" CommandParameter="{Binding RelativeSource={RelativeSource  AncestorType=controls:Pagination,Mode=FindAncestor}, Path=PageIndex}"></behaviors:InvokeCommandAction>
                            </behaviors:EventTrigger>
                        </behaviors:Interaction.Triggers>
                    </controls:Pagination>
                </Grid>
            </Border>
4.6.2,Xaml代码
xaml 复制代码
<UserControl x:Class="WpfAssets.Assets.Controls.Pagination"
             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:sys="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d" 
             d:DesignHeight="45" d:DesignWidth="800">
    <UserControl.Resources>
        <x:Array Type="sys:Int32" x:Key="counts">
            <sys:Int32>10</sys:Int32>
            <sys:Int32>15</sys:Int32>
            <sys:Int32>20</sys:Int32>
            <sys:Int32>30</sys:Int32>
            <sys:Int32>50</sys:Int32>
        </x:Array>
        <LinearGradientBrush x:Key="MouseOver.Background" 
                     EndPoint="0,1" StartPoint="0,0">
            <GradientStop Color="#FFECF4FC" Offset="0.0"/>
            <GradientStop Color="#FFDCECFC" Offset="1.0"/>
        </LinearGradientBrush>

        <Style TargetType="Button" x:Key="FlipButtonStyle">
            <Setter Property="Foreground" Value="#555"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Width" Value="33"/>
            <Setter Property="Height" Value="27"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Border Background="{TemplateBinding Background}" 
                                BorderBrush="#EEE" BorderThickness="{TemplateBinding BorderThickness}"
                                CornerRadius="5"
                                Name="back">
                            <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" Name="content"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="{StaticResource MouseOver.Background}"/>
                    <Setter Property="BorderBrush" Value="#FF7EB4EA"/>
                </Trigger>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter Property="Foreground" Value="#CCC"/>
                    <Setter Property="BorderThickness" Value="0"/>
                </Trigger>
            </Style.Triggers>
        </Style>

        <Style TargetType="CheckBox" x:Key="PageNumberButtonStyle">
            <Setter Property="Margin" Value="1,0"/>
            <Setter Property="Foreground" Value="#888"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="CheckBox">
                        <Border Background="{TemplateBinding Background}" Name="root" CornerRadius="5" 
                        BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="#EEE"
                        Height="27" Width="27">
                            <TextBlock Text="{TemplateBinding Content}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#EEE"/>
                </Trigger>
                <Trigger Property="IsChecked" Value="True">
                    <Setter Property="Background" Value="#409EFE"/>
                    <Setter Property="Foreground" Value="White"/>
                </Trigger>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter Property="BorderThickness" Value="0"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
            <Setter Property="OverridesDefaultStyle" Value="true"/>
            <Setter Property="IsTabStop" Value="false"/>
            <Setter Property="Focusable" Value="false"/>
            <Setter Property="ClickMode" Value="Press"/>
            <Setter Property="Height" Value="27"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ToggleButton}">
                        <Border x:Name="templateRoot" SnapsToDevicePixels="true" Background="Transparent" 
                        BorderBrush="#EEE" BorderThickness="1" CornerRadius="5">
                            <Border x:Name="splitBorder" Width="25" SnapsToDevicePixels="true" Margin="0" 
                                    HorizontalAlignment="Right" BorderThickness="1" BorderBrush="Transparent">
                                <Path x:Name="arrow" VerticalAlignment="Center" Margin="0" HorizontalAlignment="Center"
                                      Stroke="Gray" StrokeThickness="1"
                                      Data="M0 0 4 4 8 0"/>
                            </Border>
                        </Border>
                        <ControlTemplate.Triggers>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>
                                    <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
                                    <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="false"/>
                                </MultiDataTrigger.Conditions>
                                <Setter Property="Background" TargetName="templateRoot" Value="{StaticResource MouseOver.Background}"/>
                                <Setter Property="BorderBrush" TargetName="templateRoot" Value="#FF7EB4EA"/>
                            </MultiDataTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ComboBox}">
            <Border Background="Transparent" CornerRadius="0,10,10,0">
                <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="20"/>
                    </Grid.ColumnDefinitions>
                    <Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" 
                           IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" 
                           Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" 
                           Placement="Bottom" PlacementTarget="{Binding ElementName=toggleButton}">

                        <Border x:Name="dropDownBorder" BorderBrush="#EEE" BorderThickness="1" 
                                Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
                                MaxHeight="{TemplateBinding MaxDropDownHeight}"
                                MinWidth="{Binding ActualWidth,ElementName=templateRoot}"
                                CornerRadius="5" Margin="3">
                            <Border.Effect>
                                <DropShadowEffect BlurRadius="5" Color="Gray" ShadowDepth="0" Opacity="0.2"/>
                            </Border.Effect>
                            <ScrollViewer x:Name="DropDownScrollViewer" Background="Transparent">
                                <Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
                                    <Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                        <Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" 
                                           Height="{Binding ActualHeight, ElementName=dropDownBorder}"
                                           Width="{Binding ActualWidth, ElementName=dropDownBorder}"
                                           RadiusX="5" RadiusY="5"/>
                                    </Canvas>
                                    <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" 
                                                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                </Grid>
                            </ScrollViewer>
                        </Border>
                    </Popup>
                    <ToggleButton x:Name="toggleButton"  BorderThickness="0" Background="Transparent" 
                                  Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                                  Style="{StaticResource ComboBoxToggleButton}"/>
                    <ContentControl x:Name="contentPresenter" Margin="5,0"
                                    ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" 
                                    ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" 
                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                    IsHitTestVisible="false"
                                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                    VerticalAlignment="Center">
                        <TextBlock>
                            <Run Text="{Binding SelectionBoxItem,RelativeSource={RelativeSource AncestorType=ComboBox,Mode=FindAncestor},Mode=OneWay}"/>
                            <Run Text="条/页"/>
                        </TextBlock>    
                    </ContentControl>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="HasItems" Value="false">
                    <Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
                </Trigger>
                <Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
                    <Setter Property="Canvas.Top" TargetName="opaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
                    <Setter Property="Canvas.Left" TargetName="opaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <Style x:Key="PagesComboBoxStyle" TargetType="{x:Type ComboBox}">
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Foreground" Value="#777"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
            <Setter Property="Padding" Value="6,3,5,3"/>
            <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
            <Setter Property="ScrollViewer.PanningMode" Value="Both"/>
            <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
            <Setter Property="Margin" Value="1,0"/>
            <Setter Property="Template" Value="{StaticResource ComboBoxTemplate}"/>
        </Style>

        <Style TargetType="ComboBoxItem">
            <Setter Property="Height" Value="30"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ComboBoxItem">
                        <Border Background="{TemplateBinding Background}" CornerRadius="4">
                            <TextBlock VerticalAlignment="Center" Margin="5,0">
                                <Run Text="{Binding Mode=OneWay}"/>
                                <Run Text="条/页"/>
                            </TextBlock>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#22409EFE"/>
                </Trigger>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="#409EFE"/>
                    <Setter Property="Foreground" Value="White"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>
    <Grid ButtonBase.Click="Grid_Click">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="auto"/>
        </Grid.ColumnDefinitions>
        <Button Style="{StaticResource FlipButtonStyle}"
                IsEnabled="{Binding IsCanPrevious,RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}"
                Tag="{Binding PreviousIndex,RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}">
            <Path Data="M5,0 0 5 5 10" Stroke="{Binding Foreground,RelativeSource={RelativeSource AncestorType=Button}}" StrokeThickness="1"
                    StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
        </Button>
        <Button Grid.Column="2" Style="{StaticResource FlipButtonStyle}"
                IsEnabled="{Binding IsCanNext,RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}"
                Tag="{Binding NextIndex,RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}">
            <Path Data="M5,0 0 5 5 10" Stroke="{Binding Foreground,RelativeSource={RelativeSource AncestorType=Button}}" StrokeThickness="1"
                    StrokeStartLineCap="Round" StrokeEndLineCap="Round" RenderTransformOrigin="0.5,0.5">
                <Path.RenderTransform>
                    <RotateTransform Angle="180"/>
                </Path.RenderTransform>
            </Path>
        </Button>
        <ComboBox Grid.Column="3" SelectedItem="{Binding PageSize,RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}"
                  ItemsSource="{Binding Source={StaticResource counts}}"
                  Style="{StaticResource PagesComboBoxStyle}"
                  Width="80"/>

        <ItemsControl Grid.Column="1" ItemsSource="{Binding PageNumList,RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Rows="1"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                     <!--1、页码   2、是否选中-->
                    <CheckBox  Content="{Binding Index}"
                              IsChecked="{Binding IsCurrent}"
                              Style="{StaticResource PageNumberButtonStyle}"
                              IsEnabled="{Binding IsEnabled}"
                              Tag="{Binding Index}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</UserControl>
4.6.3,C#代码
C# 复制代码
/// <summary>
    /// Pagination.xaml 的交互逻辑
    /// </summary>
    public partial class Pagination : UserControl, System.ComponentModel.INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public static readonly DependencyProperty PageSizeProperty;
        public static readonly DependencyProperty PageIndexProperty;
        public static readonly DependencyProperty TotalCountProperty;
        public static readonly RoutedEvent PageIndexChangedEvent;
        public Pagination()
        {
            InitializeComponent();
            // this.DataContext = this;
        }
        static Pagination()
        {
            PageSizeProperty = DependencyProperty.Register("PageSize", typeof(int), typeof(Pagination), new FrameworkPropertyMetadata(20, FrameworkPropertyMetadataOptions.AffectsRender|FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,null,PageSizeCoerceValueCallback));
            PageIndexProperty = DependencyProperty.Register("PageIndex", typeof(int), typeof(Pagination), new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsRender|FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,null,PageIndexCoerceValueCallback));
            TotalCountProperty = DependencyProperty.Register("TotalCount", typeof(int), typeof(Pagination), new FrameworkPropertyMetadata(35, FrameworkPropertyMetadataOptions.AffectsRender));
            PageIndexChangedEvent = EventManager.RegisterRoutedEvent("PageIndexChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<int>), typeof(Pagination));
        }
        private static object PageIndexCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (Convert.ToInt32(baseValue) < 1) return 1;
            return baseValue;
        }
        private static object PageSizeCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (int.Parse(baseValue.ToString()) < 0) return 0;
            return baseValue;
        }
        /// <summary>
        /// 页面索引值变化时触发该事件
        /// </summary>
        public event RoutedPropertyChangedEventHandler<int> PageIndexChanged
        {
            add
            {
                this.AddHandler(PageIndexChangedEvent, value);
            }
            remove
            {
                this.RemoveHandler(PageIndexChangedEvent, value);
            }
        }
        //public int PerPageCount { get; set; } = 20;
        /// <summary>
        /// 页面大小,一页包含的项目数量
        /// </summary>
        [Browsable(true), Category("自定义属性"), Description("页面大小,一页包含的项目数量")]
        public int PageSize
        {
            get { return (int)GetValue(PageSizeProperty); }
            set
            {
                SetValue(PageSizeProperty, value);
            }
        }
        /// <summary>
        /// 当前选择的页面索引
        /// </summary>
        [Browsable(true), Category("自定义属性"), Description("当前选择的页面索引")]
        public int PageIndex
        {
            get { return (int)GetValue(PageIndexProperty); }
            set
            {
                SetValue(PageIndexProperty, value);
            }
        }
        /// <summary>
        /// 项目总数
        /// </summary>
        [Browsable(true), Category("自定义属性"), Description("项目总数")]
        public int TotalCount
        {
            get => (int)GetValue(TotalCountProperty);
            set => SetValue(TotalCountProperty, value);
        }
        public ObservableCollection<PageNumberModel> PageNumList { get; set; } =
         new ObservableCollection<PageNumberModel>();
        private bool _isCanPrevious = true;
        public bool IsCanPrevious
        {
            get => _isCanPrevious;
            set
            {
                _isCanPrevious = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsCanPrevious"));
            }
        }
        private bool _isCanNext = true;
        public bool IsCanNext
        {
            get => _isCanNext;
            set
            {
                _isCanNext = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsCanNext"));
            }
        }
        private int _previousIndex;
        /// <summary>
        /// 前一条数据的Index     如果当前Index=2  1   3
        /// </summary>
        public int PreviousIndex
        {
            get => _previousIndex;
            set
            {
                _previousIndex = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PreviousIndex"));
            }
        }
        private int _nextIndex;
        public int NextIndex
        {
            get => _nextIndex;
            set
            {
                _nextIndex = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NextIndex"));
            }
        }
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            FillPageNumbers(TotalCount);
        }
        /// <summary>
        /// 填充刷新页码
        /// </summary>
        /// <param name="sumCount">数据总条目数</param>
        public void FillPageNumbers(int sumCount)
        {
            // 总条目数:100    per  20     num=5
            // 第一次刷新:pageIndex=1
            // 第二次刷新:点击了页码:5
            // 第三次刷新:因为重新选择了每页数量   per 30   num=4
            // 简单点的话:当重新选择了每页数量后,直接从1页开始
            // 算法处理一下的话:页码超出范围,显示最后一页
            // 这里进行PageNumList的修改     3.0    2    =1.5   == 2
            int num_count = (int)Math.Ceiling(sumCount * 1.0 / PageSize);
            if (num_count != 0 && PageIndex>num_count )
            {
                PageIndex = num_count;
            }
            PreviousIndex = PageIndex - 1;
            NextIndex = PageIndex + 1;
            // 处理前一页和后一页按钮的可用性
            //if (PageIndex == 1)
            IsCanPrevious = PageIndex != 1;
            //if (PageIndex == num_count)
            IsCanNext = PageIndex != num_count;
            // 页面的显示
            // 20   30  40   导致页面显示不了
            // 1 2 3 4 5 6 7 8 9 ...  16
            // 1 ...3 4 5 6 7 8 9 ...  16
            // 1 ... 11 12 13 14 15 16
            int min = PageIndex - 4;
            if (min <= 1) min = 1;
            else min = PageIndex - 3;
            //6
            int max = PageIndex + 4;
            if (PageIndex <= 5)
                max = Math.Min(9, num_count);
            else
            {
                if (max >= num_count) max = num_count;
                else max = PageIndex + 3;
            }
            if (PageIndex >= num_count - 4)
                min = Math.Max(1, num_count - 8);
            List<string> temp = new List<string>();
            if (min > 1)
            {
                temp.Add("1");
                temp.Add("···");
            }
            for (int i = min; i <= max; i++)
                temp.Add(i.ToString());
            if (max < num_count)
            {
                temp.Add("···");
                temp.Add(num_count.ToString());
            }
            PageNumList.Clear();
            //for (int i = 1; i <= num_count; i++)
            foreach (string str in temp)
            {
                bool state = int.TryParse(str, out int index);
                PageNumList.Add(new PageNumberModel
                {
                    Index = str,
                    IsCurrent = index == PageIndex,
                    IsEnabled = state
                });
            }
        }
        private void Grid_Click(object sender, RoutedEventArgs e)
        {
            if(e.OriginalSource is System.Windows.Controls.Primitives.ButtonBase btn)
            {
                string tag = btn.Tag?.ToString();
                if (tag != null)
                {
                    if(int.TryParse(tag,out int index))
                    {
                        int oldIndex = PageIndex;
                        PageIndex = index;
                        int newIndex = PageIndex;
                        RoutedPropertyChangedEventArgs<int> args = new RoutedPropertyChangedEventArgs<int>(oldIndex, newIndex);
                        args.RoutedEvent = PageIndexChangedEvent;
                        args.Source = this;
                        this.RaiseEvent(args);
                    }
                }
            }
        }
        public class PageNumberModel
        {
            public string Index { get; set; }
            public bool IsEnabled { get; set; } = true;
            public bool IsCurrent { get; set; }
        }
    }

5,Demo链接

https://download.csdn.net/download/lingxiao16888/92897866

相关推荐
LateFrames13 小时前
520 - 如何说晚安 (WPF)
c#·wpf·浪漫·ui体验
heimeiyingwang1 天前
【架构实战】日志体系ELK:集中化日志管理实践
elk·架构·wpf
CPU不够了1 天前
WPF 多选下拉+搜索过滤_wpf下拉选项增加搜索
wpf
FuckPatience1 天前
WPF 列表控件自动拉伸子元素的宽度
wpf
LCG元1 天前
【Go后端开发】从 0 到生产级:高性能分布式网关全实现 + 接口限流熔断降级实战
分布式·golang·wpf
枫叶林FYL2 天前
项目九:异步高性能爬虫与数据采集中枢 —— 基于 Crawl<sub>4</sub>AI 与 Playwright 的现代化数据采集平台 项目总览
爬虫·python·深度学习·wpf
她说彩礼65万2 天前
WPF 多值转换器
wpf
无心水2 天前
【分布式利器:金融级】金融级分布式架构开源框架全景解读
人工智能·分布式·金融·架构·开源·wpf·金融级框架
她说彩礼65万2 天前
WPF 转换器
wpf