views:

458

answers:

3

I want to be able to have a dynamic Gradient in Silverlight, such as below:

<RadialGradientBrush GradientOrigin="0.20,0.5" Center="0.25,0.50" 
                     RadiusX="0.75" RadiusY="0.5">
  <GradientStop Color="{Binding Path=GradientStart}" Offset="0" />
  <GradientStop Color="{Binding Path=GradientEnd}" Offset="1" />
</RadialGradientBrush>

I am binding to two properties which return the type "Color" however I always get this message:

AG_E_PARSER_BAD_PROPERTY_VALUE

If I try to bind to a GradientStop Collection this also has the same problem, what is the solution to this problem that:

  1. Allows the start and end of a Gradient to be changed at runtime
  2. Works in Silverlight 3.0 and is not a WPF solution

If there is a work around or anyway to duplicate this behaviour this would be acceptable, I have solutions that work with LinearGradients as I can just bind somethings "Fill" property to this - however in this situation that won't work, plus there may be other gradient types I may use and others may use in future which this solution / alternative will apply to.

A: 

Have you confirmed the type being used as the DataContext where your gradient brush is defined? As you haven't specified a Source in your binding, it will use the DataContext by default.

Drew Noakes
This is fine as other items such as SolidColorBrushes, where this comes from can bind to values without a problem - this error occurs when the program runs - it would be nice if it said what was wrong with the bound type - but it never gets that far.
RoguePlanetoid
I've done more WPF development than Silverlight development, but when running a WPF app with bad bindings, quite useful messages are spat out in the 'Output' panel of Visual Studio. They show the binding path, target type and source type. Do you see that?
Drew Noakes
No but I have seen those messages when there is a conversion, such as binding a string to a brush for example, however binding to colours does not seem to be possible or to gradient stops as it never gets this far as it accepts no binding - which explains the error.
RoguePlanetoid
When I first read your question I looked in .NET Reflector at the `GradientStop` class and saw that `Color` does actually look like a dependency property. I wonder why that's the case, even though `GradientStop` is not a `FrameworkElement`.
Drew Noakes
+4  A: 

The problem is that GradientStop does not derive from FrameworkElement therefore cannot be data bound. Unfortunately that means you have to set it from code.

Shawn Wildermuth
Thanks for this answer, I have heard of items being derived from FrameworkElement, so this is one that is not - guess Colours would be the same - now I understand, I'll set it in code as there really is no alternative - maybe Silverlight 4 will allow this?
RoguePlanetoid
+1 for this. I was originally going to answer that this wasn't possible because `Color` wasn't a dependency property. A brief look in .NET Reflector shows that it follows that pattern, so I was confused. Turns out that `Color` is created via `Animatable.RegisterProperty`. So even though it's not bindable, it is animatable.
Drew Noakes
@RoguePanetiod: Actually things are a little more difficult than that. Typically the Brush is a referenced object, new instances of the brush are not created per item. Hence if you modify the brush referenced by one item you may well find you are modifying the brush reference by all the other items as well.
AnthonyWJones
+2  A: 

To really make this happen you have two choices.

Bind the display items Brush property to a Brush property in the data

Have the data source carry a property which exposes which brush you want to use on for each item and you bind the property of the display item that takes the brush, say a Fill property. This works if the set of distinct values you would have for pairs of Start and Stop values is small. You'd create an instance of each brush for each pair and then data item would expose the correct one.

Bind the display items Brush property using a Value Converter

If your Start and Stop values a more variable you will need a new instance of a Brush type for each displayed item. In this case you would bind the display items brush property using a Value converter, for example :-

 <Rectangle Fill="{Binding Converter={StaticResource MyBrushBuilder} }" ... >

See this answer for a complete description of building a Converter.

In this case though your convert method implementation would look like this:-

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
  YourItemsType item = (YourItemsType)value;

  var start = new GradientStop()
  start.Offset = 0;
  start.Color = item.GradientStart;

  var stop = new GradientStop()
  start.Offset = 1;
  start.Color = item.GradientStop;

  var result = new RadialGradientBrush();
  result.GradientOrigin = new Point(0.20, 0.5);
  result.Center = new Point(0.25, 0.5);
  result.RadiusX = 0.75;
  result.RadiusY = 0.5;
  result.GradientStops = new GradientStopCollection();
  result.GradientStops.Add(start);
  result.GradientStops.Add(stop);

  return result;
}

Caveat

Whenever data binding occurs a whole bunch of brushes are created one for each item. This may be expensive and undesirable. Hence if this binding converter approach is deemed necessary then I would recommend you use static dictionary of brushes. The key into this dictionary would be the hash of the two colors. You would only create a new brush when necessary and reuse a previously created brush when possible.

AnthonyWJones