Click here to Skip to main content
15,867,594 members
Articles / Mobile Apps / Windows Phone 7

XAML Masking for WPF, Silverlight, and WP7 - Rating control and more

Rate me:
Please Sign up or sign in to vote.
4.88/5 (19 votes)
15 May 2011Ms-PL2 min read 39K   668   17   11
Create Masking using XAML for WP7, WPF and Silverlight

Introduction

In the last couple of months since Microsoft has released WP7, I've started to build games and apps in XNA and Silverlight.

Building games is different than building applications, especially the UI... and most developers don't like dealing with UI, but UI doesn't have to be so scary and I'll start this article with a nice and simple technique I learned when I worked with Photoshop called "Masking".

The first example, I'll take from a game I built called "Raise My Dog". Raise My Dog is a Tamagotchi like game for WP7. The player gets to raise a dog inside his mobile device, feed it, and watch it grow.

1.png

Heart Control

3.png

An important part of the UI is the "heart control" that shows the dog's health. The heart has, well, a heart shape :) and thus we need to fill it without exceeding its borders; how do we do that?

As I mentioned in the article's title: using mask - to help mask out the heart fill without harming the border; this technique will save you a lot of time. The initial goal is to separate the heart border from the heart background. In my demo, I used Paths and not Images, but the concept remains the same.

2.png

Step 1: Placing the Heart Fill

The heart fill needs to be above everything.

XML
<Path x:Name="path" Canvas.Left="246.758" Canvas.Top="199.33" 
   Stretch="Fill" Data="F1 M 246.759,219.964C 246.977,241.504 267.975,
            259.233 284.206,265.059L 284.206,265.059C 300.302,
            259.376 320.885,241.829 320.593,
            220.288L 320.593,220.288C 320.441,208.875 311.149,199.525 299.872,
            199.475L 299.872, 199.475C 293.324,199.446 287.546,202.799 283.884,
            207.667L 283.884,207.667C 283.766,207.823 283.651,207.982 283.536,
            208.142L 283.536,208.142C 279.778,202.988 273.756,199.359 266.995,
            199.33L 266.995,199.33C 263.721,199.316 260.634,200.087 257.902,
            201.468L 257.902,201.468C 251.226,204.844 246.679, 211.865 246.759,219.964 Z"
  Margin="15,20,0,0" RenderTransformOrigin="0.5,0.5" Height="62.369" 
  UseLayoutRounding="False" VerticalAlignment="Top" 
  HorizontalAlignment="Left" Width="65.75">
    <Path.RenderTransform>
        <CompositeTransform/>
    </Path.RenderTransform>
    <Path.Fill>
        <RadialGradientBrush RadiusX="0.470614" RadiusY="0.535016" 
                  Center="0.50193,0.500556" 
                  GradientOrigin="0.50193,0.500556">
            <RadialGradientBrush.GradientStops>
                <GradientStop Color="#FFED272A" Offset="0"/>
                <GradientStop Color="#FF611317" Offset="1"/>
            </RadialGradientBrush.GradientStops>
            <RadialGradientBrush.RelativeTransform>
                <TransformGroup>
                    <SkewTransform CenterX="0.50193" 
                       CenterY="0.500556" AngleX="0.881065" AngleY="0"/>
                    <RotateTransform CenterX="0.50193" 
                       CenterY="0.500556" Angle="0.279347"/>
                </TransformGroup>
            </RadialGradientBrush.RelativeTransform>
        </RadialGradientBrush>
    </Path.Fill>
</Path>

Step 2: Placing the Mask Between the Fill and Border

XML
<Rectangle x:Name="Mask" Fill="{StaticResource PhoneBackgroundBrush}" 
   VerticalAlignment="Top" Margin="4,17,154,0" 
   Height="{Binding HeartValue,Converter={StaticResource HeartValueConverter}}"/>

I've added a converter to adjust the health to the mask size: if the dog's health is 50% then I need to change the mask height to the center of the heart border (half...), but the size has to be relative to the border so the mask size will be 34.126.

C#
//The max height of the heart border.
//100 represent the max scale 
private const double Max = 68.253;
public object Convert(object value, Type targetType, object parameter, 
                      System.Globalization.CultureInfo culture)
{        
    var health = (int)value; 
    return ((((health) * Max) / 100) - Max) * -1;
}
  • Health = 0 then Mask Height= 68.253
  • Health = 50 then Mask Height= 34.12
  • Health = 90 then Mask Height= 6.8

Step 3: Placing the Border

