Dude, where’s my (default) style?

April 5th, 2009

This week, I added a WPF custom control to an existing project. I thought it was going to be pretty straight forward, but ran into a couple project configuration issues.

The project that contained my control was initially created with the Visual Studio Class Library template, and did not start life from one of the WPF project templates (this was important, as I found out later). The WPF project templates are WPF Application, WPF Browser Application, WPF User Control Library and WPF Custom Control Library.

A custom control does not have a XAML file, and finds it default style and control template from a generic resource dictionary. The generic resource dictionary is named generic.xaml and is expected to be in project folder named themes.

WheresMyStyle-properties

Inside generic.xaml, there is a ResourceDictionary, and a Style with a TargetType that is the type of the custom control:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ClassLibrary" >
  <Style TargetType="{x:Type local:CustomControl1}">
    :
    :
  </Style>
</ResourceDictionary>
  

Additionally, the DefaultStyleKey property should also be set to the type of the custom control:

static CustomControl1()
{
  DefaultStyleKeyProperty.OverrideMetadata(
    typeof (CustomControl1), new FrameworkPropertyMetadata
      (typeof (CustomControl1)));
}

This seems a strange way to set a dependency property, but is the recommended way to set the DefaultStyleKey:

This property is typically not set through any of its direct property accessors. Instead, you override the type-specific metadata of this dependency property every time you create a new FrameworkElement derived class. When you derive a control, call the OverrideMetadata method against the DefaultStyleKeyProperty identifier, within the static constructor of the control derived class

With everything all setup (or so I thought), I launched the UI harness to verify my work. To my surprise, the control came up blank – the default style (and control template) was not found.

After digging around, and looking at the code generated by the WPF Custom Control Library project template, I stumbled upon the ThemeInfo assembly attribute.  The WPF project templates insert a ThemeInfo assembly attribute into the AssemblyInfo.cs file.

[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, //where theme specific
    // resource dictionaries are located 
    ResourceDictionaryLocation.SourceAssembly //where the
    // generic resource dictionary is located 
)]

The ThemeInfo constructor takes two ResourceDictionaryLocation values. The first value specifies where to look for theme specific resource dictionaries. The second parameter specifies where to look for the generic resource dictionary.

Once I added the ThemeInfo assembly attribute to my project, my custom control appeared as expected.

You can read more about WPF custom controls in WPF in Action chapter 13.

Ready? Set… Are you unset?

April 1st, 2009

When displaying a collection of domain objects with WPF, there are times when an ImageSource is bound to a domain object’s state.

ReadSetUnset

This list of devices was generated with the following XAML:

<ListBox x:Name="deviceList"
    HorizontalContentAlignment="Stretch" >
  <ListBox.ItemTemplate ItemsSource="{Binding Devices}">
    <DataTemplate>
      <Border Margin="5" BorderBrush="DarkBlue"
          BorderThickness="3" CornerRadius="10"
          Padding="5" HorizontalAlignment="Stretch">
        <StackPanel Orientation="Horizontal">
          <Image Source="{Binding ImagePath}"
            Width="32" Height="32"/>
          <StackPanel Margin="3">
            <TextBlock Text="{Binding DeviceName}" />
            <TextBlock Text="{Binding DeviceType}" />
          </StackPanel>
        </StackPanel>
      </Border>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Unfortunately, when the code runs in the debugger, a nice first chance exception message appears in the Debug Output window.

A first chance exception of type ‘System.NotSupportedException’ occurred in System.dll

System.Windows.Data Error: 22 : Cannot convert ‘<null>’ from type ‘<null>’ to type ‘System.Windows.Media.ImageSource’ for ‘en-US’ culture with default conversions; consider using Converter property of Binding. NotSupportedException:’System.NotSupportedException: ImageSourceConverter cannot convert from (null).

 

The culprit is the TouchPad device – it returns null for the ImagePath property, and the ImageSourceConverter (called by the binding engine) throws an exception when trying to convert a null value.

To avoid/prevent the exception we need to convert the null into a value that is not a value, but is not null. Enter DependencyProperty.UnsetValue:

UnsetValue is a sentinel value that is used for scenarios where the WPF property system is unable to determine a requested DependencyProperty value. UnsetValue is used rather than a null reference, because a null reference could be a valid property value, as well as a valid (and frequently used) DefaultValue.

The binding engine expects IValueConverter.Convert to return DependencyProperty.UnsetValue:

A return value of DependencyProperty.UnsetValue indicates that the converter produced no value and that the binding uses the FallbackValue, if available, or the default value instead.

A return value of Binding.DoNothing indicates that the binding does not transfer the value or use the FallbackValue or default value.

Building a NullToUnsetConverter is very simple:

 

public class NullToUnsetConverter : IValueConverter
{
  public object Convert(object value, Type targetType,
      object parameter, CultureInfo culture)
  {
    return value ?? DependencyProperty.UnsetValue;
  }

  public object ConvertBack(object value, Type targetType,
      object parameter, CultureInfo culture)
  {
    throw new NotImplementedException();
  }
}

and so is using it in the binding expression:

 

<Window.Resources>
    <local:NullToUnsetConverter x:Key="unsetConverter" />
</Window.Resources>
  :
  :
<Image Source="{Binding ImagePath, Converter={StaticResource unsetConverter}}"
    Width="32" Height="32"/>

        

 

No more first chance exceptions!

Note: the png files used in the sample came from the Tango Icon Library.

Can you see it?

March 29th, 2009

WPF and Silverlight both have a set of controls that are used to display content and collections of content. The cool thing about these controls, is that they can display any form of content, not just other UI controls.

