Silverlight Templating: Bending Controls to Your Will
Posted by Dave Bouwman | Posted in Sample Code, Silverlight | Posted on 22-06-2009
0
I’m building an forms-over-data application that needs to control edit access based on the user’s role, and the active item’s workflow status.
When building similar functionality in ASP.NET MVC applications, we’ve handled this scenario by having the server render a partial page, and based on the user’s edit access, we emit form elements (text areas, select boxes etc), or just content. The partial is injected into a containing DIV element, and all is well.
Of course, this is not an option with Silverlight – well I guess we could build separate read-only and read-write Views, but that would be a bunch more work. Why not leverage the IsEnabled property instead – that should work, and it’s ok from a sematic point of view (purists may want to sub-class all the controls and add a "IsReadOnly" property and use that, but I’m a pragmatist – IsEnabled is a reasonable compromise).
The only problem is that by default the control’s content is grayed out when it is disabled (shown below). This makes the "Read Only" state less than "readable"…
With WinForms, we’d just be out of luck. But since this is XAML, we can override the control’s template and change it’s look and feel for the various states.
Since we’ll want these styles accessible across our entire application, we’ll be adding the templates into App.xaml. But before we do that we’ll need to add a namespace for the visual state manager (vsm)
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="sl_templating.App" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" >
From there, we grab the default template from MSDN ( http://msdn.microsoft.com/en-us/library/dd334408(VS.95).aspx ) and assign it a key so we can force our combo box control to load it’s template from App.xaml instead of it’s dll.
<Style TargetType="ComboBox" x:Key="DTSComboBox">
...
</Style>
Then in our page (or to be more specific our Views) we tell the combobox to load it’s style from the static resource
<ComboBox x:Name="cboComboBox" IsEnabled="True" Grid.Row="0" Margin="5,5,5,5"
Style="{StaticResource DTSComboBox}"> <ComboBoxItem Content="Thing One"/> <ComboBoxItem Content="Thing Two" IsSelected="True"/> <ComboBoxItem Content="Thing Three"/> </ComboBox>
Now it’s time to have fun – we change the template to match what we need. For the first cut here’s what I was looking for:
Combo Box: When disabled, gray out the drop down button, and the border, but leave the text readable
TextBox: Gray out the border, but leave the text readable.
Visual States
For this sort of thing, we are going to be modifying the visual states for the controls. The state we are interested in is called (not surprisingly) "Disabled". So we locate this in the Visual State Manager section of the control template…
<vsm:VisualState x:Name="Disabled"> <Storyboard> <DoubleAnimation Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To=".7" Duration="0"/> </Storyboard> </vsm:VisualState>
and we see that when the control’s state is changed to Disabled, a Storyboard with the name "DisabledVisualElement" is called. So we go and locate that…
<Border x:Name="DisabledVisualElement" Background="#A5F7F7F7" BorderBrush="#A5F7F7F7" BorderThickness="{TemplateBinding BorderThickness}" Opacity="0" IsHitTestVisible="False"/>
And from the looks of this, we can see that the Background and Border are set to a lovely gray (F7F7F7). Since we know that text content of a control is rendered in the Background color, we simply remove that property.
<Border x:Name="DisabledVisualElement" BorderBrush="#A5F7F7F7" BorderThickness="{TemplateBinding BorderThickness}" Opacity="0" IsHitTestVisible="False"/>
Voila! The disabled state for our TextBox will leave the text color as is, and just gray out the border, thus giving a visual cue that it’s read-only.
For the ComboBox, it’s a similar process, but complicated by the fact that the ComboBox is made up of multiple parts. We can leave the ToggleButton as is – it gray’s itself out nicely, and that’s fine with us. The other default behavior is to draw the control as a filled rectangle with a opacity set. This is what "grays" out the ComboBox item. Instead of setting a Fill, we just set the Stroke to a nice gray (the illustrious F7F7F7 again).
<Rectangle x:Name="DisabledVisualElement" RadiusX="3" RadiusY="3" Stroke="#A5F7F7F7" Opacity="0" IsHitTestVisible="false" />
That’s it. Now our controls enable & disable nicely, and the content is still readable.
This is clearly just scratching the surface of what you can do with templating, and I’m sure I’ll be digging into more controls like this, but it’s a nice capability to have.

