Adam Sills
Adam is a senior consultant at Catapult Systems and blogs about development on the .NET Framework, with a focus on Silverlight and WPF application development.
Jul 262011

Silverlight projects I’ve been a part of tend to develop a robust set of converters. Anything I can do to keep my view model in a relatively pure state (see Part 0 for more details) keeps me happy. However there are times when I also don’t want to write a converter for one simple “throw-away” data conversion. I know I’m not likely to reuse this and I know I also don’t want to change my view model for this one conversion either. Instead, I’ve been using a simple “Code behind converter” where you can specify your conversion code in the code behind of your view.

   1: using System;
   2: using System.Globalization;
   3: using System.Windows.Data;
   4:  
   5: namespace SilverlightApplication5 {
   6:     public class CodeBehindConverter : IValueConverter {
   7:         public event EventHandler<ConverterEventArgs> Convert = delegate { };
   8:         public event EventHandler<ConverterEventArgs> ConvertBack = delegate { };
   9:  
  10:         object IValueConverter.Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
  11:             ConverterEventArgs args = new ConverterEventArgs( value, targetType, culture );
  12:             Convert( this, args );
  13:  
  14:             return args.ConvertedValue;
  15:         }
  16:  
  17:         object IValueConverter.ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
  18:             ConverterEventArgs args = new ConverterEventArgs( value, targetType, culture );
  19:             ConvertBack( this, args );
  20:  
  21:             return args.ConvertedValue;
  22:         }
  23:     }
  24:  
  25:     public class ConverterEventArgs : EventArgs {
  26:         public object Value { get; private set; }
  27:         public object ConvertedValue { get; set; }
  28:         public CultureInfo Culture { get; private set; }
  29:         public Type TargetType { get; private set; }
  30:  
  31:         public ConverterEventArgs( object value, Type targetType, CultureInfo culture ) {
  32:             TargetType = targetType;
  33:             Value = value;
  34:             ConvertedValue = value;
  35:         }
  36:     }
  37: }

This converter simply provides a Convert and ConvertBack event that can be handled on the code behind.

For an example of it in use, here is a business entity (which has a status enumeration):

   1: namespace SilverlightApplication5 {
   2:     public class BusinessEntity {
   3:         public BusinessEnumeration Status { get; set; }
   4:     }
   5:  
   6:     public enum BusinessEnumeration {
   7:         OperationNotCompleted,
   8:         OperationPending,
   9:         OperationWorking,
  10:         OperationCompleted
  11:     }
  12: }

And here is a view model for a business view:

   1: namespace SilverlightApplication5 {
   2:     public class BusinessViewModel {
   3:         private BusinessEntity _entity;
   4:  
   5:         public BusinessEnumeration Status {
   6:             get { return _entity.Status; }
   7:         }
   8:  
   9:         public BusinessViewModel() {
  10:             _entity = new BusinessEntity() {
  11:                 Status = BusinessEnumeration.OperationPending
  12:             };
  13:         }
  14:     }
  15: }

And here is the view that displays a friendly version of the status enumeration:

   1: <UserControl x:Class="SilverlightApplication5.BusinessView"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:     xmlns:local="clr-namespace:SilverlightApplication5">
   7:     <UserControl.Resources>
   8:         <local:CodeBehindConverter x:Key="StatusConverter" Convert="ConvertStatus"></local:CodeBehindConverter>
   9:     </UserControl.Resources>
  10:     <UserControl.DataContext>
  11:         <local:BusinessViewModel></local:BusinessViewModel>
  12:     </UserControl.DataContext>
  13:     <Grid x:Name="LayoutRoot" Background="White">
  14:         <TextBlock Text="{Binding Status, Converter={StaticResource StatusConverter}}"></TextBlock>
  15:     </Grid>
  16: </UserControl>

And here is the view’s code behind:

   1: using System.Windows.Controls;
   2:  
   3: namespace SilverlightApplication5 {
   4:     public partial class BusinessView : UserControl {
   5:         public BusinessView() {
   6:             InitializeComponent();
   7:         }
   8:  
   9:         private void ConvertStatus( object sender, ConverterEventArgs e ) {
  10:             switch( (BusinessEnumeration) e.Value ) {
  11:                 case BusinessEnumeration.OperationNotCompleted:
  12:                     e.ConvertedValue = "Not completed";
  13:                     break;
  14:                 case BusinessEnumeration.OperationPending:
  15:                     e.ConvertedValue = "Pending";
  16:                     break;
  17:                 case BusinessEnumeration.OperationWorking:
  18:                     e.ConvertedValue = "Working";
  19:                     break;
  20:                 case BusinessEnumeration.OperationCompleted:
  21:                     e.ConvertedValue = "Complete";
  22:                     break;
  23:             }
  24:         }
  25:     }
  26: }

If it turns out you need this elsewhere, it’s easy to change this into its own converter because you followed the same approach as you would have when needing a converter in the first place. Move the code into a custom IValueConverter class and update your usages of this to your new converter.


Jun 212011

Happened upon a Silverlight bug today. In essence, if you have a ComboBox with its SelectedValue bound to a property and set that property to null, the ComboBox throws out your binding. No, really. There’s even a Connect bug about it.

For posterity, here is an incredibly simple reproduction scenario. No view models or anything fancy, just one user control:

   1: <UserControl x:Class="SilverlightApplication6.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:     mc:Ignorable="d"
   7:     d:DesignHeight="300" d:DesignWidth="400">
   8:  
   9:     <Grid x:Name="LayoutRoot" Background="White">
  10:         <StackPanel Orientation="Vertical">
  11:             <ComboBox ItemsSource="{Binding Items}" 
  12:                       SelectedValue="{Binding SelectedValue, Mode=TwoWay}"
  13:                       DisplayMemberPath="Value"
  14:                       SelectedValuePath="Value"></ComboBox>
  15:             <TextBlock Text="{Binding SelectedValue}"></TextBlock>
  16:             <Button Content="Clear Selection" Click="Button_Click"></Button>
  17:         </StackPanel>
  18:     </Grid>
  19: </UserControl>

This code sets up a ComboBox with the appropriate settings, a TextBlock bound to the same property as the ComboBox’s SelectedValue binding, and a button to clear our selection (by setting said property to null). Before we get into code we have to define our “Item” class – when using SelectedValue and SelectedValuePath, we need to be binding to a set of objects:

   1: public class MyItem {
   2:     public string Value { get; set; }
   3: }

