Monthly Archives: September 2010

RelativeSource Binding with FindAncestor mode in Silverlight

Silverlight 3 introduced the RelativeSource Binding that is well known from WPF. Unfortunately Silverlight only supports the two modes Self and TemplatedParent.

image 

I don’t know why the FindAncestor mode is missing in Silverlight’s RelativeSource binding, but there is a real need for that in Silverlight too. I’m finding me quite often in a dead-end ifI want to set a binding to a command or value in the view model within a list box item template. The problem in the example below is that the list box item has its own data context set to an item in the items-collection.

image

In the case above I really need a relative binding to set a binding to the RemoveCommand and to the global Picklist collection as comboxbox source. On solution is to create a bidirectional relationship between the view models behind the scene in order to get a reference to the parent view model within the child view model. But this not the solution I was looking for.

After some googling I found a binding helper written by Colin Eberhardt which enables it to use a relative binding with help of an attached dependency property.

clip_image001

Explained in brief: Colin uses an attached property to set his own relative source binding configuration for an specific element. When the attached property becomes attached to the target element it adds a handler for the elements loaded event. Within the event handler, he walks up the visual tree to find the specified ancestor and constructs a binding expression between the source and target properties.

Based on Colin’s idea I’ve created a similar implementation which provides some different behaviors:

  • Support for a list of relative binding. This allows binding more than one property of an element.
  • It does not use a relay object in the middle of the binding. It is a pure binding without any custom code in between. This gain in a better performance, especially when using a storyboard to animate the data bound value.
  • I have added some special type of relative binding mode which is called as ParentDataContext mode. In that mode it walks up the visual tree until it finds a new data context. This is very helpful when having templates in an ItemsControl, ListBox or DataGrid.
  • Support for non-dependency property as source, e.g. direct usage of view model properties.
  • Support for property path syntax for the source property, e.g. DataContext.Person.Name
  • The AncestorType allows to set a base class as type criteria instead of the concrete type, e.g. if the concrete element is of type Grid, then the AncestorType can be set to Panel.
  • Allows to set the relative binding for attached properties too, e.g. to bind TooltipService.Tooltip property.
  • Allows to use OneWay or OneTime binding mode.
  • Allows to set any other binding parameter such as ValidatesOnNotifyDataErrors, ValidatesOnExceptions, ValidatesOnDataErrors, NotifyOnValidationError and ConverterCulture.
  • Enables to set the XAML namespace for controls which are not in the core-control assembly.

How to use

Let’s have a look at some examples.

1. Simply relative binding with ancestor type

<ComboBox>
    <local:BindingHelper.Binding>
            <local:RelativeSourceBinding Path="DataContext.Picklist"
                    TargetProperty="ItemsSource" RelativeMode="FindAncestor"
                    AncestorType="UserControl" />
    </local:BindingHelper.Binding>
</ComboBox>
 

 

2. Bind two or more properties by adding a list of binding definitions

<ComboBox>
    <local:BindingHelper.Binding>
        <local:BindingList>
            <local:RelativeSourceBinding Path="DataContext.Picklist"
                    TargetProperty="ItemsSource" RelativeMode="FindAncestor"
                    AncestorType="UserControl" />
            <local:RelativeSourceBinding Path="DataContext.Tooltip"
                    TargetProperty="(ToolTipService.ToolTip)" RelativeMode="FindAncestor"
                    AncestorType="UserControl" />
        </local:BindingList>
    </local:BindingHelper.Binding>
</ComboBox>

 

 

3. Bind any attached dependency property

<local:RelativeSourceBinding Path="DataContext.Tooltip"
        TargetProperty="(ToolTipService.ToolTip)" RelativeMode="FindAncestor"
        AncestorType="UserControl" />

 

 

4. Bind an attached dependency property from any assembly. Just specify the XAML namespace as in the example below

<local:RelativeSourceBinding Path="DataContext.Tooltip"
        TargetProperty="(DemoAttachedElement.Value)"
        TargetNamespace="clr-namespace:RelativeSourceBindingDemo;assembly=RelativeSourceBindingDemo"
        RelativeMode="FindAncestor" AncestorType="UserControl" />

 

 

