在WPF中实现向导进度控制

29

在WPF中实现这样的控件有最佳的方法吗?

Wizard Progress Control

我可以轻松复制文本标签和进度条(没有每个标签上方的圆形“隆起”),但我想知道是否已经有控件或最佳实践可用于创建这种类型的控件在WPF中。


我认为现在没有任何完全像这样的东西。我猜你想让标签和比例动态化,是吗? - EightyOne Unite
最好的情况是,我希望能够放置一个标签的ItemsControl(或其他列表)并选择当前步骤或索引,但我也愿意考虑其他选项。 - Adam Maras
3个回答

95

在这种情况下很难说什么是最佳实践,但以下是我会做的方式。

你截图中的向导控件看起来像是 ProgressBarItemsControl 的组合,在这种情况下,对我来说从 ItemsControl 派生并实现进度功能似乎更容易,但这也取决于你希望它如何工作(例如,如果你想要平滑的进度条还是只逐个点亮)。

使用下面的 ItemTemplateUniformGrid 作为 ItemsPanel,我们得到以下外观(Steps 是字符串列表
enter image description here

<ItemsControl ItemsSource="{Binding Steps}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Rows="1"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Ellipse HorizontalAlignment="Center" Height="20" Width="20" Stroke="Transparent" Fill="Blue"/>
                <TextBlock Grid.Row="1" Text="{Binding}" HorizontalAlignment="Center" Margin="0,5,0,0"/>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

ItemsPanel中添加DropShadowEffect,在ItemTemplate中添加两个Path元素,并使用两个DataTriggers来确定当前项目是否是要显示/隐藏左/右Path中的第一个或最后一个项目,这样我们可以获得与您的屏幕截图相似的外观。
enter image description here

ItemsPanel

<UniformGrid Rows="1" SnapsToDevicePixels="True">
    <UniformGrid.Effect>
        <DropShadowEffect Color="Black"
                          BlurRadius="5"
                          Opacity="0.6"
                          ShadowDepth="0"/>
    </UniformGrid.Effect>
</UniformGrid>

ItemTemplate

<DataTemplate>
    <DataTemplate.Resources>
        <Style TargetType="Path">
            <Setter Property="Data" Value="M0.0,0.0 L0.0,0.33 L1.0,0.33 L1.0,0.66 L0.0,0.66 L0.0,1.0"/>
            <Setter Property="StrokeThickness" Value="0"/>
            <Setter Property="Height" Value="21"/>
            <Setter Property="Stretch" Value="Fill"/>
            <Setter Property="Fill" Value="{StaticResource wizardBarBrush}"/>
            <Setter Property="StrokeEndLineCap" Value="Square"/>
            <Setter Property="StrokeStartLineCap" Value="Square"/>
            <Setter Property="Stroke" Value="Transparent"/>
        </Style>
    </DataTemplate.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Path Name="leftPath"/>
        <Path Name="rightPath" Grid.Column="1"/>
        <Ellipse Grid.ColumnSpan="2" HorizontalAlignment="Center" Height="20" Width="20" Stroke="Transparent" Fill="{StaticResource wizardBarBrush}"/>
        <TextBlock Grid.ColumnSpan="2" Grid.Row="1" Text="{Binding}" HorizontalAlignment="Center" Margin="0,5,0,0"/>
    </Grid>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}"
                     Value="{x:Null}">
            <Setter TargetName="leftPath" Property="Visibility" Value="Collapsed"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={markup:IsLastItemConverter}}"
                     Value="True">
            <Setter TargetName="rightPath" Property="Visibility" Value="Collapsed"/>
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

如果您决定使用此方法,您可能可以想出如何使其余部分正常工作,例如:

  • 在可重复使用的自定义控件中实现此功能
  • 只在进度部分(DropShadowEffect)上而不是文本中获取描边
  • 实现进度功能等

无论如何,我上传了一个名为WizardProgressBar的自定义控件的示例项目和一个使用它的演示项目,位置在这里:https://www.dropbox.com/s/ng9vfi6uwn1peot/WizardProgressBarDemo2.zip?dl=0

它看起来像这样
enter image description here

有关示例的注意事项

  • 我最终面临的情况是,在进度部分和标题中同时获得DropShadowEffect 或在每个项目之间获得细线(如图片中所示)。 我想不出轻松摆脱它的方法,所以也许这并不是最好的方法 :)
  • 进度部分很简单。 它只有一个值介于0-100之间,然后转换器决定该项目是否应点亮
  • 此控件可能会对性能产生一些影响,但我不能确定,因为今天我电脑上的所有东西都跑得很慢......

更新

对示例项目进行了一些更改,将演示分成了两个ItemsControls,以消除每个项目之间的细线。 现在它看起来是这样的:
enter image description here
在此处上传:https://www.dropbox.com/s/ng9vfi6uwn1peot/WizardProgressBarDemo2.zip?dl=0

更新结束

以下是上面示例代码中缺少的部分

<LinearGradientBrush x:Key="wizardBarBrush" StartPoint="0.5,0.0" EndPoint="0.5,1.0">
    <GradientStop Color="#FFE4E4E4" Offset="0.25"/>
    <GradientStop Color="#FFededed" Offset="0.50"/>
    <GradientStop Color="#FFFCFCFC" Offset="0.75"/>
</LinearGradientBrush>