Very simple, just a class with a Value property. Now for the codebehind of the user control:

   1: using System.Collections.Generic;
   2: using System.ComponentModel;
   3: using System.Windows.Controls;
   4:  
   5: namespace SilverlightApplication6 {
   6:     public partial class MainPage : UserControl, INotifyPropertyChanged {
   7:         public event PropertyChangedEventHandler PropertyChanged = delegate { };
   8:         private string _selectedValue;
   9:  
  10:         public List<MyItem> Items { get; set; }
  11:  
  12:         public string SelectedValue {
  13:             get { return _selectedValue; }
  14:             set {
  15:                 _selectedValue = value;
  16:                 PropertyChanged( this, new PropertyChangedEventArgs( "SelectedValue" ) );
  17:             }
  18:         }
  19:  
  20:         public MainPage() {
  21:             InitializeComponent();
  22:  
  23:             Items = new List<MyItem>() {
  24:                 new MyItem(){
  25:                     Value = "First item"
  26:                 },
  27:                 new MyItem(){
  28:                     Value = "Second item"
  29:                 },
  30:                 new MyItem(){
  31:                     Value = "Third item"
  32:                 }
  33:             };
  34:  
  35:             this.DataContext = this;
  36:         }
  37:  
  38:         private void Button_Click( object sender, System.Windows.RoutedEventArgs e ) {
  39:             SelectedValue = null;
  40:         }
  41:     }
  42: }

This defines the SelectedValue property (and necessary INotifyPropertyChanged implementation), sets the DataContext to itself, and when the button is clicked it sets the SelectedValue property to null. If you’re familiar with Silverlight, nothing here should look out of place.

When the application is run and the ComboBox selection is changed, the TextBlock’s text updates as expected.

image

When you click Clear selection, both the ComboBox and TextBlock clear:

image

Finally, making another selection in the ComboBox demonstrates the issue:

image

What’s going on here? If I put a breakpoint on the SelectedValue property setter…
image
…execution never stops there! This means my binding, which was working before, is either broken or the ComboBox removed it. Because I’m writing this after the fact, I know that the ComboBox, on a null value in its SelectedValue binding, will clear out the SelectedValue binding. In my application, I like the way the view model is structured and the XAML is laid out and I don’t want to change the way things work, so what do I do? If my binding is being deleted on a null value, can I re-add it? Is that a valid workaround?

Yes, and yes (in my opinion). There are other workarounds such as subclassing ComboBox, but I’m of the opinion that much of the Silverlight and WPF framework code wasn’t written with subclassing in mind and it can be more of a pain to subclass than it is to attach a behavior of some sort to an existing class. In addition to this, I personally find it easier to attach a behavior and if the reason for the behavior is ever fixed, I can simply remove the behavior wherever it’s used. Nice and neat.

So instead of subclassing, I’m going to go with behavior attaching, and this is where the Expression SDK comes in. In a previous post, I mentioned Behaviors, Triggers and Actions and mostly dismissed Behaviors because a behavior is essentially a trigger and an action combined. In this case, it makes no sense to separate our trigger (a null value set on a ComboBox) and our action (fixing the binding on said ComboBox) so it makes sense to keep it all together.

To start, what we need to do is whenever the SelectedValue changes, if it’s non-null, store the current binding. If SelectedValue is ever null, we need to restore the SelectedValue binding onto the ComboBox. To do this, we’ll create a class that inherits from Behavior<T>:

   1: using System.Windows.Controls;
   2: using System.Windows.Data;
   3: using System.Windows.Interactivity;
   4:  
   5: namespace SilverlightApplication6 {
   6:     public class SelectedValueNullBugFix : Behavior<ComboBox> {
   7:     }
   8: }

This class has two virtual methods that are important to override: OnAttached and OnDetaching. OnAttached is called when the behavior is loaded and attached to an associated element (ComboBox in my case). On Detaching is called when it is being unloaded from an element (and thus is being detached). In our case, we want to handle the ComboBox.SelectedValueChanged method:

   1: protected override void OnAttached() {
   2:     base.OnAttached();
   3:  
   4:     this.AssociatedObject.SelectionChanged += new SelectionChangedEventHandler( AssociatedObject_SelectionChanged );
   5: }
   6:  
   7: protected override void OnDetaching() {
   8:     base.OnDetaching();
   9:  
  10:     this.AssociatedObject.SelectionChanged -= new SelectionChangedEventHandler( AssociatedObject_SelectionChanged );
  11: }

In the event handler, we need to cache the binding when it’s available and restore it when it’s been cleared. To do this, we’ll use the FrameworkElement.GetBindingExpression method to get the binding on the SelectedValue property.

   1: private Binding _cachedBinding;
   2:  
   3: private void AssociatedObject_SelectionChanged( object sender, SelectionChangedEventArgs e ) {
   4:     // On selection changed, if the selected value is null **and** the binding is missing,
   5:     // restore it.
   6:     if( this.AssociatedObject.SelectedValue == null ) {
   7:         if( _cachedBinding != null && GetExistingBinding() == null ) {
   8:             this.AssociatedObject.SetBinding( ComboBox.SelectedValueProperty, _cachedBinding );
   9:         }
  10:     }
  11:     else {
  12:         CacheExistingBinding();
  13:     }
  14: }
  15:  
  16: private void CacheExistingBinding() {
  17:     Binding binding = GetExistingBinding();
  18:     if( binding != null ) {
  19:         _cachedBinding = binding;
  20:     }
  21: }
  22:  
  23: private Binding GetExistingBinding() {
  24:     BindingExpression bindingExpr = this.AssociatedObject.GetBindingExpression( ComboBox.SelectedValueProperty );
  25:     if( bindingExpr == null )
  26:         return null;
  27:  
  28:     return bindingExpr.ParentBinding;
  29: }

The CacheExistingBinding method stores the binding in a local field, and the SelectionChanged handler restores the cached binding if it’s discovered to be null. With this behavior added to our ComboBox:

   1: <ComboBox ItemsSource="{Binding Items}" 
   2:             SelectedValue="{Binding SelectedValue, Mode=TwoWay}"
   3:             DisplayMemberPath="Value"
   4:             SelectedValuePath="Value">
   5:     <i:Interaction.Behaviors>
   6:         <local:SelectedValueNullBugFix />
   7:     </i:Interaction.Behaviors>
   8: </ComboBox>

The bug no longer affects us and the ComboBox continually updates the binding so the TextBlock displays the active selection.


Jun 152011

Converters are a bit of a touchy subject in MVVM. Some would argue that the ViewModel is itself a “converter on steroids” and for the most part I agree, specifically when you are dealing with complex values (“I have an object of type Contract but I want to display a Customer on screen”). My view of MVVM is within view of the dogmatic MVVM, however I also have a strong pragmatic voice in my head telling me not to go overboard on things. So while I do think that a view model should closely resemble the data a view needs, I’m not of the opinion that it must match exactly.

The dogmatic view would mean that if a view needs a Foo formatted in a specific way as a string, then the view model should have a property on it that returns the Foo in that format. For example, if a view needs to display a decimal as a currency, it should have a string property that calls ToString(“C”) on the backing decimal (contrived example, and yes I’m aware of the format string in bindings as of Silverlight 4). I’ve never liked this approach – I would always prefer to have my view model have a decimal property and let the view format the decimal as needed. That’s the point of a view is it not? To format and display the data as needed? Similarly, there are times when I use a single property to power multiple items. For instance, I may have a drop down of choices bound to an enumeration property. At the same time each choice would have its own panel that shows or hides. Some would insist on one property for the drop down and one Visibility property for each of the panels. Personally, I would rather just bind the panels’ visibility directly to the same property as the drop down and use a converter to evaluate it to a Visibility.