XML
<Path x:Name="border" Canvas.Left="243.55" Canvas.Top="195.863" 
  Stretch="Fill" Data="F1 M 243.553,218.219C 243.888,241.644 266.682,261.781 284.255, 
           268.184L 284.255,268.184C 301.628,262.071 323.779,242.306 323.365,218.88L 323.365,
           218.88C 323.143,206.466 313.059,196.258 300.868,196.157L 300.868, 196.157C 293.56,
           196.097 287.157,199.683 283.246,205.238L 283.246,205.238C 279.159,
           199.616 272.639,195.925 265.329,195.864L 265.329,195.864C 261.79,
           195.835 258.456,196.66 255.51,198.151L 255.51,198.151C 248.309,201.793 243.427,
           209.41 243.553,218.219 Z M 284.205,265.049C 268.588,259.359 248.32,
           240.575 248.022, 219.761L 248.022,219.761C 247.868,208.729 256.54,199.805 267.373,
           199.894L 267.373,199.894C 273.871,199.948 279.662,203.228 283.295,208.224L 283.295,
           208.224C 286.767,203.286 292.456,200.102 298.951,200.154L 298.951,
           200.154C 309.784,200.245 318.746,209.315 318.942,220.346L 318.942,
           220.346C 319.271,238.91 303.666,255.593 289.337,262.84L 289.337,262.84C 287.598,
           263.72 285.875,264.461 284.205,265.049 Z " 
  Margin="12,17,0,0" RenderTransformOrigin="0.5,0.5" 
  Width="70.75" Height="68.253" UseLayoutRounding="False" 
  HorizontalAlignment="Left" VerticalAlignment="Top">
    <Path.RenderTransform>
        <CompositeTransform/>
    </Path.RenderTransform>
    <Path.Fill>
        <LinearGradientBrush StartPoint="0.00275276,0.496476" 
                EndPoint="1.0027,0.496476">
            <LinearGradientBrush.RelativeTransform>
                <TransformGroup>
                    <SkewTransform CenterX="0.00275276" 
                       CenterY="0.496476" AngleX="1.353" AngleY="0"/>
                    <RotateTransform CenterX="0.00275276" 
                       CenterY="0.496476" Angle="0.520549"/>
                </TransformGroup>
            </LinearGradientBrush.RelativeTransform>
            <LinearGradientBrush.GradientStops>
                <GradientStop Color="#FFFAF6AF" Offset="0"/>
                <GradientStop Color="#FFF1E091" Offset="0.13058"/>
                <GradientStop Color="#FFE9CA74" Offset="0.159348"/>
                <GradientStop Color="#FF9C6636" Offset="0.510986"/>
                <GradientStop Color="#FFC29855" Offset="0.595192"/>
                <GradientStop Color="#FFE9CA74" Offset="0.75824"/>
                <GradientStop Color="#FFFAF6AF" Offset="0.972534"/>
                <GradientStop Color="#FFFAF6AF" Offset="1"/>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </Path.Fill>
</Path>

4.png

Rating

5.png

The Rating control is more common in applications. The same concept as heart masking applies except that here I use the same Path for Border and Fill and the difference between them is the Fill property set to Yellow in all Star objects. Also, instead of changing the height of the mask I'm changing the width.

Step 1: Define the Star Path as StaticResource

In App.Xaml, I create a Static Resource called StarPath.

XML
<Application.Resources>
    <sys:String x:Key="StarPath">F1 M 0,217.042L 227.5,217.042L 297.875,
        0L 367.542,217L 595.542,217L 410.208,353.667L 480.708,
        569.667L 297.208,436.667L 116.208,568.167L 185.708,352.667L 0,217.042 Z</sys:String>
</Application.Resources>

Step 2: Create 5 Stars Using Path

XML
<Path Name="star" Stretch="Uniform" Data="{StaticResource StarPath}" 
  Fill="Yellow" Width="73" Height="73" HorizontalAlignment="Left" 
  Margin="3,0.667,0,0" VerticalAlignment="Top" d:LayoutOverrides="Width, Height" />
<Path x:Name="star2" Stretch="Uniform" Data="{StaticResource StarPath}" 
  Fill="Yellow" Width="73" Height="73" Margin="81.922,0.333,0,0" 
  HorizontalAlignment="Left" VerticalAlignment="Top" />
<Path x:Name="star3" Stretch="Uniform" Data="{StaticResource StarPath}" 
  Fill="Yellow" Width="73" Height="73" Margin="161.569,0.333,0,0" 
  HorizontalAlignment="Left" VerticalAlignment="Top" />
<Path x:Name="star4" Stretch="Uniform" Data="{StaticResource StarPath}" 
  Fill="Yellow" Width="73" Height="73" Margin="240.895,0.333,0,0" 
  HorizontalAlignment="Left" VerticalAlignment="Top" />
