Friday, November 28, 2008

Setting up Theme-Specific Resource Dictionaries

Configuring theme-specific resource dictionaries is a common stumbling block. Follow these rules to ensure they are set up correctly:

  • 1. In the AssemblyInfo for your assembly, set the ThemeInfo attribute. This will specify where the theme-specific and generic resource dictionaries for the styleable resources referenced and defined in the assembly can be found.
  • [assembly: ThemeInfo(
        ResourceDictionaryLocation.SourceAssembly, 
        // Where theme-specific resource dictionaries are located.
        // (Used if a resource is not found in the page, 
        // or application resource dictionaries.)
     
        ResourceDictionaryLocation.SourceAssembly 
        // Where the generic resource dictionary is located.
        // (Used if a resource is not found in the page, 
        // app, or any theme specific resource dictionaries.)
    )]
    
  • 2. Put your theme-specific resource dictionaries in the Themes folder of the relevant assembly so they will be automatically picked up (do not use LoadComponent). Also add a generic resource dictionary with the name generic.xaml.



  • 3. Any control-specific theme-specific resource dictionaries should also be added to the Themes folder. The dictionary should then be merged into a main resource dictionary like this:
  • <!-- Aero.NormalColor.xaml: -->
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary
            Source="/MyAssemblyName;component/Themes/MyControl.Aero.NormalColor.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    
  • 4. Do the same with any control-specific generic resource dictionaries:
  • <!-- generic.xaml: -->
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary
            Source="/MyAssemblyName;component/Themes/UnthemedControl.generic.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    
  • 5. Any resources defined in your assembly required to pick up styles from the theme-specific or generic resource dictionaries should override the default style key in their static constructor. You should also do this if you want to override the default style of the base class of the resource.
  • // Register the default style for MyControl.
    DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), 
        new FrameworkPropertyMetadata(typeof(MyControl)));
    
  • 6. If you want to set the style of a control not defined in your assembly, you will need to explicitly load the resource dictionary:
  • ResourceDictionary dict = Application.LoadComponent(
        new Uri("MyAssemblyName;component/Themes/NewDefaultStyle.xaml", UriKind.Relative))
        as ResourceDictionary;
    
    Application.Current.Resources.MergedDictionaries.Add(dict);
    
    
    ...and that's it! That should cover everything you need to do to get your resources picking up their styles from the correct place.

    Download Full Source Code



    Thursday, November 27, 2008

    Notification when items are added or removed from an ItemsControl
    (ListBox, TabControl, TreeView etc.)

    It's not obvious how to listen to changes to the Items of an ItemsControl. This post outlines various problems and identifies a simple way to listen to this collection regardless of how the ItemsControl was populated.

    In general, there are two ways to add items to an ItemsControl:
    • Manually add to the ItemCollection of the control. This is accessed using the Items property of the control.
    • Assign a notifying source to the ItemsSource of the control. This will update the Items of the control automatically as the collection changes.

    If you are adding items manually to your ItemsControl, then listening to changes isn't such a big deal since you can just call a method after you have added an item. If you are using an ItemsSource, however, then listening to changes is often required for UI tasks such as bringing a new item into view.

    The standard way of listening to a notifying source is by using a CollectionViewSource. If you were to attempt to use it to listen to the items of a ListBox you would do something like this:
    <Window.Resources>
        <CollectionViewSource x:Key="cvs" x:Name="cvs" Source="{Binding}"/>
    </Window.Resources>
           
    <ListBox x:Name="mListBox" ItemsSource="{Binding}"/>
    
    public MyWindow()
    {
        InitializeComponent();
        
        CollectionViewSource cvs = (CollectionViewSource)FindResource("cvs");
     cvs.View.CollectionChanged += View_CollectionChanged;
    }
    
    private void View_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            // scroll the new item into view
            mListBox.ScrollIntoView(e.NewItems[0]);
        }   
    }
      
    
    To me, this is quite an ugly solution since it isn't resistant to a change of ItemsSource on the ListBox. At first, it seems this could be fixed by binding the Source of the CollectionViewSource directly to the ListBox:
    <Window.Resources>
        <CollectionViewSource x:Key="cvs" x:Name="cvs" 
            Source="{Binding ElementName=mListBox, Path=ItemsSource}"/>
    </Window.Resources>
           
    <ListBox x:Name="mListBox" ItemsSource="{Binding}"/>
    

    But this doesn't help. Since the View property of the CollectionViewSource has no update mechanism (it isn't a dependency property), there's no way of knowing when it changes.

    A different approach is required...

    The first thing most developers look for when attacking this problem is an event handler on Items. But, when you look at the members generated by Intellisense you find...



    ...that there's no CollectionChanged event handler here, so how do you listen to changes in the collection?

    The problem is that the INotifyCollectionChanged interface which contains the event handler is explicitly implemented, which means you have to first cast the ItemCollection before the event handler can be used:
    public MyWindow()
    {
        InitializeComponent();
        
        ((INotifyCollectionChanged)mListBox.Items).CollectionChanged +=
            mListBox_CollectionChanged;
    }
    
    private void mListBox_CollectionChanged(object sender, 
        NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            // scroll the new item into view
            mListBox.ScrollIntoView(e.NewItems[0]);
        }    
    }  
    

    Ta da! This is much better - it's simple and robust.

    So, in conclusion, this is the best way to listen to changes to the Items of an ItemsControl. There's no messing about with CollectionViewSource and worrying about updating when a new ItemsSource is assigned...it works regardless of whether ItemsSource is being used and is resistant to changes in ItemsSource.



    Wednesday, November 26, 2008

    Understand Bubbling and Tunnelling in 5 minutes

    Here's a quick and visual way to understand Routed Events and their bubbling and tunnelling nature.
    • 1. Download and run Snoop. It is a fantastic utility for debugging WPF applications.
    • 2. Run a WPF application, and Snoop it using Snoop's binoculars button. I'm using Expression Blend in this example.
    • 3. Snoop can display the visual tree of your application at run time. Move your cursor over a visual element in your application and hold down the Shift and Control keys. The element will now have a red border and Snoop will pop up the Visual Tree for your application with the element selected like this:



    • 4. Click on the Events tab at the top of the right hand pane. This tab contains a list which records all routed events that hit the selected element.
    • 5. Open the combo box at the top of the right hand pane. Here you can check the Routed Events that you are interested here.


    • 6. In the list, uncheck everything except MouseDown and PreviewMouseDown. These are the routed events that we will look at.
    • 7. Clear the list of recorded routed events by clicking the X button to the right of the combo box at the top of the right hand pane.
    • 8. Go back to the selected element in your app and click on it. Notice that this will have been recorded in Snoop. If an entry is shown in green, then it has been handled.


    • 9. Now expand the PreviewMouseDown entry in the right hand pane and look at the list the elements which are shown. It will be something like this:
    • 10. Here's the interesting bit. Click down the list one-by-one starting at the top. As you do this, notice that the selection in the Visual Tree in the left hand pane starts at the top and moves downwards. This is because PreviewMouseDown is a Tunnelling routed event, that is, it is first received by the top-most parent element and then goes down the visual tree one by one until it reaches the bottom element. If any of the elements handle the event and set e.Handled = true then the event stops being routed there.
    • 11. Now expand the MouseDown entry and look at the list of elements shown.
    • 12. Since MouseDown is a Bubbling routed event, it is first received by the bottom element and then bubbles up the visual tree one by one until it reaches the top element. If any of the elements handle the event and set e.Handled = true then the event stops being routed there.

    So there you have it, Snoop is a great tool for debugging WPF applications and an easy way to observe and unserstand what is going with your routed events.

    For more detailed information about the motivation for the design of routed events, visit the MSDN page for routed events here.



    Friday, November 21, 2008

    How to set bindings on CLR Properties using DataResource

    It can be pretty annoying that bindings are only allowed on dependency properties of elements that are part of an element tree. For example, in all of these scenarios binding isn't allowed:
    • on an InputBinding's CommandParameter (not a dependency property)
    • on an object in an element's Resources if that object doesn't derive from Freezable (not part of an element tree)
    • on an element set to a FrameworkElement’s Tag property (not part of an element tree)
    • on a ValidationRule (not a dependency object)


    This post introduces a new technique allowing you to add bindings to CLR properties and is a perfectly good workaround for all the scenarios above. it's good because it:
    • helps you use binding in many more scenarios :)
    • is very easy to use: just drop one source file (DataResource.cs) into your app and use it like this:
      
      <Window.Resources>
          
          <my:DataResource x:Key="actualWidth" BindingTarget="{Binding ActualWidth}"/>
      </Window.Resources>
      
      <my:MyControl>
          <my:MyControl.MyClrProperty>
              <my:DataResourceBinding DataResource="{StaticResource actualWidth}"/>
          </my:MyControl.MyClrProperty>
      </my:MyControl>
      
    It works by using:
    • Mike Hillberg's technique of using Freezable to gain access to the DataContext of the host element
    • a custom markup extension to retrieve the object that the DataResource is bound to and returns this as the host of the markup extension
    DataResource.cs
    Download sample app with source code