For example, instead of this property:

   1: private Visibility _advancedSectionVisibility;
   2:  
   3: public Visibility AdvancedSectionVisibility {
   4:     get { return _advancedSectionVisibility; }
   5:     set {
   6:         if( value != _advancedSectionVisibility ) {
   7:             _advancedSectionVisibility = value;
   8:             RaisePropertyChangedEvent( "AdvancedSectionVisibility" );
   9:         }
  10:     }
  11: }

I prefer this property:

   1: private bool _isAdvancedSectionVisible;
   2:  
   3: public bool IsAdvancedSectionVisible {
   4:     get { return _isAdvancedSectionVisible; }
   5:     set {
   6:         if( value != _isAdvancedSectionVisible ) {
   7:             _isAdvancedSectionVisible = value;
   8:             RaisePropertyChangedEvent( "IsAdvancedSectionVisible" );
   9:         }
  10:     }
  11: }

To use the second property as the Visibility of a UIElement, I need a converter that is capable of converting a Boolean into a Visibility value. In my opinion, a ViewModel represents the data a view uses and a View should do any transformations necessary to use the data as it sees fit. While building complex Silverlight applications, I often find myself creating properties like the second one above and using them in more than one place on a View. I’m not a fan of inter-element binding when I already have a ViewModel property capable of handling it, so I frequently have situations like below:

   1: <CheckBox Content="Show Advanced Settings" IsChecked="{Binding IsAdvancedSectionVisible}"></CheckBox>
   2: <Grid Visibility="{Binding IsAdvancedSectionVisible, Converter={StaticResource BooleanVisibilityConverter}}">
   3:     <!-- advanced settings ... -->
   4: </Grid>

For more information on converters, how they work, how to define them, etc., here are several links. For even more links, google has quite a few more.


Jun 092011

The Expression Blend SDK has some interesting controls in it that you can easily miss if you’re not poking around the SDK namespaces. The one I’m going to talk about right now is called LineArrow and is a simplistic way to draw a curved or straight line with optional arrow heads on it. At its simplest, it draws a line from one corner of itself to another corner:

   1: <UserControl x:Class="SilverlightApplication5.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
   6:     d:DesignHeight="300" d:DesignWidth="400">
   7:  
   8:     <Grid x:Name="LayoutRoot" Background="White">
   9:         <ed:LineArrow Margin="10"
  10:                       StartArrow="OvalArrow"
  11:                       EndArrow="StealthArrow"
  12:                       BendAmount="-1"
  13:                       StartCorner="BottomLeft"
  14:                       Stroke="Black"
  15:                       StrokeThickness="1"
  16:                       ArrowSize="15"></ed:LineArrow>
  17:     </Grid>
  18: </UserControl>

And when executed it looks like this:

image

To put this to good use, one could arrange a set of visual elements to visualize a process flow perhaps:

image

And the XAML:

   1: <UserControl x:Class="SilverlightApplication5.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
   5:     Height="300" Width="400">
   6:  
   7:     <Grid x:Name="LayoutRoot" Background="White">
   8:         <Canvas>
   9:             <Rectangle Stroke="DarkGreen" StrokeThickness="2" RadiusX="10" RadiusY="10" Fill="Green" Width="90" Height="30" Canvas.Left="10" Canvas.Top="10"></Rectangle>
  10:             <TextBlock Foreground="White" Text="Element 1" Canvas.Left="15" Canvas.Top="15"></TextBlock>
  11:  
  12:             <Rectangle Stroke="DarkGreen" StrokeThickness="2" RadiusX="10" RadiusY="10" Fill="Green" Width="90" Height="30" Canvas.Left="10" Canvas.Top="60"></Rectangle>
  13:             <TextBlock Foreground="White" Text="Element 2" Canvas.Left="15" Canvas.Top="65"></TextBlock>
  14:  
  15:             <Rectangle Stroke="DarkGreen" StrokeThickness="2" RadiusX="10" RadiusY="10" Fill="Green" Width="90" Height="30" Canvas.Left="10" Canvas.Top="110"></Rectangle>
  16:             <TextBlock Foreground="White" Text="Element 3" Canvas.Left="15" Canvas.Top="115"></TextBlock>
  17:  
  18:             <ed:LineArrow Canvas.Left="100" Canvas.Top="22" Width="210" Height="150" ArrowSize="10" StartArrow="OvalArrow" EndArrow="Arrow" Stroke="Black" BendAmount=".5"></ed:LineArrow>
  19:             <ed:LineArrow Canvas.Left="100" Canvas.Top="72" Width="210" Height="50" ArrowSize="10" StartArrow="OvalArrow" EndArrow="Arrow" Stroke="Black" BendAmount="-.5"></ed:LineArrow>
  20:             <ed:LineArrow Canvas.Left="100" Canvas.Top="122" Width="210" Height="100" ArrowSize="10" StartArrow="OvalArrow" EndArrow="Arrow" Stroke="Black" BendAmount="-1"></ed:LineArrow>
  21:  
  22:  
  23:             <Rectangle Stroke="DarkRed" StrokeThickness="2" RadiusX="10" RadiusY="10" Fill="Red" Width="90" Height="30" Canvas.Left="310" Canvas.Top="110"></Rectangle>
  24:             <TextBlock Foreground="White" Text="Element 2" Canvas.Left="315" Canvas.Top="115"></TextBlock>
  25:  
  26:             <Rectangle Stroke="DarkRed" StrokeThickness="2" RadiusX="10" RadiusY="10" Fill="Red" Width="90" Height="30" Canvas.Left="310" Canvas.Top="160"></Rectangle>
  27:             <TextBlock Foreground="White" Text="Element 1" Canvas.Left="315" Canvas.Top="165"></TextBlock>
  28:  
  29:             <Rectangle Stroke="DarkRed" StrokeThickness="2" RadiusX="10" RadiusY="10" Fill="Red" Width="90" Height="30" Canvas.Left="310" Canvas.Top="210"></Rectangle>
  30:             <TextBlock Foreground="White" Text="Element 3" Canvas.Left="315" Canvas.Top="215"></TextBlock>
  31:         </Canvas>
  32:     </Grid>
  33: </UserControl>

While it may look useful, it may also be a bit of a pain to use in a dynamic view that has to drive the view dynamically. Deciding an ideal bend and the X/Y and Width/Height of the arrow may not be easy on a data driven view, but for a static view this control can certainly be useful.


Jun 012011

The Expression Blend SDK has some interesting controls in it that you can easily miss if you’re not poking around the SDK namespaces. The one I’m going to talk about right now is called Callout. The Callout control is a content control that renders itself with an optional arrow anchored at a given point. What makes this control interesting (and fun) is it has an option for a displaying itself light a thought bubble and a word balloon.

   1: <UserControl x:Class="SilverlightApplication5.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
   5:     Height="300" Width="400">
   6:  
   7:     <Grid x:Name="LayoutRoot" Background="White">
   8:         <Image Source="bindispider.jpg" HorizontalAlignment="Left" VerticalAlignment="Top" Width="150"></Image>
   9:         <ed:Callout AnchorPoint="0,0" Width="150" Height="50" CalloutStyle="Oval" Stroke="Black" Fill="White">
  10:             <TextBlock Text="Me llamo Bindi"></TextBlock>
  11:         </ed:Callout>
  12:     </Grid>
  13: </UserControl>

