Blog Archives

WPF: ProgressBar With Marquee Text Display and TemplateBinding

 Introduction
In this blog I want to demonstrate several things. The first is how to reveal a text message in a ProgressBar. This is easy to do and you can get reasonably close to the moving marquee effect that you can see on some digital displays.
A more important topic that I'll cover is TemplateBinding in ControlTemplates. This makes it easy to use a core template, but allow individual instances of the control type that use the template to tweak selected properties. In this case I'll use the FontSize property.
Finally, you sometimes come across a situation where the control (in this case the ProgressBar) doesn't have a property that maps directly to a property that exists in the ControlTemplate. Although this might seem improbable, it actually happens quite often. We'll look at how we can work round this limitation and in the example I will show you can set a Content property on a ProgressBar control, even though the ProgressBar doesn't actually possess such a property.

If you've read my last three or four blogs you may be thinking that I'm a bit obsessed with ProgressBars! That's not the case and it's simply that they represent a good case study for ControlTemplates, they require named parts and I think they make a nice change from the demos that mostly seem to use Buttons.

The ControlTemplate
In the previous blogs on ProgressBars, you will have seen that I have stored the ControlTemplates in the Window.Resources collection. This time, the ControlTemplate is going to be stored in the Application.Resources collection. The thinking behind this is that this template is designed to be reused in several Windows across the application, with different values being placed on some properties in some instances. So having it in a central location clearly makes it available application-wide and makes sense.

Here is the markup for the initial version of the ControlTemplate: 

    <ControlTemplate x:Key="PBWordReveal" TargetType="{x:Type ProgressBar}">

      <Grid>

        <Border Name="PART_Track" 

          BorderThickness="2" CornerRadius="5"

          Background="LightSkyBlue" BorderBrush="Navy"  />

 

        <ContentControl Name="PART_Indicator"

           Content="  Loading   . . . .   Loading   . . . . .   Loading"

           Margin="4,0" FontSize="14"

           Foreground="Black" Background="LightSkyBlue"

           VerticalAlignment="Center"

           HorizontalAlignment="Left"/>

      </Grid>

    </ControlTemplate> 

If you are not familiar with the concept of named parts – specifically the PART_Track and PART_Indicator shown above – you get an explanation in my previous ProgressBar blogs.

The outer track is a Border element, while the Indicator uses a ContentControl. The key property is probably the Content property, which as you can see contains the text that will be displayed. The HorizontalAlignment property is also quite important to the look of the final effect.

ProgressBar Instance
To use this template, I will create a ProgressBar instance in a Window. 

    <ProgressBar Name="PBReveal"

     Template="{StaticResource PBWordReveal}"

            Width="200" Height="40"    /> 

