Page view counter

Jesse Liberty - Silverlight Geek

More Signal Less Noise

Digging Into Custom Controls

Last night’s post was something of a preface, but let’s get started.

[ For those of you who crave the details, the code, the feel of bits between your fingers, watch for a series of videos on this subject to be released in the next couple weeks with source in VB and C# ]

As I started to say last night, the key distinction in writing custom controls in Silverlight as opposed to other GUI environments is the strict division between logic and visuals embodied in the Parts and States Model.

As an aside, this is where we always point out that there is nothing in Silverlight that requires or enforces that you implement your custom control using the Parts and States model, but it is the model recommended by Microsoft, and it is the model understood and supported by Expression Blend. The fact is, I can’t imagine creating a custom control that does not conform to the P&S model except to show that it can be done.

A Brief Introduction to the Parts and States Model

The key concept behind the P&S model is that your control will have a strict separation of logic from visuals, and the visuals will be managed by the Visual State Manager which will need to know (a) what States might the control be in (states are defined in just a moment) and (b) what parts of the control might be under VSM control.

States are familiar to those who’ve worked with Templates, and in truth, if you haven’t you want to stop right here and go do that.  I posted three videos on styles and templates that will get you started as well as a few useful blog entries 

From a P&S model perspective, a control is either in a state or transitioning from one state to another.  The Visual State Manager is responsible for running the storyboard associated with your control being in a given state (such as MouseOver).

If you are templating an existing control, the states have been enumerated already, you can’t add new states unless you create a custom control. More on that in a moment

Parts

Controls are of course made up of many parts (little p) but from a P&S perspective they aren’t considered Parts unless they will be called by methods of the control itself.

For example, the ScrollBar is a control available in the Silverlight toolbox. From the P&S view point it can be decomposed into four Parts.

  1. · Down Repeat Button
  2. · Up Repeat Button
  3. · Scroll Bar
  4. · Thumb

ScrollBar

While there may be other elements in a Scrollbar, these are the Parts, because these elements are the only elements that other elements of the Scrollbar must address directly.

Many controls, for that support the P&S model, such as Button, have no parts at all (!)

Creating the Contract

When you create a custom control in Silverlight you create a “contract” stating “this part is under the domain of the VSM” and the rest is considered logic that is on the “other side of the wall.”

Attributes are a mechanism to store metadata within a .NET program.  You can see an example in this excerpt from a Ratings control, which can be “lit” or not depending on the user’s action.  (For more on attributes see any good book on C# or VB )

   1: [TemplatePart( Name = "Core", Type = typeof( FrameworkElement ) )]
   2:  
   3: [TemplateVisualState( Name = "Normal", GroupName = "CommonStates" )]
   4: [TemplateVisualState( Name = "MouseOver", GroupName = "CommonStates" )]
   5: [TemplateVisualState( Name = "Pressed", GroupName = "CommonStates" )]
   6:  
   7: [TemplateVisualState( Name = "Lit", GroupName = "RatingStates" )]
   8: [TemplateVisualState( Name = "Norm", GroupName = "RatingStates" )]
   9:  
  10: public class RatingControl : Control

 

This snippet shows six attributes being added to a new Custom Control. The first is the only “Part” named “Core” (stolen directly from Karen Corby).  The next three are the three “common states” this new control will support. Notice that they share the GroupName of “CommonStates”.  Finally, on lines 7 and 8 are the two RatingStates of Lit and Norm. 

The Contract Divides Logic from Visuals

These few lines draw a powerful contract that the developer and designers can rely on, as can Expression Blend. They state clearly that the “Core” object (to be created in Xaml) will be under the management of internal methods as a Part,  that the new control will have two state groups, and it enumerates the states within each group.

Further, the class definition shows that our new control derives from the base class Control.

Implementing the Contract

It is up to me now to implement the contract. The steps to getting here were:

  1. Create a Silverlight Application and choose Project Type Web Site
  2. Right click on the solution and Add New Project of type Silverlight Class Library
  3. Add a new class to the Class Library (I named it Rating) which generates Rating.cs
  4. Throw away Class1.cs which was created for you
  5. Right click on the Class Library Project and choose Add->New Item. Pick the Silverlight User Control template and name it generic.xaml. It must have that name (!)
  6. Throw away generic.xaml.cs

Here’s where we are

  • Rating.cs will contain the code for your custom control, along with the meta-data to create the contract for the Parts and States model
  • generic.xaml will contain the default appearance of your control (in Xaml)
  • Once your control is created you will make an instance of it in the Page.xaml of the project you created back in step 1 above.
   1: <UserControl x:Class="BookRater1.Page"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     xmlns:Controls="clr-namespace:ClassLibrary;assembly=ClassLibrary"
   5:     xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
   6:     Width="600" Height="400">
   7:  
   8:     <Grid x:Name="LayoutRoot" Background="White">
   9:         <Grid.RowDefinitions>
  10:             <RowDefinition Height=".5*" />
  11:             <RowDefinition Height=".5*" />
  12:         </Grid.RowDefinitions>
  13:         <Grid.ColumnDefinitions>
  14:             <ColumnDefinition Width=".5*" />
  15:             <ColumnDefinition Width=".5*" />
  16:         </Grid.ColumnDefinitions>
  17:         
  18:         <Controls:RatingControl x:Name="Rating1"  Grid.Row="0" Grid.Column="0" />
  19:         <Controls:RatingControl x:Name="Rating2"  Grid.Row="1" Grid.Column="0" Template="{StaticResource RatingControlControlTemplate1}"   />
  20:     </Grid>
  21: </UserControl>

A Few Things To Notice

Remember that this is a view of Page.xaml – the page that is using the custom control.

  • On line 4 you set up the namespace for the class library.
  • On lines 7 and 8 you create two instances of the custom control, the second of which overrides the default appearance by using a template, just as you might do with any other control (we’ve not seen the creation of that template yet)

What is in Rating.cs and generic.xaml?

generic.xaml

   1: <ResourceDictionary
   2:    xmlns=  -- Many of these -->
   3:   <Style TargetType="controls:RatingControl">
   4:     <Setter Property="Template">
   5:       <Setter.Value>
   6:         <ControlTemplate TargetType="controls:RatingControl">
   7:           <Grid x:Name="LayoutRoot">
   8:             <Grid.Resources>
   9:               <Storyboard x:Key="UnLight" >
  10:                 <DoubleAnimation 
  11:                    Storyboard.TargetName="Core" 
  12:                    Storyboard.TargetProperty="(UIElement.Opacity)" 
  13:                    Duration="0:0:0.01" From="1" To=".5"/>
  14:               </Storyboard>
  15:               <Storyboard x:Key="Light" >
  16:                 <!-- -->
  17:               </Storyboard>
  18:               <Storyboard x:Key="Bounce" RepeatBehavior="forever" >
  19:                 <DoubleAnimationUsingKeyFrames 
  20:                     BeginTime="00:00:00" 
  21:                     Duration="00:00:01" 
  22:                     Storyboard.TargetName="Core" 
  23:                     Storyboard.TargetProperty="(UIElement.RenderTransform).
  24:                     (TransformGroup.Children)[3].(TranslateTransform.Y)">
  25:                         <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
  26:                         <SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="25"/>
  27:                         <SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="0"/>
  28:                         <SplineDoubleKeyFrame KeyTime="00:00:00.75" Value="50"/>
  29:                         <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
  30:                 </DoubleAnimationUsingKeyFrames>
  31:               </Storyboard>
  32:               <Storyboard x:Key="Dip" >
  33:                 <!-- -->
  34:               </Storyboard>
  35:             </Grid.Resources>
  36:             <vsm:VisualStateManager.VisualStateGroups>
  37:               <vsm:VisualStateGroup x:Name="CommonStates">
  38:                 <vsm:VisualState x:Name="Normal" />
  39:                 <vsm:VisualState x:Name="MouseOver" 
  40:                     Storyboard="{StaticResource Bounce}"/>
  41:                 <vsm:VisualState x:Name="Pressed" 
  42:                     Storyboard="{StaticResource Dip}"/>
  43:               </vsm:VisualStateGroup>
  44:               <vsm:VisualStateGroup x:Name="RatingStates">
  45:                 <vsm:VisualState  x:Name="Norm" 
  46:                     Storyboard="{StaticResource UnLight}" />
  47:                 <vsm:VisualState x:Name="Lit" 
  48:                     Storyboard="{StaticResource Light}" />
  49:               </vsm:VisualStateGroup>
  50:             </vsm:VisualStateManager.VisualStateGroups>
  51:             <Ellipse 
  52:                 x:Name="Core" 
  53:                 Width="200" 
  54:                 Height="200" RenderTransformOrigin="0.5,0.5" >
  55:               <Ellipse.RenderTransform>
  56:                 <TransformGroup>
  57:                   <ScaleTransform/>
  58:                   <SkewTransform/>
  59:                   <RotateTransform/>
  60:                   <TranslateTransform/>
  61:                 </TransformGroup>
  62:               </Ellipse.RenderTransform>
  63:               <Ellipse.Fill>
  64:                 <RadialGradientBrush>
  65:                   <GradientStop Color="#FFFFD954" Offset="0.004"/>
  66:                   <GradientStop Color="#FFE9F515" Offset="1"/>
  67:                   <GradientStop Color="#FFF1F712" Offset="0.911"/>
  68:                 </RadialGradientBrush>
  69:               </Ellipse.Fill>
  70:             </Ellipse>
  71:           </Grid>
  72:         </ControlTemplate>
  73:       </Setter.Value>
  74:     </Setter>
  75:   </Style>
  76: </ResourceDictionary>

This file has been cut down, but you can see that it looks very much like a standard template file. We begin the substantive work on line 8 creating a Resources sections. In here we create a Storyboard for each of the behaviors we might want in a given state. That is, if we have decided that the behavior when we hover over the custom control will be for it to bounce up and down hyperactively  we would create the storyboard for that here in the resources area (as we do on lines 18-31).

After the Resources (line 35) we define the Visual State Groups (lines 36-50) and within each of the groups, the visual states.  The job here is to assign the appropriate story board to each of the states.

Finally, on line 51 we create our custom control’s default appearance, including the named Part, “Core” which is the ellipse defined on lines 51 to  70.  In this simplified example that happens to be the only object in the control, but more complex controls may have many unnamed elements as well.

Rating.cs

The code file for our class defines both the logic and the enabling (private) code for the translation of CLR events to states that the VSM will recognize.  It is also here that we apply either the default look (generic.xaml) or the templated look that was requested when the control was instantiated in page.xaml. This is done, essentially by calling firing the base class’s OnApplyTemplate event.

We extract the named part from the Xaml and hold onto it in a member variable, as we’ll use it quite a bit and then we tell the control to GoToState, a private helper method that checks other member variables and determines how to call the Visual State Manager’s static GoToState method,

   1: public class RatingControl : Control
   2: {
   3:    private FrameworkElement corePart;
   4:    private bool isMouseOver;
   5:    private bool isPressed;
   6:    public event RoutedEventHandler Click;
   7:      public RatingControl()
   8:      {  DefaultStyleKey = typeof(RatingControl);  }
   9:  
  10:      public override void OnApplyTemplate()
  11:      {
  12:          base.OnApplyTemplate();
  13:          CorePart = (FrameworkElement)GetTemplateChild("Core");
  14:          GoToState(false);
  15:      }
  16:  
  17:      private void GoToState(bool useTransitions)
  18:      {
  19:          if (isPressed)
  20:          { VisualStateManager.GoToState(this, "Pressed", useTransitions); }
  21:          else if (isMouseOver)
  22:          { VisualStateManager.GoToState(this, "MouseOver", useTransitions); }
  23:        //...
  24:      }
  25:      //...
  26:    }

The two major missing pieces are converting the CLR events to the VSM events and the sneaky fact that the setter for the private member CorePart doesn’t just set the CorePart but it also unregisters its old event handlers and registers its new event handlers for MouseEnter, MouseLeave, MouseLeftButtonDown and MouseButtonUp.  This latter step lets us accomplish the former step with event handlers like this

   1: void corePart_MouseEnter(object sender, MouseEventArgs e)
   2: {
   3:     isMouseOver = true;
   4:     GoToState(true);
   5: }
   6:  
   7: void corePart_MouseLeave(object sender, MouseEventArgs e)
   8: {
   9:     isMouseOver = false;
  10:     GoToState(true);
  11: }

Even walking through it fairly carefully it can get very confusing; there are some pretty complex attachments going on. Thus, rather than add insult to injury I’ll stop here and recap and then wait until the first video where you can see the pieces working together before going any further.

Putting It Together

In a nutshell, you have 5 files working together when all is done.

  1. In the ClassLibrary, Rating.cs  which defines the logic and methods as well as the attributes of the custom control. The attributes define the contract, and are what make the control skinnable with the assistance of tools like Blend
  2. Also in the ClassLibrary is a file that must be named generic.xaml that defines the default look for your custom control (in xaml).
  3. In your application there are three files (as usual) that do the heavy lifting: Page.xaml, Page.xaml.cs and App.xaml.  They do their normal jobs here. That is: Page.xaml makes an instance of the control and may or may not include a Template statement asking for a Template in Page.xaml or (more likely) in App.xaml to override the default look and feel
  4. Page.xaml.cs contains the logic of the application (not of the control) just as it always does
  5. App.xaml may have a template for your new Custom control just as it may have a template for button or checkbox.

More soon.

Comments

2008 September 13 - Links for today « My (almost) Daily Links said:

Pingback from  2008 September 13 - Links for today &laquo; My (almost) Daily Links

# September 13, 2008 3:43 AM

Maciek said:

This is exactly what I've been waiting for Jesse, thank you a LOT. I want MOOOOOOOOOOOOOORRRRRRRREEEEEEEEEEEEE :))))))))))))))))))

I have a small side request/question :

I've been having some fun with states and I've noticed that it is still possible to record storyboards (Blend 2.5 Beta)as we used to in SL Beta 1 .

Let's assume that I've created two states :

State A

State B

Let's assume that I've recorded 2 storyboards, 1 for each state :

StoryBoard A = 2 frames(0s, 2s)

StoryBoard B = 2 frames(0s, 3s)

Let's assume that I've added transition lenghts of 1s each, to each state.

How does one affect the other? I don't seem to understand it ?

# September 13, 2008 9:53 AM

Dew Drop - September 13, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - September 13, 2008 | Alvin Ashcraft's Morning Dew

# September 13, 2008 9:53 AM

jesseliberty said:

Maciek,

First, thank you for the kind words. As for your question, let me put something together that will illustrate the question you are asking as it is an interesting one.

Thanks.

# September 13, 2008 6:21 PM

Maciek said:

Awesome, I can't wait.

# September 14, 2008 9:58 AM

mikeb123 said:

Great article Jesse.

If the xaml for the custom control needs to be in generic.xaml, how does one define multiple custom controls in a single class library?

Thanks

# September 14, 2008 3:06 PM

Maciek said:

That's how :

<ResourceDictionary

  xmlns=  -- Many of these>

<Style TargetType="controls:Control1">

</Style>

<Style TargetType="controls:Control2">

</Style>

<Style TargetType="controls:Control3">

</Style>

</ResourceDisctionary>

# September 14, 2008 6:04 PM

Santiago Palladino » Controls Contract said:

Pingback from  Santiago Palladino &raquo; Controls Contract

# September 15, 2008 9:29 AM

jesseliberty said:

One error that I thought I fixed, but didn't. I wrote at the top of the article that the Parts are those elements under the control of the VSM. <wrong, but thanks for playing!>.  What I should have said is that Parts are those elements that are called by methods of the control itself -- the cannonical example is when you click on the repeat button of a scroll bar, the thumb has to move; the scroll bar is responsible for all of this, and so both the repeat button and the thumb are Parts.

Karen Corby was kind enough to point out (at my request)  that I fouled the wording about OnApplyTemplate implying that the control calls this, but of course the platform does.

I do owe a demo program on how transition timing works. That may take a week or two as things pile up here, but I'll see what I can do asap.

# September 15, 2008 4:37 PM

borith said:

could i have the source? i can't follow your instructions. thanks

# September 15, 2008 10:26 PM

Mirrored Blogs said:

In&#160; a previous post I began talking about Custom Controls, and I will continue that discussion over

# September 17, 2008 9:23 AM

Microsoft Weblogs said:

In&#160; a previous post I began talking about Custom Controls, and I will continue that discussion over

# September 17, 2008 9:57 AM

Odegaard said:

Where does the RatingStates and CommonStates groups come into the picture? I don't see you mentioning them anywhere besides declaring them.

# September 17, 2008 1:29 PM

jesseliberty said:

>> Where does the RatingStates and CommonStates groups come into the picture? I don't see you mentioning them anywhere besides declaring them. <<

Ahh, excellent... yes, that is coming, but we have so much to cover first :-)

# September 17, 2008 2:27 PM

Community Blogs said:

Chris Cavenagh has his YouCube interactive, Jesse Liberty on Custom Controls, Pete Brown with SL TechFest

# September 17, 2008 3:17 PM

Frank La Vigne said:

# September 17, 2008 5:01 PM

Jesse Liberty - Silverlight Geek said:

In&#160; a previous post I began talking about Custom Controls, and I will continue that discussion over

# September 17, 2008 9:13 PM

Jesse Liberty - Silverlight Geek said:

It will be helpful as we explore custom controls to have a common starting project. You may&#160; remember

# September 21, 2008 8:54 AM

Mirrored Blogs said:

It will be helpful as we explore custom controls to have a common starting project. You may&#160; remember

# September 21, 2008 9:11 AM

Microsoft Weblogs said:

It will be helpful as we explore custom controls to have a common starting project. You may&#160; remember

# September 21, 2008 9:17 AM

Amin Mahpour » Blog Archive » Silverlight post collections said:

Pingback from  Amin Mahpour  &raquo; Blog Archive   &raquo; Silverlight post collections

# September 22, 2008 5:54 AM

Mirrored Blogs said:

This is the first in a series of explorations of breaking changes in the Release Candidate for Silverlight

# September 27, 2008 1:26 PM