Series  - Create a UserControl with Silverlight 2.0 Beta x

Part I   - Create a UserControl with Silverlight 2.0 Beta 1
Part II - Create a UserControl with SilverLight 2.0 Beta  

DependencyProperties

In my post Create a UserControl with Silverlight 2.0 Beta 1. I started creating a UserControl in Silverlight using Silverlight 2.0 Beta 1, Beta 2 is out now.
Because I want to get familair with Silverlight (WPF, Silverlight should be a subset of WPF and thus IMO a great way to get more knowledge of this technology).

In my first part of the story, I made a UserControl. I did it with the .NET knowledge I already had, but doing so, I completely forgot to RTFM.
Later I did and learned about DependencyProperties. A great other way to work with properties.

MSDN:
A property that is backed by the WPF property system is known as a dependency property.
The purpose of dependency properties is to provide a way to compute the value of a property based on the value of other inputs. These other inputs might include system properties such as themes and user preference, just-in-time property determination mechanisms such as data binding and animations/storyboards, multiple-use templates such as resources and styles, or values known through parent-child relationships with other elements in the element tree. In addition, a dependency property can be implemented to provide self-contained validation, default values, callbacks that monitor changes to other properties, and a system that can coerce property values based on potentially runtime information. Derived classes can also change some specific characteristics of an existing property by overriding dependency property metadata, rather than overriding the actual implementation of existing properties or creating new properties.

The concept is a little overwhelming at first, seeing DependencyProperty code the first time can be confusing.
In Part 1 the HC.Silverlight.TryOut.GenderChooser had the property Gender.

   1:  public GenderChoice Gender
   2:  {
   3:      get { return _Gender; }
   4:      set 
   5:      { 
   6:      _Gender = value;
   7:      SetColorAccordingToChoice();
   8:      OnGenderChosen(this, _Gender);
   9:      }
  10:  }

Listing 1

In the setter of the Gender property, the method SetColorAccordingToChoice is called, this sets the colors of the symbols in a particular way and it fires the OnGenderChosen event.
Every containing control can subscribe to this event and react to changes when it is fired.

Now, in WPF and Silverlight DependencyProperties are available, this property is a great candidate for this concept. But how does it work?
First we refactor our Property  to use GetValue in the setter and SetValue in the setter. We also add a GenderProperty of type DependencyProperty and call the static method Register on the DependencyProperty object:

   1:  public GenderChoice Gender
   2:  {
   3:     get { return (GenderChoice)GetValue(GenderProperty); }
   4:     set { SetValue(GenderProperty, value); }
   5:  }
   6:   
   7:  public static readonly DependencyProperty GenderProperty =
   8:     DependencyProperty.Register("Gender", typeof(GenderChoice), 
   9:                                           typeof(GenderChooser), 
  10:                                           new PropertyMetadata(
  11:                                           new PropertyChangedCallback(OnGenderChanged)));

Listing 2

Now we got the skeleton to use the DependencyProperty concept in the getter and setter of the property. And we also got a DependencyProperty with the name GenderProperty registered on the FrameworkElement.

Lets analyse the registering of the DependencyProperty. First we look at the signature for the public static Register method on the System.Windows.DependencyProperty class.

   1:  // Summary:
   2:  //     Registers a dependency property with the specified property name, 
   3:  //       property type, owner type, and property metadata for the property.
   4:  //
   5:  // Parameters:
   6:  //   name:
   7:  //     The name of the dependency property to register.
   8:  //
   9:  //   propertyType:
  10:  //     The type of the property.
  11:  //
  12:  //   ownerType:
  13:  //     The owner type that is registering the dependency property.
  14:  //
  15:  //   typeMetadata:
  16:  //     A property metadata instance. 
  17:  //       This can contain a System.Windows.PropertyChangedCallback
  18:  //     implementation reference.
  19:  //
  20:  // Returns:
  21:  //     A dependency property identifier that should be used to set the value of
  22:  //     a public static readonly field in your class. That identifier is then used
  23:  //     to reference the dependency property later, for operations such as setting
  24:  //     its value programmatically.
  25:  public static DependencyProperty Register(string name, 
  26:                                            Type propertyType, 
  27:                                            Type ownerType, 
  28:                                            PropertyMetadata typeMetadata);

