Page view counter

Jesse Liberty - Silverlight Geek

More Signal Less Noise

Custom Controls – The Dénouement

Silverlight Logo 

 

 

Over the past month I've posted half a dozen min-articles about creating custom controls that can interact with the Visual State Manager, and the underlying engine that makes it work; especially Dependency Properties and the Parts and States Model.

With this background, we're ready to move on to the steps of implementing visual states in your custom control. The key concept that we've been building to is this: there is a strict separation between the logic of your control, and the visuals of your control. The place that this becomes most clear is often when you click on a control.

Visually, many controls respond to a click with some form of visual change. Logically they respond with some behavior- or they raise an even that allows "listeners" to respond. Keeping these separate is key to the Parts and States model.

The Custom Control that we're going to create (in a whirlwind fashion here, more carefully and slowly in a forthcoming tutorial and videos) is derived from Control, draws its own shape, is designed to be skinnable, and supports the parts and states model.

As such, it defines three CommonStates

  • Normal
  • MouseOver
  • Pressed

It also defines two CustomStates

  • Normal
  • On

Notice that because the StateGroups (CommonStates and CustomStates) are orthogonal, there is no problem having a NormalState in both.

State Changes.

The custom control appears as a grey ball when it is in its Normal CommonState and in its Normal CustomState, but its visual appearance changes noticeably when it moves into any of the other states.  In addition, I've added a template that changes its appearance in its normal state and in its other visual states and to cap it off the control publishes an event for when it is clicked allowing the page that houses the control (templated or not) to respond to the click. <whew!>

While we'll save the nitty gritty for the tutorial and the videos, the point to focus on here is this: how does the control convey its visual contract to the Visual State Manager (and to Expression Blend?). 