The AnchorPoint property dictates where the optional arrow will be anchored on the callout. The CalloutStyle property has 4 options: Cloud, Oval, Rectangle, RoundedRectangle. The first two support arrows and the last two do not. To see this in action:

image

If I switch CalloutStyle to Cloud and move the anchor point to “0,1” (and move the Callout up a bit with a margin) I end up with this:

image

And a quick example of Rectangle and RoundedRectangle (which are much more boring without arrows):

image

image


May 272011

When you need a visual element from anywhere other than the main Silverlight namespaces/assemblies, it can be a pain to register namespaces. If I want a control from the Silverlight toolkit System.Windows.Layout.Toolkit assembly:

   1: xmlns:layouttk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit"

These are a pain because either you wait for Intellisense to pop up once the cursor is inside the quotation marks (I’ve noticed some don’t even realize Intellisense works here as it can sometimes take so long to open) or you memorize the format. You can also use Resharper, but that defeats the purpose of what I’m about to discuss.

Once I have registered my namespace and namespace prefix, I can use the prefix to specify things in the associated assembly/namespace:

   1: <layouttk:LayoutTransformer></layouttk:LayoutTransformer>

What if I wanted to register the System.Windows.Controls.Data.Toolkit assembly as well? Here comes another namespace!

   1: xmlns:layouttk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit"
   2: xmlns:datatk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Toolkit"

And so on, compounded with my own user controls, converters, etc, until I end up with tens of namespaces on all of my XAML files. Luckily Microsoft did think of this and provide us with a simple shortcut. To see it in action, start declaring a namespace but let Intellisense open:

image

What you see here are a number of XML namespaces defined that contain multiple .NET namespace and assembly combinations. With this one namespace declaration:

   1: xmlns:tk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"

…I have access to everything in the Silverlight Toolkit. If you are using any of the Expression Blend SDK, there are some namespaces you can register for them as well:

image

These can be incredibly useful when dealing with Microsoft’s various extension libraries, but what about my libraries? I tend to write a lot of reusable converters, user controls, view models, etc. To enable this with my own code, I simply need to use the XmlnsDefinitionAttribute.

   1: [assembly: System.Windows.Markup.XmlnsDefinition( "http://foo.bar", "SilverlightApplication5.Controls" )]
   2: [assembly: System.Windows.Markup.XmlnsDefinition( "http://foo.bar", "SilverlightApplication5.Converters" )]

When I do this, I now have access to all my registered namespaces via the “http://foo.bar” XML namespace.

   1: xmlns:my="http://foo.bar"

As I build up a library of shared code, I can easily reference my shared code in XAML via these defined XML namespaces and save myself the hassle of having to manually register individual assemblies and namespaces.


May 112011

One type of Trigger available in the Expression SDK is the DataTrigger. This a trigger that uses a Binding to determine when to execute its actions. If you are using MVVM (or any other pattern that relies heavily on view models or presentation models), this class is a perfect way to interact with the UI without needing to write any custom code in the model or codebehind.

For the purpose of this post, I’m going to be creating a simple header/button control that is powered by a view model and alters the user interface of the control based on data changes.

image

The view model representing the 3 header controls:

   1: using System;
   2: using System.ComponentModel;
   3:  
   4: namespace UsefulCode {
   5:     public class HeaderViewModel : INotifyPropertyChanged {
   6:         public event PropertyChangedEventHandler PropertyChanged = delegate { };
   7:  
   8:         private bool _isEnabled = true;
   9:  
  10:         public Uri Image { get; set; }
  11:         
  12:         public string HeaderText { get; set; }
  13:  
  14:         public bool IsEnabled {
  15:             get { return _isEnabled; }
  16:             set {
  17:                 if( _isEnabled != value ) {
  18:                     _isEnabled = value;
  19:                     PropertyChanged( this, new PropertyChangedEventArgs( "IsEnabled" ) );
  20:                 }
  21:             }
  22:         }
  23:     }
  24: }

Nothing special here, it’s a simple class that notifies on property changed and provides an image, text and an enabled Boolean property. The concept here is the application will have one or more headers that can be enabled or disabled and are intended to give the user visual feedback when the change happens.

Next we need to create a UserControl to provide a visual representation for our header:

   1: <UserControl x:Class="UsefulCode.HeaderButton"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
   6:     xmlns:ia="http://schemas.microsoft.com/expression/2010/interactions"
   7:     xmlns:e="http://schemas.microsoft.com/expression/2010/effects"
   8:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
   9:     <Grid x:Name="LayoutRoot" Background="White" VerticalAlignment="Top">
  10:         <Grid.ColumnDefinitions>
  11:             <ColumnDefinition Width="96"></ColumnDefinition>
  12:             <ColumnDefinition Width="Auto"></ColumnDefinition>
  13:         </Grid.ColumnDefinitions>
  14:         <Image x:Name="img" 
  15:                Grid.Column="0" 
  16:                Source="{Binding Image}" 
  17:                Width="96" Height="96" Margin="4" 
  18:                Stretch="Uniform">
  19:         </Image>
  20:         <TextBlock x:Name="tb" 
  21:                    VerticalAlignment="Center"
  22:                    Text="{Binding HeaderText}" Grid.Column="1" Margin="4" 
  23:                    FontSize="18" FontWeight="Bold"></TextBlock>
  24:     </Grid>
  25: </UserControl>

As it stands like this, the images and text appear but when the application disables a header, the control doesn’t know. One solution to this would be to find a way to bind the IsEnabled property to one or more visual properties inside this control and create one or more converters to convert from true/false to the appropriate values. For instance, suppose we had a converter that converted from Boolean to Colors. We could change the color of the TextBlock easily:

   1: <TextBlock x:Name="tb" 
   2:             VerticalAlignment="Center"
   3:             Text="{Binding HeaderText}" Grid.Column="1" Margin="4" 
   4:             Foreground="{Binding IsEnabled, Converter={StaticResource BooleanColorConverter}}"
   5:             FontSize="18" FontWeight="Bold"></TextBlock>

While this is easy to do (and I’ve done it numerous times), if there are multiple visual properties you have to deal with you’ll need to create more bindings and more converters. It’s not the best solution if you need to change a lot of properties. So what can we do better? A storyboard with a DataTrigger:

   1: <i:Interaction.Triggers>
   2:     <ia:DataTrigger Binding="{Binding IsEnabled}" Comparison="Equal" Value="false">
   3:         <ia:ControlStoryboardAction Storyboard="{StaticResource DisableStoryboard}"></ia:ControlStoryboardAction>
   4:     </ia:DataTrigger>
   5:     <ia:DataTrigger Binding="{Binding IsEnabled}" Comparison="Equal" Value="true">
   6:         <ia:ControlStoryboardAction Storyboard="{StaticResource EnableStoryboard}"></ia:ControlStoryboardAction>
   7:     </ia:DataTrigger>
   8: </i:Interaction.Triggers>

