Implementing a wizard progress control in WPF

前端 未结 3 1580
闹比i
闹比i 2020-12-23 01:51

Is there a best way to implement a control like this in WPF?

\"Wizard

I can easily r

相关标签:
3条回答
  • 2020-12-23 02:28

    You could draw the full progress indicator, set up a clipping mask for the progress indicator, and either alter or swap that mask with another mask at the appropriate points during your program's execution. If you wanted to get really creative, you could make a control that can define any arbitrary number of points.

    This article tells you about generic clipping masks in Expression: http://expression.microsoft.com/en-us/cc197119

    This article shows you some code that may be a little more relevant: http://blog.pixelingene.com/2009/02/animating-graphs-in-wpf-using-clipping-masks/ and in this code, you could easily adjust the RectangleGeometry at runtime.

    So what I get from all this reading and pondering is that you'd possibly try the Clip property on your blue progress indicator, and leave the background as-is.

    That's the route I would probably take. Hope this helps!

    0 讨论(0)
  • 2020-12-23 02:32

    I did something similar to this too. It's actually fairly easy in WPF. Basically I created 2 rectangles and overlapped them. The rectange in the background has the gradient colors and the rectangle in the foreground is the gray area used to cover up the gradient rectangle.

    Simply adjust the width of the gray rectangle to give the illusion of the bar moving right or left.

    Below is an image of what I did, along with the XAML.

    enter image description here

    <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>
    
    0 讨论(0)
  • 2020-12-23 02:40

    It's hard to say what the best practice is in this case but here is how I would do it.

    The wizard control in your screenshot looks like a combination of a ProgressBar and an ItemsControl and in this case it seems easier to me to derive from ItemsControl and implement the progress functionality than the other way around but it also depends on how you want it to work (if you want a smooth progress or just light up the dots one-by-one for example).

    Using a UniformGrid as ItemsPanel and the ItemTemplate below, we get the following look (Steps is a List of strings)
    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>
    

    Adding a DropShadowEffect to the ItemsPanel, two Path elements to the ItemTemplate and two DataTriggers to determine if the current item is the first or last item to show/hide the left/right Path and we can get a pretty similar look to your screenshot
    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>
    

    If you decide to use this approach you can probably work out how to get the rest of it going, like

    • Implement this in a reusable custom control
    • Only get the stroke (DropShadowEffect) on the progress-part and not in the text
    • Implement the progress functionality etc.

    Anyway, I uploaded a sample project with a custom control called WizardProgressBar and a demo project using it here: https://www.dropbox.com/s/ng9vfi6uwn1peot/WizardProgressBarDemo2.zip?dl=0

    It looks like this
    enter image description here

    Things to note about the sample

    • I ended up in a situation where I would get the DropShadowEffect on the progress-part and the headers or get a thin line between each item (as seen in the picture). I can't think of an easy way to get rid of it so maybe this isn't the best approach after all :)
    • The progress-part is simple. It just has a value between 0-100 and then a converter decides if the item should be lit or not
    • This control might have a small performance impact but I can't be sure since my computer seems to be running everything slow today...

    Update

    Made a few changes to the sample project where I split the presentation into two ItemsControls to get rid of the thin lines between each item. It now looks like this
    enter image description here
    Uploaded it here: https://www.dropbox.com/s/ng9vfi6uwn1peot/WizardProgressBarDemo2.zip?dl=0

    End of Update

    And here are the missing parts from the sample code above

    <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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题