That is accomplished using attributes. These are placed above the definition of the CustomControl and constitute the contract between the control and the Visual State Manager; they assert the state groups and the states within those state groups,

   1: [TemplatePart       ( Name = "Core",      Type = typeof( FrameworkElement ) )]
   2: [TemplateVisualState( Name = "Normal",    GroupName = "CommonStates" )]
   3: [TemplateVisualState( Name = "MouseOver", GroupName = "CommonStates" )]
   4: [TemplateVisualState( Name = "Pressed",   GroupName = "CommonStates" )]
   5: [TemplateVisualState( Name = "On",        GroupName = "CustomStates" )]
   6: [TemplateVisualState( Name = "Norm",      GroupName = "CustomStates" )]
   7: public class CustomControl : Control
   8: {

 

Line 1 defines the one part of our control; we give its name and its type (in this case, the type is FrameworkElement—which is general enough to encompass any UIControll and, moreover any element that might participate in the Silverlight layout system, have a lifetime and might need support for databinding.

The five TemplateVisualState attributes define two state groups: CommonStates and CustomStates.

Converting CLR events into state changes is the job of the custom class. For example, the CLR knows nothing at all about "mouse over" – that is not a state recognized by Windows or Silverlight. However, Silverlight does recognize MouseEnter and MouseLeave, which gives us all we need. 

We begin by creating three member variables,

private FrameworkElement corePart;
private bool isMouseOver;
private bool isPressed;

The first of these is for the body of our control, which we'll obtain immediately upon applying the template,

public override void OnApplyTemplate()
{
   base.OnApplyTemplate();
   CorePart = GetTemplateChild( "Core" ) as FrameworkElement;

(This of course relies on naming the appropriate element "Core" in the Xaml, which we, of course, do…)

<Ellipse
   x:Name="Core"
    Width="200"
    Height="200" RenderTransformOrigin="0.5,0.5" >

With the CorePart in hand, and our two private variables, we can set the event handlers, and make the conversion from CLR events to visual state changes. But borrowing from smarter developers we're going to be tricky; we'll set the event handler for Core when we set Core's property, and we'll do so in a fail-safe manner (allowing for the possibility that either Core doesn't currently exist or that we've been handed a null core).  Here's the complete property that fronts the data member,

   1: private FrameworkElement CorePart
   2: {
   3:    get
   4:    {
   5:       return corePart;
   6:    }
   7:    set
   8:    {
   9:       FrameworkElement oldCorePart = corePart;
  10:       if ( oldCorePart != null )
  11:       {
  12:          oldCorePart.MouseEnter -= new MouseEventHandler( corePart_MouseEnter );
  13:          oldCorePart.MouseLeave -= new MouseEventHandler( corePart_MouseLeave );
  14:          oldCorePart.MouseLeftButtonDown -= new MouseButtonEventHandler( corePart_MouseLeftButtonDown );
  15:          oldCorePart.MouseLeftButtonUp -= new MouseButtonEventHandler( corePart_MouseLeftButtonUp );
  16:       }
  17:       corePart = value;
  18:       if ( corePart != null )
  19:       {
  20:          corePart.MouseEnter += new MouseEventHandler( corePart_MouseEnter );
  21:          corePart.MouseLeave += new MouseEventHandler( corePart_MouseLeave );
  22:          corePart.MouseLeftButtonDown += new MouseButtonEventHandler( corePart_MouseLeftButtonDown );
  23:          corePart.MouseLeftButtonUp += new MouseButtonEventHandler( corePart_MouseLeftButtonUp );
  24:       }
  25:    }
  26: }

This isn't as scary as it looks. It just says "if i'm setting the Core property, I first make a copy of my existing member variable. If that member variable is not null, i unregister all its event handlers. Next, if I was given a non-null new corepart, I register event handlers for the new value.

The event handlers look for MouseEnter/MouseLeave and for buttonDown and buttonUp.

These are translated into visual events, and each time I experience one I call a private method called GoToState. Thus,

void corePart_MouseEnter( object sender, MouseEventArgs e )
{
   isMouseOver = true;
   GoToState( true );
}

You can read this: "The clr tells me the mouse just passed over the core, that means I want to enter MouseOver state, set the flag and call my GoToState method, passing in the flag saying I do want to use transitions (that is, I want to go to the new state but using the transition timings so it doesn't look goofy)

GoToState calls the Visual State Machine and tells it what state to transition to based on (you guessed it) the flags and whether you said to use the transitions or not)

   1: private void GoToState( bool useTransitions )
   2: {
   3:    if ( isPressed )
   4:    {
   5:       VisualStateManager.GoToState( this, "Pressed", useTransitions );
   6:  
   7:    }
   8:    else if ( isMouseOver )
   9:    {
  10:       VisualStateManager.GoToState( this, "MouseOver", useTransitions );
  11:    }
  12:    else
  13:    {
  14:       VisualStateManager.GoToState( this, "Normal", useTransitions );
  15:    }
  16:  
  17:  
  18:    
  19:    if ( IsOn )
  20:    {
  21:       VisualStateManager.GoToState( this, "On", useTransitions );
  22:    }
  23:    else
  24:    {
  25:       VisualStateManager.GoToState( this, "Norm", useTransitions );
  26:    }
  27: }

When the Visual State Manager goes to the new state it uses the storyboards that you wrote either in your generic.xaml or in your template. For example, generic.xaml's response to isMosueOver is captured in its VisualState MouseOver which causes the control to bounce in an appealing way,

   1: <vsm:VisualState x:Name="MouseOver">
   2:     <Storyboard x:Key="Bounce" RepeatBehavior="forever" >
   3:         <DoubleAnimationUsingKeyFrames 
   4:          BeginTime="00:00:00" 
   5:          Duration="00:00:01" 
   6:          Storyboard.TargetName="Core" 
   7:          Storyboard.TargetProperty="(UIElement.RenderTransform).
   8:          (TransformGroup.Children)[3].(TranslateTransform.Y)">
   9:             <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
  10:             <SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="25"/>
  11:             <SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="0"/>
  12:             <SplineDoubleKeyFrame KeyTime="00:00:00.75" Value="50"/>
  13:             <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
  14:         </DoubleAnimationUsingKeyFrames>
  15:     </Storyboard>
  16: </vsm:VisualState>

The template takes the same state and does something a bit different with it, as you'd expect,

   1: <vsm:VisualState x:Name="MouseOver">
   2:     <Storyboard>
   3:         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
   4:                 Duration="00:00:00.0010000" 
   5:                 Storyboard.TargetName="Core" 
   6:                 Storyboard.TargetProperty="(UIElement.RenderTransform).
   7:                                        (TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
   8:             <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1.25"/>
   9:         </DoubleAnimationUsingKeyFrames>
  10:         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
  11:                  Duration="00:00:00.0010000" 
  12:                  Storyboard.TargetName="Core" 
  13:                  Storyboard.TargetProperty="(UIElement.RenderTransform).
  14:                                        (TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
  15:             <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1.25"/>
  16:         </DoubleAnimationUsingKeyFrames>
  17:     </Storyboard>
  18: </vsm:VisualState>

 

 

I have to confess, this is what I really like about custom controls; the closer you look, the more there is to see. That said, I think we've gone about as far as we can without exploring this in a video, so I'll turn my attention in coming blog posts to some other emerging features.

(This is the run-up to RTW and so posting may not be as reliable as I might like, please bear with us as we work quickly to ensure that RTW happens without a hitch!)

Thanks

-jesse

Comments

BenHayat said:

------------

   * Intro to Custom Controls Part 1

   * Intro to Custom Controls Part 2

   * Creating Custom Controls

   * Generic.xaml

   * Dependency Properties Part 1

   * Dependency Properties Part 2

   * Dependency Property Precedence

-------------

Hi Jesse;

Long time no talk; Hope you doing well.

Seeing the above section of your post, prompted me to write this comment. You've done a lot of blogs about different subjects and sometime the same subject but at different levels and different times. For example about "Custom controls", you have talked about it in various times (not necessarily in continues time frame).

As a reader, I might have read one post of custom control a month ago, then another one two weeks ago, and another one today, it makes it hard to put mentally everything together and it also seems like it looses it effectiveness.

As you did in this post, by showing and grouping all the related posts in a chronological order, I think it would be great to set aside some time and create a table of contents of all the related posts you have done in the past year. Heck, you can even create a new book out of it. And from this point on, every time you   add a new post, update the table of contents. You can include tutorial links, video links and etc. like the following.

1. Animations:

 a) xxxxxx

 b) yyyyyy

 c) zzzzzz

2. Data Binding:

 a) xxxxx

 b) yyyyy

 c) zzzzz