The DataTrigger uses a Binding to the IsEnabled property and when it’s a specific value (false or true), we use a ControlStoryboardAction (also from the Expression SDK) to begin a storyboard to perform our desired animations. When our header is disabled, we’ll change the image to black & white, change the text color to light gray and shrink the text. Here are the storyboards to do this:

   1: <UserControl.Resources>
   2:     <Storyboard x:Key="DisableStoryboard" Duration="00:00:00.5" FillBehavior="HoldEnd">
   3:         <ObjectAnimationUsingKeyFrames Storyboard.TargetName="img" Storyboard.TargetProperty="Effect">
   4:             <DiscreteObjectKeyFrame KeyTime="00:00:00.0">
   5:                 <DiscreteObjectKeyFrame.Value>
   6:                     <e:ColorToneEffect DarkColor="Black" LightColor="White"></e:ColorToneEffect>
   7:                 </DiscreteObjectKeyFrame.Value>
   8:             </DiscreteObjectKeyFrame>
   9:         </ObjectAnimationUsingKeyFrames>
  10:         <ColorAnimation Storyboard.TargetName="tb" 
  11:                         Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)" 
  12:                         To="LightGray"></ColorAnimation>
  13:         <DoubleAnimation Storyboard.TargetName="tb"
  14:                             Storyboard.TargetProperty="FontSize"
  15:                             To="12"></DoubleAnimation>
  16:     </Storyboard>
  17:     <Storyboard x:Key="EnableStoryboard" Duration="00:00:00.5" FillBehavior="HoldEnd">
  18:         <ObjectAnimationUsingKeyFrames Storyboard.TargetName="img" Storyboard.TargetProperty="Effect">
  19:             <DiscreteObjectKeyFrame KeyTime="00:00:00.0" Value="{x:Null}"></DiscreteObjectKeyFrame>
  20:         </ObjectAnimationUsingKeyFrames>
  21:         <ColorAnimation Storyboard.TargetName="tb" 
  22:                         Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" 
  23:                         To="Black"></ColorAnimation>
  24:         <DoubleAnimation Storyboard.TargetName="tb"
  25:                             Storyboard.TargetProperty="FontSize"
  26:                             To="18"></DoubleAnimation>
  27:     </Storyboard>
  28: </UserControl.Resources>

In DisableStoryboard we use the ColorToneEffect bitmap effect to convert the image to black and white (this effect is also provided by the Expression SDK). After that we use a standard ColorAnimation and DoubleAnimation to perform the rest of the animations (text color and font size).

With these added, clicking the toggle buttons disables our headers and performs the desired animations:

image

For reference here is my MainPage.xaml:

   1: <UserControl x:Class="UsefulCode.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:     mc:Ignorable="d"
   7:     xmlns:local="clr-namespace:UsefulCode"
   8:     d:DesignHeight="300" d:DesignWidth="400">
   9:  
  10:     <Grid x:Name="LayoutRoot" Background="White">
  11:         <StackPanel Orientation="Vertical">
  12:             <ItemsControl ItemsSource="{Binding Headers}">
  13:                 <ItemsControl.ItemsPanel>
  14:                     <ItemsPanelTemplate>
  15:                         <StackPanel Orientation="Horizontal"></StackPanel>
  16:                     </ItemsPanelTemplate>
  17:                 </ItemsControl.ItemsPanel>
  18:                 <ItemsControl.ItemTemplate>
  19:                     <DataTemplate>
  20:                         <local:HeaderButton></local:HeaderButton>
  21:                     </DataTemplate>
  22:                 </ItemsControl.ItemTemplate>
  23:             </ItemsControl>
  24:             
  25:             <StackPanel Orientation="Horizontal">
  26:                 <Button Content="Toggle Captain Planet" Click="ToggleButton1"></Button>
  27:                 <Button Content="Toggle Ninja Turtles" Click="ToggleButton2"></Button>
  28:                 <Button Content="Toggle Spider Dog" Click="ToggleButton3"></Button>
  29:             </StackPanel>
  30:         </StackPanel>
  31:     </Grid>
  32: </UserControl>

And the codebehind for it:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Windows;
   5: using System.Windows.Controls;
   6:  
   7: namespace UsefulCode {
   8:     public partial class MainPage : UserControl {
   9:         public IEnumerable<HeaderViewModel> Headers { get; private set; }
  10:  
  11:         public MainPage() {
  12:             InitializeComponent();
  13:  
  14:             Headers = new[] {
  15:                 new HeaderViewModel() {
  16:                     HeaderText = "Captain Planet",
  17:                     IsEnabled = true,
  18:                     Image = new Uri("captainplanet.jpg", UriKind.Relative)
  19:                 },
  20:                 new HeaderViewModel() {
  21:                     HeaderText = "Ninja Turtles",
  22:                     IsEnabled = true,
  23:                     Image = new Uri("ninjaturtles.jpg", UriKind.Relative)
  24:                 },
  25:                 new HeaderViewModel() {
  26:                     HeaderText = "Spider Dog",
  27:                     IsEnabled = true,
  28:                     Image = new Uri("spiderdog.jpg", UriKind.Relative)
  29:                 }
  30:             };
  31:  
  32:             this.DataContext = this;
  33:         }
  34:  
  35:         private void ToggleButton1( object sender, RoutedEventArgs e ) {
  36:             HeaderViewModel vm = Headers.Skip( 0 ).First();
  37:             vm.IsEnabled = !vm.IsEnabled;
  38:         }
  39:  
  40:         private void ToggleButton2( object sender, RoutedEventArgs e ) {
  41:             HeaderViewModel vm = Headers.Skip( 1 ).First();
  42:             vm.IsEnabled = !vm.IsEnabled;
  43:         }
  44:  
  45:         private void ToggleButton3( object sender, RoutedEventArgs e ) {
  46:             HeaderViewModel vm = Headers.Skip( 2 ).First();
  47:             vm.IsEnabled = !vm.IsEnabled;
  48:         }
  49:     }
  50: }

