What's the best way to enable dynamic skinning of a WPF Application when some items requiring skin modification do not support values of type DynamicResourceExtention? In particular, our problem is that ConverterParameters require StaticResourceExtentions.
Here's our situation with ConverterParameters Using Visual Studio 2008 and WPF 3.5.
We have a custom converter which takes a value and a parameter and simply returns their product. Very simple, works fine, and we use it for various tasks, including setting some window element sizes. For example, passing a value of "Source={x:Static SystemParameters.PrimaryScreenHeight}" and a parameter of "0.1" enables us to set an element's height to exactly 1/10 of the screen height.
Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight},
Converter={StaticResource PctConverter},
ConverterParameter=0.1}"
where PctConverter is a resource reference to our custom converter. No problem there.
Now we want to skin the application dynamically, by extracting the ConverterParameter and putting it in a seperate resource. For example, we might want the element height to be 0.1 of the screen height in some skins, and say 0.25 of the screen height in others. Initially we thought we'd simply set the ConverterParameter to a DynamicResource, but this is not supported, so we have to set it using a StaticResourceExtension like this:
Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight},
Converter={StaticResource PctConverter},
ConverterParameter={StaticResource OurElementHeightParameter}}"
where OurElementHeightParameter is defined in a seperate ResourceDictionary (call it MainResource.xaml) as follows:
<sys:Double x:Key="OurElementHeightParameter">0.1</sys:Double>
(where namespace is defined as xmlns:sys="clr-namespace:System;assembly=mscorlib".)
This works fine, as far as extracting the CustomParameter is concerned, but it still hasn't enabled us to change our ConverterParameter by swapping skins on the fly.
After researching this some more, in particular the following articles
How to assign wpf resources to other resource tags
Skinning using a color as staticresource for another color
what we think we need to do now is take our StaticResourceExtention and set its value dynamically behind the scenes using resource aliases.
Trying to do this, we replaced the previous OurElementHeightParameter resource with the following two resources
<sys:Double x:Key="SkinnedHeightRatio">0.1</sys:Double>
<StaticResourceExtension x:Key="OurElementHeightParameter" ResourceKey="SkinnedHeightRatio" />
which works fine, producing an identical result.
When that worked okay, we thought it would be a simple matter of placing the SkinnedHeightRatio resource in a seperate ResourceDictionary (call it Skin.xaml) and merging that with the original MainResource.xaml ResourceDictionary and we would have the dynamic skinning we are after.
But, as soon as we extract <sys:Single x:Key="SkinnedHeightRatio">0.1</sys:Single>
to another ResourceDictionary we encounter build error as follows:
Unknown build error, 'Index was out of range. Must be non-negative and less than the size of the collection.'
Even more strange is that if we keep the two resources above in the same ResourceDictionary and just seperate them by putting another random resource between them, for example
<sys:Double x:Key="SkinnedHeightRatio">0.1</sys:Double>
<Thickness x:Key="SomeRandomResource" >5</Thickness>
<StaticResourceExtension x:Key="OurElementHeightParameter" ResourceKey="SkinnedHeightRatio" />
then the OurElementHeightParameter points to the SomeRandomResource directly above it and not the resource specified in its ResourceKey property (SkinnedHeightRatio) which is only 2 lines above it... In this case, the parameter passed to the converter is the Thickness SomeRandomResource.
All very confusing, and makes us think we are barking up the wrong tree completely. So where are we going wrong?
If anyone needs full code for an application reproducing the problem, I can post it up.
Any pointers greatly appreciated.
It might be simpler to create a multi-value converter and bind to two values for it.