c# - WPF Combobox Colors Binding issues


Keywords:c# 


Question: 

To have it simple I have a collection of colors and a combobox that is binded to it. Working, no prop.

But when I want to expand the colors a bit with some Gradient Features, the binding does not work and I have tried numerous things. I really dont see the big difference.

This is what I have and its working:

XAML

<ComboBox x:Name="colorCombo" Style="{StaticResource myComboBoxStyle}" Height="25" ItemsSource="{Binding ColorCollection}"      HorizontalAlignment="Left" Margin="5" Grid.Row="3" Grid.Column="4" Width="110">
<ComboBox.ItemTemplate>
    <DataTemplate>
        <Border Height="15" Width="{Binding ElementName=colorCombo, Path=Width}" Background="{Binding Converter={StaticResource ColorToBrushConverter} }"/>
    </DataTemplate>
</ComboBox.ItemTemplate>

ViewModel:

private Collection<Color> _colorCollection;
public Collection<Color> ColorCollection
{
  get { return _colorCollection; }
  set
  {
    _colorCollection = value;
    this.NotifyPropertyChanged( x => x.ColorCollection );
  }
}

OnLoad of the viewModel the collection gets filled, so dont worry about that. Again this is working!!

Now why would this not work:

XAML

        <ComboBox x:Name="colorCombo2" Style="{StaticResource myComboBoxStyle}" Height="25" ItemsSource="{Binding ColorCollection2}" HorizontalAlignment="Right" Margin="5" Grid.Row="3" Grid.Column="4" Width="110">
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <Border Height="15" Width="{Binding ElementName=colorCombo2, Path=Width}" BorderBrush="{Binding BorderColor, Converter={StaticResource ColorToBrushConverter}}" >
                            <Border.Background >
                                <LinearGradientBrush EndPoint="0.504,1.5" StartPoint="0.504,0.03">
                                    <GradientStop Color="{Binding Color1, Converter={StaticResource ColorToBrushConverter}}" Offset="0"/>
                                    <GradientStop Color="{Binding Color2, Converter={StaticResource ColorToBrushConverter}}" Offset="0.567"/>
                                </LinearGradientBrush>
                            </Border.Background>
                        </Border>
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>

ViewModel:

private Collection<ColorGradientHelper> _colorCollection2;
public Collection<ColorGradientHelper> ColorCollection2
{
  get { return _colorCollection2; }
  set
  {
    _colorCollection2 = value;
    this.NotifyPropertyChanged( x => x.ColorCollection2 );
  }
}

HelperClass:

Public class ColorGradientHelper:ObservableBase {

private Color _color1;
public Color Color1
{
  get { return _color1; }
  set
  {
    _color1 = value;
    this.NotifyPropertyChanged( x => x.Color1 );
  }
}

private Color _color2;
public Color Color2
{
  get { return _color2; }
  set
  {
    _color2 = value;
    this.NotifyPropertyChanged( x => x.Color2 );
  }
}

private Color _borderColor;
public Color BorderColor
{
  get { return _borderColor; }
  set
  {
    _borderColor = value;
    this.NotifyPropertyChanged( x => x._borderColor );
  }
}

Converter:

public class ColorToBrushConverter : IValueConverter {
public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
  System.Drawing.Color col = (System.Drawing.Color) value;
  Color c = Color.FromArgb( col.A, col.R, col.G, col.B );
  return new SolidColorBrush( c );
}

public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
  SolidColorBrush c = (SolidColorBrush) value;
  System.Drawing.Color col = System.Drawing.Color.FromArgb( c.Color.A, c.Color.R, c.Color.G, c.Color.B );
  return col;
}

}


1 Answer: 

As Shawn Wildermuth states:

GradientStop does not derive from FrameworkElement therefore cannot be data bound.

A workaround is the clever use of Tag property of FrameworkElement. Finally, inspired by rmoore's answer I've ended up with this solution:

XAML

<ComboBox x:Name="colorCombo2" Height="25" ItemsSource="{Binding ColorCollection}" HorizontalAlignment="Right" Margin="5" Width="110">
  <ComboBox.ItemTemplate>
    <DataTemplate>
      <Grid>
        <Grid.Resources>
          <local:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
        </Grid.Resources>
        <Border Height="20" Width="{Binding ElementName=colorCombo2, Path=Width}" 
                BorderThickness="1"
                BorderBrush="{Binding BorderColor, Converter={StaticResource ColorToBrushConverter}}">
          <Border.Background>
            <LinearGradientBrush EndPoint="0.504,1.5" StartPoint="0.504,0.03">
              <GradientStop Color="{Binding ElementName=Border1, Path=Tag}" Offset="0" />
              <GradientStop Color="{Binding ElementName=Border2, Path=Tag}" Offset="0.567" />
            </LinearGradientBrush>
          </Border.Background>
        </Border>
        <Grid Visibility="Collapsed">
          <FrameworkElement Tag="{Binding Color1}" x:Name="Border1" />
          <FrameworkElement Tag="{Binding Color2}" x:Name="Border2" />
        </Grid>
      </Grid>
    </DataTemplate>
  </ComboBox.ItemTemplate>
</ComboBox>

ViewModel (quite identical with OP's ViewModel)

public partial class MainWindow6 : Window, INotifyPropertyChanged {
    public MainWindow6() {
        DataContext = this;
        InitializeComponent();
        var colors = new Collection<ColorGradientHelper>();
        colors.Add(new ColorGradientHelper {
            BorderColor = Colors.Orange,
            Color1 = Colors.Purple,
            Color2 = Colors.White
        });

        colors.Add(new ColorGradientHelper {
            BorderColor = Colors.Orange,
            Color1 = Colors.Black,
            Color2 = Colors.Yellow
        });
        ColorCollection = colors;
    }

    private Collection<ColorGradientHelper> _colorCollection;

    public Collection<ColorGradientHelper> ColorCollection {
        get {
            return _colorCollection;
        }
        set {
            _colorCollection = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

And that output looks like this:

enter image description here