And the full header control:

   1: <UserControl x:Class="UsefulCode.HeaderButton"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
   6:     xmlns:ia="http://schemas.microsoft.com/expression/2010/interactions"
   7:     xmlns:e="http://schemas.microsoft.com/expression/2010/effects"
   8:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
   9:     <UserControl.Resources>
  10:         <Storyboard x:Key="DisableStoryboard" Duration="00:00:00.5" FillBehavior="HoldEnd">
  11:             <ObjectAnimationUsingKeyFrames Storyboard.TargetName="img" Storyboard.TargetProperty="Effect">
  12:                 <DiscreteObjectKeyFrame KeyTime="00:00:00.0">
  13:                     <DiscreteObjectKeyFrame.Value>
  14:                         <e:ColorToneEffect DarkColor="Black" LightColor="White"></e:ColorToneEffect>
  15:                     </DiscreteObjectKeyFrame.Value>
  16:                 </DiscreteObjectKeyFrame>
  17:             </ObjectAnimationUsingKeyFrames>
  18:             <ColorAnimation Storyboard.TargetName="tb" 
  19:                             Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)" 
  20:                             To="LightGray"></ColorAnimation>
  21:             <DoubleAnimation Storyboard.TargetName="tb"
  22:                                 Storyboard.TargetProperty="FontSize"
  23:                                 To="12"></DoubleAnimation>
  24:         </Storyboard>
  25:         <Storyboard x:Key="EnableStoryboard" Duration="00:00:00.5" FillBehavior="HoldEnd">
  26:             <ObjectAnimationUsingKeyFrames Storyboard.TargetName="img" Storyboard.TargetProperty="Effect">
  27:                 <DiscreteObjectKeyFrame KeyTime="00:00:00.0" Value="{x:Null}"></DiscreteObjectKeyFrame>
  28:             </ObjectAnimationUsingKeyFrames>
  29:             <ColorAnimation Storyboard.TargetName="tb" 
  30:                             Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" 
  31:                             To="Black"></ColorAnimation>
  32:             <DoubleAnimation Storyboard.TargetName="tb"
  33:                                 Storyboard.TargetProperty="FontSize"
  34:                                 To="18"></DoubleAnimation>
  35:         </Storyboard>
  36:     </UserControl.Resources>
  37:     <i:Interaction.Triggers>
  38:         <ia:DataTrigger Binding="{Binding IsEnabled}" Comparison="Equal" Value="false">
  39:             <ia:ControlStoryboardAction Storyboard="{StaticResource DisableStoryboard}"></ia:ControlStoryboardAction>
  40:         </ia:DataTrigger>
  41:         <ia:DataTrigger Binding="{Binding IsEnabled}" Comparison="Equal" Value="true">
  42:             <ia:ControlStoryboardAction Storyboard="{StaticResource EnableStoryboard}"></ia:ControlStoryboardAction>
  43:         </ia:DataTrigger>
  44:     </i:Interaction.Triggers>
  45:     <Grid x:Name="LayoutRoot" Background="White" VerticalAlignment="Top">
  46:         <Grid.ColumnDefinitions>
  47:             <ColumnDefinition Width="96"></ColumnDefinition>
  48:             <ColumnDefinition Width="Auto"></ColumnDefinition>
  49:         </Grid.ColumnDefinitions>
  50:         <Image x:Name="img" 
  51:                Grid.Column="0" 
  52:                Source="{Binding Image}" 
  53:                Width="96" Height="96" Margin="4" 
  54:                Stretch="Uniform">
  55:         </Image>
  56:         <TextBlock x:Name="tb" 
  57:                     VerticalAlignment="Center"
  58:                     Text="{Binding HeaderText}" Grid.Column="1" Margin="4" 
  59:                     FontSize="18" FontWeight="Bold"></TextBlock>
  60:     </Grid>
  61: </UserControl>

May 022011

Prior to the Expression SDK, you had two main choices to create reusable behaviors that could be applied to UI elements. First and most obvious is to subclass a control type and add whatever functionality you wanted. For instance, if you wanted a TextBox to raise an event (or execute an ICommand) whenever the Enter key was pressed, you could create a subclassed TextBox and add that.

The second way was to create manual “attached behaviors” via a trick commonly used in WPF with attached properties. In short, you create an attached property and use the property change notification to attach to events. Using the earlier example, here is a working attached behavior that exeuctes an ICommand on enter key press in a TextBox:

   1: using System.Windows;
   2: using System.Windows.Controls;
   3: using System.Windows.Input;
   4:  
   5: namespace SilverlightBehaviors {
   6:     public class EnterKeyPressed {
   7:         public static readonly DependencyProperty CommandProperty =
   8:             DependencyProperty.RegisterAttached(
   9:                 "EnterKeyPressedCommand",
  10:                 typeof( ICommand ),
  11:                 typeof( EnterKeyPressed ),
  12:                 new PropertyMetadata( null, OnCommandChanged ) );
  13:         public static readonly DependencyProperty CommandParameterProperty =
  14:             DependencyProperty.RegisterAttached( 
  15:                 "CommandParameter", 
  16:                 typeof( object ), 
  17:                 typeof( EnterKeyPressed ), 
  18:                 new PropertyMetadata( null ) );
  19:  
  20:         public static ICommand GetCommand( TextBox obj ) {
  21:             return (ICommand) obj.GetValue( CommandProperty );
  22:         }
  23:  
  24:         public static void SetCommand( TextBox obj, ICommand value ) {
  25:             obj.SetValue( CommandProperty, value );
  26:         }
  27:  
  28:         public static object GetCommandParameter( TextBox obj ) {
  29:             return (object) obj.GetValue( CommandParameterProperty );
  30:         }
  31:  
  32:         public static void SetCommandParameter( TextBox obj, object value ) {
  33:             obj.SetValue( CommandParameterProperty, value );
  34:         }
  35:  
  36:         private static void OnCommandChanged( object sender, 
  37:             DependencyPropertyChangedEventArgs e ) {
  38:             TextBox tb = sender as TextBox;
  39:             if( tb != null ) {
  40:                 if( e.NewValue != null ) {
  41:                     tb.KeyDown += new KeyEventHandler( OnKeyDown );
  42:                 }
  43:                 else {
  44:                     tb.KeyDown -= new KeyEventHandler( OnKeyDown );
  45:                 }
  46:             }
  47:         }
  48:  
  49:         private static void OnKeyDown( object sender, KeyEventArgs e ) {
  50:             if( e.Key == Key.Enter ) {
  51:                 e.Handled = true;
  52:  
  53:                 ICommand command = GetCommand( (TextBox) sender );
  54:                 object parameter = GetCommandParameter( (TextBox) sender );
  55:  
  56:                 if( command.CanExecute( parameter ) )
  57:                     command.Execute( parameter );
  58:             }
  59:         }
  60:     }
  61: }

The meat of the code comes from lines 36-60. On 40-45, we attach or detach an event handler to the KeyDown event depending on whether the supplied ICommand is null. Then on lines 49-60 we handle the KeyDown event and execute the command. To use this behavior, just reference it inside a TextBox:

   1: <UserControl x:Class="SilverlightBehaviors.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:     xmlns:local="clr-namespace:SilverlightBehaviors" Width="400" Height="150">
   7:  
   8:     <Grid x:Name="LayoutRoot" Background="White" VerticalAlignment="Top">
   9:         <TextBox local:EnterKeyPressed.Command="{Binding ExecuteSearch}"
  10:                  local:EnterKeyPressed.CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}"></TextBox>
  11:     </Grid>
  12: </UserControl>

All that is needed is the ViewModel/DataContext expose an ICommand property called ExecuteSearch and now we’ve got a way to repeatedly apply a common behavior to any TextBox in our application without much effort.

One thing you may notice here, however, is our behavior describes not only the action to be taken, but also the condition in which it happens. This is limiting and can end up causing duplicated code as we create variants of our shared behaviors. Enter the Expression SDK.

