如何在ProgressBar中值为0时显示PART_Indicator? WPF

时间:2018-05-31 18:21:12

标签: c# wpf

我有ProgressBar的自定义样式,我试图在条形图的开头显示一个椭圆,但是当ProgressBar.Value = 0椭圆没有显示时,只有当ProgressBar.Value > 5,有没有办法强制PART_Indicator显示椭圆?

这是一个完整且可重现的示例,只需单击开始按钮即可启动计时器:

欢迎任何想法,提前谢谢!

代码隐藏:

namespace ProgressBar
{
    using System;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Threading;

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private TimeSpan _elapsedTime;

        private TimeSpan _estimatedTotalTime;

        private bool _isIndeterminate;

        private DispatcherTimer _progressBarTimer;

        private double _timeProgress;

        public MainWindow()
        {
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public TimeSpan ElapsedTime
        {
            get => _elapsedTime;
            set
            {
                _elapsedTime = value;
                NotifyPropertyChanged();
            }
        }

        public TimeSpan EstimatedTotalTime
        {
            get => _estimatedTotalTime;
            set
            {
                _estimatedTotalTime = value;
                NotifyPropertyChanged();
            }
        }

        public bool IsIndeterminate
        {
            get => _isIndeterminate;
            set
            {
                _isIndeterminate = value;
                NotifyPropertyChanged();
            }
        }

        public DispatcherTimer ProgressBarTimer
        {
            get => _progressBarTimer;
            set
            {
                _progressBarTimer = value;
                NotifyPropertyChanged();
            }
        }

        public double TimeProgress
        {
            get => _timeProgress;
            set
            {
                _timeProgress = value;
                NotifyPropertyChanged();
            }
        }

        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private void OnStart(object sender, RoutedEventArgs routedEventArgs)
        {
            EstimatedTotalTime = new TimeSpan(0, 0, 60);
            ElapsedTime = new TimeSpan();

            ProgressBarTimer = new DispatcherTimer(
                                   new TimeSpan(0, 0, 1),
                                   DispatcherPriority.Normal,
                                   OnTick,
                                   Dispatcher.CurrentDispatcher) {
                                                                    IsEnabled = false 
                                                                 };
            ProgressBarTimer.Start();
        }

        private void OnTick(object sender, EventArgs e)
        {
            ElapsedTime = ElapsedTime.Add(new TimeSpan(0, 0, 1));

            if (ElapsedTime.TotalSeconds.Equals(EstimatedTotalTime.TotalSeconds))
            {
                IsIndeterminate = true;

                return;
            }

            TimeProgress = ElapsedTime.TotalSeconds * 100 / EstimatedTotalTime.TotalSeconds;
        }
    }
}

XAML:

<Window x:Class="ProgressBar.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:ProgressBar"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="800"
        Height="450"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        mc:Ignorable="d">
    <Window.Resources>
        <Style x:Key="LevelMeterProgressBarStyle"
               TargetType="{x:Type ProgressBar}">
            <Setter Property="Background" Value="#FFBDBDBD" />
            <Setter Property="Foreground" Value="#FF348781" />
            <Setter Property="BorderThickness" Value="0" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ProgressBar}">
                        <Grid Name="TemplateRoot">

                            <Rectangle Name="PART_Track"
                                       Height="5.5"
                                       HorizontalAlignment="Stretch"
                                       VerticalAlignment="Center"
                                       Fill="{TemplateBinding Background}"
                                       SnapsToDevicePixels="True" />

                            <Decorator x:Name="PART_Indicator"
                                       HorizontalAlignment="Left"
                                       VerticalAlignment="Center">

                                <Grid HorizontalAlignment="Stretch"
                                      VerticalAlignment="Stretch">

                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        <ColumnDefinition Width="Auto" />
                                    </Grid.ColumnDefinitions>

                                    <Rectangle Grid.Column="0"
                                               Height="5.5"
                                               VerticalAlignment="Center"
                                               Fill="{TemplateBinding Foreground}" />

                                    <Ellipse Grid.Column="1"
                                             Width="33"
                                             Height="33"
                                             VerticalAlignment="Center"
                                             Fill="{TemplateBinding Foreground}"
                                             Stretch="Fill" />
                                </Grid>
                            </Decorator>

                            <Border BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}"
                                    CornerRadius="2" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <StackPanel>
        <Button Margin="20"
                HorizontalAlignment="Center"
                Click="OnStart"
                Content="Start" />
        <TextBlock Margin="20">
            <TextBlock.Text>
                <MultiBinding StringFormat="{}{0:00}:{1:00}:{2:00} / {3:00}:{4:00}:{5:00}">
                    <Binding Path="EstimatedTotalTime.Hours" />
                    <Binding Path="EstimatedTotalTime.Minutes" />
                    <Binding Path="EstimatedTotalTime.Seconds" />
                    <Binding Path="ElapsedTime.Hours" />
                    <Binding Path="ElapsedTime.Minutes" />
                    <Binding Path="ElapsedTime.Seconds" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
        <ProgressBar Margin="20"
                     IsIndeterminate="{Binding IsIndeterminate}"
                     Maximum="100"
                     Minimum="0"
                     Style="{DynamicResource LevelMeterProgressBarStyle}"
                     Value="{Binding TimeProgress}" />
    </StackPanel>