3. Navigation:

 a) xxxx

 b) yyyy

 c) zzzz

I'm sure you get the picture.

Hope this helps!

..Ben

# October 9, 2008 11:07 AM

jesseliberty said:

I think that is a great idea, but why limit it to just blog posts, why not include videos and tutorials. Then, once it is created, it wouldn't be that hard to keep up to date.

I will do that, after we ship <smile>.  Though if someone else wants to do it as a start, I'd be very happy to steal, er, use it.

I think the way I'd do it would be as an xml file and a fairly simple silverlight application - each leaf would have the title and a link and a given leaf might appear under more than one node.    The silverlight application would let you choose which way you want to see the TOC: by topic, by skill level, filtered (no videos, etc.).  

Very cool idea. Maybe, in fact, I'll put it in a database, and make my friends who want to see a video on building an SL app that takes data in and out of a db happy :-) -- make it a web service, and while I'm at it, each node would have an entry for who created it so that if Tim wanted to use it he could and then folks could say just jesse, just tim or both.

Is this called feature creep?

# October 9, 2008 2:22 PM

BenHayat said:

>>why limit it to just blog posts, why not include videos and tutorials.<<

I did say that in the last sentence of my last paragraph: "You can include tutorial links, video links and etc. like the following."

>>I think the way I'd do it would be as an xml file and a fairly simple silverlight application - each leaf would have the title and a link and a given leaf might appear under more than one node.    The silverlight application would let you choose which way you want to see the TOC: by topic, by skill level, filtered (no videos, etc.).<<

