Click here to Skip to main content
13,629,039 members
Click here to Skip to main content
Add your own
alternative version

Stats

45.3K views
794 downloads
18 bookmarked
Posted 3 Sep 2014
Licenced CPOL

The Problem of Unwanted WPF ProgressBar Animation: a Clearer Solution

Rate this:
Please Sign up or sign in to vote.
A monstrous XAML solution is hardly adequate to such a simple problem

WPF is like a naughty horse: sometimes, you can perform high jumps on it, but sometimes, when you want it to turn to the right on the haunches and stay still, it will buck.

V. Pupkin, Equestrian

Table of Contents

1. Introduction

This article is pretty much trivial.

The main reason for me to write on this topic is the high demand in such a solution which I noticed when I faced with the problem myself. I faced with this problem while working at my Sound Recorder described in my previous article: Practical Sound Recorder with Sound Activation.

All the solutions I found on the Web looked unsatisfactory, if not monstrous. I think the reason for it is not a lack of knowledge, but some kind of wrong thinking, or some inertia of thinking.

People, exactly like myself, tried to get rid of that annoying animation effect of the WPF ProgressBar. It can be really important for them, because they wanted to use it as the graphic indicator of some value, not necessarily progress. In addition to unwanted look, there is one other problem: this unwanted animation considerably compromise performance by using some CPU time during prolonged period of time. Not nice at all that it cannot be turned off.

Also, it looks like a very general WPF problem.

If you just start entering first words of [WPF ProgresBar remove…] into Google search, you will immediately see that this request is quite popular. Some other related cases were to simulate Aero style for this control: [WPF aero ProgressBar] (WPF has nothing to do with Aero.)

2. The Problem

I found tens of attempts to solve the problem, but, amazingly, many "authors" simply copied the solution verbosely from one post to another, so I have no idea who was the original author of one of the most quoted code sample. The earliest posts I managed to find were these two:

http://stackoverflow.com/questions/1875404/wpf-progressbar-continue-animation
http://www.techques.com/question/1-1875404/WPF-Progressbar-continue-animation

As even the names of the pages are identical, it's likely that one is copied from another one. :-))

What's wrong with this solution? Well, just look at it and think: how much of the functionality of the base class ProgressBar is actually used. Next to nothing, right? At the same time, this code will considerably contaminate XAML, where the actual indent would be having just one line declaring the use of this control with some attribute.

Also, what happens to the code reuse? Generally, reuse level with XAML is already pretty low. Even though you can put the code shown above in a resource dictionary, what if in the same or different applications the colors should be different? (Please see my take-my-eye-out coloration in the sample code screen shot shown above. Sorry if the colors are too irritating, for me, it was more important to illustrate the idea. :-)) What, starting it all over again and again?

Why is it so? Well, first of all, because XAML is a notoriously naughty horse: some obviously trivial options appeared to be surprisingly hard to implement. But the main reason is some inertia of thinking. If some desired behavior almost perfectly matches the behavior of some available class, it does not mean that this class would be a perfect base class. In this case, tiny little annoyance, the unwanted animated effect, spoils it all.

What to do?

3. The Solution

I explained why I found the XAML solution described above inappropriate. At the same time, the required behavior is so trivial, that it could be modeled using available primitive shapes. This is the simple prototype:

<Border BorderBrush="Black" BorderThickness="0.6">
    <Rectangle Name="rectangleValue" HorizontalAlignment="Left" Fill="Blue" Width="30"/>
</Border>

But of course this is a purely ad-hoc "solution" which should not be used in practice. But this idea is easy to implement in code.

3.1. Step One

This is the complete solution:

namespace Demo.Article.Samples {
    using System.Windows;
    using System.Windows.Shapes;
    using System.Windows.Controls;
    using System.Windows.Media;

    public partial class StripIndicator : ContentControl {

        static class DefinitionSet {
            internal const double DefaultSize = 20;
            internal const double DefaultMaximumValue = 100;
            internal const double BorderThickness = 0.6;
            internal static readonly Brush Border = Brushes.Black;
            internal static readonly Brush StripFill = Brushes.Blue;
        } //class DefinitionSet