To animate the ProgressBar in the code-behind of the Window (in this example, a Window named 'WordReveal': 

Imports System.Windows.Media.Animation

 

Partial Public Class WordReveal

 

    Private Sub WordReveal_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

         Dim a As New DoubleAnimation

        a.From = 0

        a.To = 100

        a.Duration = New TimeSpan(0, 0, 6)

 

        PBReveal.BeginAnimation(ProgressBar.ValueProperty, a)

    End Sub

End Class 

When you run this project, the text content in the ProgressBar will be revealed from left to right. The screenshot shows the bar with the animation approx 60% complete:

And this is the display when the animation has ended:

Effectively all that is happening is that the ContentControl, the PART_Indicator element of the template, is revealed over the period of the animation – the revelation commencing from left to right.

If you want more of a marquee type effect, you can change the HorizontalAlignment : 

  HorizontalAlignment="Right" 

This will make the text scroll from right to left:

Setting the HorizontalAlignment to either Center or Stretch will produce a different result again.

TemplateBinding
TemplateBinding allows you to hand back control of selected properties to the instance of the ProgressBar that is using this ControlTemplate. It is simple to implement and in this example only requires the change shown below to the PART_Indicator named part of the template:  

       <ContentControl Name="PART_Indicator"

           Content="  Loading   . . . .   Loading   . . . . .   Loading"

           Margin="4,0"

              FontSize="{TemplateBinding FontSize}"

           Foreground="Black" Background="LightSkyBlue"

           VerticalAlignment="Center"

           HorizontalAlignment="Right"

          /> 

The TemplateBinding points to a specific property – FontSize. This can now be set in an instance of the control that uses the template:  

    <ProgressBar Name="PBReveal"

     Template="{StaticResource PBWordReveal}"

     Width="200" Height="40"

        FontSize="22"/> 

Note that if you choose not to set a value on the FontSize property in the ProgressBar instance, the default value for that property will be used (12).

Mapping Between Properties
As you can see from the markup, the PART_Indicator uses a ContentPresenter. The ContentPresenter has a Content property and this is what is used to set the value of the text to be displayed. The basic ProgressBar however doesn't have such a property. So if you want to include TemplateBinding to allow different text to be used in different instances that use this template, you need to map the ContentPresenter property to some other property of the ProgressBar.

In most cases, if you have only one property that needs this kind of mapping, the solution is simple. You can use the Tag property: 

        <ContentControl Name="PART_Indicator"

             Content="{TemplateBinding Tag}"

           Margin="4,0"

           FontSize="{TemplateBinding FontSize}"

           Foreground="Black" Background="LightSkyBlue"

           VerticalAlignment="Center"

           HorizontalAlignment="Right"

          /> 

And you insert the required text in the markup for the ProgressBar instance : 

    <ProgressBar Name="PBReveal"

     Template="{StaticResource PBWordReveal}"

     Width="200" Height="40"

     FontSize="22"

       Tag="  Please Wait . . . . . . . . . ."/> 

These two templated changes result in the following display:

As you can see, both the FontSize and the Content have changed.

If you need to include more than one of these kind of cross property mappings, there is nothing (apart from common sense and experience) stopping you from hijacking other ProgressBar properties. You can identify a property that you absolutely, categorically know won't be used (and there's the start of the slippery slope right there) you can use this to map to.

The more professional alternative, of course, is to create a custom control of your own which contains all the properties you desire. I'll be looking at the steps involved in creating a custom control in a future blog.

WPF: How to Create a Reversing ProgressBar

 

 

In some of my earlier blogs – here, here and here, I looked at some rather colorful, non-rectangular versions of the ProgressBar. Although you probably wouldn't want to use some of those versions outside of a game environment, I hope it did show you how you can create and tweak non-rectangular versions. In this blog, I want to examine a less bizarre variation and along the way hopefully show you some more useful WPF basics.

Reversing the Default ProgressBar
The default ProgressBar is rectangular, has slightly rounded corners, a Gray Background, Linear Gradient Green Foreground and, most importantly, it traverses from left to right. I was wondering about how to change this and have a ProgressBar that traversed from right to left – a kind of a countdown effect instead of the usual fill-increasing effect. Because I was in ControlTemplate mode, I confess that I initially overlooked the obvious solution – total simplicity in WPF – and created a whole new template that did the job. I'll show you the obvious WPF solution shortly, but first let's just look at how this can be done as a ControlTemplate task.

ControlTemplate
I set about this by using a combination of Expression Blend and Visual Studio. I know I could have done it all in Blend using the Edit Template tools, but I came up this WPF route the hard way, from back in the early days when there were no tools and you had to become a XAML-head to get anywhere with this new-fangled thing called WPF. So I find that I still prefer to create my ControlTemplates in XAML in Visual Studio and use Blend when I want to do anything fancy with graphics or animation.

If you find it easier to do it all in Blend, I have no problem with that, but here's my way. First create a ControlTemplate, Key it as 'ReverseBasicPB' and set its TargetType to ProgressBar. The visual elements of the template will all be held inside a Grid. 

    <ControlTemplate x:Key="ReverseBasicPB" TargetType="{x:Type ProgressBar}">

      <Grid>

 

      </Grid>

    </ControlTemplate> 

