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.