Monday, September 29, 2008

Silverlight RC0 Custom Controls: App.xaml vs Generic.xaml

Motivation
Silverlight 2 RC0 is now available to developers and early adopters. Summary of release and Breaking Changes are reproduced in my previous post available here.

Best Practices around developing Custom Controls is still a shaping story. Many tutorials currently available on the web prefer the use of App.xaml or the UserControl itself for custom styling and templates. In fact, Styles and Templates are still a bit confusing from a design and usage perspective.

The existence of yet another option, the use of generic.xaml further confuses approach to styling. For those of you who have been scratching their heads as much as I have been, I hope that the basics I'll outline in this and future posts will help the Silverlight community refine the "right" way approach Custom Control creation, Styling, and Templating.

Goals
• Propose strategy for using Generic.xaml and App.xaml
• Project structure should enable Control Designers, Control Developers and Application Developers to work independently of one another.
• Create a single Silverlight Control library of reusable Custom and User Controls, each with customized Look-and-feel.
• Look-and-Feel of each control needs to be "baked in" so that they can be used "as is" by our Application.
• Controls should still be open to further styling and customization by the Application Developer at the application layer.

Requirements
VS 2008 SP1, .net 3.5 SP1, Silverlight Tools with RC0 builds + SP1. For download locations, see my previous post here.

Setup
• a Silverlight Class Library called YControlsLib
• a SilverlightApplication called YControlsDemo
• allowed VS2008 to automagically generate a YControlsDemo.Web asp.net project to host YControlsDemo project

Download The Solution
The solution is available here for download.

YControlsLib
Here, I will wear the Control Developer and Control Designer hats. The simplest control to play with, in my opinion, is the Button control.

Controls can fall into three categories:
1. Combine two or more basic controls into a single control
2. Enhance a single, existing control
3. Create something completely new from scratch

My custom Button resides in the 2nd category.

Because I am simply enriching an existing control I decided to subclass Button and create my own Custom Control, called Ybutton. I will customize Button's look-and-feel. Idea is to be able to use Ybutton with its custom style out-of-the-box in my Application, without the need to override the Button's template.

Although subclassing does not always make sense, I do feel that there are clear benefits in this case:
• For Application Developer, encapsulates control functionality and branding
• Clearly differentiates your organization's control from the default
• Allows you to customize certain behaviors of the control without modifying the original
• Provides some protection against changes to the original control. Subclassing or Wrapping (the former in this case) is perhaps an enterprise-friendly approach.

I started with a simple .cs file without any XAML files. This is what we can refer to as a lookless control stub.

YButton.cs
namespace YControlsLib
{
public class YButton : Button
{
public YButton()
{
this.DefaultStyleKey = typeof(YButton);
}
}
}

DefaultStyleKey is important. This is the only line of custom code needed to bind my custom Button to my custom Template. This template is defined in generic.xaml.

Themes/generic.xaml
As per the Breaking Changes document, generic.xaml needs to reside within the Themes folder. Your Application will not recognize the template binding if this file lives anywhere else.

There is no template in VS for creating this file. I just create a text file and modify its extension. Also, it has to compiled as a Resource (not an Embedded Resource) for runtime and Blend to know what to do with it. More on Blend later.

Here is the basic layout:



YControlsDemo/Page.xaml
Page.xaml is authored by the Application Developer. All Controls required for this project are made available to the App Developer via Silverlight's Default Controls library and the Controls provided by the YControlsLib dll. For simplicity, the YControlLib project is simply referenced from YControlsDemo.


Page.xaml is the typical UserControl which defines one View from our Application. Our App is effectively just a Demo of three Controls:
i. Default Button
ii. My custom button, Ybutton
iii. Ybutton with a tweak from App Developer

Page.xaml only contains:
 <UserControl x:Class="YControlsDemo.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ycontrols="clr-namespace:YControlsLib;assembly=YControlsLib">
<StackPanel x:Name="LayoutRoot" Background="White" Orientation="Horizontal">

<!-- (i) This is just a plain ol' boring Button -->
<Button Width="100" Height="45" Content="Button"/>

<!-- (ii) YButton inherits everything from above Button but also contains it's own
template. Because the custom template is inherent to the control, App
Developer can use it as is -->
<ycontrols:YButton Width="100" Height="45" Content="YButton"/>

<!-- (iii) This is a YButton with a custom Style that App Owner defines in App.xaml.
Useful for disobeying your company's Branding guidelines -->
<ycontrols:YButton Width="100" Height="45" Content="Ybutton Style"
Style="{StaticResource TweakedYButtonStyle}"/>