</Window>

2 个答案:

答案 0 :(得分:2)

我觉得这并不容易。进度条指示符的宽度是根据此公式以私有方法设置的:

 this._indicator.Width = (this.IsIndeterminate || maximum <= minimum ? 1.0 : (num - minimum) / (maximum - minimum)) * this._track.ActualWidth;

您必须根据RangeBase

编写自己的自定义进度条

答案 1 :(得分:1)

最快的解决方案是在MinWidth="33"上设置"PART_Indicator" - MinWidth优先于Width,因此椭圆将始终完全可见。这种方法的缺点是椭圆将保持“静止”,直到值足够高,以使指示器的总宽度高于椭圆的宽度(我认为在你的情况下它是5),我认为这是不合需要的

解决方案的关键在于将椭圆移动到"PART_Indicator"之外,如@GenericTeaCup所述。这个想法是设置轨道条的水平边距和指标等于椭圆宽度的一半,这样当值最小和最大时,它的左右两侧正好在椭圆的中心点之下。这样我们就可以轻松地将椭圆移动到指示器的实际宽度,并使其始终位于我们想要的确切位置(直接位于指示器右侧的中心)。现在我不确定你对赛道栏的开始和结束有什么期望,所以我会给你两个我认为最常见的选项。

第一个看起来像这样(我为椭圆增加了一些透明度,以便明显区别):

Start

End

此模板应产生描述结果:

<ControlTemplate TargetType="{x:Type ProgressBar}">
    <Grid x:Name="TemplateRoot">
        <Rectangle x:Name="LeftFill" Height="5.5" Width="17" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}" SnapsToDevicePixels="True" />
        <Rectangle x:Name="RightFill" Height="5.5" Width="17" HorizontalAlignment="Right" VerticalAlignment="Center" Fill="{TemplateBinding Background}" SnapsToDevicePixels="True" />
        <Rectangle x:Name="PART_Track" Height="5.5" Margin="16.5,0" HorizontalAlignment="Stretch" VerticalAlignment="Center" Fill="{TemplateBinding Background}" SnapsToDevicePixels="True" />
        <Rectangle x:Name="PART_Indicator" Height="5.5" Margin="16.5,0" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}" SnapsToDevicePixels="True" />
        <Ellipse Width="33" Height="33" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}" Stretch="Fill" SnapsToDevicePixels="True">
            <Ellipse.RenderTransform>
                <TranslateTransform X="{Binding Source={x:Reference PART_Indicator}, Path=ActualWidth}" />
            </Ellipse.RenderTransform>
        </Ellipse>
        <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2" SnapsToDevicePixels="True" />
    </Grid>
</ControlTemplate>

第二个选项是:

Start

End

为了获得它,您只需从模板中删除"LeftFill""RightFill"即可。请注意,跟踪栏似乎相对于ProgressBar本身缩进。

如果您对在椭圆上使用RenderTransform感到不舒服,可以将其放在Canvas并绑定Canvas.Left属性中。