0

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.

Will
  • 3,413
  • 7
  • 50
  • 107
  • Most of what you need is here: http://stackoverflow.com/questions/2109756/how-to-get-color-from-hexadecimal-color-code-using-net – Ulric Apr 16 '15 at 14:46
  • @Ulric : Not really - I know how to convert hex values to colors. I don't want to convert the slider value to a string, I want to convert the slider values of the three sliders to a single color. – Will Apr 16 '15 at 14:52
  • @Tim I was not familiar with that interface - thanks. I will retract my comment. – D Stanley Apr 16 '15 at 14:52
  • @Will where are you planning to use this converter? Are you trying to set the (for instance) background color of something to the output of those sliders? Showing us some of your XAML where you plan to use this will make it easier for us to help you. – Tim Apr 16 '15 at 14:54
  • @Tim - The intention of the control is to allow users a simple interface for defining colors for programs (My client is really big on user customization experience). The control itself will have a property to which other controls will be able to bind (at least, that is the intention, see the edit). – Will Apr 16 '15 at 15:04
  • 2
    Please chek this link http://www.zagstudio.com/blog/458#.VS_PAJPsStc – lerner1225 Apr 16 '15 at 15:22

2 Answers2

1

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>
olitee
  • 1,683
  • 10
  • 12
  • Yes - that is good; thanks - Rather than passing a parameter, instead I just defined resources and passed those. – Will Apr 16 '15 at 15:41
0

Okay - I want to thank everyone for their help; I was finally able to come up with a solution - in the event anyone is interested or stumbles upon this, it's also a good lesson in MVVM (maybe; I don't know...).

Anyway :

This is the MVVM I implemented :

public class ColorViewModel : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    private Color _Color = Colors.Black;

    public double A {
        get { return this.Color.ScA; }
        set {
            this._Color.ScA = ( float )value;
            if ( this.PropertyChanged != null ) {
                this.PropertyChanged( this, new PropertyChangedEventArgs( "A" ) );
                this.PropertyChanged( this, new PropertyChangedEventArgs( "Color" ) );
            }
        }
    }
    public double R {
        get { return this.Color.ScR; }
        set {
            this._Color.ScR = ( float )value;
            if ( this.PropertyChanged != null ) {
                this.PropertyChanged( this, new PropertyChangedEventArgs( "R" ) );
                this.PropertyChanged( this, new PropertyChangedEventArgs( "Red" ) );
                this.PropertyChanged( this, new PropertyChangedEventArgs( "Color" ) );
            }
        }
    }
    public double G {
        get { return this.Color.ScG; }
        set {
            this._Color.ScG = ( float )value;
            if ( this.PropertyChanged != null ) {
                this.PropertyChanged( this, new PropertyChangedEventArgs( "G" ) );
                this.PropertyChanged( this, new PropertyChangedEventArgs( "Green" ) );
                this.PropertyChanged( this, new PropertyChangedEventArgs( "Color" ) );
            }
        }
    }
    public double B {
        get { return this._Color.ScB; }
        set {
            this._Color.ScB = ( float )value;
            if ( this.PropertyChanged != null ) {
                this.PropertyChanged( this, new PropertyChangedEventArgs( "B" ) );
                this.PropertyChanged( this, new PropertyChangedEventArgs( "Blue" ) );
                this.PropertyChanged( this, new PropertyChangedEventArgs( "Color" ) );
            }
        }
    }

    public Color Color {
        get { return this._Color; }
        set {
            this._Color = value;
            if ( this.PropertyChanged != null )
                this.AllChanged( );
        }
    }
    public Color Red { get { return Color.FromScRgb( 1.0F, ( float )this.R, 0.0F, 0.0F ); } }
    public Color Green { get { return Color.FromScRgb( 1.0F, 0.0F, ( float )this.G, 0.0F ); } }
    public Color Blue { get { return Color.FromScRgb( 1.0F, 0.0F, 0.0F, ( float )this.B ); } }

    private void AllChanged( ) {
        this.PropertyChanged( this, new PropertyChangedEventArgs( "A" ) );
        this.PropertyChanged( this, new PropertyChangedEventArgs( "R" ) );
        this.PropertyChanged( this, new PropertyChangedEventArgs( "G" ) );
        this.PropertyChanged( this, new PropertyChangedEventArgs( "B" ) );
        this.PropertyChanged( this, new PropertyChangedEventArgs( "Red" ) );
        this.PropertyChanged( this, new PropertyChangedEventArgs( "Green" ) );
        this.PropertyChanged( this, new PropertyChangedEventArgs( "Blue" ) );
        this.PropertyChanged( this, new PropertyChangedEventArgs( "Color" ) );
    }
}

This is the control class :

public partial class ColorDefiner : UserControl {

    public static readonly DependencyProperty
        _Color = DependencyProperty.Register( "Color", typeof( Color ), typeof( ColorDefiner ) );

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

    private ColorViewModel CVM { get { return this.DataContext as ColorViewModel; } }

    public ColorDefiner( ) {
        InitializeComponent( );
        Binding B = new Binding( "Color" ) { Source = this.DataContext };
        this.SetBinding( ColorDefiner._Color, B );
    }
}

This is the XAML for the User Control (yes, I did it with a UserControl; this required a data context, and I really don't want to fuss with that yet in a Custom Control - this is really only going to be my own personal control for my own use anyway) :

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:Components="clr-namespace:WPFTools.Components"
    xmlns:Controls="clr-namespace:WPFTools.Controls"
    x:Class="WPFTools.Controls.ColorDefiner"
    mc:Ignorable="d"
    Width="100" Height="100" FontFamily="Arial" FontWeight="Bold">
    <UserControl.DataContext>
        <Controls:ColorViewModel/>
    </UserControl.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="5"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <Components:WPFGLabel Text="A" StrokeThickness="0.5" TextAlignment="Right" Stroke="#99000000">
                <Components:WPFGLabel.Fill>
                    <SolidColorBrush Color="{Binding Color}"/>
                </Components:WPFGLabel.Fill>
            </Components:WPFGLabel>
        </Viewbox>
        <Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="1">
            <Components:WPFGLabel Text="R" StrokeThickness="0.5" TextAlignment="Right" Stroke="#99000000">
                <Components:WPFGLabel.Fill>
                    <SolidColorBrush Color="{Binding Red, Mode=OneWay}"/>
                </Components:WPFGLabel.Fill>
            </Components:WPFGLabel>
        </Viewbox>
        <Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="2">
            <Components:WPFGLabel Text="G" StrokeThickness="0.5" TextAlignment="Right" Stroke="#99000000">
                <Components:WPFGLabel.Fill>
                    <SolidColorBrush Color="{Binding Green, Mode=OneWay}"/>
                </Components:WPFGLabel.Fill>
            </Components:WPFGLabel>
        </Viewbox>
        <Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="3">
            <Components:WPFGLabel Text="B" StrokeThickness="0.5" TextAlignment="Right" Stroke="#99000000" RenderTransformOrigin="-7.272,0.575">
                <Components:WPFGLabel.Fill>
                    <SolidColorBrush Color="{Binding Blue, Mode=OneWay}"/>
                </Components:WPFGLabel.Fill>
            </Components:WPFGLabel>
        </Viewbox>
        <Viewbox HorizontalAlignment="Stretch" Grid.Row="2" VerticalAlignment="Stretch">
            <Slider Orientation="Vertical" TickPlacement="Both" LargeChange="0.1" Maximum="1" SmallChange="0.01" TickFrequency="0.02" Height="90" TabIndex="0" Value="{Binding A, Mode=TwoWay}"/>
        </Viewbox>
        <Viewbox HorizontalAlignment="Stretch" Grid.Row="2" Grid.Column="1"  VerticalAlignment="Stretch">
            <Slider Orientation="Vertical" TickPlacement="Both" LargeChange="0.1" Maximum="1" SmallChange="0.01" TickFrequency="0.02" Height="90" TabIndex="1" Value="{Binding R, Mode=TwoWay}"/>
        </Viewbox>
        <Viewbox HorizontalAlignment="Stretch" Grid.Row="2" Grid.Column="2"  VerticalAlignment="Stretch">
            <Slider Orientation="Vertical" TickPlacement="Both" LargeChange="0.1" Maximum="1" SmallChange="0.01" TickFrequency="0.02" Height="90" TabIndex="2" Value="{Binding G, Mode=TwoWay}"/>
        </Viewbox>
        <Viewbox HorizontalAlignment="Stretch" Grid.Row="2" Grid.Column="3"  VerticalAlignment="Stretch">
            <Slider Orientation="Vertical" TickPlacement="Both" LargeChange="0.1" Maximum="1" SmallChange="0.01" TickFrequency="0.02" Height="90" Value="{Binding B, Mode=TwoWay}"/>
        </Viewbox>
    </Grid>
</UserControl>

I bound the Brush Color of the Labels to their respective colors in the View Model (A got the entire thing). I Two-Way Bound the value of each slider to it's respective value within the view model, and in the code I bound the Dependency Property to the view model Color property. I tested this, and it worked.

Once again, thank you all for your help.

Will
  • 3,413
  • 7
  • 50
  • 107