Skinning: Using a Color as StaticResource for another Color
Asked Answered
H

4

58

I implemented skinning in my application. The application loads its Brushes.xaml resource dictionary which uses colors which reside in a skin-specific resource dictionary. So only one Color.xaml is loaded depending on the chosen skin.

Skin-Specific Color.xaml

    <Color x:Key="TextBoxBackgroundColor">#C4AF8D</Color>
    <Color x:Key="TextBoxForegroundColor">#6B4E2C</Color>
    <Color x:Key="ToolBarButtonForegroundColor">#6B4E2C</Color>

Brushes.xaml:

    <SolidColorBrush 
        x:Key="TextBoxBackground" 
        Color="{DynamicResource TextBoxBackgroundColor}" />
    <SolidColorBrush 
        x:Key="TextBoxForeground" 
        Color="{DynamicResource TextBoxForegroundColor}" />

As you can see, multiple colors (TextBoxForegroundColor and ToolBarButtonForegroundColor) are the same. I'd like to circumvent that as it is getting more and more confusing especially since the used colors are not recognizable by their hex value. You could advise now to merge both Colors into one but I have skins where the TextBoxForegroundColor is different from the ToolBarButtonForegroundColor.

What I would like to do is something like this:

<Color x:Key="DarkBrown">#C4AF8D</Color>

<Color x:Key="TextBoxBackgroundColor" Color={StaticResource DarkBrown} />
<Color x:Key="ToolBarButtonForegroundColor" Color={StaticResource DarkBrown} />

Is this at all possible in Xaml? I didn't find a way.

Hellman answered 26/1, 2011 at 8:9 Comment(0)
G
62

This?

<Color x:Key="DarkBrown">#C4AF8D</Color>

<DynamicResource x:Key="TextBoxBackgroundColor" ResourceKey="DarkBrown"/>
<DynamicResource x:Key="ToolBarButtonForegroundColor" ResourceKey="DarkBrown"/>

For more advanced use cases and multiple levels of aliasing see this answer.

