A couple of the guys at my company have started working on an internal Silverlight application. One of the guys is a former WPF developer, which gives him some serious advantages when starting with Silverlight. It actually gives him some serious advantages compared to my own sorry ass as well. However, it has some downsides as well. Downsides that sometimes end up with him sending me annoyed MSN messages.
He expects a bunch of WPF specific features to be available in Silverlight. I understand the "confusing". SIlverlight development is very close to WPF, except for the fact that Silverlight is in some aspects is tiny compared to WPF.
Among the things he has be "complaining" about are element bindings and the possibility of storing styles outside of user control, without having to store it in app.xaml. I'm not sure I interpeted his request correctly, but it got me thinking. There is, as far as I know, no way to simply store your style definitions in a separate file, but still simply apply them to controls in the application. So, I decided to create a way to do this. I also though this turned out to be a good introduction to attached properties.
An attached property, is a special sort of property that an object can declare and make available on other controls. So, basically I can create a class that defines a property, that can then be set on another control. The declaring control can also assign a callback to be called when an object uses the property. The callback gets both the object that has used the property as well as the property's value. Using this functionality, it is possible to create a class with some functionality that can be added to existing objects as sort of an extension of that class. This could for example be a class that when used, adds drag and drop support to controls without having to add the support in each individual control. (This example is not taken from the top of my head, there is actually an implementation like this available on the web. Can't remember where though...try googling it if you need it)
There is one limitation though. Only objects that inherit from DependencyObject can use this functionality. Luckily, all controls in Silverlight does that. DependencyObject is the root object for A LOT of objects in Silverlight. This inheritance adds GetValue() and SetValue() methods, that are used for setting and getting property values for properties that are based on the DependencyProperty object. For more information about this, I would recommend googling it. There is a lot of information available on this subject on the net, and I'm not sure I'm the right person to give this information at the moment. There might be more about this subject in the future, when I myself grasp the concept and its foundations better. So, I will stop talking about it now, and start talking about my ExternalStyleManager.
So, the idea I came up with was to store the styles in ResourceDictionary objects in external xaml files. These files would then be embedded in the application as resources. Then there would be an attached property pointing out the key of the style to use and the manager would automatically scour the resources looking for that style and add it dynamically. Sounds good? Well, I do at least...and the code is really simple and short...
To start off, I created some xml files. I put them in a directory under my project called "Resources" just to get them out of the way. I called the xml files XXX.resource.xaml. The "resource.xaml" extension is there to identify them as external styles directories. In the resource files I added a ResourceDictionary that would hold the styles.
[code:c#]
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="myStyle" TargetType="Button">
</Style>
</ResourceDictionary>
[/code]
I also set the "Build Action" to "Embedded Resource" and cleared the "Custom Tool" field. This will cause the xaml to be embedded in the assembly in a nice way. The Style can contain anything that you would normally use. There is no limitations in the manager. Just create styles just like you normally would. Next up is the ExternalStyleManager class. Just create a new class. I named it "ExternalStyleManager", but that's just my name... The first thing you have to do when adding an attached property, is to declare a public static member of type DependencyProperty. The name standard for this is to name the member <property name>Property. In my case the property is called "ExternalStyleKey", which causes the DependencyProperty being named "ExternalStyleKeyProperty". The class must also implement getters and setters for the property. Since the DependencyProperty is static, all the supporting methods will be so to. The property will be attached to other classes, but functionality in the declaring type. That type will not be instantiated, but used statically. So the setters will not be the normal property setter and getter. Instead it will be static methods. The names of these methods must be Set<property name>() and Get<property name>(). So the getters and setters will be "SetExternalStyleKey()" and "GetExternalStyleKey()". These methods will be used to set the property on the relevant object.
[code:c#]
public static void SetExternalStyleKey(DependencyObject obj, string key)
{
obj.SetValue(ExternalStyleKeyProperty, key);
}
public static string GetExternalStyleKey(DependencyObject obj)
{
return (string)obj.GetValue(ExternalStyleKeyProperty);
}
[/code]
In this case the property will be of type String, so the getter returns string. And then to the actual magic. Since everything is static, the class needs a static constructor. Inside the constructor I register the attached property.
[code:c#]
static ExternalStyleManager()
{
ExternalStyleKeyProperty = DependencyProperty.RegisterAttached("ExternalStyleKey",
typeof(string),
typeof(ExternalStyleManager),
new PropertyMetadata(ExternalStylePropertyChanged));
}
[/code]
The RegisterAttached() method on the DependencyProperty class does just that, registers the new property. It takes in the name of the property, the type of the property, what type holds the declaration and a PropertyMetadata object. The PropertyMetadata object takes a PropertyChangedCallback delegate that is called as soon as a controls sets the property. That way the SetExternalStyleKey() method can do only the setting. The getters and setters should have a single responsibility, get or set the value. The other functionality that should be executed when the value is changed, should be in the callback. Single responsibility is very important... Anyhow... THat is the way it is supposed to work. That is all I really need to do to get the attached property to work. The only thing I have to do is implement the callback. It contains a lot of rows of validation, but otherwise it is a very simple method...
[code:c#]
private static void ExternalStylePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
throw new Exception("Cannot change style at runtime");
string styleKey = e.NewValue as String;
if (String.IsNullOrEmpty(styleKey))
throw new Exception("ExternalStyleKey must be a string longer than 0");
Style style = FindStyle(styleKey);
if (style == null)
throw new Exception("Could not find style "+styleKey);
FrameworkElement element = obj as FrameworkElement;
if (element == null)
throw new Exception("Only object of type FrameworkElement can use the ExternalStyleManager");
element.Style = style;
}
[/code]
What the callback does is pretty simple. First of, it validates that the property is set for the first time. It is not possible to change style at runtime, so thathas to be validated. Next the value is verified to be a string and cast to that. After that, a helper method called FindStyle() is called. It is responsible for going through the ResourceDisctionary objects looking for a style with the correct name. If it doesn't find one, it returns null, which causes an exception to be thrown. Finally the DependencyObject is verified to be a FrameworkElement. This base class defines the Style property that is needed. Always try to go as far up the inheritance chain as possible, making the method useful for as many objects as possible. If the DependencyObject is a FrameworkElement, its Style property is set. So...how isthe FindStyle() method implemented. Well, before we can look at that, there is some more things that needs to be covered.
It is potentially possible, or even likely, that there will be several external ResourceDictionary objects. Because of this, storing those objects, a list is needed. So I decided to store the ResourceDictionary objects in a List<ResourceDictionary> that is stored as a global, private field. This field is created in the static constructor...
[code:c#]
static ExternalStyleManager()
{
ExternalStyleKeyProperty = DependencyProperty.RegisterAttached("ExternalStyleKey",
typeof(string),
typeof(ExternalStyleManager),
new PropertyMetadata(ExternalStylePropertyChanged));
_dictionaries = new List<ResourceDictionary>();
GetResourceDictionaries();
}
[/code]
The GetResourceDictionaries method is responsible for going through the embedded resources and get all the ResourceDictionary objects. That method will be covered in just a moment. First, I just want to show the FindStyle() method. This is a very simple helper that is basically only there to clean up the code.
[code:c#]
static Style FindStyle(string styleKey)
{
foreach (var dictionary in _dictionaries)
{
Style style = dictionary[styleKey] as Style;
if (style != null)
{
return style;
}
}
return null;
}
[/code]
So, as you see, all it does is to iterate the ResourceDictionary list looking for a style with the correct key. That is allmost the entire implementation. The last method is the GetResourceDictionaries(). It gets a reference to the executing assembly. Then it iterates through all the embedded resources using the GetManifestResourceNames() method. It verifies if it should use the resource by its name. If you remember, external style resource names ends with ".resource.xaml". If that is the case, it gets a stream to the resource and wraps it in a StreamReader to simplify reading. It is only text. so a StreamReader is the simplest way to read it. It then instantiates the ResourceDictionary from the xaml string by using the XamlReader object.
[code:c#]
static void GetResourceDictionaries()
{
Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
foreach (var key in assembly.GetManifestResourceNames())
{
if (key.ToLower().EndsWith(".resource.xaml"))
{
Stream resource = assembly.GetManifestResourceStream(key);
StreamReader reader = new StreamReader(resource);
string xaml = reader.ReadToEnd();
reader.Close();
ResourceDictionary dict = XamlReader.Load(xaml) as ResourceDictionary;
_dictionaries.Add(dict);
}
}
}
[/code]
So...that was it. A hopefully helpful little class that also works as a simple demo of what you can do with the DependencyProperty object. There are probably several things in the code that needs tweaking. I know that the eceptions thrown in the ExternalStylePropertyChanged() callback, should be a bit more specialized. There should also be some more type/null checks in some places to be totally secure. Wether or not exceptions should be thrown when the style handling isn't working is a matter of discussion. Is it better to just let the application be uggly, than have it break down? Of course, the exceptions could be handled in a good way, but that leaves a tad bit of too much responsibility to the programmer using it... Well... I wont judge in this matter. As you see, I have implemented exception throwing... But that is a matter of taste...
Well...I haven't showed how to use the manager. Maybe I should do that. Just imagine that I have a ResourceDictionary in an external resource file. In the dictionary, there is a style called "btnStyle" and set to target the Button type. Then I could style a button with this style by simply adding a xml namespace that points to my clr namespace and then set the controls ExternalStyleKey property.
[code:c#]
<UserControl x:Class="StylingAndTemplatingApp.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:MyControl;assembly=MyControl"
xmlns:local="clr-namespace:StylingAndTemplatingApp">
<Grid x:Name="LayoutRoot">
<Button x:Name="btn" local:ExternalStyleManager.ExternalStyleKey="btnStyle" />
</Grid>
</UserControl>
[/code]
So...that was it. Hmmm...I said that once before. But this time it is really it. The cool thing is that this works in the VS designer as well as in Expression Blend. I hope you got some useful tips as well as some useful code. Attached properties add a LOT of potential in "extending" the functionality of controls using external assemblies. That means that functionality can be developed and fine tuned in an external project and then re-used throughout several projects. Less to do in the next project. Just remember to include the development for the external assembly in every project that you use it. That way you get paid over and over for your work, which justifies putting some extra time in fine tuning it. Just remember to make it as open and configurable as possible so that you can really re-use it...
[UPDATE]
This functionality is already available in the Silverlight Toolkit. More info here...