As you will know from the earlier blogs, the ProgressBar requires two named parts – PART_Track and PART_Indicator. In this version, PART_Track will consist of a Rectangle inside a Border and PART_Indicator will be a Rectangle with a reasonable copy of the Green Linear Brush.
(As a side note, it is possible to dig into the WPF namespaces and get hold of the ProgressBarGlassyHighlight and ProgressBarTopHighlight Brushes, but I was content to go with my approximation.)

Here is the markup for the PART_Track: 

       <Border Name="PART_Track" BorderBrush="DarkGray"

               BorderThickness="2" CornerRadius="3" >

          <Rectangle Fill="LightGray"

               RadiusX="3" RadiusY="3"/>

        </Border> 

Note that to achieve the rounded corner effect, Border uses CornerRadius and Rectangle uses RadiusX and RadiusY.

The markup for the Indicator is just as simple: 

          <Rectangle Name="PART_Indicator" HorizontalAlignment="Right"

                    RadiusX="3" RadiusY="3" 

                    Margin="2,2,2,2">

          <Rectangle.Fill>

            <LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">

              <GradientStop Color="#FF5FBB23" Offset="0.525"/>

              <GradientStop Color="White"/>

              <GradientStop Color="#FF63BC28"/>

              <GradientStop Color="#FF95EDA5" Offset="0.542"/>

            </LinearGradientBrush>

          </Rectangle.Fill>

        </Rectangle> 

The key property here is the HorizontalAlignment. By setting it to Right we ensure that the ProgressBar Indicator will start at the right hand side and stretch out towards the left.

Demo
The following markup in a Window will create two ProgressBars, one default style and the other using this ControlTemplate: 

    <Grid>

    <Grid.RowDefinitions>

      <RowDefinition Height="87*" />

      <RowDefinition Height="175*" />

    </Grid.RowDefinitions>

 

    <ProgressBar x:Name="PBDefault" Width="150" Height="33">

 

    </ProgressBar>

    <ProgressBar Name="MyPB" Width="150" Height="33"

          Template="{StaticResource ReverseBasicPB}" Margin="22"

          Grid.Row="1"

          VerticalAlignment="Top" />

  </Grid> 

Add in the animation code-behind used in previous examples: 

    Private Sub ReverseBasic_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim a As New DoubleAnimation

        a.From = 0

        a.To = 100

        a.Duration = New TimeSpan(0, 0, 6)

 

        MyPB.BeginAnimation(ProgressBar.ValueProperty, a)

 

        a.From = 0

        a.To = 100

        a.Duration = New Duration(TimeSpan.Parse("0:0:6"))

 

        PBDefault.BeginAnimation(ProgressBar.ValueProperty, a)

    End Sub 

When you run this, completed ProgressBars are reasonably close in looks, the only major difference being that the templated one at the bottom runs right to left.

 

