How can I stop the WPF ProgressBar pulsing/animating when it reaches 100%?

后端 未结 5 966
心在旅途
心在旅途 2021-02-05 03:52

I have an MVVM-based WPF 4 application which uses a ProgressBar to show the percentage completion of a long-running operation.



        
5条回答
  •  我在风中等你
    2021-02-05 04:17

    I wrote a generalized solution for this using an attached property, allowing me to toggle the behavior on any ProgressBar simply through a direct property or style setter, like so:

    
    

    The code:

    public static class ProgressBarHelper {
        public static readonly DependencyProperty StopAnimationOnCompletionProperty =
            DependencyProperty.RegisterAttached("StopAnimationOnCompletion", typeof(bool), typeof(ProgressBarHelper),
                                                new PropertyMetadata(OnStopAnimationOnCompletionChanged));
    
        public static bool GetStopAnimationOnCompletion(ProgressBar progressBar) {
            return (bool)progressBar.GetValue(StopAnimationOnCompletionProperty);
        }
    
        public static void SetStopAnimationOnCompletion(ProgressBar progressBar, bool value) {
            progressBar.SetValue(StopAnimationOnCompletionProperty, value);
        }
    
        private static void OnStopAnimationOnCompletionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
            var progressBar = obj as ProgressBar;
            if (progressBar == null) return;
    
            var stopAnimationOnCompletion = (bool)e.NewValue;
    
            if (stopAnimationOnCompletion) {
                progressBar.Loaded += StopAnimationOnCompletion_Loaded;
                progressBar.ValueChanged += StopAnimationOnCompletion_ValueChanged;
            } else {
                progressBar.Loaded -= StopAnimationOnCompletion_Loaded;
                progressBar.ValueChanged -= StopAnimationOnCompletion_ValueChanged;
            }
    
            if (progressBar.IsLoaded) {
                ReevaluateAnimationVisibility(progressBar);
            }
        }
    
        private static void StopAnimationOnCompletion_Loaded(object sender, RoutedEventArgs e) {
            ReevaluateAnimationVisibility((ProgressBar)sender);
        }
    
        private static void StopAnimationOnCompletion_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) {
            var progressBar = (ProgressBar)sender;
    
            if (e.NewValue == progressBar.Maximum || e.OldValue == progressBar.Maximum) {
                ReevaluateAnimationVisibility(progressBar);
            }
        }
    
        private static void ReevaluateAnimationVisibility(ProgressBar progressBar) {
            if (GetStopAnimationOnCompletion(progressBar)) {
                var animationElement = GetAnimationElement(progressBar);
                if (animationElement != null) {
                    if (progressBar.Value == progressBar.Maximum) {
                        animationElement.SetCurrentValue(UIElement.VisibilityProperty, Visibility.Collapsed);
                    } else {
                        animationElement.InvalidateProperty(UIElement.VisibilityProperty);
                    }
                }
            }
        }
    
        private static DependencyObject GetAnimationElement(ProgressBar progressBar) {
            var template = progressBar.Template;
            if (template == null) return null;
    
            return template.FindName("PART_GlowRect", progressBar) as DependencyObject;
        }
    }
    

    Basically, it adds a ValueChanged handler which adjusts the visibility of the animated element.

    A few notes:

    • I'm using "PART_GlowRect" to find the animated element, although someone called this a hack. I disagree: this element name is officially documented through TemplatePartAttribute, which you can see in ProgressBar's declaration. While it's true that this doesn't necessarily guarantee that the named element exists, the only reason it should be missing is if the animation feature isn't supported at all. If it is supported but uses a different element name than the one documented, I would consider that a bug, not an implementation detail.

    • Since I'm pulling an element out of the template, it's also necessary to handle the Loaded event (which is raised when a template is applied) to wait for the template to become available before attempting to set initial visibility, and if necessary set it again when the template is replaced on the fly by a theme change.

    • Rather than explicitly toggling Visibility between Collapsed and Visible, I'm using SetCurrentValue to set to Collapsed, and InvalidateProperty to reset it. SetCurrentValue applies a value that does not take priority over other value sources, and InvalidateProperty re-evaluates the property without taking the SetCurrentValue setting into consideration. This ensures that if there are existing styles or triggers which would affect the visibility under normal conditions (i.e. when it is not at 100%), it would reset to that behavior if the progress bar is reused (going from 100% back to 0%) rather than being hard-coded to Visible.

提交回复
热议问题