Honestly, I was going to say, write an SL app with TreeView and display like that

>>Very cool idea. Maybe, in fact, I'll put it in a database, and make my friends who want to see a video on building an SL app that takes data in and out of a db happy :-) -- make it a web service, and while I'm at it, each node would have an entry for who created it so that if Tim wanted to use it he could and then folks could say just jesse, just tim or both.<<

Or why don't you use ADO.Net Data Services for all your data access, it's now fully functional. I'm right now playing with it, and its great.

Take a look at this link Jesse:

blogs.telerik.com/.../RadControls_for_Silverlight_Demo_with_ADO_NET_Data_Services.aspx

I just installed it and works beautiful.

You can play video in the same app, show the content and etc. Now you got me started... ;-)

..Ben

# October 9, 2008 3:25 PM

BenHayat said:

>>Is this called feature creep?<<

Some people use that term, but I'm one of those developers who strives of delivering full fledge apps. I feel ashamed when I've skipped obvious features from an app. An application (my work) is a representative of me and my dad always said, when you do something, do it right the first time. To me, it's a pride thing!

The more I think about it, the more I want to do it... Hummmm

..Ben

# October 9, 2008 3:32 PM

Community Blogs said:

In this issue: Andrea Boschin, Jeff Handley, Mike Snow, Laurence Moroney, Terence Tsang, and Jesse Liberty

# October 10, 2008 12:43 AM

2008 October 10 - Links for today « My (almost) Daily Links said:

Pingback from  2008 October 10 - Links for today &laquo; My (almost) Daily Links

# October 10, 2008 4:05 AM

Custom Controls - the handling at Blog von J??rgen Ebner said:

Pingback from  Custom Controls - the handling at Blog von J??rgen Ebner

# October 10, 2008 6:15 AM

Silverlight news for October 10, 2008 said:

Pingback from  Silverlight news for October 10, 2008

# October 10, 2008 9:44 AM

Dew Drop - October 10, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - October 10, 2008 | Alvin Ashcraft's Morning Dew

# October 10, 2008 9:47 AM

Steve Strong said:

I tried to build the example from the code you have here and you are missing just enough details so it does not work.   is the source code posted somewhere?

Also can you remove the line numbers from your samples, it makes it very hard to cut and past.

# October 10, 2008 9:31 PM

jesseliberty said:

Steve, the code in blog postings is not intended to be complete, and I'd hate for anyone to cut and paste thinking they have a complete sample (we do provide that with the tutorials and the videos). I'd also hate to give up the line numbers, as I often use them to talk through the posting.

That said, I usually provide the source for anythhing reasonably complete, and should have in this case.  I'll upload it and add a link at the top of the article.

Thanks.

-j

# October 10, 2008 11:01 PM

jesseliberty said:

Ah, I spoke too soon; at this very moment I don't have access to a machine with a released version of Silvelright on it. Therefore, allow me to amend my previous comment to say, I will upload the code as soon as I can <smile>.

Sorry for the delay.  

If the truth be known, I went a bit further in the blog than I intended, and this complex subject really needs to be sewn together into a tutorial; it will be the next one.   But no need for you to wait, I'll get that code prepared ASAP.

-j

# October 10, 2008 11:04 PM

Dew Drop - October 10, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - October 10, 2008 | Alvin Ashcraft's Morning Dew

# October 12, 2008 6:05 PM

Silverlight news for October 13, 2008 said:

Pingback from  Silverlight news for October 13, 2008

# October 13, 2008 7:54 AM

robertlair said:

Any updated ETA on the custom control video tutorials you spoke of?

# October 23, 2008 3:00 AM