Listing 3

The first parameter in the static Register method is name (string). Name is the name of the identifier field that you use to store the name and characteristics of the dependency property must be the Name you chose for the dependency property as part of the Register call appended by the literal string Property.
It is very important to follow the naming pattern, if you fail to designers might not report your property correctly, and certain aspects of property system style application might not behave as expected.

In our example,

  • the name of the dependency property and its CLR accessor is Gender;
  • The identifier field is GenderProperty;
  • The type of the property is GenderChoice;
  • The type that registers the dependency property is GenderChooser;
  • The PropertyMetadata is used to register the PropertyChangedCallback OnGenderChanged on the DependencyProperty, whenever the value in the property changes (is set) this callback is fired. In this callback you put the code to do your validation or any code that needs to run when the value of the property changes. 

So now we need a delegate to declare our EventHandler. The delegate OnGenderChangedEventHandler returns nothing but takes an object and a GenderChoice as parameters.
Furthermore we need a public event GenderChanged and a private static void OnGenderChanged which is the callback function that is fired when the value of the Property changes.

   1:  public delegate void OnGenderChangedEventHandler(object sender, GenderChangedEventArgs e);
   2:   
   3:  public event OnGenderChangedEventHandler GenderChanged;
   4:   
   5:  /// <summary>
   6:  /// Raises Event for DependencyProperty
   7:  /// </summary>
   8:  /// <param name="d"></param>
   9:  /// <param name="e"></param>
  10:  private static void OnGenderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  11:  {
  12:     if (d is GenderChooser)
  13:     {
  14:         ((GenderChooser)d).SetColorAccordingToChoice(e);
  15:         ((GenderChooser)d).OnGenderChosen((GenderChooser)d, 
  16:                            new GenderChangedEventArgs((GenderChoice)e.NewValue));
  17:     }
  18:  }

Listing 4

This is all the code you need to make a DependencyProperty.
But in our UserControl from Part 1 ,the GenderChooser, we need the GenderChosen event to fire as soon as the value of our Gender property changes. As we see on line 15 of Listing 4 the OnGenderChanged method must be static, because the DependencyProperty.Register method on line 8 of Listing 3 is static and it calls the callback.

Because the callback function OnGenderChanged is static, we need to cast the DependencyObject d to the right object, GenderChooser, after we check if d is of type Genderchooser ofcourse.
Next we call the methods on the calling object itself with the help of a cast (((GenderChooser)d).SetColorAccordingToChoice(e);).

As is shown on line 15 of Listing 4 the OnGenderChosen method is called, which takes an object and a GenderChangedEventArgs, with a GenderChoice as parameter in the constructor, as parameters.
This is the method that will raise the event GenderChosen, if a class is attached to the eventhandler.

So we need the following code to make the event GenderChosen to work.

   1:  // Outside the class!
   2:  public delegate void GenderChosenEventHandler(object sender, GenderChoice gender);
   3:   
   4:   
   5:  // public partial class GenderChooser : UserControl
   6:  // {
   7:   
   8:  public event GenderChosenEventHandler GenderChosen;
   9:   
  10:  private void OnGenderChosen(object sender, GenderChangedEventArgs e)
  11:  {
  12:      if (GenderChosen != null)
  13:      {
  14:          GenderChosen(sender, e.SelectedGender);
  15:      }
  16:  }

Listing 5

The GenderChosenEventHandler needs to be global, so needs to be outside the class and inside the namespace.
On line 10 (Listing 5) you see GenderChangedEventArgs, this is a class derived from EventArgs with nothing more than a TimeStamp and a GenderChoice property.
So it is easy to pass the SelectedGender in the Event.