In the samples below, the domain model contains a Person class, and 2 derived classes of Teacher and Student. The People collection has a mix of Person, Teacher and Student instances.

To add content directly to a control:

<ItemsControl Grid.Row="1" ItemsSource="{Binding People}" />

<ContentControl Content="{Binding People[0]}" />

CanYouSeeIt-raw

By default (for non-visual objects), content is rendered  by displaying the text returned by the ToString() method. Controls are not omnipotent and do not magically know how to display the Person objects - they need instructions in the form of DataTemplates.

From MSDN:

You use a DataTemplate to specify the visualization of your data objects. DataTemplate objects are particularly useful when you are binding an ItemsControl such as a ListBox to an entire collection. Without specific instructions, a ListBox displays the string representation of the objects in a collection. In that case, you can use a DataTemplate to define the appearance of your data objects. The content of your DataTemplate becomes the visual structure of your data objects.

Explicit DataTemplates

DataTemplates are usually defined in a resource, but can also be created directly in the control definition. The following XAML snippet defines a DataTemplate resource:

 

<DataTemplate x:Key="personTemplate">
  <Grid Background="Azure" Margin="5">
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>
    <TextBlock Text="First Name:" Grid.Row="0" Grid.Column="0" />
    <TextBlock Text="{Binding FirstName}" Grid.Row="0" Grid.Column="1" />
    <TextBlock Text="Last Name:" Grid.Row="1" Grid.Column="0" />
    <TextBlock Text="{Binding LastName}" Grid.Row="1" Grid.Column="1" />
  </Grid>
</DataTemplate>

You then explicitly instruct the controls to use the template by setting the DataTemplate property:

 

<ItemsControl Grid.Row="1" ItemsSource="{Binding People}"
      ItemTemplate="{StaticResource personTemplate}" />
  :
  :
<ContentControl Content="{Binding People[0]}"
      ContentTemplate="{StaticResource personTemplate}" />

Explicitly setting the DataTemplate works great if your items are homogenous. But what if your working with heterogeneous data (such as mix of Person, Student, and Teacher objects)?

Implicit DataTemplates

WPF controls can automatically determine which DataTemplate should be applied to a control based on the type of content.  Unfortunately, this feature is not implemented in Silverlight. 

To enable this automatic behavior, the DataTemplate resource must be declared with the DataType attribute instead of the Key attribute:

 

<DataTemplate DataType="{x:Type local:Person}">

The controls automatically select a DataTemplate, so remove the explicit declarations:

<ItemsControl Grid.Row="1" ItemsSource="{Binding People}" />
  :
  :
<ContentControl Content="{Binding People[0]}" />

This sample application is using a different DataTemplate for Person, Student, and Teacher objects:

CanYouSeeIt-templated

 DataTemplate Properties

Here are some of the WPF and Silverlight controls that have DataTemplate properties:

Control Derived Controls DataTemplate property
ContentControl Button, Label, ToolTip ContentTemplate
ItemsControl ListBox ItemTemplate
TabControl   ContentTemplate
HeaderedContentControl TabItem 

Expander 

GroupBox

HeaderTemplate (and ContentTemplate)
HeaderedItemsControl MenuItem 

ToolBar 

TreeViewItem

HeaderTemplate (and ItemTemplate)
DataGrid   RowDetailsTemplate
DataGridTemplateColumn   CellTemplate 

CellEditingTemplate

GridView   ColumnHeaderTemplate

You can read more about DataTemplates on MSDN, in WPF in Action chapter 12 and in Silverlight in Action chapter 5.

Download solution - CanYouSeeIt sample applications

Knowing your own name

March 27th, 2009

Yesterday, I ran into a pretty nasty XAML parsing error.

“Unknown attribute Name on element…”

The error was thrown both when running the application and when trying to view the XAML with the designer.

What made this error nasty was that the error message had absolutely nothing to do with source of the error.

How could the x:Name attribute not be known? From MSDN:

The specified x:Name becomes the name of a field that is created in the underlying code when XAML is processed, and that field holds a reference to the object. In Silverlight, using the managed API, the process of creating this field is performed by the MSBuild target steps, which also are responsible for joining the partial classes for a XAML file and its code-behind.

I finally found a post from Matt42 in the Silverlight forums that allowed me to figure out the problem. Matt42 summed up the issue:

To generalize: I guess that exceptions which are thrown by the constructor of your custom control cause that problem. You might want to instantiate your control using the default constructor via code to check the detailed exception.

In my case, the control constructor was instantiating a helper class whose constructor threw an exception.

Interestingly, WPF displays a different message (”Could not create an instance of…”) which is a bit more helpful.

UnknownName

Expression Blend provides the same message as the WPF designer.

UnknownNameInBlend

You can download a solution that demonstrates this problem here. The solution contains both WPF and Silverlight projects.

Full XAML View

March 25th, 2009

By default, the WPF and Silverlight XAML editors in Visual Studio open up in a split XAML/Design view.

I rarely use the design view, especially when working on Silverlight as the Silverlight designer is read only. This means that when I open the file, there is a long delay while the designer spins up and parses the file. Finally, I have to manually switch to the full XAML view.

Really freaking annoying!

While listening to a .Net Rocks podcast today, I heard Brian Noyes talk about the “Always open documents in full XAML view” visual studio option.  This is an option I was unaware of, but am really glad to discover.

 MSDN documentation says:

Use this setting to control whether Design view appears when XAML documents are loaded.

Always open documents in full XAML view
Specifies whether XAML documents appear only in XAML view, without Design view. Useful for loading large documents.

To change the XAML editor options:

  1. From the Tools menu, select Options.
  2. In the Options dialog box, expand the Text Editor item.
  3. Expand XAML.

OpenFullXaml