Misiu
Misiu

Reputation: 4919

WPF Progressbar with images and fill effect

I'd like to create custom progressbar style that will display image filling up from bottom. I've created two images, background:

enter image description here

and foreground:

enter image description here

Idea is to create something like this:

enter image description here

Inside Blend I've created this style:

<Style x:Key="ImageFill" TargetType="{x:Type ProgressBar}">
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ProgressBar}">
                <Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
                    <Image x:Name="PART_Track" Source="Play_Background.png" Margin="1" Stretch="Fill"/>
                    <Rectangle x:Name="PART_Indicator" Margin="1" HorizontalAlignment="Left" Fill="#FFD6931C">
                        <Rectangle.OpacityMask>
                                    <RadialGradientBrush>
                                <GradientStop Color="Black" Offset="0.87"/>
                                <GradientStop Color="Transparent" Offset="0.87"/>
                            </RadialGradientBrush>
                        </Rectangle.OpacityMask>
                    </Rectangle>
                    <Image Source="Play_Foreground.png" Margin="1" Stretch="Fill"/>
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

But when setting value to something like 60 I get this:

enter image description here

I can change OpacitMmask to this:

<RadialGradientBrush Center="106,104" GradientOrigin="60,60" MappingMode="Absolute" RadiusY="97" RadiusX="98">
    <GradientStop Color="Black" Offset="0.87"/>
    <GradientStop Color="Transparent" Offset="0.87"/>
</RadialGradientBrush>

but then when I resize my progress bar I get unwanted behaviour:

enter image description here

How this can be fixed? I need mask to have MappingMode set to RelativeToBoundingBox so I can set different size to progressbar.

Below is full XAML I've generated in Blend:

<Window x:Class="ImageProgressBar.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="389" Width="523">
    <Window.Resources>
<Style x:Key="ImageFill" TargetType="{x:Type ProgressBar}">
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ProgressBar}">
                <Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
                    <Image x:Name="PART_Track" Source="Play_Background.png" Margin="1" Stretch="Fill"/>
                    <Rectangle x:Name="PART_Indicator" Margin="1" HorizontalAlignment="Left" Fill="#FFD6931C">
                        <Rectangle.OpacityMask>
                            <RadialGradientBrush Center="106,104" GradientOrigin="60,60" MappingMode="Absolute" RadiusY="97" RadiusX="98">
                                <GradientStop Color="Black" Offset="0.87"/>
                                <GradientStop Color="Transparent" Offset="0.87"/>
                            </RadialGradientBrush>
                        </Rectangle.OpacityMask>
                    </Rectangle>
                    <Image Source="Play_Foreground.png" Margin="1" Stretch="Fill"/>
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
    </Window.Resources>
    <Grid>
        <ProgressBar 
        Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}"
        HorizontalAlignment="Left" Height="200" Margin="10,10,0,0" VerticalAlignment="Top" Width="200" Style="{DynamicResource ImageFill}"/>

                <ProgressBar 
        Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}"
        HorizontalAlignment="Left" Height="100" Margin="294,10,0,0" VerticalAlignment="Top" Width="100" Style="{DynamicResource ImageFill}"/>

        <Slider  Name="MySlider" HorizontalAlignment="Left" Margin="10,215,0,0" VerticalAlignment="Top" Width="200" Minimum="0" Maximum="100" Value="0"/>

    </Grid>
</Window>

I've found http://vbcity.com/blogs/xtab/archive/2009/11/24/wpf-controltemplates-creating-a-non-rectangular-progressbar.aspx but can't use this inside my style.

Upvotes: 2

Views: 2236

Answers (2)

Misiu
Misiu

Reputation: 4919

I've managed to create style that has effect I wanted. Below is how it looks:

enter image description here

and below is working code with style and slider to change value of progressbar:

<?xml version="1.0" encoding="UTF-8"?>
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ImageProgressBar.MainWindow" Title="ProgressBar Image Fill" Height="284" Width="598" Background="#FFEAE0E0">
   <Window.Resources>
      <Style x:Key="ImageFill" TargetType="{x:Type ProgressBar}">
         <Setter Property="Template">
            <Setter.Value>
               <ControlTemplate TargetType="{x:Type ProgressBar}">
                  <Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
                     <Image x:Name="PART_Track" Source="Play_Background.png" Margin="1" Stretch="Fill" />
                     <Rectangle x:Name="PART_Indicator" Margin="1" HorizontalAlignment="Left" Fill="{TemplateBinding Foreground}">
                        <Rectangle.OpacityMask>
                           <RadialGradientBrush Center="106,104" GradientOrigin="60,60" MappingMode="Absolute" RadiusY="97" RadiusX="98">
                              <GradientStop Color="Black" Offset="0.87" />
                              <GradientStop Color="Transparent" Offset="0.87" />
                           </RadialGradientBrush>
                        </Rectangle.OpacityMask>
                     </Rectangle>
                     <Image Source="Play_Foreground.png" Margin="1" Stretch="Fill" />
                  </Grid>
                  <ControlTemplate.Triggers>
                     <!-- Getting vertical style working using technique described here: http://stackoverflow.com/a/6849237/7532 -->
                     <Trigger Property="Orientation" Value="Vertical">
                        <Setter TargetName="PART_Indicator" Property="LayoutTransform">
                           <Setter.Value>
                              <RotateTransform Angle="270" />
                           </Setter.Value>
                        </Setter>
                        <Setter TargetName="PART_Indicator" Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}" />
                        <Setter TargetName="PART_Indicator" Property="Height" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}" />
                        <Setter TargetName="PART_Indicator" Property="VerticalAlignment" Value="Bottom" />
                     </Trigger>
                  </ControlTemplate.Triggers>
               </ControlTemplate>
            </Setter.Value>
         </Setter>
      </Style>
      <Style x:Key="SimpleImageFill" TargetType="{x:Type ProgressBar}">
         <Setter Property="Template">
            <Setter.Value>
               <ControlTemplate TargetType="{x:Type ProgressBar}">
                  <Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
                     <Image x:Name="PART_Track" Source="Play_Game_Empty.png" />
                     <Canvas ClipToBounds="True" x:Name="PART_Indicator" HorizontalAlignment="Left">
                        <Image x:Name="Image_Fill" Source="Play_Game_Fill.png"
                        Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}"
                        Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}" />
                     </Canvas>
                  </Grid>
                  <ControlTemplate.Triggers>
                     <Trigger Property="Orientation" Value="Vertical">
                        <Setter TargetName="PART_Indicator" Property="LayoutTransform">
                           <Setter.Value>
                              <RotateTransform Angle="270" />
                           </Setter.Value>
                        </Setter>
                        <Setter TargetName="Image_Fill" Property="LayoutTransform">
                           <Setter.Value>
                              <RotateTransform Angle="-270" />
                           </Setter.Value>
                        </Setter>
                        <Setter TargetName="PART_Indicator" Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}" />
                        <Setter TargetName="PART_Indicator" Property="Height" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}" />
                        <Setter TargetName="PART_Indicator" Property="VerticalAlignment" Value="Bottom" />
                     </Trigger>
                  </ControlTemplate.Triggers>
               </ControlTemplate>
            </Setter.Value>
         </Setter>
      </Style>
   </Window.Resources>
   <Grid>
      <ProgressBar Foreground="Orange" Orientation="Vertical" Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}" HorizontalAlignment="Left" Height="200" Margin="10,10,0,0" VerticalAlignment="Top" Width="200" Style="{DynamicResource ImageFill}" />
      <ProgressBar Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}" HorizontalAlignment="Left" Height="200" Margin="227.5,10,0,0" VerticalAlignment="Top" Width="200" Style="{DynamicResource ImageFill}" />
      <Slider Name="MySlider" HorizontalAlignment="Left" Margin="10,215,0,0" VerticalAlignment="Top" Width="200" Minimum="0" Maximum="100" Value="0" />
      <ProgressBar Orientation="Vertical" Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}" HorizontalAlignment="Left" Height="150" Margin="432.5,10,0,0" VerticalAlignment="Top" Width="150" Style="{DynamicResource SimpleImageFill}" />
   </Grid>
</Window>

I've actually created two styles:

  1. First one is using two images (top two from my question) and circle between them. Idea was to fill circle from bottom or from left. Circle is filled with foreground color.
  2. Second is much simpler, it uses only two images (empty and filled). When value changes filled image, that is on top of empty, is shown from left or from bottom. I've used canvas that is filled with image and I change it width or height.

I'm just starting with WPF, so is someone knows a better solution please post answer.

Below I'm adding two images used by second style applied to third progressbar.

enter image description hereenter image description here

Upvotes: 8

rmn36
rmn36

Reputation: 656

Check this out WPF: Anchor points don't work well sometimes The problem is that your sizing and margins are absolutes not relative the the size of your control.

Upvotes: 0

Related Questions