The Expression SDK (in particular, two assemblies: System.Windows.Interactivity and Microsoft.Expression.Interactions) breaks this up into separate concerns. With it you can create the “when” (a trigger for instance) and the “what” (an action for instance) separately to more easily mix and match. In the Expression SDK there are behaviors, triggers and actions. Behaviors are all-in-one sets of reusable functionality like we did above, but from here on I’ll only be discussing triggers and actions, which enable us to separate the when and the what.

The trigger for the enter key pressed behavior is the enter key being pressed inside a TextBox. To represent this, we create a class that inherits from TriggerBase:

   1: using System.Windows.Controls;
   2: using System.Windows.Input;
   3: using System.Windows.Interactivity;
   4:  
   5: namespace SilverlightBehaviors {
   6:     public class EnterKeyPressedTrigger : TriggerBase<TextBox> {
   7:         protected override void OnAttached() {
   8:             base.OnAttached();
   9:  
  10:             this.AssociatedObject.KeyDown += new KeyEventHandler( AssociatedObject_KeyDown );
  11:         }
  12:  
  13:         protected override void OnDetaching() {
  14:             base.OnDetaching();
  15:  
  16:             this.AssociatedObject.KeyDown -= new KeyEventHandler( AssociatedObject_KeyDown );
  17:         }
  18:  
  19:         private void AssociatedObject_KeyDown( object sender, KeyEventArgs e ) {
  20:             if( e.Key == Key.Enter ) {
  21:                 e.Handled = true;
  22:                 this.InvokeActions( null );
  23:             }
  24:         }
  25:     }
  26: }

We override OnAttached and subscribe to the TextBox’s KeyDown event and when the Enter key was pressed we call the InvokeActions method. Very svelte and to the point. What about the action though? Luckily, the Expression SDK has support for invoking a command built in via the InvokeCommandAction, so we’re done except for our XAML:

   1: <UserControl x:Class="SilverlightBehaviors.ExpressionBlendInteractivity"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:     xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
   7:     xmlns:local="clr-namespace:SilverlightBehaviors"
   8:     mc:Ignorable="d"
   9:     d:DesignHeight="300" d:DesignWidth="400">
  10:     
  11:     <Grid x:Name="LayoutRoot" Background="White">
  12:         <TextBox VerticalAlignment="Top">
  13:             <i:Interaction.Triggers>
  14:                 <local:EnterKeyPressedTrigger>
  15:                     <i:InvokeCommandAction Command="{Binding ExecuteSearch}" 
  16:                                            CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}"/>
  17:                 </local:EnterKeyPressedTrigger>
  18:             </i:Interaction.Triggers>
  19:         </TextBox>
  20:     </Grid>
  21: </UserControl>

What if we wanted to do something other than invoke a command? With the full DIY attached property behavior, we’d have to create a new class that contained the new behavior. By factoring our trigger out into a class, we can use any of the built-in actions. What if we wanted to set the user control’s background to green?

   1: <UserControl x:Class="SilverlightBehaviors.ExpressionBlendInteractivity"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:     xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
   7:     xmlns:ia="http://schemas.microsoft.com/expression/2010/interactions"
   8:     xmlns:local="clr-namespace:SilverlightBehaviors"
   9:     mc:Ignorable="d"
  10:     x:Name="UserControl1"
  11:     d:DesignHeight="300" d:DesignWidth="400">
  12:     
  13:     <Grid x:Name="LayoutRoot" Background="White">
  14:         <TextBox VerticalAlignment="Top">
  15:             <i:Interaction.Triggers>
  16:                 <local:EnterKeyPressedTrigger>
  17:                     <ia:ChangePropertyAction TargetObject="{Binding ElementName=UserControl1}"
  18:                                              PropertyName="Background"
  19:                                              Value="Green"></ia:ChangePropertyAction>
  20:                 </local:EnterKeyPressedTrigger>
  21:             </i:Interaction.Triggers>
  22:         </TextBox>
  23:     </Grid>
  24: </UserControl>

If you have an action that’s not natively supported by the Expression SDK, you can create a class that inherits from TriggerAction.

For more info, see this Expression blog post on Behaviors, Triggers and Actions.


Apr 202011

At some point, every .Net developer comes across WebRequest.Create. It’s a simple way to create any type of request to a web resource, frequently an HTTP resource. In Silverlight there is an alternate method you can use as well: WebRequest.CreateHttp. I swore until a couple minutes ago that method has existed in the full .Net framework for ages, but MSDN and Visual Studio are telling me otherwise.

Regardless, looking at the documentation for CreateHttp it appears to only support “http” or “https” requests, and returns an HttpWebRequest instead of a plain old WebRequest. To me, this seems like a convenience method: Microsoft’s developers realized the majority of the use of this class is for HTTP web requests, so why not eliminate some casting?

Unfortunately that’s not the case. Here is why it’s not the case. Given this code, should the output be the same?

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Net;
   5:  
   6: namespace SilverlightApplication3 {
   7:     public partial class MainPage : UserControl {
   8:         public MainPage() {
   9:             InitializeComponent();
  10:  
  11:             WebRequest requestViaCreate = WebRequest.Create( "http://www.google.com" );
  12:             WebRequest requestViaCreateHttp = WebRequest.CreateHttp( "http://www.google.com" );
  13:  
  14:             string message = "WebRequested created via {0} is of type {1} and was created by {2}.";
  15:  
  16:             this.CreateTextBlock.Text = string.Format(
  17:                 message,
  18:                 "Create",
  19:                 requestViaCreate.GetType().Name,
  20:                 requestViaCreate.CreatorInstance.GetType().Name );
  21:  
  22:             this.CreateHttpTextBlock.Text = string.Format(
  23:                 message,
  24:                 "CreateHttp",
  25:                 requestViaCreateHttp.GetType().Name,
  26:                 requestViaCreateHttp.CreatorInstance.GetType().Name );
  27:         }
  28:     }
  29: }

Ah, if only:

image

Turns out, when you use CreateHttp, it is created with the ClientHttp browser stack. For more information on the different network stacks, see here.

Why does this matter? Well, if you are using the default behavior of Silverlight (browser stack), rely on cookies of any sort (session, authentication, etc) and create a web request to your server manually that needs those cookies (again, for authentication, session, etc), any web requests created via CreateHttp will not pass your cookies to the server. Authentication won’t work, you won’t have the same session you do if you use RIA, WCF or WebRequest.Create.

This recently came up at one of my clients. The Silverlight application uses both Windows authentication and Forms authentication to log in (both handled from within the application itself with a backing website and Membership provider). The first step is to try to auto-login the user with a WebRequest.Create to the page that forces Windows authentication (the whole site can’t require Windows auth as we have to support external users as well). If that succeeds a FormsAuthentication ticket is passed back to the client in the form of a cookie. The problem here is, we’re using the browser HTTP stack (on purpose) but I was using CreateHttp to create the WebRequest (because it felt like a convenience method). By using CreateHttp, none of my cookies created inside the Windows authentication page were being used by my WCF or RIA service calls (that were using the BrowserHttp stack). Simply switching to the Create method shared all my cookies across WCF, RIA and WebRequests.


