views:

766

answers:

2

I have a BaseSkin and multiple UserSkins in a separate dll from my WPF application.

Depending on who is using the application, the base skin and one of the user skins will be merged into a resource dictionary and loaded for the application to use.

What I'm aiming for is the ability to specify a style in a BaseSkin file, and then on a specific UserSkin file be able to override it, changing any properties I need to.

I know I can accomplish this by using the BasedOn attribute like this:

Base:

<Style x:Key="ButtonBg" TargetType="{x:Type Button}">
    <Setter Property="Background" Value="Green"/>
</Style>

User:

<Style x:Key="CustomButtonBg" TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonBg}">
    <Setter Property="Background" Value="Blue"/>
</Style>

The problem is that now the elements have to have a Style of CustomButtonBg which may not actually be implemented. Is there any way to have both styles use the same key (ButtonBg), and when they're merged have the application look for a style named ButtonBg in User first, and if one doesn't exist, use the one in base?

I was thinking that if I could give the assembly name in the BasedOn attribute to point towards the BaseSkin file, I could avoid naming errors when I give them the same key, but I can't find any way to do that. The other options are to just force an implementation of each style even if nothing gets changed, or check programatically in the skins, but those are last resorts.

A: 

You might just name your Base as BaseButtonBg and when you don't merge a user based ResourceDictionary merge a generic one containing:

<Style x:Key="ButtonBg" TargetType="{x:Type Button}" BasedOn="{StaticResource BaseButtonBg}"/>
Mark Synowiec
Actually I always merge a user based RD, it just isn't guaranteed to have the implementation of ButtonBg. This would work, but as I said near the end of the question, I'm not sure if I want to force them to specify a blank style.
Brandon
+1  A: 

You could try to take advantage of the resource lookup logic. When WPF is trying to find a resource by the key, it first look in the current element's ResourceDictionary, then its parent's, then the parent of that, and so on.

So since you said it is conditional to the user, that could be merged in the ResourceDictionary at the Window level while your original base is at the Application level.

Edit: I have better information. From MSDN on Merged Dictionaries:

Merged Dictionary Behavior

Resources in a merged dictionary occupy a location in the resource lookup scope that is just after the scope of the main resource dictionary they are merged into. Although a resource key must be unique within any individual dictionary, a key can exist multiple times in a set of merged dictionaries. In this case, the resource that is returned will come from the last dictionary found sequentially in the MergedDictionaries collection. If the MergedDictionaries collection was defined in XAML, then the order of the merged dictionaries in the collection is the order of the elements as provided in the markup. If a key is defined in the primary dictionary and also in a dictionary that was merged, then the resource that is returned will come from the primary dictionary. These scoping rules apply equally for both static resource references and dynamic resource references.

That means you can define your Base skin in a different ResourceDictionary and merge it into another ResourceDictionary. Have the User skin in the latter, and it will find it first, otherwise it will keep drilling down to the merged dictionary that contains Base. Each of your User dictionaries can merge the Base dictionary and you just load the User dictionary into the app instead of both separately.

Joel B Fant
If I merge it at the Window level, won't I have to merge it each time?
Brandon
Possibly, copying and pasting a block of code to each Window can be a pain. There may be a way to insert a level beyond App or between App and Window. You might even consider creating something that will grab and insert them into the Window's ResourceDictionary with a one-liner so that you still have a single source, ultimately. I just don't know quite enough to make a definite suggestion. I just had an idea is all.
Joel B Fant
I haven't tried this yet, but it seems like it should work. Thanks for the help.
Brandon