        public StripIndicator() {
            Border border = new Border();
            border.VerticalAlignment = VerticalAlignment.Stretch;
            border.HorizontalAlignment = HorizontalAlignment.Stretch;
            border.BorderThickness = new Thickness(DefinitionSet.BorderThickness);
            border.BorderBrush = DefinitionSet.Border;
            border.Child = rectangleValue;
            this.Content = border;
            this.Foreground = DefinitionSet.StripFill;
        } //Indicator

        public double Maximum {
            get { return maximum; }
            set {
                if (value == maximum) return;
                maximum = value;
                Refresh();
            } //set Maximum
        } //Maximum
        public double Minimum {
            get { return minimum; }
            set {
                if (value == minimum) return;
                minimum = value;
                Refresh();
            } //set Maximum
        } //Maximum
        public double Value {
            get { return currentValue; }
            set {
                if (value == currentValue) return;
                currentValue = value;
                Refresh();
            } //set Value
        } //Value
        public Orientation Orientation {
            get { return orientation; }
            set {
                if (value == orientation) return;
                orientation = value;
                Refresh();
            } //set Value
        } //Orientation

        void Refresh() {
            double fullSize;
            if (orientation == Orientation.Horizontal)
                fullSize = this.ActualWidth;
            else
                fullSize = this.ActualHeight;
            double newSize;
            if (maximum == 0)
                newSize = 0;
            else
                newSize = fullSize * this.currentValue / (maximum - minimum);
            if ((newSize < 0 || double.IsPositiveInfinity(newSize)
               || double.IsNaN(newSize)))
                return;
            if (orientation == Orientation.Horizontal) {
                this.rectangleValue.HorizontalAlignment = HorizontalAlignment.Left;
                this.rectangleValue.VerticalAlignment = VerticalAlignment.Stretch;
                this.rectangleValue.Width = newSize;
                this.rectangleValue.Height = double.NaN;
            } else {
                this.rectangleValue.HorizontalAlignment = HorizontalAlignment.Stretch;
                this.rectangleValue.VerticalAlignment = VerticalAlignment.Bottom;
                this.rectangleValue.Height = newSize;
                this.rectangleValue.Width = double.NaN;
            } //if
        } //Refresh

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {
            if (orientation == Orientation.Horizontal && double.IsNaN(Height))
                Height = DefinitionSet.DefaultSize;
            if (orientation == Orientation.Vertical && double.IsNaN(Width))
                Width = DefinitionSet.DefaultSize;
            base.OnRenderSizeChanged(sizeInfo);
            Refresh();
        } //OnRenderSizeChanged

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) {
            base.OnPropertyChanged(e);
            if (e.Property == ForegroundProperty)
                rectangleValue.Fill = this.Foreground;
        } //OnPropertyChanged

        double currentValue;
        double maximum = DefinitionSet.DefaultMaximumValue;
        double minimum;
        Orientation orientation = Orientation.Horizontal;
        Rectangle rectangleValue = new Rectangle();

    } //class StripIndicator

} //namespace Demo.Article.Samples

It will perfectly work for any application when the properties are read and assigned in code. It will also work in XAML, but the XAML users of the control may expect some feature very natural to WPF development, and might be surprised to see that it won't work. What is it? Binding.

3.2. The Problem: How to Use Binding?

Let's try to do one crazy thing which nevertheless models some really useful behavior XAML users usually expect.

Let's say we have some panel named "top"; and we want to make some instance of StripIndicator the indicator of actual height of this panel. When the user changes the size of the window, the panel is automatically resized, and the indicator should show the value proportional this height. Of course, it's easy to implement in code. If the user wants to do this in pure XAML (why not?), this person will try something like this:

<controls:StripIndicator Value="{Binding ActualHeight, ElementName=top}" />

It won't build:

A 'Binding' cannot be set on the 'Value' property of type 'StripIndicator'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject

So we are coming to understanding of the rational for dependency properties. There are many long and complex articles explaining this matter, but we can see how it works on a simple practical example. Let's improve our implementation to allow binding.

3.3. By Virtue of Dependency Property

Theoretically speaking, if we simply add a property, such as Value, a programming system would be able to recognize it using reflection and use in binding. But this is not how the technology based on XAML works. Reflection would be really too expensive in terms of performance.

Instead, WPF suggests a universal mechanism used to expose some properties in on uniform way which allows automatic manipulations with them in the type-agnostic way. For this purpose, WPF supports the static singleton data structure, property metadata, which keeps all requires references to the properties of some .NET properties, as well as some static delegate instances needed to work with such properties, called dependency properties.

Two things are needed to make a property a dependency property: 1) it should be statically registered in the global property metadata, 2) its getter and setter (or one of these two methods) should be implemented using the common mechanism via System.Windows.DependencyObject.GetValue and System.Windows.DependencyObject.GetValue.

To illustrate that, I created a new partial declaration of the class StripIndicator, removed implementation of the property StripIndicator.Value and implemented this property as a dependency property:

namespace WPF.Components {
    using System.Windows;
    using System.Windows.Controls;

    public partial class StripIndicator : ContentControl {

        static FrameworkPropertyMetadata ValuePropertyMetadata =
            new FrameworkPropertyMetadata(
                0d, //default
                new PropertyChangedCallback((@object, eventArgs) => {
                    if (eventArgs.OldValue == eventArgs.NewValue) return;
                    ((StripIndicator)@object).Refresh();
                }));

        static DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(StripIndicator), ValuePropertyMetadata);
        public double Value {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); Refresh(); } 
        } //Value
        
    } //class StripIndicator

} //namespace namespace WPF.Components

Now, the binding I described in previous section really works. Such effects are included in the demo application provided with the source code for the present article.

Note that I only implemented the property Value as a dependency property. Strictly speaking, other properties also should be implemented like that, notably Minimum and Maximum. Those properties are just not so important, So, to avoid bloating of this code, I would like to leave it for a really easy home exercise for the readers. I think it's much more important to explain the idea clearly.

For further information on dependency properties, please see:
http://msdn.microsoft.com/en-us/library/ms752914%28v=vs.110%29.aspx,
http://msdn.microsoft.com/en-us/library/ms753358%28v=vs.110%29.aspx.

4. Control Source Code, Demo Application, and Compatibility

All further detail can be found in the source code attached. The code comes in two assemblies put in one solution: the library with the control and the demo application, which also includes code samples used in the present article. The whole solution is targeted to .NET v.3.5, to have the earliest possible target usable with the most versions of .NET, Visual Studio or alternative open-source IDE. Later versions of Visual Studio will automatically convert the solution and these two projects.

5. Conclusions

This article provides much clearer implementation of the control which is very much wanted, as the Web search show. Also, it illustrate some more general ideas for dealing with some pretty typical WPF hassles and the idea of developing simplest custom controls, in particular, using of the dependency properties.

I hope it can help the beginners to tame such a naughty horse as WPF. Some horses like that are naughty, but are still valued for their exceptional performance. The rider just needs to understand the character and develop the approach.

I will be much grateful if someone could share some better idea, as well as for some criticism.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sergey Alexandrovich Kryukov
Architect
United States United States
No Biography provided