Jan 092011

Prior to Silverlight 4 implicit styles (styles that weren’t given a key) didn’t exist and all styles had to be manually applied. Naturally when I found out they were added in Silverlight 4 I was very happy, until I realized this: implicit styles defined outside of a ControlTemplate or DataTemplate won’t always apply to said template.

Given this XAML, what should it look like?

   1:  <UserControl x:Class="DataTemplatesImplicitStyles.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      mc:Ignorable="d"
   7:      d:DesignHeight="300" d:DesignWidth="400">
   8:      <UserControl.Resources>
   9:          <Style TargetType="TextBlock">
  10:              <Setter Property="Foreground" Value="Red"/>
  11:          </Style>
  12:          <Style TargetType="TextBlock" x:Key="RedText">
  13:              <Setter Property="Foreground" Value="Red"/>
  14:          </Style>
  15:      </UserControl.Resources>
  16:      <Grid x:Name="LayoutRoot" Background="White">
  17:          <TextBlock Text="My Red Text"></TextBlock>
  18:      </Grid>
  19:  </UserControl>

If you guess it should be the words “My Red Text” with a red foreground, you would be correct.

image

Now, what if I have a custom button template that uses a TextBlock?

   1:  <UserControl x:Class="DataTemplatesImplicitStyles.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      mc:Ignorable="d"
   7:      d:DesignHeight="300" d:DesignWidth="400">
   8:      <UserControl.Resources>
   9:          <Style TargetType="TextBlock">
  10:              <Setter Property="Foreground" Value="Red"/>
  11:          </Style>
  12:          <Style TargetType="TextBlock" x:Key="RedText">
  13:              <Setter Property="Foreground" Value="Red"/>
  14:          </Style>
  15:      </UserControl.Resources>
  16:      <Grid x:Name="LayoutRoot" Background="White">
  17:          <Button>
  18:              <Button.Template>
  19:                  <ControlTemplate TargetType="Button">
  20:                      <TextBlock Text="My Button Text"></TextBlock>
  21:                  </ControlTemplate>
  22:              </Button.Template>
  23:          </Button>
  24:      </Grid>
  25:  </UserControl>

If you guessed it was red text, you’d be wrong.

image

Okay, so that’s a custom button template, but what about a DataTemplate, like used in an ItemsControl?

   1:  <UserControl x:Class="DataTemplatesImplicitStyles.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      mc:Ignorable="d"
   7:      d:DesignHeight="300" d:DesignWidth="400">
   8:      <UserControl.Resources>
   9:          <Style TargetType="TextBlock">
  10:              <Setter Property="Foreground" Value="Red"/>
  11:          </Style>
  12:          <Style TargetType="TextBlock" x:Key="RedText">
  13:              <Setter Property="Foreground" Value="Red"/>
  14:          </Style>
  15:      </UserControl.Resources>
  16:      <Grid x:Name="LayoutRoot" Background="White">
  17:          <ItemsControl ItemsSource="randomstring">
  18:              <ItemsControl.ItemTemplate>
  19:                  <DataTemplate>
  20:                      <StackPanel Orientation="Horizontal">
  21:                          <TextBlock Text="Implicit: " Margin="4"></TextBlock>
  22:                          <TextBlock Text="{Binding}" Margin="4"></TextBlock>
  23:   
  24:                          <TextBlock Text="Explicit: " Margin="4"></TextBlock>
  25:                          <TextBlock Text="{Binding}" Margin="4" Style="{StaticResource RedText}"></TextBlock>
  26:                      </StackPanel>
  27:                  </DataTemplate>
  28:              </ItemsControl.ItemTemplate>
  29:          </ItemsControl>
  30:      </Grid>
  31:  </UserControl>

Can you guess what happens here? In it we’re using multiple TextBlocks and also using an explicitly style (one that has a key defined). What will the above output look like?

image

All TextBlocks without a Style defined have default styling. If the implicit styling was working correctly, every bit of text above would be red (because they’re all TextBlocks).

Okay, all we’ve tested so far are TextBlock controls. What about a Button, will it not work there as well?

   1:  <UserControl x:Class="DataTemplatesImplicitStyles.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      mc:Ignorable="d"
   7:      d:DesignHeight="300" d:DesignWidth="400">
   8:      <UserControl.Resources>
   9:          <Style TargetType="Button">
  10:              <Setter Property="Foreground" Value="Red"></Setter>
  11:          </Style>
  12:      </UserControl.Resources>
  13:      <Grid x:Name="LayoutRoot" Background="White">
  14:          <ItemsControl ItemsSource="randomstring">
  15:              <ItemsControl.ItemTemplate>
  16:                  <DataTemplate>
  17:                      <Button Content="{Binding}"></Button>
  18:                  </DataTemplate>
  19:              </ItemsControl.ItemTemplate>
  20:          </ItemsControl>
  21:      </Grid>
  22:  </UserControl>

Using our past experience, we can guess that the implicit style won’t work here either.

image

It works for Button but not TextBlock? What is going on here? A template is considered an encapsulation boundary in Silverlight. When looking up a resource, anything that is not derived from Control will stop at this boundary when looking up implicit styles. Anything that is Control-derived will continue above the template boundary while looking up implicit resources.

This has been commented on by Microsoft, although the original Connect bug it was commented on seems to have disappeared, nor can I find anything related to this in documentation. The issue I take with this notion of encapsulation for implicit styles is that it doesn’t encapsulate against explicit styles. From a template if I have an explicitly defined style, I’m not “protected” from those styles like I am with implicit styles.

So, what do you do about it? Define all implicit styles inside your template.

   1:  <UserControl x:Class="DataTemplatesImplicitStyles.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      mc:Ignorable="d"
   7:      d:DesignHeight="300" d:DesignWidth="400">
   8:      <UserControl.Resources>
   9:          <Style TargetType="TextBlock" x:Key="RedText">
  10:              <Setter Property="Foreground" Value="Red"/>
  11:          </Style>
  12:      </UserControl.Resources>
  13:      <Grid x:Name="LayoutRoot" Background="White">
  14:          <ItemsControl ItemsSource="randomstring">
  15:              <ItemsControl.ItemTemplate>
  16:                  <DataTemplate>
  17:                      <StackPanel Orientation="Horizontal">
  18:                          <StackPanel.Resources>
  19:                              <Style TargetType="TextBlock" BasedOn="{StaticResource RedText}"></Style>
  20:                          </StackPanel.Resources>
  21:                          <TextBlock Text="Implicit: " Margin="4"></TextBlock>
  22:                          <TextBlock Text="{Binding}" Margin="4"></TextBlock>
  23:   
  24:                          <TextBlock Text="Explicit: " Margin="4"></TextBlock>
  25:                          <TextBlock Text="{Binding}" Margin="4" Style="{StaticResource RedText}"></TextBlock>
  26:                      </StackPanel>
  27:                  </DataTemplate>
  28:              </ItemsControl.ItemTemplate>
  29:          </ItemsControl>
  30:      </Grid>
  31:  </UserControl>

And like expected, all text shows up red.

image


 Next >>