Although the creation of the ControlTemplate was only a few minutes work and opens the way for further visual changes (which I'll probably blog about later), if you just want the default ProgressBar reversed, the simple way is as follows:

Add a new ProgressBar to the Window, but this time include a LayoutTransform to rotate it 180 degrees. 

    <ProgressBar x:Name="PBDefaultReversed"

                 Width="150" Height="33"

                 Grid.Row="2" >

      <ProgressBar.LayoutTransform>

        <RotateTransform Angle="180" />

      </ProgressBar.LayoutTransform>

    </ProgressBar> 

(You'll also need to add the extra row to the Grid if you are following along and creating this sample yourself:) 

    <Grid.RowDefinitions>

      <RowDefinition Height="87*" />

      <RowDefinition Height="85*" />

      <RowDefinition Height="90*" />

    </Grid.RowDefinitions> 

And don't forget to animate it in the code-behind.

The final result, with the default ProgressBar at the top, the templated one in the middle and the transformed one at the bottom, looks like this:

I chose not to use a gradient for the background Gray, but of course you can implement this if you prefer.

WPF: Changing ProgressBar Appearance As Values Change

 

Introduction
In this earlier blog, I looked at how to create a non-rectangular ProgressBar. As the next step, I want to look at how you can change properties dynamically as the current Value of the ProgressBar changes.

I'll start with the easy (and fairly realistic) scenario where you want to tone down the ProgressBar once it has reached its maximum value.

Currently the ProgressBar coloring looks like this:

If you want to apply changes to the look of the ProgressBar as it is running, you can use a DataTrigger in XAML or you can simply use the ValueChanged event of the ProgressBar in the code-behind. I say "simply", but you do in fact have to understand how to dig into the ControlTemplate and drill down in order to make the code-behind approach work.

There's a trade-off between these two approaches – DataTrigger or code-behind. DataTrigger offers a slightly more concise syntax, but only as long as your requirements are very basic. Once you step beyond the absolute basics, the XAML becomes quite complex. And as you are no doubt more familiar with VB, what's the point in struggling just to prove the point that it can be created in XAML? Sometimes, common sense has to win over technical ego.

Overall, in most cases, code-behind is the easiest answer. But just to be sure you know what a DataTrigger is and how it works, let's run through it.

DataTrigger
A DataTrigger is fired (and as a result some predefined change happens) when a value is reached. In the scenario we are looking at here, a DataTrigger could be set to fire when the Value property of a ProgressBar reaches its maximum. So in the case of the ProgressBar defined below that would of course be when it reaches 100.  

    <ProgressBar x:Name="CurvyPB" Width="300" Height="60"

       Template="{StaticResource PBCurvy}"

       Foreground="{StaticResource BlueGreenRed}"

       Minimum="0" Maximum="100" /> 

The 'predefined change' might be to alter the color of the outside edge of the control and also to dim its Opacity, so that it can be more easily ignored now that its work is done.

The DataTrigger sits in a Triggers collection in the ControlTemplate for the ProgressBar. The syntax is as follows: 

      <ControlTemplate.Triggers>

        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Value}"

                    Value="100">

          <Setter TargetName="PART_Track" Property="Stroke" Value="DarkGray" />

          <Setter TargetName="PART_Track" Property="Opacity" Value="0.3" />

        </DataTrigger>

      </ControlTemplate.Triggers> 

(Note that this is only one section of the complete ControlTemplate.)

The DataTrigger Binding looks awkward and is one of those rather peculiar constructs that makes XAML hard to decipher (and create) sometimes. Essentially it translates to :
"Keep checking the Value property of the ProgressBar instance that is using this ControlTemplate. If and when it reaches a Value of 100, fire the trigger."

The Setters both target the Path named 'PART_Track'. The first changes the Stroke to DarkGray and the second one turns down the Opacity of that Path (which effectively reduces the Opacity of the templated ProgressBar).

Here is the full XAML for the Window which contains the ControlTemplate and the ProgressBar instance: 

<Window x:Class="CurvyWithTriggers"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:converter="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Luna"  

   Title="Curved ProgressBar" Height="200" Width="400">

  <Window.Resources>

    <ControlTemplate x:Key="PBCurvy" TargetType="{x:Type ProgressBar}">

      <Grid>

        <Path x:Name="PART_Track"

             Stroke="{StaticResource BlueGreenRed}"

             StrokeThickness="5"

             Data="F1 M46.802502,0.50000018 C59.803562,0.50000006 71.553123,3.7052743 79.942001,

              8.9014616 C88.330879,3.7052746 100.08044,0.5 113.0815,0.50000018 C125.92575,

              0.5 137.54851,3.6284194 145.9305,8.6908474 C154.3125,3.6284194 165.93524,

              0.50000006 178.7795,0.50000018 C204.35167,0.5 225.082,12.900593 225.082,

              28.1975 C225.082,43.494408 204.35167,55.895 178.7795,55.895 C165.93524,

              55.895 154.3125,52.766582 145.9305,47.704151 C137.54851,52.766582 125.92575,

              55.895 113.0815,55.895 C100.08044,55.895 88.330879,52.689728 79.942001,

              47.493538 C71.553123,52.689728 59.803562,55.895 46.802502,55.895 21.230335,

              55.895 0.5,43.494408 0.5,28.1975 0.5,12.900593 21.230335,0.5 46.802502,

              0.50000018 z"

             Stretch="Fill">

          <Path.Fill>

            <MultiBinding>

              <MultiBinding.Converter>

                <converter:ProgressBarBrushConverter />

 

              </MultiBinding.Converter>

              <Binding Path="Foreground" RelativeSource="{RelativeSource TemplatedParent}" />

              <Binding Path="IsIndeterminate" RelativeSource="{RelativeSource TemplatedParent}" />

              <Binding Path="ActualWidth" ElementName="PART_Indicator" />

              <Binding Path="ActualHeight" ElementName="PART_Indicator" />

              <Binding Path="ActualWidth" ElementName="PART_Track" />

 

            </MultiBinding>

          </Path.Fill>

        </Path>

        <Decorator x:Name="PART_Indicator" />

       </Grid>

 

      <ControlTemplate.Triggers>

        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Value}"

                    Value="100">

          <Setter TargetName="PART_Track" Property="Stroke" Value="DarkGray" />

          <Setter TargetName="PART_Track" Property="Opacity" Value="0.3" />

        </DataTrigger>

      </ControlTemplate.Triggers>

    </ControlTemplate>

 

  </Window.Resources>

  <Grid>

    <ProgressBar x:Name="CurvyPB" Width="300" Height="60"

       Template="{StaticResource PBCurvy}"

       Foreground="{StaticResource BlueGreenRed}"

       Minimum="0" Maximum="100" />

 

  </Grid>

