Search code examples
c#wpfcolorsmultibindingimultivalueconverter

How can I convert the values of three sliders into a Color?


I'm trying to create a custom user control that will allow a user to define a color in WPF. I've done this before in WinForms but in WPF it seems to be not as straight forward. This is also my first time dealing with a multi-converter.

The control has 3 Sliders - like so :

<Slider x:Name="sdrRed" Height="32" LargeChange="5" SmallChange="1" Maximum="255" Width="321" TickPlacement="Both"/>

The only difference is the name for each - sdrRed, sdrGreen, and sdrBlue.

This is the multi-value converter :

public class ByteToColorConverter : IMultiValueConverter 
{
    public object Convert( object[ ] values, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
        return Color.FromArgb( (byte)values[0], (byte)values[1], (byte)values[2], (byte)values[3]);
    }

    public object[ ] ConvertBack( object value, Type[ ] targetTypes, object parameter, System.Globalization.CultureInfo culture ) {
        Color C = ( Color )value;
        return new object[ ] { C.A, C.R, C.G, C.B };
    }
}

This is as far as I have been able to get - I haven't been able to find an example of how to proceed.

Also - how can I pass values to the multiconverter that are static, so that I may define a color with only a single slider (say, so that I may define shades of Red, Blue or Green)?

EDIT

For some clarity; The control itself will have a dependency property :

private static readonly DependencyProperty
    _Color = DependencyProperty.Register( "Color", typeof( Color ), typeof( ColorDefiner ), new PropertyMetadata( Colors.Black ) );

public Color Color {
    get { return ( Color )this.GetValue( ColorDefiner._Color ); }
    set { this.SetValue( ColorDefiner._Color, value ); }
}

This control will pass the converted Color value to that binding, so that other colors may be bound to it. I hope that clears things up.


Solution

  • First of all, I'd recommend making these changes to your ByteToColorConverter:

    public class DoubleToColorConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // Values bound to sliders are going to be doubles.
            return Color.FromScRgb((float)(double)values[0], (float)(double)values[1], (float)(double)values[2], (float)(double)values[3]);
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            Color C = (Color)value;
            return new object[] { (double)C.ScA, (double)C.ScR, (double)C.ScG, (double)C.ScB };
        }
    }
    

    I've switched from bytes to doubles, as the slider value you're trying to bind to will only return/accept doubles. The (float)(double) cast is to deal with unboxing the values in the array.

    With this XAML, I was able to get a basic ARGB colour mixer working. Notice I've changed the Min/Max values on the slider, now we're not dealing with bytes anymore.

    <StackPanel>
        <Slider x:Name="sdrAlpha" Height="32" LargeChange="0.5" SmallChange="0.1" Minimum="0" Maximum="1" Width="321" TickPlacement="Both"/>
        <Slider x:Name="sdrRed" Height="32" LargeChange="0.5" SmallChange="0.1" Minimum="0" Maximum="1" Width="321" TickPlacement="Both"/>
        <Slider x:Name="sdrGreen" Height="32" LargeChange="0.5" SmallChange="0.1" Minimum="0" Maximum="1" Width="321" TickPlacement="Both"/>
        <Slider x:Name="sdrBlue" Height="32" LargeChange="0.5" SmallChange="0.1" Minimum="0" Maximum="1" Width="321" TickPlacement="Both"/>
        <Border x:Name="colourBorder" Height="200" HorizontalAlignment="Stretch">
            <Border.Background>
                <SolidColorBrush>
                    <SolidColorBrush.Color>
                        <MultiBinding Converter="{StaticResource colorConverter}">
                            <Binding ElementName="sdrAlpha" Path="Value" Mode="TwoWay" />
                            <Binding ElementName="sdrRed" Path="Value" Mode="TwoWay" />
                            <Binding ElementName="sdrGreen" Path="Value" Mode="TwoWay" />
                            <Binding ElementName="sdrBlue" Path="Value" Mode="TwoWay" />
                        </MultiBinding>
                    </SolidColorBrush.Color>
                </SolidColorBrush>
            </Border.Background>
        </Border>
    </StackPanel>
    

    If you wanted to bind a single slider to the converter, you could update your converter to inspect the number of values in the values[] array. You could perhaps use the ConverterParameter to pass in the colour you'd like that single slider to affect ... something like this:

    <MultiBinding Converter="{StaticResource colorConverter}" ConverterParameter="Red">
        <Binding ElementName="sdrRed" Path="Value" Mode="TwoWay" />
    </MultiBinding>