What is the appropriate method for creating a binding property on a user control in WPF?

advertisements

This is a follow up to this question. I've developed a simple interface for a control that is supposed to allow a user to be able to define a color in the simplest way possible - by controlling the ARGB channels of the color itself.

I want this control to be able to bind directly to a color property so as to allow a user to adjust it via the sliders.

I've hit a wall - I've tried to implement MVVM with it but that does not work because in doing so I've utterly failed to be able to extract from the control the color property defined by the control itself.

I actually do not even feel that this is the correct way to be going about this. I need to have in place several controls that will allow users to change the behavior and look of our application, but I cannot figure out how to get a UserControl to bind to the settings of the application (I've been able to bind single, simple controls but when it comes to composite controls like this one, I'm getting no where).

This is the code for the control itself and the MVVM :

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 ); }
    }

    public ColorDefiner( ) { InitializeComponent( ); }
}

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" ) );
    }
}

I've tried binding the Color dependency property to the Color View Model in code; I've tried binding it via a Style Setter in XAML :

<UserControl.Resources>
    <Style TargetType="Controls:ColorDefiner">
        <Setter Property="Color" Value="{Binding Color, Mode=TwoWay}"/>
    </Style>
</UserControl.Resources>

Nothing works - What is the appropriate way to do this? (or the best way, or the most proper way, or the most commonly accepted practice?) How do I extract defined Color property from the Color View Model attached to the control? Is that even the right way to go about doing this?


Your ColorDefiner control does not react when the Color property has changed. It should register a PropertyChangedCallback with dependency property metadata. The property metadata could also be used to specify that the property binds two-way by default. You should also follow naming conventions in WPF, and name the DependencyProperty field ColorProperty:

public partial class ColorDefiner : UserControl
{
    public static readonly DependencyProperty ColorProperty =
        DependencyProperty.Register(
            "Color", typeof(Color), typeof(ColorDefiner),
            new FrameworkPropertyMetadata(
                Colors.Black,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                (o, e) => ((ColorDefiner)o).ColorPropertyChanged((Color)e.NewValue)));

    public Color Color
    {
        get { return (Color)GetValue(ColorProperty); }
        set { SetValue(ColorProperty, value); }
    }

    public ColorDefiner()
    {
        InitializeComponent();
    }

    private void ColorPropertyChanged(Color color)
    {
        sliderA.Value = (double)color.A;
        sliderR.Value = (double)color.R;
        sliderG.Value = (double)color.G;
        sliderB.Value = (double)color.B;
    }

    private void SliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        Color = Color.FromArgb((byte)sliderA.Value,
            (byte)sliderR.Value, (byte)sliderG.Value, (byte)sliderB.Value);
    }
}

The SliderValueChanged event handler is used for all four Sliders in the control's XAML:

<UserControl  ...>
    <StackPanel>
        <Slider x:Name="sliderA" Maximum="255" ValueChanged="SliderValueChanged"/>
        <Slider x:Name="sliderR" Maximum="255" ValueChanged="SliderValueChanged"/>
        <Slider x:Name="sliderG" Maximum="255" ValueChanged="SliderValueChanged"/>
        <Slider x:Name="sliderB" Maximum="255" ValueChanged="SliderValueChanged"/>
    </StackPanel>
</UserControl>

This simple example shows how the control works:

<Grid>
    <Grid.Background>
        <SolidColorBrush x:Name="brush" Color="AliceBlue"/>
    </Grid.Background>
    <local:ColorDefiner Color="{Binding Color, ElementName=brush}"/>
</Grid>

It could similarly be bound to any view model with a Color property.