基于WPF实现步骤控件的示例代码
驚鏵 人气:0WPF 实现步骤控件
框架使用.NET40
;
Visual Studio 2019
;
Step
继承 ItemsControl
使用 Grid
嵌套 ProgressBar
和 ItemsPresenter
.
ProgressBar
用来当作步骤后面的线条,宽等于控件的(ActualWidth / Items.Count) * (Items.Count - 1)
,Maximum = Items.Count - 1
。ItemsPresenter
用来展示步骤Item
。
ItemsPanel - ItemsPanelTemplate - UniformGrid Rows="1"
横向展示,UniformGrid Columns="1"
可以控制竖向显示,只不过需要重新自定义 ItemContainerStyle
的样式。
然后创建 StepItem
继承 ContentControl
增加两个属性 Index
用来记录当前是步骤 与 State
记录状态 (等待中、进行中、已完成)。
因为继承了 ContentControl
所以可以在使用时指定 Content
显示内容,在每个步骤下方显示。
实现代码
1) Step.xaml
代码如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:controls="clr-namespace:WPFDevelopers.Controls" xmlns:converts="clr-namespace:WPFDevelopers.Converts"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Basic/ControlBasic.xaml"/> </ResourceDictionary.MergedDictionaries> <converts:IndexConverter x:Key="IndexConverter"/> <Style x:Key="DefaultStepItem" TargetType="{x:Type controls:StepItem}" BasedOn="{StaticResource ControlBasicStyle}"> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type controls:StepItem}"> <StackPanel> <controls:SmallPanel> <Ellipse Width="45" Height="30" Fill="{DynamicResource WindowForegroundColorBrush}" HorizontalAlignment="Center"/> <Border Background="{TemplateBinding Background}" HorizontalAlignment="Center" CornerRadius="15" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Height="30" Width="30"> <controls:SmallPanel> <TextBlock Foreground="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="{TemplateBinding FontSize}" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:StepItem}}, Converter={StaticResource IndexConverter}}" Name="PART_Index"/> <Path Data="{StaticResource PathComplete}" Fill="{TemplateBinding Foreground}" Stretch="Uniform" Width="12" Height="12" Name="PART_PathComplete" Visibility="Collapsed"/> </controls:SmallPanel> </Border> </controls:SmallPanel> <ContentPresenter HorizontalAlignment="Center" TextElement.FontWeight="Black" ContentTemplate="{Binding ItemTemplate,RelativeSource={RelativeSource AncestorType=controls:Step}}" TextElement.Foreground="{DynamicResource RegularTextSolidColorBrush}" Margin="0,6,0,0"/> </StackPanel> <ControlTemplate.Triggers> <Trigger Property="Status" Value="Waiting"> <Setter Property="Foreground" Value="{DynamicResource PrimaryTextSolidColorBrush}"/> <Setter Property="Visibility" TargetName="PART_PathComplete" Value="Collapsed"/> <Setter Property="Visibility" TargetName="PART_Index" Value="Visible"/> <Setter Property="Background" Value="{DynamicResource BaseSolidColorBrush}"/> </Trigger> <Trigger Property="Status" Value="InProgress"> <Setter Property="Foreground" Value="{DynamicResource DefaultBackgroundSolidColorBrush}"/> <Setter Property="Visibility" TargetName="PART_PathComplete" Value="Collapsed"/> <Setter Property="Visibility" TargetName="PART_Index" Value="Visible"/> <Setter Property="Background" Value="{DynamicResource PrimaryNormalSolidColorBrush}"/> </Trigger> <Trigger Property="Status" Value="Complete"> <Setter Property="BorderBrush" Value="{DynamicResource DefaultBackgroundSolidColorBrush}"/> <Setter Property="Background" Value="{DynamicResource DefaultBackgroundSolidColorBrush}"/> <Setter Property="Visibility" TargetName="PART_PathComplete" Value="Visible"/> <Setter Property="Visibility" TargetName="PART_Index" Value="Collapsed"/> <Setter Property="Foreground" Value="{DynamicResource PrimaryNormalSolidColorBrush}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="DefaultStep" TargetType="{x:Type controls:Step}" BasedOn="{StaticResource ControlBasicStyle}"> <Setter Property="ItemContainerStyle" Value="{StaticResource DefaultStepItem}"/> <Setter Property="VerticalContentAlignment" Value="Top"/> <Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type controls:Step}"> <controls:SmallPanel> <ProgressBar x:Name="PART_ProgressBar" Margin="0,18" Height="1" Value="{Binding StepIndex,RelativeSource={RelativeSource AncestorType=controls:Step}}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/> <ItemsPresenter/> </controls:SmallPanel> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <UniformGrid Rows="1"/> </ItemsPanelTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type controls:StepItem}" BasedOn="{StaticResource DefaultStepItem}" /> <Style TargetType="{x:Type controls:Step}" BasedOn="{StaticResource DefaultStep}" /> </ResourceDictionary>
2) Step.cs
代码如下:
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; namespace WPFDevelopers.Controls { [TemplatePart(Name = ProgressBarTemplateName, Type = typeof(ProgressBar))] public class Step : ItemsControl { private const string ProgressBarTemplateName = "PART_ProgressBar"; private ProgressBar _progressBar; public int StepIndex { get => (int)GetValue(StepIndexProperty); set => SetValue(StepIndexProperty, value); } public static readonly DependencyProperty StepIndexProperty = DependencyProperty.Register( "StepIndex", typeof(int), typeof(Step), new PropertyMetadata(0, OnStepIndexChanged)); private static void OnStepIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var step = (Step)d; var stepIndex = (int)e.NewValue; step.UpdateStepItemState(stepIndex); } void UpdateStepItemState(int stepIndex) { var count = Items.Count; if (count <= 0) return; if (stepIndex >= count) { StepIndex--; return; } if (stepIndex < 0) { StepIndex++; return; } for (var i = 0; i < stepIndex; i++) { if (ItemContainerGenerator.ContainerFromIndex(i) is StepItem stepItem) stepItem.Status = Status.Complete; } if (ItemContainerGenerator.ContainerFromIndex(stepIndex) is StepItem itemInProgress) itemInProgress.Status = Status.InProgress; for (var i = stepIndex + 1; i < Items.Count; i++) { if (ItemContainerGenerator.ContainerFromIndex(i) is StepItem stepItem) stepItem.Status = Status.Waiting; } } public override void OnApplyTemplate() { base.OnApplyTemplate(); _progressBar = GetTemplateChild(ProgressBarTemplateName) as ProgressBar; } protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); var count = Items.Count; if (_progressBar == null || count <= 0) return; _progressBar.Maximum = count - 1; _progressBar.Value = StepIndex; _progressBar.Width = (ActualWidth / count) * (count - 1); } protected override bool IsItemItsOwnContainerOverride(object item) { return item is StepItem; } protected override DependencyObject GetContainerForItemOverride() { return new StepItem(); } public Step() { ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; } public void Next() { StepIndex++; } public void Previous() { StepIndex--; } private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { var count = Items.Count; if (count <= 0) return; UpdateStepItemState(StepIndex); } } } }
3) StepItem.cs
代码如下:
using System.Windows; using System.Windows.Controls; namespace WPFDevelopers.Controls { public class StepItem : ContentControl { public static readonly DependencyProperty IndexProperty = DependencyProperty.Register( "Index", typeof(int), typeof(StepItem), new PropertyMetadata(-1)); public int Index { get => (int)GetValue(IndexProperty); internal set => SetValue(IndexProperty, value); } public static readonly DependencyProperty StatusProperty = DependencyProperty.Register( "Status", typeof(Status), typeof(StepItem), new PropertyMetadata(Status.Waiting)); public Status Status { get => (Status)GetValue(StatusProperty); internal set => SetValue(StatusProperty, value); } } }
4) Status.cs
代码如下:
namespace WPFDevelopers.Controls { /// <summary> ///状态值 /// </summary> public enum Status { /// <summary> /// 等待中 /// </summary> Waiting, /// <summary> /// 正在进行中 /// </summary> InProgress, /// <summary> /// 完成 /// </summary> Complete } }
5) StepExample.xaml
代码如下:
<UserControl x:Class="WPFDevelopers.Samples.ExampleViews.StepExample" 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:controls="clr-namespace:WPFDevelopers.Samples.Controls" xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers" xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <controls:CodeViewer> <StackPanel VerticalAlignment="Center" > <UniformGrid Columns="2" Name="PART_UniformGrid"> <wd:Step x:Name="PART_Step" StepIndex="{Binding Progress}"> <wd:StepItem Content="填写账号"/> <wd:StepItem Content="身份验证"/> <wd:StepItem Content="设置新密码"/> <wd:StepItem Content="完成"/> </wd:Step> <wd:Step StepIndex="0" ItemsSource="{Binding Steps}"> </wd:Step> </UniformGrid> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="10"> <Button Content="上一步" Command="{Binding PreviousCommand}" CommandParameter="{Binding ElementName=PART_UniformGrid}" Style="{StaticResource PrimaryButton}"/> <Button Content="下一步" Command="{Binding NextCommand}" CommandParameter="{Binding ElementName=PART_UniformGrid}" Style="{StaticResource PrimaryButton}"/> </StackPanel> </StackPanel> <controls:CodeViewer.SourceCodes> <controls:SourceCodeModel CodeSource="/WPFDevelopers.SamplesCode;component/ExampleViews/StepExample.xaml" CodeType="Xaml"/> <controls:SourceCodeModel CodeSource="/WPFDevelopers.SamplesCode;component/ExampleViews/StepExample.xaml.cs" CodeType="CSharp"/> </controls:CodeViewer.SourceCodes> </controls:CodeViewer> </UserControl>
6) StepExample.xaml.cs
代码如下:
using System; using System.Collections.ObjectModel; using System.Linq; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using WPFDevelopers.Controls; using WPFDevelopers.Samples.Helpers; namespace WPFDevelopers.Samples.ExampleViews { /// <summary> /// StepExample.xaml 的交互逻辑 /// </summary> public partial class StepExample : UserControl { public ObservableCollection<string> Steps { get; set; } public StepExample() { InitializeComponent(); Steps = new ObservableCollection<string>(); Steps.Add("Step 1"); Steps.Add("Step 2"); Steps.Add("Step 3"); Steps.Add("Step 4"); this.DataContext = this; } public ICommand NextCommand => new RelayCommand(new Action<object>((sender) => { var uniformGrid = sender as UniformGrid; if (uniformGrid == null) return; foreach (var step in uniformGrid.Children.OfType<Step>()) step.Next(); })); public ICommand PreviousCommand => new RelayCommand(new Action<object>((sender) => { var uniformGrid = sender as UniformGrid; if (uniformGrid == null) return; foreach (var step in uniformGrid.Children.OfType<Step>()) step.Previous(); })); } }
效果图
加载全部内容