WPF Gradient in 2 directions

Muis picture Muis · Dec 6, 2010 · Viewed 7.4k times · Source

If i want to make the edges of a selected item in a Listbox look smooth I do this:

<Setter Property="Background" TargetName="Bd">
     <Setter.Value>
         <LinearGradientBrush EndPoint="0,0" StartPoint="1,0">
           <GradientStop Offset="0" Color="Transparent"/>
           <GradientStop Offset="0.05" Color="{x:Static SystemColors.HighlightColor}"/>
           <GradientStop Offset="0.95" Color="{x:Static SystemColors.HighlightColor}"/>
           <GradientStop Offset="1" Color="Transparent"/>
         </LinearGradientBrush>
     </Setter.Value>
 </Setter>

However, this only makes the left and right edge smooth, not the top and bottom. If I change StartPoint and EndPoint, I can make the top and bottom smooth, but then I loose the smoothness on the left&right sides. So how can I make all 4 borders smooth using a Gradient brush?

Answer

Ian Griffiths picture Ian Griffiths · Dec 6, 2010

The OpacityMask is one way to do this, as others have already suggested, but it's slightly challenging because you cannot set an OpacityMask on a brush. You can only set it on a visual - OpacityMask is something that is done on a per-visual basis. But the Background of a ListBox isn't a separate element in the visual tree - it's just a property of the ListBox, and it's usually template bound to something like a Border element somewhere in the template.

Same goes for the use of bitmap effects that some people have suggested here - those are also applied to whole visuals, not to individual brushes.

However, you can handle this with a VisualBrush - that lets you define a brush by using a visual tree. So I think this does roughly what you're looking for:

<Setter Property="Background" TargetName="Bd">
  <Setter.Value>
    <VisualBrush>
      <VisualBrush.Visual>
        <Rectangle Width="1" Height="1">
          <Rectangle.Fill>
            <LinearGradientBrush EndPoint="0,0" StartPoint="1,0">
              <GradientStop Offset="0" Color="Transparent"/>
              <GradientStop Offset="0.05" Color="{x:Static SystemColors.HighlightColor}"/>
              <GradientStop Offset="0.95" Color="{x:Static SystemColors.HighlightColor}"/>
              <GradientStop Offset="1" Color="Transparent"/>
            </LinearGradientBrush>
          </Rectangle.Fill>
          <Rectangle.OpacityMask>
            <LinearGradientBrush EndPoint="0,0" StartPoint="0,1">
              <GradientStop Offset="0" Color="Transparent"/>
              <GradientStop Offset="0.05" Color="White"/>
              <GradientStop Offset="0.95" Color="White"/>
              <GradientStop Offset="1" Color="Transparent"/>
            </LinearGradientBrush>
          </Rectangle.OpacityMask>
        </Rectangle>
      </VisualBrush.Visual>
    </VisualBrush>
  </Setter.Value>
</Setter>

The corners might not be quite what you're looking for though - depends on how big they end up being. They don't look especially round when you use this technique. So you could go down the effect route. You might prefer this:

<Setter Property="Background" TargetName="Bd">
  <Setter.Value>
    <VisualBrush Viewbox="0.1,0.1,0.8,0.8">
      <VisualBrush.Visual>
        <Border Width="100" Height="100" CornerRadius="10"
             Background="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}">
          <Border.Effect>
            <BlurEffect Radius="20"/>
          </Border.Effect>
        </Border>
      </VisualBrush.Visual>
    </VisualBrush>
  </Setter.Value>
</Setter>

Note that I've used an Effect rather than a BitmapEffect. You have fewer options with Effect, but they're usually a better option as they're designed to render in hardware.