Yecats
Yecats

Reputation: 1815

How do you bind multiple colors to fill a rectangle in WPF?

I am binding an ItemsControl to an observable collection called SwatchThumbnails which will contain the data in order to generate rectangles. The rectangle can have between 1-3 colors associated with it, and needs to display that many colors when it renders. I think I understand how to bind just one color to the fill, but I need a way to bind up to three and have them display evenly. How can this be done?

Here is my XAML:

<ScrollViewer x:Name="sv_Thumbnails" Grid.ColumnSpan="2" Grid.Row="1" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto">
    <ItemsControl x:Name="ug_Thumbnails" ItemsSource="{Binding SwatchThumbnails, ElementName=mainWindow}">
         <ItemsControl.ItemsPanel >
             <ItemsPanelTemplate>
                <UniformGrid Columns="6" RenderTransformOrigin="0,0.5" Cursor="Hand">
                  <Rectangle Width="50" Height="50" Fill="{Binding Color}" Margin="0 0 5 0" />
                </UniformGrid>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</ScrollViewer>

I was originally inserting the rectangle shape into the UniformGrid via C# code, so I have the original code that was written for splitting out the colors. Basically, this was the original code that I had for generating the rectangles:

        System.Windows.Shapes.Rectangle swatch = new System.Windows.Shapes.Rectangle();
        swatch.Width = 50;
        swatch.Height = 50;
        swatch.Margin = new Thickness(0, 5, 5, 0);
        swatch.StrokeThickness = 1;
        swatch.Stroke = System.Windows.Media.Brushes.Gray;
        swatch.Name = "s_" + _name.ToString();
        double groupsize = 100 / _colors.Count();
        DrawingBrush blackBrush = new DrawingBrush();
        DrawingGroup checkersDrawingGroup = new DrawingGroup();
        List<SolidColorBrush> brushes = _colors;
        double location = 0;
        for (int i = 0; i < _colors.Count(); i++)
        {
            GeometryDrawing drawing = new GeometryDrawing(brushes[i], null,
                new RectangleGeometry(new Rect(0, location, groupsize, groupsize)));
            checkersDrawingGroup.Children.Add(drawing);
            location += groupsize;
        }
        blackBrush.Drawing = checkersDrawingGroup;
        swatch.Fill = blackBrush;
        swatch.MouseUp += new MouseButtonEventHandler(loadSwatchResources);

Lastly, here's my observable collection and swatch thumbnail definition.

    private ObservableCollection<SwatchThumbnail> swatchThumbnails = new ObservableCollection<SwatchThumbnail>();
    public ObservableCollection<SwatchThumbnail> SwatchThumbnails 
    {
        get { return swatchThumbnails; }
        set { swatchThumbnails = value; }
    }

public class SwatchThumbnail : INotifyPropertyChanged
{
    public string name { get; set; }
    public List<Color> colors { get; set; }
    public bool selected { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
}

Upvotes: 2

Views: 3218

Answers (2)

King King
King King

Reputation: 63377

The Background you want for each thumbnail in fact looks like a vertical LinearGradientBrush so the best choice here is LinearGradientBrush, not DrawingBrush which is too complicated (and overkill) for this scenario. You need some Colors property (with INotifyPropertyChanged implemented) holding the Colors range. To convert this to a LinearGradientBrush, you need a Converter. I understand that all the color bands should have equal Height (and full Width). Fortunately that the Offset in LinearGradientBrush is relative (by some ratio) to the whole Height, so we don't need any info on the actual Height of the related Rectangle. Here is the Converter class:

public class ColorsToLinearGradientBrushConverter : IValueConverter {
   public object Convert(object value, Type targetType, object parameter,
                                                        CultureInfo culture){
      var colors = (List<Color>) value;
      var brush = new LinearGradientBrush(){ StartPoint = new Point(),
                                             EndPoint = new Point(0,1)};
      var w = 1d / colors.Count;
      for(var i = 0; i < colors.Count - 1; i++){
         var offset = w * (i+1);
         var stop1 = new GradientStop(colors[i], offset);
         var stop2 = new GradientStop(colors[i+1], offset);
         brush.GradientStops.Add(stop1);
         brush.GradientStops.Add(stop2);
      }
      return brush;
   }
   //this way back is not needed (bind OneWay)
   public object ConvertBack(object value Type targetType, object parameter,
                                                           CultureInfo culture){
      throw new NotImplementedException();
   }
}

Note that the Colors property should have Type of List<Color> (your original property has Type of List<SolidColorBrush>).

Define your Converter as some Resource in your XAML and use it normally for the Binding (I hope you know this). Here is the XAML code:

<ScrollViewer x:Name="sv_Thumbnails" Grid.ColumnSpan="2" Grid.Row="1"
    VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto">
   <ScrollViewer.Resources>
     <local:ColorsToLinearGradientBrushConverter x:Key="brushConverter"/>
   </ScrollViewer.Resources>
   <ItemsControl x:Name="ug_Thumbnails" ItemsSource="{Binding SwatchThumbnails, 
                                                      ElementName=mainWindow}">
     <!--ItemsControl.ItemsPanel>
         <ItemsPanelTemplate>
            <UniformGrid Columns="6" RenderTransformOrigin="0,0.5" Cursor="Hand">
            </UniformGrid>
        </ItemsPanelTemplate>
     </ItemsControl.ItemsPanel-->

     <ItemsControl.ItemTemplate>
        <DataTemplate>
          <Rectangle Width="50" Height="50" Fill="{Binding Colors,
                                     Converter={StaticResource brushConverter}}" 
                         Margin="0 0 5 0" />
        </DataTemplate>
     </ItemsControl.ItemTemplate>
   </ItemsControl>
</ScrollViewer>

Note that you have to use some ItemTemplate here, your original XAML just has ItemsPanel which changes the whole container of the items. If this is what you want, just specify that panel normally but remember that it's just related to how to arrange and layout the items. That means we cannot bind Background for each item based on just that.

Update: I assumed that you implemented the SwatchThumbnail properly. But looks like you've not. So try the following implementation instead to see if it works:

public class SwatchThumbnail : INotifyPropertyChanged {
  public event PropertyChangedEventHandler PropertyChanged;
  protected void OnPropertyChanged(string prop){
    var handler = PropertyChanged;
    if(handler != null) handler(this, new PropertyChangedEventArgs(prop));
  }
  string _name;
  public string name {
    get { return _name;}
    set { 
       if(_name != value) {
          _name = value;
          OnPropertyChanged("name");
       }
    }
  }
  List<SolidColorBrush> _colors;
  public List<SolidColorBrush> colors {
    get {
       return _colors;
    }
    set {
       _colors = value;
       OnPropertyChanged("colors");
    }
  }
  bool _selected;
  public bool selected {
    get {
       return selected;
    }
    set {
       if(_selected != value){
          _selected = value;
          OnPropertyChanged("selected");
       }
    }
  }
}

Upvotes: 2

pgenfer
pgenfer

Reputation: 622

As far as I know the easiest way would be to use a DrawingBrush instead of a SolidColorBrush.

Your DrawingBrush would describe the pattern you use to fill your Rectangle (in your case, you would use your three SolidColor brushes to draw the content of the DrawingBrush). The Fill property of your rectangle should then be bound to the DrawingBrush.

See a description on how to use a DrawingBrush as Rectangle fill here.

Upvotes: 0

Related Questions