GenderChangedEventArgs code

   1:  using System;
   2:   
   3:  namespace HC.Silverlight.TryOut
   4:  {
   5:      public class GenderChangedEventArgs: EventArgs
   6:      {
   7:          #region Properties
   8:          public DateTime Stamp { get; set; }
   9:          public GenderChoice SelectedGender { get; set; }
  10:          #endregion
  11:   
  12:          #region C'tors
  13:          public GenderChangedEventArgs(GenderChoice selectedGender)
  14:          {
  15:              Stamp = DateTime.Now;
  16:              SelectedGender = selectedGender;
  17:          }
  18:          #endregion
  19:      }
  20:  }

Listing 6

In Part I Line 36 to 55 the region 'Events' and the region 'EventHandler' can be removed! (thank you, Micheal Ashby (Microsoft) for pointing this out...).

Last but not least, the project is available and can be downloaded here.

DependencyProperties is a well thought out concept it was one of the first things designed when Microsoft started with WPF.
I cannot forget Mark Miller, I attended his session on WPF at the DevDays in Holland, he said: "WPF is designed by geniuses, but implemented by ...", I think the first part means a lot coming from Mark...

Series  - Create a UserControl with Silverlight 2.0 Beta x

Part I   - Create a UserControl with Silverlight 2.0 Beta 1
Part II - Create a UserControl with SilverLight 2.0 Beta  

Henry Cordes
My thoughts exactly...


 

When I migrated my blog over to a new provider and blogengine, I lost some images,replaced them using VS2010, so some screenshots look 'newer'.

Series  - Create a UserControl with Silverlight 2.0 Beta x  

Part I   - Create a UserControl with Silverlight 2.0 Beta 1 (this post)
Part II - Create a UserControl with SilverLight 2.0 Beta

Wanting to know what all the hype is really about, I started to think about creating a web application with Silverlight.
Before I will create an application, my idea is to start of with a UserControl. A really simple one too. For the application I am going to maybe be building with Silverlight, I need a UI component to choose the gender of a person.
You could use two radiobuttons, make them a member of the same group and we're off. But with Silverlight we can try a more graphical approach, maybe a more intuitive control. I want to create a control that has the Male and Female Symbol that are clickable, through clicking on a symbol you select the gender.

I installed:

  • Microsoft Expression Design
  • Microsoft Expression Blend 2.5 March 2008 Preview
  • Microsoft Visual Studio 2008
  • Microsoft Silverlight Tools Beta 1 for Visual Studio 2008

First thing you learn when starting to use this technology is that you really need a designers eye on things. So if you are not a creative person, that likes designing stuff, maybe you need a designer to do it for you.
We are going to try to do the design ourselves for now.

Create Solution and projects
First I Fired up Visual Studio 2008. I created a new Silverlight Application (so it is easy to try our control out).


Create Silverlight Application

As soon as you click OK, you are presented a choice, because Silverlight will be hosted on or inside a web page, you have to choose from the following options:

  1. Create only the Silverlight app and let Studio generate an HTML page for you to test the app with (only in bin/debug)
  2. Add a Website project to your solution
  3. Add a Web Application Project to your solution

 


Select hosting application type

I choose option 3. a Web Application Project. Now I got two projects inside my solution. I right click on the Page.xaml file and choose "Open in Expression Blend"

 


Open in MS Blend

Blend
Rightclick on the Silverlight project file and choose "Add New Item..." from the menu

Add new item to Silverlight application project in Blend Add New Item

Select UserControl as template and give the control a name.

Give the xaml file for your UserControl a name New Item