</Window> 

This markup in the Application.xaml file creates the LinearGradientBrush: 

    <Application.Resources>

    <LinearGradientBrush x:Key="BlueGreenRed"

       EndPoint="1,0.5" StartPoint="0,0.5">

      <GradientStop Color="#FF2D3ADD" Offset="0"/>

      <GradientStop Color="#FFF13E14" Offset="1"/>

      <GradientStop Color="#FF9775D8" Offset="0.192"/>

      <GradientStop Color="#FF3F893B" Offset="0.481"/>

      <GradientStop Color="#FF2B9518" Offset="0.625"/>

      <GradientStop Color="#FECC7638" Offset="0.812"/>

    </LinearGradientBrush>

  </Application.Resources> 

The code-behind to animate the ProgressBar: 

Imports System.Windows.Media.Animation

 

Partial Public Class CurvyWithTriggers

 

    Private Sub CurvyWithTriggers_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim a As New DoubleAnimation

        a.From = 0

        a.To = 100

        a.Duration = New TimeSpan(0, 0, 8)

 

        CurvyPB.BeginAnimation(ProgressBar.ValueProperty, a)

    End Sub

End Class 

Apologies if you already have all that code and markup from the previous blog. Personally I hate it when someone says "I've used the same code as in my earlier blog", which means I then have to go and find that blog, dig through it and find the missing bits before I can test out what I'm working on now. So I much prefer to repeat it and make life easier for you, at the expense of a slightly longer blog entry.

After the ProgressBar completes its mission, it hits that Value of 100 and the look changes to this:

So if you're sat there saying to yourself that there's nothing there that you couldn't have easily done in code-behind, I almost agree with you. Certainly the Opacity change would be easy: 

        If progressBar1.Value = 100 Then

            progressBar1.Opacity = 0.3

        End If 

But what about changing the value of the Stroke property though? The ProgressBar doesn't have a Stroke property. That's tucked away inside the ControlTemplate and is a property of the Path named PART_Track. To get to that, we will need a way to access the ControlTemplate and then drill down into the Path.

Before we look at the code-behind approach, I just want to mention a problem with DataTriggers, especially as they relate to ProgressBars. The only arithmetic operator available to you in the DataTrigger in XAML is the equals operator. For fairly obvious reasons, the less-than and greater-then operator symbols have the potential to cause problems in a language that uses them as element delimiters.

The reason that this is a particular problem with the ProgressBar is that the algorithm that breaks the ProgressBar movement into its time chunks will rarely, if ever, space them across whole numbers. That is to say if the Minimum Value is 0 and the Maximum Value is 100 and the duration is 10 seconds, you might reasonably suppose that each new block will appear at intervals of 0, 10, 20, 30, etc. However it doesn't work that way and the values are more likely to be something like :
0
0.252538
0.299015
0.613981
: etc
: ending with
99.769844
99.890324
100

