We can set the colour of any WPF control that supports it, but what about being able to specifically sett individual colour channels (RED, GREEN, BLUE, ALPHA)
I've seen many solutions that contain complex custom controls, but one of the key philosophies of WPF is to reuse what you already have, and change the look or extend the functionality via control templates and the dependency system.
Therefore, I have created a colour picker widget that uses only standard controls, and doesn't even change their control templates. Everything is accomplished via the use of attached properties, which are a very underrated tool in the WPF tool-box.
Here’s the attached property class
public static class BrushExtender
{
public readonly static DependencyProperty BrushProperty =
DependencyProperty.RegisterAttached("Brush", typeof(Brush),
typeof(BrushExtender), new PropertyMetadata(Brushes.Black, DoBrushChanged));
public readonly static DependencyProperty RedChannelProperty =
DependencyProperty.RegisterAttached("RedChannel", typeof(int),
typeof(BrushExtender), new PropertyMetadata(DoColorChangedRed));
public readonly static DependencyProperty GreenChannelProperty =
DependencyProperty.RegisterAttached("GreenChannel", typeof(int),
typeof(BrushExtender), new PropertyMetadata(DoColorChangedGreen));
public readonly static DependencyProperty BlueChannelProperty =
DependencyProperty.RegisterAttached("BlueChannel", typeof(int),
typeof(BrushExtender), new PropertyMetadata(DoColorChangedBlue));
public readonly static DependencyProperty AlphaChannelProperty =
DependencyProperty.RegisterAttached("AlphaChannel", typeof(int),
typeof(BrushExtender), new PropertyMetadata(DoColorChangedAlpha));
public readonly static DependencyProperty ColourValueProperty =
DependencyProperty.RegisterAttached("ColourValue", typeof(string),
typeof(BrushExtender), new PropertyMetadata(DoValueChanged));
public static void SetRedChannel(DependencyObject o, int value)
{
o.SetValue(RedChannelProperty, value);
}
public static void SetGreenChannel(DependencyObject o, int value)
{
o.SetValue(GreenChannelProperty, value);
}
public static void SetBlueChannel(DependencyObject o, int value)
{
o.SetValue(BlueChannelProperty, value);
}
public static void SetAlphaChannel(DependencyObject o, int value)
{
o.SetValue(AlphaChannelProperty, value);
}
public static void SetBrush(DependencyObject o, SolidColorBrush brush)
{
o.SetValue(BrushProperty, brush);
}
public static void SetColourValue(DependencyObject o, string value)
{
o.SetValue(ColourValueProperty, value);
}
private static void DoColorChangedRed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
DoColorChange(d, (int)e.NewValue, c => c.R, () =>
Color.FromArgb(color.A, ((byte)(int)e.NewValue), color.G, color.B));
}
private static void DoColorChangedGreen(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
DoColorChange(d, (int)e.NewValue, c => c.G, () =>
Color.FromArgb(color.A, color.R, ((byte)(int)e.NewValue), color.B));
}
private static void DoColorChangedBlue(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
DoColorChange(d, (int)e.NewValue, c => c.B, () =>
Color.FromArgb(color.A, color.R, color.G, (byte)(int)e.NewValue));
}
private static void DoColorChangedAlpha(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
DoColorChange(d, (int)e.NewValue, c => c.A, () =>
Color.FromArgb((byte)(int)e.NewValue, color.R, color.G, color.B));
}
private static void DoColorChange(DependencyObject d, int newValue, Func<Color, int> colorCompare,
Func<Color> getColor)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
if (colorCompare(color) == newValue)
return;
var newBrush = new SolidColorBrush(getColor());
d.SetValue(BrushProperty, newBrush);
d.SetValue(ColourValueProperty, newBrush.Color.ToString());
}
private static void DoValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
if (color.ToString() == (string)e.NewValue)
return;
Color? newColour = null;
try
{
newColour = (Color) ColorConverter.ConvertFromString((string)e.NewValue);
}
catch { }
if (newColour == null)
return;
var newBrush = new SolidColorBrush(newColour.Value);
d.SetValue(BrushProperty, newBrush);
}
private static void DoBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == e.OldValue)
return;
var colour = ((SolidColorBrush)e.NewValue).Color;
d.SetValue(RedChannelProperty, (int)colour.R);
d.SetValue(GreenChannelProperty, (int)colour.G);
d.SetValue(BlueChannelProperty, (int)colour.B);
d.SetValue(AlphaChannelProperty, (int)colour.A);
d.SetValue(ColourValueProperty, colour.ToString());
}
}
And here is the simple WPF widget that makes use of this new functionality
<Window x:Class="ColourBlender.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ColourBlender="clr-namespace:ColourBlender"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Red" />
<TextBlock Text="Green" Grid.Row="1" />
<TextBlock Text="Blue" Grid.Row="2" />
<TextBlock Text="Alpha" Grid.Row="3" />
<Slider Name="redSlider" Grid.Column="1" Minimum="0" Maximum="255" Width="200" Height="20"
Grid.ColumnSpan="2" Value="{Binding ElementName=rect,
Path=(ColourBlender:BrushExtender.RedChannel), Mode=TwoWay}" />
<Slider Name="greenSlider" Grid.Column="1" Grid.Row="1" Minimum="0" Maximum="255" Width="200" Height="20"
Grid.ColumnSpan="2" Value="{Binding ElementName=rect,
Path=(ColourBlender:BrushExtender.GreenChannel), Mode=TwoWay}" />
<Slider Name="blueSlider" Grid.Column="1" Grid.Row="2" Minimum="0" Maximum="255" Width="200" Height="20"
Grid.ColumnSpan="2" Value="{Binding ElementName=rect,
Path=(ColourBlender:BrushExtender.BlueChannel), Mode=TwoWay}" />
<Slider Name="alphaSlider" Grid.Column="1" Grid.Row="3" Minimum="0" Maximum="255" Width="200" Height="20"
Grid.ColumnSpan="2" Value="{Binding ElementName=rect,
Path=(ColourBlender:BrushExtender.AlphaChannel), Mode=TwoWay}" />
<Rectangle Fill="SandyBrown" Name="rect" Width="200" Height="50" Grid.Row="4" Grid.ColumnSpan="3"
Margin="0,20,0,10" ColourBlender:BrushExtender.Brush="{Binding RelativeSource={RelativeSource Self},
Path=Fill, Mode=TwoWay}"/>
<TextBlock Text="Colour Value" Margin="5,0,5,0" Grid.Row="5" HorizontalAlignment="Center" />
<TextBox Text="{Binding ElementName=rect, Path=(ColourBlender:BrushExtender.ColourValue), Mode=TwoWay}"
Margin="0,0,0,0" Grid.Row="5" Grid.Column="1" Width="100" HorizontalAlignment="Center" />
<Button Content="Update" Grid.Row="5" Grid.Column="3" />
</Grid>
</Window>
This widget allows you to alter the Fill colour of a rectangle via red, green, blue and alpha sliders. There is also a textbox that shows the colour code (which you can also edit and the change is reflected in the slider positions)
Here are some screen shots