Rightclick in the area named: "Objects and Timeline" in the left side of Blend, on the UserControl and select Rename, give the control a name (GenderChoose), it cannot be the same as the name you gave the .xaml file (this is confusing, because the New Item dialog suggests you name the Usercontrol, but all you do is name the xaml and these names both must be unique.

Rename the UserControl in blend Rename Control

Because the background of the LayoutRoot is white  We need to change it to transparent, we do this by selecting the LayoutRoot inside the UserControl

Select LayoutRoot Grid in Blend Select LayoutRoot

After this,we select the little square that is on the right of the BackGround Brush setting

Background brush

In the contextmenu choose "Reset" and the background is transparent

No brush

Design

Now it is time to fire up Microsoft Expression Design, it is a vector based drawing and design tool. I need to create the Male and Female symbols with it.
Click File > New and create a document with Width: 120 px and Height 140 px.
From the toolbox select an Ellipse

Select Ellipse

Draw a circle and select a line, to draw the arrow on the upper right of the circle. Set the width of the line to 10px. You should be making something like this:

Male symbol inside Microsoft Expression Design Male Symbol


Select all paths in your drawing, than click Object > Compound Path > Make, or use Ctrl + 8. Than again select all on your drawing

Compounded path

and paste into your control in Blend.

Male symbol pasted into Blend Path inside Blend Control

Rename the Path to MalePath. Do the same for the Female symbol (draw, make compound path, copy and paste into Blend) and rename this path into FemalePath. Give the paths a color you like, by selecting and choosing the right color in Brush option. Now we got something like this

Symbols have color Colors added

And the result in Xaml is this:

   1:  <UserControl
   2:      xmlns="http://schemas.microsoft.com/client/2007"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      mc:Ignorable="d"

   7:      x:Class="HC.Silverlight.Tyout.GenderChooser"
   8:      d:DesignWidth="288" d:DesignHeight="160" x:Name="GenderChoose">
   9:   
  10:      <Grid x:Name="LayoutRoot" Height="160" Width="244" >
  11:          <Path x:Name="MalePath"    
  12:              MouseLeftButtonDown="MalePath_MouseLeftButtonDown" 
  13:              MouseEnter="MalePath_MouseEnter"    
  14:              MouseLeave="MalePath_MouseLeave"   
  15:              Stretch="Fill" StrokeThickness="10" 
  16:              StrokeLineJoin="Round" 
  17:              Stroke="#4C0E1B4B" 
  18:              Data="M71.182777,43.484818 C88.632889,57.770176 91.340874,83.323097 77.231247,
  19:                  100.56004 C63.121323,117.79601 37.536755,120.18899 20.086346,
  20:                  105.90402 C2.6360354,91.620079 -0.071950652,66.066154 14.037977,
  21:                  48.829205 C28.147804,31.592951 53.732468,29.20006 71.182777,
  22:                  43.484818 z M71.687004,42.605003 L102.30298,5.2050009 L104.313,
  23:                  34.850189 M71.437004,42.401001 L102.053,5.000001 L72.594521,8.885973" 
  24:              Height="120.23" 
  25:              HorizontalAlignment="Left" 
  26:              Margin="2.93199992179871,2.15100002288818,0,37.6189994812012" 
  27:              VerticalAlignment="Stretch" 
  28:              Width="109.313" 
  29:              d:LayoutOverrides="Width"/>
  30:          <Path x:Name="FemalePath"  
  31:              MouseLeftButtonDown="FemalePath_MouseLeftButtonDown"    
  32:              MouseEnter="FemalePath_MouseEnter"    
  33:              MouseLeave="FemalePath_MouseLeave"   
  34:              Stretch="Fill"   

  35:              StrokeThickness="10"   
  36:              StrokeLineJoin="Round"   
  37:              Stroke="#4CF502E4"   
  38:              Data="M 75.6667,46.1667C 98.2183,46.1667 116.5,64.2245 116.5,86.5C 116.5,108.775 98.2183,126.833 75.6667,
  39:                  126.833C 53.1151,126.833 34.8333,108.775 34.8333,86.5C 34.8333,64.2245 53.115,46.1667 75.6667,
  40:                  46.1667 Z M 76.5,126.5L 76.5,185.167M 103.833,159.167L 48.8333,159.167"   
  41:              HorizontalAlignment="Left"   
  42:              Margin="122.833000183105,0.166999995708466,0,10.8330001831055"   
  43:              Width="91.667"   
  44:              d:LayoutOverrides="Width"/>
  45:      </Grid>
  46:  </UserControl>

Listing 1

Now we open Visual studio again, we get the file modified message

We select Yes to all and in the upper side of the Studio Design screen we click reload.
We open the code window for our GenderChooser class (GendserChooser.xaml.cs) and I wrote the following code:

 

   1:  namespace HC.Silverlight.Tryout
   2:  {
   3:      #region Enums
   4:      /// <summary>
   5:      /// Tells which Gender
   6:      /// </summary>
   7:      public enum GenderChoice
   8:      {
   9:          /// <summary>
  10:          /// Gender for man
  11:          /// </summary>
  12:          Male = 0,
  13:          /// <summary>
  14:          /// Gender for woman
  15:          /// </summary>
  16:          Female = 1,
  17:          /// <summary>
  18:          /// Not sure :-)
  19:          /// </summary>
  20:          Unknown = 2
  21:      }
  22:   
  23:      #endregion
  24:   
  25:      #region Delegates
  26:      /// <summary>
  27:      /// Handles GenderChosen Event
  28:      /// </summary>
  29:      /// <param name="sender"></param>
  30:      /// <param name="e"></param>
  31:      public delegate void GenderChosenEventHandler(object sender, GenderChoice gender);
  32:  #endregion
  33:   
  34:      public partial class GenderChooser : UserControl
  35:      {
  36:          #region Events
  37:          /// <summary>
  38:          /// GenderChosen
  39:          /// </summary>
  40:          public event GenderChosenEventHandler GenderChosen;
  41:          #endregion
  42:   
  43:          #region EventHandler
  44:          /// <summary>
  45:          /// Raises event, if EventHandler not null
  46:          /// </summary>
  47:          /// <param name="message"></param>
  48:          private void OnGenderChosen(object sender, GenderChoice selectedGender)
  49:          {
  50:              if (GenderChosen != null)
  51:              {
  52:                  GenderChosen(sender, selectedGender);
  53:              }
  54:          }
  55:          #endregion
  56:   
  57:   
  58:          #region Privates
  59:          private GenderChoice _Gender = GenderChoice.Unknown;
  60:          private SolidColorBrush _MaleColor = new SolidColorBrush(Color.FromArgb(255, 14, 27, 75));
  61:          private SolidColorBrush _MaleColorDisabled = new SolidColorBrush(Color.FromArgb(70, 14, 27, 75));
  62:          private SolidColorBrush _FemaleColor = new SolidColorBrush(Color.FromArgb(255, 245, 2, 228));
  63:          private SolidColorBrush _FemaleColorDisabled = new SolidColorBrush(Color.FromArgb(70, 245, 2, 228));
  64:          #endregion
  65:   
  66:          #region Properties
  67:   
  68:          #region Colors
  69:          /// <summary>
  70:          /// Gets or sets MaleColorDisabled, the color if the Male symbol is disabled
  71:          /// </summary>
  72:          public SolidColorBrush MaleColorDisabled

  73:          {
  74:              get { return _MaleColorDisabled; }
  75:              set { _MaleColorDisabled = value; }
  76:          }
  77:   
  78:          /// <summary>
  79:          ///  Gets or sets FemaleColorDisabled, the color if the Female symbol is disabled
  80:          /// </summary>
  81:          public SolidColorBrush FemaleColorDisabled
  82:          {
  83:              get { return _FemaleColorDisabled; }
  84:              set { _FemaleColorDisabled = value; }
  85:          }
  86:   
  87:          /// <summary>
  88:          /// Gets or sets MaleColor
  89:          /// </summary>
  90:          public SolidColorBrush MaleColor
  91:          {
  92:              get { return _MaleColor; }
  93:              set { _MaleColor = value; }
  94:          }
  95:   
  96:          /// <summary>
  97:          /// Gets or sets FemaleColor
  98:          /// </summary>
  99:          public SolidColorBrush FemaleColor
 100:          {
 101:              get { return _FemaleColor; }
 102:              set { _FemaleColor = value; }
 103:          }

 104:          #endregion
 105:   
 106:          /// <summary>
 107:          /// Gets or sets GenderChoice
 108:          /// </summary>
 109:          public GenderChoice Gender
 110:          {
 111:              get { return _Gender; }
 112:              set 
 113:              { 
 114:                  _Gender = value;
 115:                  SetColorAccordingToChoice();
 116:                  OnGenderChosen(this, _Gender);
 117:              }
 118:          }
 119:          #endregion
 120:   
 121:          #region C'tor
 122:          public GenderChooser()
 123:          {
 124:              // Required to initialize variables
 125:              InitializeComponent();
 126:   
 127:          }

 128:          #endregion
 129:   
 130:          #region MouseEvents
 131:          private void MalePath_MouseEnter(object sender, MouseEventArgs e)
 132:          {
 133:              MalePath.Stroke = MaleColor;
 134:          }
 135:   
 136:          private void MalePath_MouseLeave(object sender, MouseEventArgs e)
 137:          {
 138:              if (Gender == GenderChoice.Male)
 139:              {
 140:                  MalePath.Stroke = MaleColor;
 141:              }
 142:              else
 143:              {
 144:                  MalePath.Stroke = MaleColorDisabled;
 145:              }
 146:          }
 147:   
 148:          private void FemalePath_MouseEnter(object sender, MouseEventArgs e)
 149:          {
 150:              FemalePath.Stroke = FemaleColor;
 151:          }
 152:   
 153:   
 154:          private void FemalePath_MouseLeave(object sender, MouseEventArgs e)
 155:          {
 156:              if (Gender == GenderChoice.Female)
 157:              {
 158:                  FemalePath.Stroke = FemaleColor;

 159:              }
 160:              else
 161:              {
 162:                  FemalePath.Stroke = FemaleColorDisabled;
 163:              }
 164:          }
 165:   
 166:   
 167:          private void MalePath_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 168:          {
 169:              Gender = GenderChoice.Male;
 170:          }
 171:   
 172:          private void FemalePath_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 173:          {
 174:              Gender = GenderChoice.Female;
 175:          }
 176:   

 177:          #endregion
 178:   
 179:          #region Private Methods
 180:          /// <summary>
 181:          /// Sets color for Male and Female according to Gender chosen
 182:          /// </summary>
 183:          private void SetColorAccordingToChoice()
 184:          {
 185:              switch (Gender)
 186:              {
 187:                  case  GenderChoice.Unknown:
 188:                      MalePath.Stroke = MaleColorDisabled;
 189:                      FemalePath.Stroke = FemaleColorDisabled;
 190:                      break;
 191:                  case GenderChoice.Female:
 192:                      MalePath.Stroke = MaleColorDisabled;
 193:                      FemalePath.Stroke = FemaleColor;
 194:                      break;
 195:                  case GenderChoice.Male:
 196:                      MalePath.Stroke = MaleColor;
 197:                      FemalePath.Stroke = FemaleColorDisabled;
 198:                      break;
 199:              }
 200:          }
 201:          #endregion
 202:      }
 203:  }

 Listing 2

I use the SolidColorBrush to change the color of the symbols, (I only change the Alpha channel). I decided to make properties for the colors, that way the colors are dynamic.
An enum is used for the Gender choice, a property Gender of type GenderChoice (the enum) holds the current Gender.
In the setter for Gender the method SetColorAccordingToChoice is called in this method the current Gender is read and accordingly the richt colors are set on the symbols.

Control in startup state Control has Female selected
Control in action

The control in action, the first picture shows the control in startup state (symbols greyed out), the second picture shows the female symbol selected, under the hood the Gender is filled with GenderChoice.Female and the GenderChosen Event is fired.

All in all I think Silverlight is cool for it's purpose. It is possible to create strong graphics for your application and use them programming the language you already are used to.
The fact that not everybody is a designer is really the downside. A lot of developers will be better of creating windows or web apps using standard controls.
Ofcourse with Silverlight 2.0 standard controls are available, but the strong point of Silverlight is it's decoupling of UI from logic.

When designers are going to adopt Blend and MS Design, it could be really taking off, but until that time we have to do it ourselves.

Series  - Create a UserControl with Silverlight 2.0 Beta x

Part I   - Create a UserControl with Silverlight 2.0 Beta 1 (this post)
Part II - Create a UserControl with SilverLight 2.0 Beta

Henry Cordes
My thoughts exactly...