This makes it impossible for you to set a DataTrigger on a value that you can be certain will be matched exactly, except for the starting and ending values of 0 and 100. In theory, you could tweak the size settings of the PART_Track and PART_Indicator to ensure whole number partitions (these being the key factors in the breakdown), but none of my experimenting with this approach worked. An alternative approach, which I haven't tried, would be to create a ValueConverter which would then allow you to set value parameters, such as < 20 or > 40 and so on. However, I didn't really see any gain in going that route, as I can use the ValueChanged event of the ProgressBar directly, together with a bit of WPF delving, as we will now see.

FindResource and FindName
When you need to get at a ControlTemplate (or other Resource, for that matter), you can use the FindResource method of the FrameworkElement class. Before, I get into that, I am going to make two changes to the markup in the Window.
The first change is to add a Name property to the Window, placing this inside the opening tag of the Window class markup:  

 x:Name="CurvyValueChangedWindow" 

This is necessary in order for the Window to be accessed from the code-behind.

The second change is purely decorative – changing the Stroke property of the PART_Track path from the LinearGradientBrush to plain Yellow.  

        <Path x:Name="PART_Track"

             Stroke="Yellow" 

 The ProgressBar now looks like this at startup:

To locate that ControlTemplate, I use the following code, which I have placed in the ValueChanged event of the ProgressBar: 

        Dim ct As New ControlTemplate()

        Try

            ct = CType(CurvyValueChangedWindow.FindResource("PBCurvy"), ControlTemplate)

 

        Catch Ex As ResourceReferenceKeyNotFoundException

            '  Design time message

            Console.WriteLine("CT Not found")

            '  Quit gracefully if not found

            Exit Sub

        End Try 

The third line does all the work and you will see now why I added a name to the WPF Window. This Name is used to identify the container in which the Resource named PBCurvy (i.e. the ControlTemplate) should be found.
Note also that if due to a typo or other error the Resource can't be found, then no further action will be taken. At this stage, the Catch is superfluous. It is however important to avoid the application crashing as we move on to the next step.

FindName
So now we have got ourselves a reference to the ControlTemplate, but we still need to drill down into the Path named PART_Track which is a sub-element of that template. To do this, you can use the following code, placing it immediately below the code used to find the ControlTemplate resource:  

        Dim p As New Path

        p = CType(ct.FindName("PART_Track", PBCurvy), Path)

 

        If IsNothing(p) Then

            Console.WriteLine("Path Not found")

            Exit Sub

        End If 

This time the FindName function searches through that ControlTemplate (now referenced as 'ct') in order to find the named Path.
Once again, the test for IsNothing isn't necessary at this stage, because we have not yet tried to do anything with the Path variable 'p'. As before though, when we add further code, this checkpoint – and the Exit Sub if the Path isn't found – are important.

Changing The values
We've now reached the point where we can manipulate values of that Path according to the current values of the ProgressBar. This code placed immediately below the previous snippet will change the color and thickness of the Stroke: 

        Select Case PBCurvy.Value

            Case Is < 25

                p.Stroke = New SolidColorBrush(Colors.Yellow)

                p.StrokeThickness = 5

            Case 25 To 49

                p.Stroke = New SolidColorBrush(Colors.Green)

                p.StrokeThickness = 7

            Case 50 To 75

                p.Stroke = New SolidColorBrush(Colors.Orange)

                p.StrokeThickness = 9

            Case 76 To 99

                p.Stroke = New SolidColorBrush(Colors.Red)

                p.StrokeThickness = 11

            Case Else

                p.Stroke = New SolidColorBrush(Colors.DarkGray)

        End Select 

When you run the application, the colors will change as things progress:  

 

 I have chosen to make those particular changes for demo purposes, but you are of course not limited to those. The key take away points in this blog are the availability of the DataTrigger and the use of the very helpful FindResource and FindName methods when you want to trigger a change via the code-behind.

WP Like Button Plugin by Free WordPress Templates