Gracegraceful answered 26/1, 2011 at 11:10 Comment(15)
That looks very interesting but I don't get it to work. I tried to set <SolidColorBrush x:Key="TextBackground" Color="{DynamicResource TextBoxBackgroundColor}" /> but this triggers the error message "An object of the type "System.Windows.DynamicResourceExtension" cannot be applied to a property that expects the type "System.Windows.Media.Color". Am I doing something wrong?Hellman
You need to reference it as a StaticResource (Color="{StaticResource TextBoxBackgroundColor}") because it is in fact a static resource even though the internal reference is dynamic.Gracegraceful
I tried that and now it compiles and works. But I still get the error in the IDE (Visual Studio 2010 Express) but now regarding the System.Windows.StaticResourceExtension. This is problematic since it effectively cripples the Design-Time usage of the Gui-Editor in the IDE. But still great that it can work that way. Do you have any idea why this happens? Is this bug in VS or am I doing something wrong?Hellman
Maaan, just do not use the bloody GUI-Editor, it sucks badly. No wonder i was not aware of any errors... (If you need this fixed that'd be the subject for another question with which i surely cannot help you. As far as this question goes i doubt you'll find any better solution, you could accept my answer by clicking the checkmark outline on the left below the votes counter)Gracegraceful
@H.B. How does the above work? When you try to use a DynamicResource in a ResourceDictionary, you get the following error message (even at run-time, not just design-time): A 'DynamicResourceExtension' cannot be used within a 'ResourceDictionary' collection. A 'DynamicResourceExtension' can only be set on a DependencyProperty of a DependencyObject.Upstanding
@H.B. I would love to see more of the xaml ... if you can indeed get the above to work. It does work, by the way, if you use StaticResource (instead of DynamicResource).Upstanding
@cplotts: The code in my answer can be placed in the resources of any control, e.g. <Window.Resources>...</Window.Resources> (does that behave differently than a resource-dictionary? If that is not it i sadly do not know what the problem might be)Gracegraceful
@H.B. Ok, this technique seems very fragile. Useful, but fragile. The reason I was having problems was that I was testing your xaml above with Kaxaml. As soon as I switched to Visual Studio, I started seeing the other exception that @Hellman talks about. However, it would compile and work (designer didn't like it as already mentioned).Upstanding
@H.B. What is interesting (continuing the previous comment) ... is that whether it works or not ... is also dependent on whether you are using WPF 3.5 or WPF 4.0. I'm going to post an answer to this question to illustrate what I'm talking about ... and in case anyone comes here in the future.Upstanding
@Upstanding Any news about how to get rid of this Exception ?Ite
@MyKuLLSKI: As mentioned i do not use GUI editors, hence i could not tell you.Gracegraceful
For me this doesn't work with StaticResources. With one it does, but if I add a second it doesn't anymore. It seems the StaticResource ignores the given ResourceKey and simply references the preceding element, wichever that is. I've explained the problem in this question. If anyone can help, that'd be amazing.Exhaust
While this would be cool if it worked, the XAML doesn't compile in VS 2019. It throws the error mentioned by others. I guess it was changed since 2011. Not aware of any other way of doing this.Seriatim
@bokibeg: Nothing changed, as noted this error only happens if you use the visual XAML designer. The project itself should compile and run just fine.Gracegraceful
FYI, I cannot get this to work in WPF 6.0, at least not in Storyboards which is what I really want to use this for.Anzus
V
12

Why don't you just make Brushes.xaml skin-specific? Then you will have this:

<Color x:Key="DarkBrown">#C4AF8D</Color>

<SolidColorBrush x:Key="TextBoxBackgroundBrush" Color="{StaticResource DarkBrown}" />
<SolidColorBrush x:Key="ToolBarButtonForegroundBrush" Color="{StaticResource DarkBrown}" />

Another point in favor of making brushes skin-specific is that there are situations where you would want to make the ToolBarButtonForegroundBrush a solid color brush in one skin and a gradient brush in another skin.

Vulcanism answered 26/1, 2011 at 9:4 Comment(3)
Thanks. My skins often but don't always use the same colors for the same brushes. So I would have to copy my Brushes.xaml for every skin and then adjust the color resources therein. I'm not sure if this isn't even more work.Hellman
Yes, but this method will give you the most flexibility and might be more beneficial in a long run.Vulcanism
I am very new to WPF. How would you use TextBoxBackgroundBrush in your window.xaml?Midkiff
U
7

H.B.'s answer is very interesting and I have played around with it quite a bit since I want to do exactly what this question is asking to do.

What I've noticed is that using a DynamicResource doesn't work for WPF 3.5. That is, it throws an exception at run time (the one Amenti talks about). However, if you make colors that are referencing the color you want to share ... a StaticResource, it works on both WPF 3.5 and WPF 4.0.

That is, this xaml works for both WPF 3.5 and WPF 4.0:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="ColorsReferencingColors.MainWindow"
    x:Name="Window"
    Title="MainWindow"
    Width="640"
    Height="480"
>
    <Window.Resources>
        <Color x:Key="DarkBlue">DarkBlue</Color>
        <StaticResource x:Key="EllipseBackgroundColor" ResourceKey="DarkBlue"/>
        <SolidColorBrush
            x:Key="ellipseFillBrush"
            Color="{DynamicResource EllipseBackgroundColor}"
        />
    </Window.Resources>
    <Grid>
        <StackPanel Margin="25">
            <Ellipse
                Width="200"
                Height="200"
                Fill="{DynamicResource ellipseFillBrush}"
            />
        </StackPanel>
    </Grid>
</Window>

Another thing that bears mentioning (again) is that this approach wreaks havoc with the designers out there (i.e the Visual Studio 2008 and 2010 designers, the Blend 3 and 4 designers). I speculate that this is the same reason why Kaxaml 1.7 wasn't liking H.B.'s xaml (if you're following the comment stream on H.B.'s answer).

But while it breaks the designers for a simple test case, it doesn't seem to break the design surface for the large scale application I work on in my day job. Plain weird! In other words, if you care about things still working in the designer, try this method out anyway ... your designer may still work!

Upstanding answered 10/3, 2011 at 23:7 Comment(2)
Thanks for your detailed description (+1). Unfortunately in my case it breaks the designer. I'm still using VS Express though, maybe that's a problem in itself (I don't believe it though).Hellman
An update. I'm so sad, but this trick ... while it works for our main application, it does not work for a couple of utility applications that share our skin files (which include the above xaml). So, unfortunately, I'm going to have to back it out.Upstanding
D
4

That last part is not possible as a Color has no Color property.

A Color does have an R, G, B and A property. So you could create four bytes as resources:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <sys:Byte x:Key="r">#23</sys:Byte>
    <sys:Byte x:Key="g">#45</sys:Byte>
    <sys:Byte x:Key="b">#67</sys:Byte>
    <sys:Byte x:Key="a">#FF</sys:Byte>

    <Color x:Key="c1" R="{StaticResource r}"
       G="{StaticResource g}"
       B="{StaticResource b}"
       A="{StaticResource a}"/>

    <Color x:Key="c2" R="{StaticResource r}"
       G="{StaticResource g}"
       B="{StaticResource b}"
       A="{StaticResource a}"/>

</ResourceDictionary>

Still not what you might prefer but it should work.

Dendroid answered 26/1, 2011 at 8:25 Comment(1)
Thanks. Well, this would work but I think changing a color becomes to tedious this way, if you have to copy & paste 4 strings instead of 1 from the graphics program.Hellman

© 2022 - 2024 — McMap. All rights reserved.