IsLastItemConverter

public class IsLastItemConverter : MarkupExtension, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        ContentPresenter contentPresenter = value as ContentPresenter;
        ItemsControl itemsControl = ItemsControl.ItemsControlFromItemContainer(contentPresenter);
        int index = itemsControl.ItemContainerGenerator.IndexFromContainer(contentPresenter);
        return (index == (itemsControl.Items.Count - 1));
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }

    public IsLastItemConverter() { }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

7
不可思议,非常全面的回答。你应该获得比这篇帖子所得的赏金多两倍的奖励。谢谢! - Adam Maras
1
@Meleak 我希望有人能解决这个问题。你应该把它放在你的博客上,这样 Google 就可以找到它。非常好的解决方案! - Bahri Gungor
向您致敬!我真的很喜欢它,但我一直在努力使步骤可点击。最终通过在数据上下文中添加按钮来发出命令并将其绑定到WizardProgressBar中,我终于让它工作了。 - Wouter
您还有在Dropbox上链接中的示例代码吗?我尝试了上面的链接,但目前都失效了。 - John Hartsock
有点惊人的是,一个更大的实体(Telerik、DevExpress)居然没有构建这个,但如果他们确实做了,我肯定找不到。非常感谢你@FredrikHedblad! - Rob Perkins
显示剩余4条评论

2
我也做过类似的事情。在WPF中,实际上非常容易。基本上我创建了两个矩形并将它们重叠在一起。背景中的矩形具有渐变颜色,前景中的矩形是用来覆盖渐变矩形的灰色区域。
只需调整灰色矩形的宽度,就可以产生条形图向左或向右移动的幻觉。
下面是我所做的图片和XAML代码。
<Border BorderThickness="2" BorderBrush="Black" CornerRadius="2">
    <Canvas x:Name="canvasMain" Height="80" Width="330"  VerticalAlignment="Top" Background="White" SnapsToDevicePixels="True">

        <Rectangle x:Name="recMainBar" Height="30" Canvas.Left="0" Canvas.Top="30" Stroke="Black" Width="300">
            <Rectangle.Fill>
                <LinearGradientBrush EndPoint="1,1" MappingMode="RelativeToBoundingBox" StartPoint="0,0" SpreadMethod="Reflect">
                    <GradientStop Color="#FFF5400A"/>
                    <GradientStop Color="#FF54C816" Offset="1"/>
                    <GradientStop Color="#FF31C614" Offset="0.996"/>
                </LinearGradientBrush>
            </Rectangle.Fill>
        </Rectangle>

        <!-- Cover of the bar -->
        <Rectangle x:Name="recMainBarCover" Height="30" Canvas.Top="30" Canvas.Left="0" Stroke="Black" Width="300" Fill="#FFEBEBEB"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="2.0" TextWrapping="Wrap" Text="0%" Canvas.Top="66.95" Width="16" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="30" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="30" Text="10%" Canvas.Top="66.95" Width="21" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="60" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="60" Text="20%" Canvas.Top="66.95" Width="21" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="90" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="90" Text="30%" Canvas.Top="66.95" Width="21" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="120" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="120" Text="40%" Canvas.Top="66.95" Width="21" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="150" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="145" FontWeight="Bold" Text="50%" Canvas.Top="66.95" Width="31" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="180" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="180" Text="60%" Canvas.Top="66.95" Width="27" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="210" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="210" Text="70%" Canvas.Top="66.95" Width="27" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="240" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="240" Text="80%" Canvas.Top="66.95" Width="27" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="270" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="270" Text="90%" Canvas.Top="66.95" Width="27" RenderTransformOrigin="-0.051,-0.9"/>

        <Path Data="M8,34 L24.5,48.95" Fill="Green" Height="6.95" Stretch="Fill" Stroke="Black" Canvas.Left="300" Canvas.Top="60" Width="5"/>
        <TextBlock FontSize="10" Height="15" Canvas.Left="300" Text="100%" Canvas.Top="66.95" Width="27" RenderTransformOrigin="-0.051,-0.9"/>

        <TextBlock Name="txtTitle" FontSize="16" FontWeight="Bold" Background="Black" Foreground="White" Height="30" Canvas.Left="0" Text="Confidence Factor" Canvas.Top="0" Width="330" HorizontalAlignment="Center" TextAlignment="Center"/>

    </Canvas>
</Border>

1
就像我之前所说的那样,重新创建文本标签和进度条(而不是在每个标签上都有一个凸起)很容易。我正在寻找精确复制我发布的设计作为可重用控件。 - Adam Maras

0
你可以绘制完整的进度指示器,为进度指示器设置裁剪蒙版,并在程序执行的适当时刻更改该蒙版或替换为另一个蒙版。如果你想变得更有创意,还可以创建一个控件,用于定义任意数量的点。
本文介绍了 Expression 中的通用裁剪蒙版:http://expression.microsoft.com/en-us/cc197119 这篇文章向你展示了一些相关代码: http://blog.pixelingene.com/2009/02/animating-graphs-in-wpf-using-clipping-masks/ 并且在这段代码中,你可以轻松地在运行时调整 RectangleGeometry。
所以我从这些阅读和思考中得出的结论是,你可能会尝试在蓝色进度指示器上使用 Clip 属性,并保留背景不变。
这可能是我会采取的方法。希望这可以帮到你!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接