You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralMy vote of 1 Pin
hoo daticus4-Aug-15 17:15
memberhoo daticus4-Aug-15 17:15 
GeneralRe: My vote of 1 Pin
Sergey Alexandrovich Kryukov4-Aug-15 17:18
mvpSergey Alexandrovich Kryukov4-Aug-15 17:18 
GeneralHmm Pin
Luka3-Aug-15 12:54
memberLuka3-Aug-15 12:54 
GeneralMore religious arguments? Pin
Sergey Alexandrovich Kryukov3-Aug-15 14:56
mvpSergey Alexandrovich Kryukov3-Aug-15 14:56 
QuestionWPF/XAML Different Approaches Pin
ILOVETOFU15-Apr-15 21:54
memberILOVETOFU15-Apr-15 21:54 
AnswerRe: WPF/XAML Different Approaches Pin
Sergey Alexandrovich Kryukov16-Apr-15 3:19
mvpSergey Alexandrovich Kryukov16-Apr-15 3:19 
AnswerRe: WPF/XAML Different Approaches Pin
SteveHolle17-Apr-15 12:08
memberSteveHolle17-Apr-15 12:08 
AnswerRe: WPF/XAML Different Approaches Pin
Sergey Alexandrovich Kryukov17-Apr-15 12:37
mvpSergey Alexandrovich Kryukov17-Apr-15 12:37 
GeneralMy vote of 5 Pin
SteveHolle15-Apr-15 10:31
memberSteveHolle15-Apr-15 10:31 
GeneralRe: My vote of 5 Pin
Sergey Alexandrovich Kryukov15-Apr-15 10:37
mvpSergey Alexandrovich Kryukov15-Apr-15 10:37 
QuestionInteresting solution suggestion, calmly discussed, and (in my opinion) valid Pin
Jason Sobell14-Apr-15 20:29
memberJason Sobell14-Apr-15 20:29 
AnswerRe: Interesting solution suggestion, calmly discussed, and (in my opinion) valid Pin
Sergey Alexandrovich Kryukov14-Apr-15 20:53
mvpSergey Alexandrovich Kryukov14-Apr-15 20:53 
GeneralRe: Interesting solution suggestion, calmly discussed, and (in my opinion) valid Pin
SteveHolle15-Apr-15 5:52
memberSteveHolle15-Apr-15 5:52 
AnswerAs I said in my April 1st article... (Re: Interesting solution suggestion, calmly discussed, and (in my opinion) valid) Pin
Sergey Alexandrovich Kryukov15-Apr-15 7:20
mvpSergey Alexandrovich Kryukov15-Apr-15 7:20 
QuestionYeah you should have used ControlTemplates Pin
Sacha Barber5-Sep-14 1:36
mvpSacha Barber5-Sep-14 1:36 
AnswerRe: Yeah you should have used ControlTemplates Pin
Sergey Alexandrovich Kryukov5-Sep-14 4:03
mvpSergey Alexandrovich Kryukov5-Sep-14 4:03 
GeneralRe: Yeah you should have used ControlTemplates Pin
Sacha Barber6-Sep-14 7:19
mvpSacha Barber6-Sep-14 7:19 
GeneralRe: Yeah you should have used ControlTemplates Pin
Sergey Alexandrovich Kryukov6-Sep-14 7:52
mvpSergey Alexandrovich Kryukov6-Sep-14 7:52 
GeneralRe: Yeah you should have used ControlTemplates Pin
johannesnestler15-Apr-15 3:15
memberjohannesnestler15-Apr-15 3:15 
GeneralRe: Yeah you should have used ControlTemplates Pin
Sergey Alexandrovich Kryukov15-Apr-15 4:19
mvpSergey Alexandrovich Kryukov15-Apr-15 4:19 
GeneralMy vote of 1 Pin
Christian Woltering4-Sep-14 23:59
memberChristian Woltering4-Sep-14 23:59 
GeneralRe: My vote of 1 Pin
Sergey Alexandrovich Kryukov5-Sep-14 3:36
mvpSergey Alexandrovich Kryukov5-Sep-14 3:36 
GeneralRe: My vote of 1 Pin
Christian Woltering5-Sep-14 4:09
memberChristian Woltering5-Sep-14 4:09 
GeneralRe: My vote of 1 Pin
Sergey Alexandrovich Kryukov9-Sep-14 6:32
mvpSergey Alexandrovich Kryukov9-Sep-14 6:32 
GeneralRe: My vote of 1 Pin
Pete O'Hanlon6-Sep-14 2:50
protectorPete O'Hanlon6-Sep-14 2:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.180712.1 | Last Updated 3 Aug 2015
Article Copyright 2014 by Sergey Alexandrovich Kryukov
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid