Clipping a border in WPF

eran otzap picture eran otzap · Jun 11, 2014 · Viewed 10.8k times · Source

I need to create a round ProgressBar template.

ControlTemplate :

<ControlTemplate TargetType="{x:Type ProgressBar}">
   <Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">                      
     <Rectangle x:Name="PART_Track" Margin="1" Fill="White" />

     <Border x:Name="PART_Indicator" HorizontalAlignment="Left" Margin="1"  >
        <Grid x:Name="Foreground" >
            <Rectangle x:Name="Indicator" Fill="{TemplateBinding Background}" />
            <Grid x:Name="Animation" ClipToBounds="true" >
               <Rectangle x:Name="PART_GlowRect" Fill="#FF86C7EB" 
                          HorizontalAlignment="Left" Margin="-100,0,0,0" Width="100"/>
            </Grid>                             
        </Grid>                                                             
     </Border>

     <Border x:Name="roundBorder" BorderBrush="{TemplateBinding BorderBrush}"
             BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="10" />
     <TextBlock />

  </Grid>
</ControlTemplate>

This results in :

image of incorrect clipping

Where PART_Indicator is the LightBlue rectangle on the left (its width is set internally in the ProgressBar control, as seen here with a value of 20) and the roundBorder.

What I need is for the the PART_Indicator to clip over the roundBorder, resulting in something like:

image of desired clipping

Answer

Sheridan picture Sheridan · Jun 11, 2014

There is a ClipToBounds property on the Border class that should clip the content at the bounds of the Border, but unfortunately, it doesn't 'do what it says on the tin':

<Border CornerRadius="25" BorderBrush="RoyalBlue" BorderThickness="3" Width="300" 
    Height="50" ClipToBounds="True"> <!-- This doesn't work as expected -->
    <Rectangle Fill="SkyBlue" />
</Border>

However, the Rectangle class also provides some properties that could help. Is there something stopping you from just using the Rectangle.RadiusX and Rectangle.RadiusY properties to round the Rectangle corners?:

<Border CornerRadius="25" BorderBrush="RoyalBlue" BorderThickness="3" Width="300" 
    Height="50">
    <Rectangle RadiusX="23" RadiusY="23" Fill="SkyBlue" />
</Border>

I'm aware that you want to clip the coloured fill of the Rectangle, but you could use the Rectangle.Clip property for that:

<Border CornerRadius="25" BorderBrush="RoyalBlue" BorderThickness="3" Width="300" 
    Height="50">
    <Grid>
        <Rectangle Name="ClipRectangle" Fill="Green" Margin="50,0,0,0" 
            Visibility="Hidden" />
        <Rectangle RadiusX="23" RadiusY="23" Fill="SkyBlue" Clip="{Binding 
            RenderedGeometry, ElementName=ClipRectangle}" />
    </Grid>
</Border>

This clips the coloured Rectangle with the RenderedGeometry of the other Rectangle named ClipRectangle... or when I say this clips, perhaps I should have said this is supposed to clip as I've just discovered that this only appears to work in the WPF Designer and not when the application is run.

enter image description here

However, I'm out of time here, so hopefully you can find the final piece of the puzzle and complete this yourself. Potentially, you could also complete this by data binding to the GradientStop.Offset property of a LinearGradientBrush that is set as the Background on the Border, so you wouldn't even need a Rectangle for this method. I'll have another look if I can later.


UPDATE >>>

I had another look at this Clip Rectangle and can't work out why it only works in the Visual Studio Designer. So, giving up with that idea, you can try the LinearGradientBrush idea instead, which is equally good. First, define your Brush:

<LinearGradientBrush x:Key="ValueBrush" StartPoint="0,0" EndPoint="1,0">
    <GradientStop Offset="0.0" Color="SkyBlue" />
    <GradientStop Offset="0.7" Color="SkyBlue" />
    <GradientStop Offset="0.7" Color="Transparent" />
    <GradientStop Offset="1.0" Color="Transparent" />
</LinearGradientBrush>

For now, I've hardcoded the values in to produce this:

enter image description here

From just this code:

<Border CornerRadius="25" BorderBrush="RoyalBlue" Background="{StaticResource 
    ValueBrush}" BorderThickness="3" Width="300" Height="50" ClipToBounds="True" />

For your actual requirements, you'd need to create a double property to data bind to the GradientStop.Offset property like this:

<LinearGradientBrush x:Key="ValueBrush" StartPoint="0,0" EndPoint="1,0">
    <GradientStop Offset="0.0" Color="SkyBlue" />
    <GradientStop Offset="{Binding MidPoint}" Color="SkyBlue" />
    <GradientStop Offset="{Binding MidPoint}" Color="Transparent" />
    <GradientStop Offset="1.0" Color="Transparent" />
</LinearGradientBrush>

Now, as long as you provide a value between 0.0 and 1.0, this will create your level meter.