<!-- (iv) This is a YButton with a custom DataTemplate -->
<ycontrols:YButton Width="100" Height="45"
ContentTemplate="{StaticResource YButtonCustom}"/>
</StackPanel>
</UserControl>

Since we have not yet completed Ybutton with it's custom Template nor defined any custom Style, our Designer will only show us the default Button:




Back to Themes/generic.xaml
We now need to wear the Control Designer hat. For this, we need to use Expression Blend. The SP1 Preview still does not handle Generic.xaml too well. Because my YButton Control is lookless, Blend will not understand that I want to create a custom template and keep it in Generic.xaml.

For a bit of a work-around, I created a UserControl (similar to Page.xaml) called ControlsForBlendsBenefit.xaml. This goes directly into the YControlLib project. This item will contain a plain Button and my Ybutton.

ControlsForBlendsBenefit.xaml
 <UserControl x:Class="YControlsLib.ControlsForBlend"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:local="clr-namespace:YControlsLib;assembly=YControlsLib">
<StackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
<Button Width="100" Height="45" Content="Button"/>
<local:YButton Width="100" Height="45" Content="YButton"/>
</StackPanel>
</UserControl>
The Button is there so that I can "Edit a Copy" of it's default template within Blend. I will be tethering this modified copy to YButton, making it the Default Template.

Now, I right-click on the xaml file and "Open in Expression Blend..."



Now, Right-Click on Button and select to "Edit a Copy…" of the Button's template.




We'll be doing moving generated content around anyway, so where we put the Copy of Button's default template is not very important. I'll put it within ControlsForBlendsBenefit.xaml.



Notice that the dialog below is asking me for a Name/Key. This is the Key that allows an Application view to specify a Style={StaticResource ButtonStyle1}. This is where things get a bit confusing. Silverlight provides two ways of overriding Look-and-Feel of controls. One is through a replacement of the Template itself, or one by simply Key-ing a new Style. The Style can reside within App.xaml or in a more localized location, the UserControl, or even more scoped, within the Control itself. The way that Blend generates this Style, the Style will contain the copy of the Template. My Button will have to use the Style attribute to access it. This is not what we want. But for Blend's sake, we'll work through it this way.

If we peek into the code of ControlsForBlendsBenefit.xaml we'll see something like:


<UserControl x:Class="YControlsLib.ControlsForBlend"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:local="clr-namespace:YControlsLib;assembly=YControlsLib" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
<UserControl.Resources>
<Style x:Key="ButtonStyle1" TargetType="Button">
<Setter Property="Background" Value="#FF1F3B53"/>
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush">

After customizing these pieces from Blend, it is the code block beginning with that will make its way over to Generic Xaml. In fact, we can do so now and continue editing from within generic.xaml. Perhaps, for the final release, the Silverlight team will make working with generic.xaml a bit more straight-forward.

Generic.xaml Revisited
This file will now include a copy of Button's default Template:


<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YControlsLib;assembly=YControlsLib">
<Style x:Key="ButtonStyle1" TargetType="Button">

You will notice many prefixes adorning VisualState prooerties in the newly crafted generic.xaml . Visual Studio does not like these. They are relics left behind by Blend. Looks like it is yet another quirk that the Silverlight Team needs to work out. A forum post for this is here: http://silverlight.net/forums/t/23959.aspx. I'll just "Quick Search/Replace" the "vsm:" bits into nothingness.

The last bit of work to complete here is to remove the generated UserControl.Resources and Style markup from the ControlsForBlendsBenefit.xaml. Remember, we are not going for inline styles. We want to override Ybutton's default Template.

If you are switching between VS and Blend, you may need to recompile your Project in VS for the new generic.xaml to properly expose Templates within Resources tab in the upper right corner.

Once Blend reload the project you should now see something like this:




We will be Editing the ButtonStyle1 resource from now on. After selecting the Resource for Edit (right-most button along-side the ButtonStyle1), we can click on the icon in the left-most corner and "Edit the Template" itself.

We should now see all the Parts and States of the Button Template we will be customizing.




I'll simply customize some gradients to give this Button template an overall Orange hue.

Finally, the last step is to apply the Style and Templates to YButton only. This is done by replacing TargetType="Button" with TargetType="local:YButton".

Themes/generic.xaml is now:
 <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YControlsLib;assembly=YControlsLib">
<Style TargetType="local:YButton">
<Setter Property="Background" Value="#FF1F3B53"/>
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush">
<Setter.Value>

</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:YButton">
<Grid>

</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

Editing Styles in App.xaml
After rebuilding the solution, we should now see the following in the ControlsForBlendsBenefit.xaml:


The markup inside is simply:

<UserControl x:Class="YControlsLib.ControlsForBlend"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:local="clr-namespace:YControlsLib;assembly=YControlsLib" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">

<StackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
<Button Width="100" Height="45" Content="Button"/>
<local:YButton Width="100" Height="45" Content="YButton"/>
</StackPanel>
</UserControl>

Notice, there is no Styling within the View. We have successfully encapsulated the custom template to the YButton Custom Control!

Back to our YControlsDemo
YControlsDemo simply references the YControlsLib as a DLL containing our Custom Controls. The code of our Page.xaml is still:
 <UserControl x:Class="YControlsDemo.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ycontrols="clr-namespace:YControlsLib;assembly=YControlsLib">
<StackPanel x:Name="LayoutRoot" Background="White" Orientation="Horizontal">
<Button Width="100" Height="45" Content="Button"/>
<ycontrols:YButton Width="100" Height="45" Content="YButton"/>
<ycontrols:YButton Width="100" Height="45" Content="YButton2"
Style="{StaticResource YButtonStyle}"/>
</StackPanel>

</UserControl>
But now the Preview shows us:





I will simply rotate the Button a bit and change the Background gradient. My end-result is:




Extra Credit: Editing the Ybutton's DataTemplate within App.xaml
The last tweak I will introduce is one introducing DataTemplate. Plain ol' Button has a simple TextBlock at its heart. What if I want something a bit more expressive while retaining the overall Template and Style of the Ybutton?

Solution is to use Blend to only Edit the DataTemplate of the 4th Control.



To keep things simple, I only introduced a two-column Grid layout with an Elipse in the first Column and a static TextBlock in the second Column.

Resulting manifestation of YButton is:



App.xaml
The resulting App.xaml in YControldDemo is then:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="YControlsDemo.App"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
xmlns:YControlsLib="clr-namespace:YControlsLib;assembly=YControlsLib" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
>
<Application.Resources>

<DataTemplate x:Key="YButtonCustom">
<Grid ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="60"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Row="0" Grid.Column="0" Margin="5,0,5,0" HorizontalAlignment="Stretch">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="#FFF4F3ED"/>
<GradientStop Color="#FFE27259" Offset="1"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<TextBox Text="Click Me" TextWrapping="Wrap" Grid.Row="0" Grid.Column="1" Background="{x:Null}" BorderBrush="{x:Null}"/>
</Grid>
</DataTemplate>

<Style x:Key="YButtonStyle" TargetType="YControlsLib:YButton">
...
</Style>

</Application.Resources>
</Application>

Done.
Rebuilding the solution, our Silverlight RC0 Demo App looks like:




Conclusion
In this post, I've attempted to exercise three of multiple roles commonly played on a large project:
1. Control Designer
2. Control Developer
3. Application Developer

Using both Visual Studio 2008 SP1 + Silverlight Tools as well as Expression Blend RC0 Preview, I subclassed a regular Button so as to completely encapsulate a custom Look-and-Feel for my version of the Button without having to re-instrument the entire control. Through the use of Generic.xaml in the Themes folder of my Control Lib, I was able to specify my custom Template and Styling for my button while giving the Application Developer a simple Control, YButton.

I also submitted a bit of extra credit by demonstrating how Application Developers retain flexibility to further modify Data Templates and the Content Templates of Custom Controls within App.xaml.

So what is App.xaml vs Generic.xaml. Generic.xaml is a compiled Resource. Silverlight is hardwired to look for default Templates within Generic.xaml. It is ideal for providing custom templates for a Control Lib. It will travel with the Control Lib without the need for further intervention by the Application Developer. It permits custom control look-and-feel out-of-the-box. App.xaml is simply a scoping mechanism for providing nuance Styling and Templates for the given Application only. To port Styles and Templates within App.xaml to other application, one would have to copy over App.xaml itself.

Hope this helps shed additional light on Custom Controls, the Silverlight 2 RC0 way! If you wish to download the entire Project, the zip archive is available here.

Related Articles from fellow Bloggers
I've spent much time reading other great Blog entries. Some of them are below:

1 comment:

  1. BTW, Jeff Prosise at Microsoft has an EXCELLENT article on creating Custom controls for Silverlight 2.

    Much of my own exploration began by working through his examples.

    One topic Jeff covers that I have not is how to override a portion of the template and not the whole thing.

    In my post, I replace the entire Template or switch to a DataTemplate. Jeff describes yet another option here: http://msdn.microsoft.com/en-us/magazine/cc721611.aspx. Thanks, Jeff.

    ReplyDelete