Подтвердить что ты не робот

Внедрение контроля прогресса мастера в WPF

Есть ли лучший способ реализовать такой элемент управления в WPF?

Wizard Progress Control

Я могу легко реплицировать текстовые метки и индикатор выполнения (без круговых "ударов" над каждой меткой), но я хотел бы знать, есть ли там уже существующий контроль или лучшая практика для создания такого рода управления в WPF.

4b9b3361

Ответ 1

Трудно сказать, что лучше всего в этом случае, но вот как я это сделаю.

Элемент управления на вашем скриншоте выглядит как комбинация ProgressBar и ItemsControl, и в этом случае мне кажется, что мне легче получить от ItemsControl и реализовать функциональность прогресса, а затем наоборот, но это также зависит от того, как вы хотите, чтобы он работал (если вы хотите добиться плавного хода или просто подсвечиваете точки один за другим, например).

Используя UniformGrid как ItemsPanel и ItemTemplate ниже, мы получим следующий вид (Шаги - List строк)
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>

Добавление DropShadowEffect в ItemsPanel, два элемента Path в ItemTemplate и два 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;
    }
}

Ответ 2

Я тоже что-то схожу с этим. Это действительно довольно легко в WPF. В основном я создал 2 прямоугольника и перекрыл их. Прямоугольник в фоновом режиме имеет цвета градиента, а прямоугольник на переднем плане - это серая область, используемая для покрытия прямоугольника градиента.

Просто отрегулируйте ширину серого прямоугольника, чтобы создать иллюзию движения вправо или влево.

Ниже приведено изображение того, что я сделал, вместе с 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>

Ответ 3

Вы можете нарисовать полный индикатор прогресса, настроить маску отсечения для индикатора прогресса и либо изменить, либо поменять эту маску с другой маской в ​​соответствующих точках во время выполнения вашей программы. Если вы хотите получить действительно творческий подход, вы можете создать элемент управления, который может определять любое произвольное количество точек.

В этой статье рассказывается об общих обтравочных масках в выражении: http://expression.microsoft.com/en-us/cc197119

В этой статье показан код, который может быть немного более уместным: http://blog.pixelingene.com/2009/02/animating-graphs-in-wpf-using-clipping-masks/  и в этом коде вы можете легко настроить RectangleGeometry во время выполнения.

Итак, что я получаю от всего этого чтения и размышлений, так это то, что вы, возможно, попробуете свойство Clip на своем голубом индикаторе прогресса и оставьте фон как есть.

Это маршрут, который я, вероятно, возьму. Надеюсь, это поможет!