Search code examples
c#wpfcolorsbindingwpf-brushes

Binding Color properties


I'm trying to make some basic color picker functionality. I have form with Image holding DrawingImage as source from which I am getting brushes to edit them.

There is a rectangle to show redacted color, textbox for color's hex value and sliders for rgb values, all of which bound to custom dependency property (ActiveBrush) that holds selected brush for editing.

Important bits:

//ActiveBrush
public static DependencyProperty ActiveBrushProperty = DependencyProperty.Register("ActiveBrush", typeof(SolidColorBrush), typeof(FrameworkElement)); 


//XAML
<StackPanel DataContext ="{Binding ElementName = window, Path = ActiveBrush}">
    <Rectangle >
        <Rectangle.Fill>
            <SolidColorBrush Color="{Binding Path=Color, Mode="TwoWay", UpdateSourceTrigger="PropertyChanged"}"/>
        </Rectangle.Fill>
    </Rectangle>

    <TextBox Text = "{Binding Path=Color, Mode="TwoWay", UpdateSourceTrigger="PropertyChanged"}" />

    <Slider Value = "{Binding Path=Color.R, Mode="TwoWay", UpdateSourceTrigger="PropertyChanged"}" />
</StackPanel>

Basically, when I change whole color (as with textbox hex string), it works as intended. But changing individual color properties (r, g, b - they are not dependency properties) with sliders is not reflected visually on screen, although when I look in debugger, they are correctly changing ActiveBrush's color value, so binding works.

I've managed it to work with Rectangle by calling InvalidateVisual on it in Slider's OnValueChanged event, but it didn't work for Image. Also, calling InvalidateProperty on my ActiveBrush dependency property, did not have any perceived effect.


Solution

  • You have correctly identified the cause of the problem: since R, G, B are not dependency properties, so even when there value changes, other controls are unaware of it. So you need to implement those notifications yourself. There are several ways to do this:

    Option a) Create separate DependencyProperty for each color component (R,G,B), and in the PropertyChanged callback for those properties update the ActiveBrush.

       public static DependencyProperty RComponentProperty = DependencyProperty.Register("RComponent", typeof(byte), typeof(YorWindow), new PropertyMetadata(0, OnColorComponentChanged)); 
       public static DependencyProperty GComponentProperty = DependencyProperty.Register("GComponent", typeof(byte), typeof(YorWindow), new PropertyMetadata(0, OnColorComponentChanged)); 
       public static DependencyProperty bComponentProperty = DependencyProperty.Register("BComponent", typeof(byte), typeof(YorWindow), new PropertyMetadata(0, OnColorComponentChanged)); 
       private static void OnColorComponentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
       {
            YorWindow @this = (YorWindow)d;
            Color activeColor = Color.FromRGB(@this.RComponent, @this.GComponent, @this.BComponent);
            d.SetCurrentValue(ActiveBrushProperty, new SolidColorBrush(activeColor));
       }
    

    In the OnPropertyChanged callback for the ActiveBrushProperty do the opposite - update all color component properties.

    Btw., you should not register Dependency Properties on FrameworkElement, use the type of your class in which the property is declared.

    class YourClass
    {
        public static DependencyProperty ActiveBrushProperty = DependencyProperty.Register("ActiveBrush", typeof(SolidColorBrush), typeof(YourClass)); 
    }
    

    Option b) Use ViewModel with INotifyPropertyChanged

    public byte RComponent 
    {
       get => ActiveBrush.Color.R;
       set 
       {
          if(value != ActiveBrush.Color.R)
          {
              ActiveBrush.Color.R = value;
              RaisePropertyChanged(nameof(RComponent));
              RaisePropertyChanged(nameof(ActiveBrush));
          }
       }
    }
    

    Option c) Implement IValueConverter to convert Brush to a single component and back, and use it in the bindings to Slider.