Page view counter

Jesse Liberty - Silverlight Geek

More Signal Less Noise

September 2008 - Posts

Dependency Property System – Deeper Dive Part 1

On the 17th I began a discussion of Dependency Properties, but as I said then, there is more to say.  This central concept to Silverlight Programming has traditionally  been taught as an advanced concept.(Can we have  traditions in a product that hasn't really been released yet?) And to some degree, with good reason: you can pretty much ignore the Dependency Property System until/unless you're writing custom controls. Here are three reasons not to:

1. They're fascinating
2. They explain a lot about how things work
3. They are critical to creating your own custom controls

Okay, the real reason is #1; they're just interesting.

There are a couple ways to approach Dependency Properties. Most of us approach them practically, to accomplish a specific goal at first ("How do I wire up a DP in this custom control?") and then later, we return to understand the entire system into which they fit. It is the purpose of today's column to begin that second stage: examining the system as a whole.

An Adventure in Spelunking the Dependency Properties System

We need to start by acknowledging a few somewhat surprising facts:

The folks who created WPF and Silverlight made some rather radical (and I would argue technically courageous) decisions:

  • they said "the current properties are inadequate for our needs, and we're going to add a new layer on top that will not break the underlying layer but that will give us tremendous additional options." 
  • Thus, the Dependency Properties system is really a radical extension to the CLR and to that which is available in C++, Java, or even pre-WPF C#/VB.NET.  
  • Then the folks at Silverlight chose which parts of the system they would use, which they'd adapt and which they'd leave behind based on optimizing for the needs of Silverlight rather than just for pure isomorphic consistency.

Controversial but highly defensible decisions, and ones that will give us early headaches trying to understand all of it,  but long term benefits.

The biggest problem, of course, is that figuring out what is really going on can be a bit problematic, especially right now. This particular moment in history is a documentation purgatory.

  • The WPF books are excellent but not everything they say applies to Silverlight
  • The Beta2 chm file and help files are very good but now partially obsolete
  • The change document is helpful but not guaranteed to be complete
  • Full documentation of Dependency Properties was never a priority as it was considered somewhat advanced/obscure

So we're in for some fun.

To make this work, I'm going to need to (a) break this up into a few related posts and (b) try to impose some discipline on myself to do the set of DP posts within a relatively short period so I don't forget what I've covered already!   I'll tag them all with "Dependency Properties" so you can find them or skip them as you choose.

Quick Review of Why Dependency Properties were Added

As noted in my posting on the 17th the WPF designers quickly found that standard CLR properties were not responsive enough nor extensible enough to support declarative, animated and databound client-side applications. What was needed was a system that could establish the value of a property, at run time, based on input from a number of sources (e.g., the current value of other properties, rapidly changing animation values, etc.).

A key value of the Dependency Properties system was the ability to build properties that automatically notify any registered interested party each time the value of the property changes.  This free, painless and automatic implementation of the observer pattern is tremendously powerful and greatly reduces the burden on the client programmer (in fact, the data-binding system depends on it!).

[We cover the observer patten in detail in Programming .NET 3.5 – for an unauthorized excerpt, click here ]

That alone is worth the price of admission. It means that if you bind numerous controls to dependency properties, you are guaranteed to be notified any time the value of the property changes, without your writing a line of code. Whooah!

Cost - Benefit

 

I believe (and obviously the designers believed) there is enormous benefit to the Dependency Properties system, but let's not ignore the fact that you do pay a price, if only in learning this system.  In this posting I'll cover that price, in the next I'll start to explore some of the benefits.

The price, such as it is….

You need to learn, and grok, a new element in your class – Dependency Properties that interact with an underlying system that didn't usta'  exist. More, some of your properties are really just wrappers to these new DP's. What that means is that the backing value for some of your properties is not a member variable or a value in a database or a computation, but a Dependency Property.  That takes a little mind-share.

Here's what that looks like in code:

// clr wrapper
public bool Valuable
{
   get { return (bool) GetValue( ValuableProperty ); }
   set { SetValue( ValuableProperty, value ); }
}

A pretty standard get and set, except that you access your backing variable using GetValue and SetValue to get and set the value of a Dependency property named ValuableProperty (and that is the idiom, the CLR property name + the word Property = the name of the DP, thus Valuable + Property = ValuableProperty.

The declaration of the DP itself is much weirder,

public static readonly DependencyProperty ValuableProperty =
  DependencyProperty.Register(
  "Valuable",
  typeof( bool ),
  typeof( MyCustomControl ),
  new PropertyMetadata( new PropertyChangedCallback(   
      MyCustomControl.OnValuablePropertyChanged ) ) );

Let's  break this down. The first line declares my object (which is really a reference to a DependencyProperty) as public; it must be static and readonly, and its type is DependencyProperty and its name (identifier) is ValuableProperty.

We set that reference to what we'll get back by calling the static Register method on the DependencyProperty class. Register takes four arguments:

  1. The name of the dependency property wrapper
  2. The type of the DP being registered
  3. The type of the object registering it
  4. The Callback

The Callback is of type PropertyMetaData. You can imagine a world in which there are various pieces of MetaData for the DependencyProperty. At the moment, however, in Silverlight, there is only one: the callback.

The constructor for the PropertyMetaData takes an object of type PropertyChangedCallback which will be called any time the effective property value of the DP property changes.  We pass it a reference to the method to call (which equates to a callback).

The net of all of this is that we present to the world a CLR property (Valuable) which is in fact backed by a DependencyProperty which will call back to the method OnValuablePropertyChanged any time the effective value of the property changes.

The callback method will take two arguments:

  • A DependencyObject (the control)
  • An object of type DependencyPropertyChangedEventArgs

Typically you'll cast the first argument to be the type of the control that contains the property, and you'll cast the NewValue property of the DependencyPropertyChangedEventArgs object to the DependencyProperty that changed. You can then take whatever action you need to based on the change in the DP's value

[Listing updated 10/1 – 10:30 am]

   1: public class MyCustomControl : Control
   2: {
   3:  
   4:    public static  readonly DependencyProperty 
   5:       ValuableProperty = DependencyProperty.Register(
   6:        "Valuable",
   7:        typeof( bool ),
   8:        typeof( MyCustomControl ),
   9:        new PropertyMetadata( new PropertyChangedCallback( MyCustomControl.OnValuablePropertyChanged ) ) );
  10:  
  11:    public bool Valuable
  12:    { 
  13:       get { return (bool) GetValue( ValuableProperty );}
  14:       set { setValue( ValuableProperty, value );}
  15:    }
  16:  
  17:    private static void OnValuablePropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
  18:    {
  19:       MyCustomControl control = d as MyCustomControl;
  20:       bool b = (bool) e.NewValue;
  21:    }
  22: }
  23:  

 

 

That's pretty much it, except getting your head around why you did all this; and for that we have to begin to explore, in more detail, the benefits of the system. That is, once you have this, what have you gained?

Stay tuned, rather than gloss over it, I'd like to take the time to explore it in depth.

-jesse

Isolated Storage – Might Be Easier Than You Think

 

Tim Heuer did an excellent video on IsolatedStoarge, but I'd like to quickly review the the fundamentals as they are quite a bit simpler than many fear.  The idea of IsolatedStorage is to provide your Silverlight Application with access to the user's disk, so that you may store… well, whatever you like when your application is not running. Whether or not you have access, and how much space is allocated is entirely under the control of the user.

In many ways, IsolatedStorage is the next generation of cookies: you can store more, more robustly, with greater precision and control. A full blown usage of IsolatedStorage might involve the creation of files within a directory structure (all of which is obfuscated on the user's hard drive), quota allocation and the storage of binary data. You might use this to store far more than window positions or the user's current "state" within your application; you could conceivably store parts of your application or other binary data to speed up or otherwise enhance the user's experience.

At its simplest though, using IsolatedStorage can be as simple as writing to one of two pre-defined dictionaries: IsolatedStorageSettings.ApplicationSettings or IsolatedStorageSettings.SiteSettings. 

In both cases, the key is defined to be a string, the value to be an object. You grab a reference to the dictionary and the IsolatedStorage system ensures that your application's dictionary is isolated from the dictionaries of other applications. SiteSettings are, as you might expect, shared among all applications on a site.

To be more exact, Applications settings are per-application, per-computer and per-user while SiteSettings are  per-domain, per-computer and per-user.

If I have IE open to two sites, http://microsoft.com/redmond/example1.xap and http://microsoft.com/redmond/example2.xap, these applications will share site settings since they share the same domain, but different application settings.

This can get tricky, though. If my browser is open to http://microsoft.com/redmond/example1.xap and http://microsoft.com/boston/example1.xap it turns out they share the same site (microsoft.com) but not the same application as these are independent .xap files as determined by the full path.

But now we're deep in the mud, and at a high level, getting started with IsolatedStorage is easy, which is the point of this posting.

The application I built (total development time 10 minutes) looks like this,

IsoAdd

Clicking the Add button adds three key/value pairs to IsolatedStorageSettings.ApplicationSettings  and 9 key/value pair to IsolatedStorageSettings.SiteSettings.

   1: void Add_Click( object sender, RoutedEventArgs e )
   2: {
   3:    try
   4:    {
   5:       appSettings.Add( "Ulysses", "Stephen Dedalus" );
   6:       appSettings.Add( "The Catcher in the Rye", "Holden Caulfield" );
   7:       appSettings.Add( "Tom Sawyer", "Huckleberry Finn" );
   8:       siteSettings.Add( "Connection", "Fios" );
   9:       siteSettings.Add( "SpeedUp", "20" );
  10:       siteSettings.Add( "SpeedDown", "5" );
  11:       siteSettings.Add( "Browser1", "IE7" );
  12:       siteSettings.Add( "Browser2", "FireFox" );
  13:       siteSettings.Add( "Browser3", "IE8" );
  14:       siteSettings.Add( "Browser4", "Safari" );
  15:       siteSettings.Add( "OS1", "Vista" );
  16:       siteSettings.Add( "OS2", "Leopard" );
  17:       Display( "Added 3 to app settings, 9 to app settings." );
  18:    }
  19:    catch ( ArgumentException ex )
  20:    {
  21:       Display( ex.Message );
  22:    }
  23: }

Retrieve is hardwired to return the value where the key is Ulysses,

   1: void Retrieve_Click( object sender, RoutedEventArgs e )
   2: {
   3:  
   4:    try
   5:    {
   6:       Display("If the user asked for the main character  in Ulysses, we'd return " + 
   7:          appSettings["Ulysses"].ToString());
   8:    }
   9:    catch ( System.Collections.Generic.KeyNotFoundException ex )
  10:    {
  11:       Display( ex.Message );
  12:    }
  13: }

 

 

 

 

 

IsoRetrieve

Finally, to demonstrate that these are normal collections, with the normal keys and values collection, I provide buttons that iterate through the collections.

IsoValues

Once again, the code is just C# iterating through a dictionary's value collection…

   1: void Values_Click( object sender, RoutedEventArgs e )
   2: {
   3:    string header = "Values: ";
   4:    string msg = header;
   5:    foreach ( object o in appSettings.Values )
   6:    {
   7:        if ( msg.Length > header.Length )
   8:             msg += ", ";
   9:          msg += o.ToString();
  10:       
  11:    }
  12:  
  13:    foreach ( object o in siteSettings.Values )
  14:    {
  15:        if ( msg.Length > header.Length )
  16:             msg += ", ";
  17:          msg += o.ToString();
  18:    }
  19:    Display( msg );
  20: }

 

 

 

 

Not Just Strings

Of course, you don't have to add just strings. The value is an object and can be anything. Let's make an incredibly simple GeekObject and substitute that for the applicationSettings.  We'll start by adding a GeekClass,

   1: namespace SimpleIsolatedStorage
   2: {
   3:    public class Geek
   4:    {
   5:       public string Name { get; set; }
   6:       public int GeekScore { get; set; }
   7:       public string FavoriteGeekBook { get; set; }
   8:       public bool PassTheBestSceneInApollo13Test { get; set; }
   9:  
  10:       public Geek( string name, int score, string book, bool passed )
  11:       {
  12:          Name = name;
  13:          GeekScore = score;
  14:          FavoriteGeekBook = book;
  15:          PassTheBestSceneInApollo13Test = passed;
  16:       }
  17:    }
  18: }

 

Notes: The GeekScore is your results on the Computer Geek test. The PassTheBestSceneInApollo13Test is very simple: what's your favorite scene in the movie Apollo 13?   The value true indicates you answered with some variation of the following:[Several technicians dump boxes containing the same equipment and tools that the astronauts have with them onto a table] Technician: We've got to find a way to make this
[square CSM LiOH canister]  fit into the hole for this [round LEM canister]  ... using nothing but that.

The changes to the code are in Add, Retrieve and Values as follows:

   1: void Add_Click( object sender, RoutedEventArgs e )
   2: {
   3:    try
   4:    {
   5:       appSettings.Add( "Jesse", new Geek("Jesse Liberty", 62, "Dragon Book", true));
   6:       appSettings.Add( "Chris", new Geek("Chris ???",30.96647,"Programming WPF",true));
   7:       appSettings.Add( "Dan", new Geek("Dan Hurwitz", 0, "Programming ASP.NET", true));

Adding the appSettings, we make sure we add Geek objects.

Retrieving, we retrieve a hardwired value again, but this time we have to parse out the Geek values,

void Retrieve_Click( object sender, RoutedEventArgs e )
{

   try
   {
      object o = appSettings["Jesse"];  // hard wired retrieve
      Geek g = o as Geek;
      string notString = g.PassTheBestSceneInApollo13Test ? String.Empty : "not ";
      Display("Retrieving Geekiness for Jesse. Score:  " + g.GeekScore.ToString() +
         " and " + g.Name + " did " + notString + "pass the Apollo 13 movie test. ");
   }
   catch ( System.Collections.Generic.KeyNotFoundException ex )
   {
      Display( ex.Message );
   }
}

IsoGeekRetrieve

 

 

 

Finally, when we tick through the values collection, we want again to make sure we're dealing with the Geek object and not just the object per se,

   1: void Values_Click( object sender, RoutedEventArgs e )
   2: {
   3:    string header = "Values: ";
   4:    string msg = header;
   5:    foreach ( object o in appSettings.Values )
   6:    {
   7:        if ( msg.Length > header.Length )
   8:             msg += ", ";
   9:  
  10:        Geek g = o as Geek;
  11:        msg += g.Name + "(" + g.FavoriteGeekBook + ")";
  12:       
  13:    }

IsoGeekValues

Full Source Code SimpleIsolatedStorage.zip 

 

Thanks.

RC0 & ContentPresenter

 

The breaking changes document points out that ContentPresenter now derives from FrameworkElement and thus loses 18 public properties  as well as TextAlignment, TextDecorations and TextWrapping. All of this calls for a bit of rewriting if you've used this powerful and useful control, and there was a request in one of the internal discussion lists that this be called out to developers; hence this blog entry.

VideoStartSerendipitously, I have a video that uses the ContentPresenter control.

The ContentPresenter control is the enabling control behind entering "Content" rather than text in the standard Button, CheckBox and so forth.  Until RC0, you could have set the fontsize, FontWeight, etc. in the ContentPresenter itself, though that never would have been good programming practice, as it always made more sense to leave that for the client (the programmer using your control.

Thus, in my template, I set few properties on the ContentPresenter itself, and when I use the control that the ContentPresenter is part of (the Button) I can set the characteristics of the content, which are then passed to the ContentPresenter. This will continue to work in RC0

Let me be explicit, if you used ContentPresenter as intended (note the Beta documentation which states "Typically, you use the ContentPresenter directly within the ControlTemplate of a ContentControl to mark where the content is to be added.") then you would probably not have used any of the properties that are no longer available to you, as you would have wanted, as I did, to leave that flexibility to the consumer of your control.

On the other hand, if you did use those properties, the fix is fairly simple, you just remove the properties from the content control, and if you need the property set, you set it when you call the control

An example will make this explicit.  Assume you define your button template in App.xaml as follows (the following listing is abridged):

   1: <ControlTemplate x:Key="RoundButton" TargetType="Button">
   2:     <Grid>
   3:         <vsm:VisualStateManager.VisualStateGroups>
   4:         </vsm:VisualStateManager.VisualStateGroups>
   5:         <Ellipse >
   6:         </Ellipse>
   7:         <ContentPresenter Margin="0,10,0,0" x:Name="RoundButtonContent" 
   8:                           RenderTransformOrigin="0.5,0.5" 
   9:                           HorizontalAlignment="Center" 
  10:                           VerticalAlignment="Center"
  11:                           FontFamily="Comic Sans MS"                           
  12:                           FontSize="24"
  13:                           FontWeight="Bold"
  14:                           Foreground="#FFFF0000">
  15:             <ContentPresenter.RenderTransform>
  16:                 <TransformGroup>
  17:                 </TransformGroup>
  18:             </ContentPresenter.RenderTransform>
  19:         </ContentPresenter>
  20:     </Grid>
  21: </ControlTemplate>

Focusing on lines 11-14, you have to ask why you would hard code into the ContentPresenter portion of your button the font characteristics and the color. This would mean that every Button the user wants to create must have a 24 point Comic Sans MS Bold red font, which seems a little restrictive.

In any case, starting in RC0 and consistent with WPF, this is no longer legal Silverlight code, and you'll now need to change your ContentPresenter definition to remove these lines. But that is a good thing, they belong instead in the xaml file that uses your button. Thus, the definition of the Button becomes:

   1: <ControlTemplate x:Key="RoundButton" TargetType="Button">
   2:     <Grid>
   3:         <vsm:VisualStateManager.VisualStateGroups>
   4:         </vsm:VisualStateManager.VisualStateGroups>
   5:         <Ellipse >
   6:         </Ellipse>
   7:         <ContentPresenter Margin="0,10,0,0" x:Name="RoundButtonContent" 
   8:                           RenderTransformOrigin="0.5,0.5" 
   9:                           HorizontalAlignment="Center" 
  10:                           VerticalAlignment="Center">
  11:             <ContentPresenter.RenderTransform>
  12:                 <TransformGroup>
  13:                 </TransformGroup>
  14:             </ContentPresenter.RenderTransform>
  15:         </ContentPresenter>
  16:     </Grid>
  17: </ControlTemplate>

and the instantiation of two RoundButtons might look like this (in, e.g., Page.xaml):

   1: <Button x:Name="Go" 
   2:         HorizontalAlignment="Right" 
   3:         VerticalAlignment="Stretch" 
   4:         Width="109" Grid.Row="5" Content="Go!" 
   5:         Template="{StaticResource RoundButton}" 
   6:         Foreground="#FF00FF00" 
   7:         FontFamily="Georgia" 
   8:         FontSize="36" 
   9:         FontWeight="Bold"/>
  10: <Button x:Name="Stop"
  11:         HorizontalAlignment="Stretch" 
  12:         VerticalAlignment="Stretch" 
  13:         Grid.Column="1" Grid.Row="5" 
  14:         Content="Stop!" 
  15:         Template="{StaticResource RoundButton}" 
  16:         Foreground="#FFFF0000" 
  17:         FontFamily="Comic Sans MS" 
  18:         FontSize="18"