I am attempting to follow the Don't Repeat Yourself principle i XAML, but I've reached a point where I'm struggling.
I am defining colors for skinning. Say I have defined the colors I want to base my skin on:
<ResourceDictionary>
...
<Color x:Key="B+">#FF999999</Color>
<Color x:Key="B">#FF808080</Color>
<Color x:Key="B-">#FF666666</Color>
...
</ResourceDictionary>
Now say I want to use the color B as my border color, I'd style the BorderBrush of my textboxs, buttons etc.:
<Style TargetType="TextBox">
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{StaticResource B}" />
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button">
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{StaticResource B}" />
</Setter.Value>
</Setter>
</Style>
Now suppose I want to use a darker tone, B-, and so instead of having (to remember) to change the resource for all controls, I would rather have an alternative named "placeholder" color from say B, so I'd only have to change it once:
<Color x:Key="controlBorderColor">B</Color><!-- Not working -->
I am at a loss on how to do this. Is there a general "pattern" or way of defining "constants" in XAML?
Workaround solution
Not possible the way I want to, see answers.
Workaround in short (Thanks to @dotsven for the neat trick to handle brightness): Define all colors as brushes, and use markup extensions to retrieve the Color
property.
AFAIK Color
doesn't support it assignment of content, so there is no easy way from XAML to define or bind to it without doing it individually to the A,R,G,B etc. attributes. The brush sort of serves as a wrapper here so we can bind to Color.
Define all colors as Brush
es
<SolidColorBrush x:Key="B">
<SolidColorBrush.Color>
#FF808080
</SolidColorBrush.Color>
</SolidColorBrush>
Create a MarkupExtension to retrieve Color
from the SolidColorBrush
:
[MarkupExtensionReturnType(typeof(Color))]
public class SkinColorExtension : MarkupExtension {
public SkinColorExtension() {
}
public SkinColorExtension(SolidColorBrush baseColor, int brightness) {
this.BaseColor = baseColor;
this.Brightness = brightness;
}
[ConstructorArgument("baseColor")]
public SolidColorBrush BaseColor { get; set; }
[ConstructorArgument("brightness")]
public int Brightness { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider) {
return SkinColor_Helper.ApplyBrightness(BaseColor.Color, Brightness);
}
}
static class SkinColor_Helper {
public static Color ApplyBrightness(Color c, int b) {
Func<int, byte> Scale = (val) => {
int scaledVal = val + b;
if (scaledVal < 0) return 0;
else if (scaledVal > 255) return 255;
else return (byte)scaledVal;
};
return Color.FromArgb(c.A, Scale(c.R), Scale(c.G), Scale(c.B));
}
}
Then create my skinning colors by brightness offsets as such:
<SolidColorBrush x:Key="controlBorder" Color="{ext:SkinColor BaseColor={StaticResource B}, Brightness=-25}"/>
<SolidColorBrush x:Key="controlBackground" Color="{ext:SkinColor BaseColor={StaticResource B}, Brightness=-100}" />
When controls need a brush, e.g. Background
, just bind to {StaticResource controlBackground}
.
If you have to bind to something that needs a Color
, bind to {ext:SkinColor BaseColor={StaticResource controlBackground}}
to retrieve it from the brush instead.
Unfortunately there's no direct support for this. But by using a custom markup extension you get a fexible workaround. Create a markup extension like this:
Then you can use it as follows:
It's important to note that you've got to implement the markup extension in a separate assembly. You'd extend the markuop extension with properties that allow you to dim, brighten or manipulate the base color in a number of ways.