5. Use the parent data context instead an ancestor type

<local:RelativeSourceBinding Path="RemoveCommand" TargetProperty="Command"
        RelativeMode="ParentDataContext" />

 

 

6. The mode ParentDataContext is set as default behavior. So in most cases you can just write xaml as following:

<Button Content="Remove" CommandParameter="{Binding}">
    <local:BindingHelper.Binding>
        <local:RelativeSourceBinding Path="RemoveCommand" TargetProperty="Command" />
    </local:BindingHelper.Binding>
</Button>

<ComboBox Grid.Column="1">
    <local:BindingHelper.Binding>
        <local:BindingList>
            <local:RelativeSourceBinding Path="Picklist" TargetProperty="ItemsSource"/>
            <local:RelativeSourceBinding Path="Tooltip" TargetProperty="(ToolTipService.ToolTip)" />
        </local:BindingList>
    </local:BindingHelper.Binding>
</ComboBox>

 

 

7. Use a converter and other binding settings

<local:RelativeSourceBinding Path="Application.IsEnabled"
        TargetProperty="Visibility" Converter="{StaticResource VisibilityConverter}"
        ValidatesOnNotifyDataErrors="True" ValidatesOnExceptions="True"
        ValidatesOnDataErrors="True" NotifyOnValidationError="True" />

 

Source Code & Demo Project

Here you can find the full source code within a demo project.

Advertisements
Tagged ,

Type.GetType implementation with help of XamlReader

The Type.GetType method is different in Silverlight than in the standard .NET runtime. In Silverlight we must provide the fully qualified assembly name to get a type from an assembly. Only built in controls such as Button, Grid, ListBox, etc. or types in the executing assembly are excluded from this rule. Fully qualified assembly name means you must provide the version, culture and public key token. That means that the following GetType usage works well, until the target assembly version changes from 1.0 to 1.1.

Type.GetType("MyComponent.MyType, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4bec85d7bec6698f")
 

And do we really want to have such Xaml or code behind files?

<local:MyCustomElement Grid.Row="2" Grid.Column="1"
   ItemType="MyComponent.MyType, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4bec85d7bec6698f" >
</local:MyCustomElement>

 

Iterate over Assembly Parts

One option to load a type just by its type-name and assembly-name is to iterate over all loaded assemblies. The method AppDomain.CurrentDomain.GetAssemblies() does not exist in Silverlight, but we can iterate over all assembly parts that are included in the deployment, e.g. all assemblies that are in the XAP file. I’ve found a blog post from Malcolm Jack in which he describes how to iterate over the parts to find a type by its name.

An this is how it works in brief: loop through each assembly part, getting the assembly, then use assembly.GetType to load the type.

This is the code this short explanation:

public static Type GetAssemblyType(string assemblyName, string className)
{
    StreamResourceInfo info = Application.GetResourceStream(new Uri(assemblyName, UriKind.Relative));
    Assembly assembly = new AssemblyPart().Load(info.Stream);
    Type type = assembly.GetType(className);
    return type;
}

public static Type GetAssemblyType(string className)
{
    Type type = null;
    foreach (AssemblyPart part in Deployment.Current.Parts)
    {
        type = GetAssemblyType(part.Source, className);
        if (type != null)
            break;
    }
    return type;
}

 

Unfortunately this solution has two drawbacks:

1. Dynamic loaded assemblies downloaded with help of a web client instance are not part of the deployment, e.g. the code cannot find types in those assemblies. You can add an additional part to the collection after each downloaded dll, but you do not have a guarantee this will be done.

AssemblyPart part = new AssemblyPart();
Assembly assm = part.Load(e.Result);
Deployment.Current.Parts.Add(part);

 

2. The second issue is when using application library caching to reduce the XAP size.

clip_image001

If this option is enabled, it will change the application manifest and the XAP no longer includes those assemblies which do support assembly caching. See Tim Heuer’s posts to get more detail how it works.

The cached assemblies are now declared as external parts as you can see in the manifest file (AppManifest.xml):

