Showing posts with label ResourceDictionary. Show all posts
Showing posts with label ResourceDictionary. Show all posts

Monday, January 11, 2010

Dynamic ControlTemplate in Silverlight

I have a Silverlight 3 application that uses a grid from DevExpress. I needed to extend the standard AgDataGridColumn so that i could show images, those images being representations of an enumeration.

To achieve this is simple enough - just override the ControlTemplate that is assigned to the DisplayTemplate property of the column. Doing this declaratively (in XAML) is pretty damn simple. But i wasn't doing it that way - as i am using a factory pattern for creating the columns from some agnostic descriptors, i needed to locate, load and assign the ControlTemplate programmatically.

The extended grid column sits in a second assembly. As i had created several templated controls in this controls assembly, the project templates had already created a ResourceDictionary called generic.xaml in the Themes folder, so i intended to use this file to define the ControlTemplate and then just load it in code, using a line of code similar to


ControlTemplate ct = Application.Current.Resources["MyImageColumnTemplate"] as ControlTemplate;

Unfortunately, this didn't work, Application.Current.Resources had no idea about my ControlTemplate. Then i realised that because it was defined in a ResourceDictionary, i needed to load that ResourceDictionary and merge it with the resources associated with the current application. To do that is pretty simple:

ResourceDictionary dict = new ResourceDictionary();
dict.Source = new Uri("path to resource dictionary", UriKind.Absolute);

Application.Current.Resources.MergedDictionaries.Add(dict);


But of course nothing is that simple, right? I found half a dozen blog or forum postings that indicated all i would have to use for my URI was this:

Uri uri = new Uri("pack://application:,,,/MySilverlightControls;component/themes/generic.xaml", UriKind.Absolute);

The scheme is correct, the assembly name is correct, the path is correct, what could go wrong? Well, first i received an error about there being no port number specified:


UriFormatException was unhandled by user code

Invalid URI: A port was expected because of there is a colon (':') present but the port could not be parsed.

A little bit more googlebinging showed me that i probably needed to register the pack scheme – i thought it would already be registered (especially as the Loaded event for several Silverlight components had been fired) but it wasn't. OK, another half step forward:

if (!UriParser.IsKnownScheme("pack"))

UriParser.Register(newGenericUriParser(GenericUriParserOptions.GenericAuthority), "pack", -1);


Once i dropped that bit of code in an appropriate place, that error went away. But then i started to be plagued with vague COM errors depending on how i tried to declare the URIs.


dict.Source = new Uri("/themes/generic.xaml", UriKind.Relative);


dict.Source = new Uri("./../../themes/generic.xaml", UriKind.Relative);


dict.Source = new Uri("../../themes/generic.xaml", UriKind.Relative);

all produced the error


Error HRESULT E_FAIL has been returned from a call to a COM component.


at MS.Internal.XcpImports.CheckHResult(UInt32 hr)


at MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper obj, DependencyProperty property, String s)


at etc, etc...



dict.Source = new Uri("pack://application:,,,/themes/generic.xaml", UriKind.Absolute);


dict.Source = new Uri("pack://application:,,,/MySilverlightControls;component/themes/generic.xaml", UriKind.Absolute);

both produced the error


Exception from HRESULT: 0x80072EE5

which effectively means "Invalid URI".

So i did what any engineer or scientist should do – remove every variable from the equation until you are left with just the single problematic item, and then try and isolate the problem. I created a new ResourceDictionary
(called Dictionary1.xaml) under the Themes folder, put the MyImageColumnTemplate XAML in there, and went back through my seven different ways of specifying the URI for the ResourceDictionary. Suddenly i had success! This ended up being the code that worked:

XAML:


<ControlTemplate x:Name="MyImageColumnTemplate" >

<Grid MaxHeight="20" MaxWidth="20">

<Grid.Resources>

<localGrid:EnumColumnImageConverter x:Key="ImageContentConverter"/>

</Grid.Resources>

<Image Source="{Binding EditValue, Converter={StaticResource ImageContentConverter}}" />

</Grid>

</ControlTemplate>


C#:

Uri uri = new Uri("/MySilverlightControls;component/themes/Dictionary1.xaml", UriKind.Relative);

ResourceDictionary dict = new ResourceDictionary();

dict.Source = uri;

Application.Current.Resources.MergedDictionaries.Add(dict);

ControlTemplate ct = (ControlTemplate)Application.Current.Resources["MyImageColumnTemplate"];

this.DisplayTemplate = ct;

That URI also worked when i pointed it at generic.xaml. All the other attempted URIs look correct, but none of them would work. Now i had a custom image column that used my custom ControlTemplate, which in turn used my Converter to translate an enumeration into the appropriate image (that image was also extracted from a resource file.... but i had no issue with that!).


Wow, this post was a bit long but i tried my best to keep it simple, and i hope it helps someone out there.