<Path x:Name="star5" Stretch="Uniform" Data="{StaticResource StarPath}" 
  Fill="Yellow" Width="73" Height="73" Margin="320.89,0.333,0,0" 
  HorizontalAlignment="Left" VerticalAlignment="Top" />

Step 3: Placing the Mask Between the Fill and Border

XML
<Rectangle x:Name="Mask" Fill="{StaticResource PhoneBackgroundBrush}" 
   VerticalAlignment="Top" Height="74" HorizontalAlignment="Left" 
   Width="{Binding Value, Converter={StaticResource RaitingValueToWidthConverter}}" 
   RenderTransformOrigin="0.5,0.5"></Rectangle> 

And I've added a converter to adjust the width to the mask size (same as Heart control).

C#
//The max Width of all stars.
//5 represent the max scale
private const double Max = 395;
public object Convert(object value, Type targetType, object parameter, 
                      System.Globalization.CultureInfo culture)
{          
    var rate = (double)value; 
    return ((((rate) * Max) / 5) - Max) * -1;
}

Step 4: Create 5 Stars Using Path But Without the Fill Property

XML
<Path Name="border"  Stretch="Uniform" Data="{StaticResource StarPath}" 
  StrokeThickness="2" Stroke="White" Width="75" Height="75" 
  HorizontalAlignment="Left" Margin="2,-0.333,0,0" 
  VerticalAlignment="Top" d:LayoutOverrides="Width, Height"/>
<Path x:Name="border2"  Stretch="Uniform" Data="{StaticResource StarPath}" 
  StrokeThickness="2" Stroke="White" Width="75" Height="75" 
  Margin="81,-0.333,0,0"  HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Path x:Name="border3"  Stretch="Uniform" Data="{StaticResource StarPath}" 
  StrokeThickness="2" Stroke="White" Width="75" Height="75" 
  Margin="160.659,-0.334,0,0"  HorizontalAlignment="Left" VerticalAlignment="Top" />
<Path x:Name="border4"  Stretch="Uniform" Data="{StaticResource StarPath}" 
  StrokeThickness="2" Stroke="White" Width="75" Height="75" 
  Margin="240.318,-0.334,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Path x:Name="border5"  Stretch="Uniform" Data="{StaticResource StarPath}" 
  StrokeThickness="2" Stroke="White" Width="75" Height="75" 
  Margin="319.98,-0.334,0,0"  HorizontalAlignment="Left" VerticalAlignment="Top" />

6.png

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Architect Sela
Israel Israel
Shai Raiten is VS ALM MVP, currently working for Sela Group as a ALM senior consultant and trainer specializes in Microsoft technologies especially Team System and .NET technology. He is currently consulting in various enterprises in Israel, planning and analysis Load and performance problems using Team System, building Team System customizations and adjusts ALM processes for enterprises. Shai is known as one of the top Team System experts in Israel. He conducts lectures and workshops for developers\QA and enterprises who want to specialize in Team System.

My Blog: http://blogs.microsoft.co.il/blogs/shair/

Comments and Discussions

 
QuestionOrientation of Star Fill Pin
Member 1000981424-Apr-13 2:11
Member 1000981424-Apr-13 2:11 
QuestionGood stuff - I refactored it and ported to WPF Pin
Meir Pletinsky3-Sep-12 0:46
Meir Pletinsky3-Sep-12 0:46 
QuestionChange control's dimension Pin
paradio13-Jun-12 5:30
paradio13-Jun-12 5:30 
Questionchange filling direction of stars Pin
Rajitha11112-Jun-12 18:49
Rajitha11112-Jun-12 18:49 
AnswerRe: change filling direction of stars Pin
Shai Raiten12-Jun-12 20:27
Shai Raiten12-Jun-12 20:27 
GeneralRe: change filling direction of stars Pin
paradio13-Jun-12 5:28
paradio13-Jun-12 5:28 
Questionmy vote of 5 Pin
mohamad yousef28-May-12 23:12
mohamad yousef28-May-12 23:12 
AnswerRe: my vote of 5 Pin
Shai Raiten28-May-12 23:29
Shai Raiten28-May-12 23:29 
QuestionI get this error message Pin
oren_david10-Dec-11 23:52
oren_david10-Dec-11 23:52 
An object of the type "System.String" cannot be applied to a property that expects the type "System.Windows.Media.Geometry".
any help??
AnswerRe: I get this error message Pin
Shai Raiten10-Dec-11 23:56
Shai Raiten10-Dec-11 23:56 
GeneralMy vote of 5 Pin
Bill SerGio, The Infomercial King5-Jul-11 9:32
Bill SerGio, The Infomercial King5-Jul-11 9:32 

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.