<Deployment ... >
  <Deployment.Parts>
     ...
  </Deployment.Parts>
  <Deployment.ExternalParts>
    <ExtensionPart Source="System.Windows.Controls.Input.Toolkit.zip" />
    <ExtensionPart Source="System.Xml.Linq.zip" />
    <ExtensionPart Source="System.Windows.Controls.zip" />
    <ExtensionPart Source="System.Windows.Controls.Toolkit.zip" />
  </Deployment.ExternalParts>
</Deployment>

 

In the end when the application starts those external assemblies will still be downloaded before the main form is added to the visual tree.

clip_image002

Iterating over all external parts is not that easy as iterating over all assembly parts, because it needs to download the source again and this is an asynchron operation.

 

The Solution: Using the XamlReader

I’ve found a solution by using the XamlReader to resolve types during parsing a xaml string. Unfortunately there is no StringToTypeConverter in Silverlight and the Xaml parser uses some hard coded logic to resolve System.Type properties only for certain type like Style and ControlTemplate. However, we can use a Style or ControlTemplate class and use the TargetType property to retrieve the type information. And these lines of code show how it works:

// create xaml with a simply Style element and set the TargetType property with the provided type name
string xaml = "<Style xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ";

// set the xaml namesapce if provided
if (!string.IsNullOrWhiteSpace(xamlNamespace))
{
    xaml += string.Format("xmlns:tmp='{0}' TargetType='tmp:{1}' />", xamlNamespace, className);
}
else
{
    // Core controls such as Button, Grid, ListBox, etc do not need a namespace
    xaml += string.Format("TargetType='{0}' />", className);
}

// let the XamlParser load the type via the TargetType property 
Style style = XamlReader.Load(xaml) as Style;

if (style != null)
{
    Type targetType = style.TargetType;
    return targetType;
}

 

Xaml parsing is not the fastest way to get a type. Therefore I encapsulated the functionality into a small class which maintains a cache for types which are already parsed.

The class provides four different ways to get a type:

// for types in the executing assembly or types in the core-control 
// assembly such as Button, Grid, etc. Just provide the type name.
TypeLoader.GetType("Grid"); 

// The type name with its xaml namespace
TypeLoader.GetType("Grid", "clr-namespace:System.Windows.Controls;assembly=System.Windows");

// Type name, namespace and assembly name as separate parameters
TypeLoader.GetType("Grid", "System.Windows.Controls", "System.Windows");

// Or with the assembly qualified type name, without version, culture and public key token
TypeLoader.GetType("System.Windows.Controls.Grid, System.Windows");

 

Here are some other examples:

// *** toolkit controls ***
// returns null, because NumericUpDown is in toolkit and not in core control assembly
//TypeLoader.GetType("NumericUpDown"); 
TypeLoader.GetType("NumericUpDown", 
               "clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit");
TypeLoader.GetType("NumericUpDown", 
               "System.Windows.Controls", "System.Windows.Controls.Input.Toolkit");
TypeLoader.GetType("System.Windows.Controls.NumericUpDown, System.Windows.Controls.Input.Toolkit");

// *** main application ***
// returns null, because MainPage is not in core-control assembly
//Type mainPage1 = TypeLoader.GetType("MainPage"); 
TypeLoader.GetType("MainPage", "clr-namespace:TypeLoaderDemo;assembly=TypeLoaderDemo");
TypeLoader.GetType("MainPage", "TypeLoaderDemo", "TypeLoaderDemo");
TypeLoader.GetType("TypeLoaderDemo.MainPage, TypeLoaderDemo");

// *** 3rd party libs ***
TypeLoader.GetType("MyButton", "clr-namespace:TypeLoaderControlLib;assembly=TypeLoaderControlLib");
TypeLoader.GetType("MyButton", "TypeLoaderControlLib", "TypeLoaderControlLib");
TypeLoader.GetType("TypeLoaderControlLib.MyButton,TypeLoaderControlLib");
 

Demo Project and Source Code

 

Here you can find the TypeLoader implementation or a full demo project including TypeLoader source, examples, assembly parts iteration and dynamic assembly loading.

 
Tagged