Dynamic Brushes in XAML - wpf

I have a definition for a data template that looks as follows:
<DataTemplate DataType="{x:Type HeatMap:BlockItem}">
<Grid Visibility="{Binding IsVisible}">
<Border Name="BlockBorder" Width="{Binding Width}" Height="{Binding Height}">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="{Binding Colour}" Offset="1"/>
<GradientStop Color="White"/>
</LinearGradientBrush>
</Border.Background>
</Border>
</Grid>
</DataTemplate>
As can be seen, BlockItem has a property of type Color called Colour which is bound to the first color of the LinearGradientBrush which fills a border, making it look like a filled rectangle.
Now I don't always want Linear Gradient Brushes to style the fill of this rectangle. Some rectangles on my canvas may need to be filled with SolidBrushes, for example. I considered creating a Brush property on the BlockItem class instead of a Color property and binding the entire Border.Background to that, but there are 2 problems with this:
I don't know how the XAML should look to specify a binding to the entire object Background property.
In the code where I create BlockItems, if I instantiate a new Brush for every single BlockItem (bear in mind, there maybe be many drawn on a canvas at a time), will this not make it really inefficient and slow?

1) You can bind the Background directly to a brush on your BlockItem:
<Border Name="BlockBorder" Background="{Binding MyBackgroundBrush}">
2) You could bind to a static resource, or create a static brush for your BlockItem.
Background="{StaticResource myStaticBrush}"

Related

How to add drop shadow to just one specific side in WPF?

I am trying to achieve a grid, with a shadow on just one side and no trace of any shadow on any of the other sides. I tried fiddling around with the direction property of the DropShadowEffect.
What I have tried:
<Grid Background="Transparent" Grid.Row="0" Grid.Column="1">
<Grid Background="White"/>
<Border CornerRadius="0,5,0,0" BorderBrush="White" BorderThickness="0" Background="White">
<Border.Effect>
<DropShadowEffect BlurRadius="5" Direction="355" RenderingBias="Quality" ShadowDepth="2"/>
</Border.Effect>
</Border>
</Grid>
</Grid>
This is what happens with my code:
I want to achieve a drop shadow only visible on the bottom side of the grid, and no trace of the shadow on any of the other sides. The above code leaves a thin gray trail on the left side, which wouldn't work for me.
Sorry if this is a silly question, I am kinda new to WPF.
I don't think the DropShadowEffect has any functionality built-in for this sort of application, however, I managed to achieve the required result using a rectangle and filling it with a linear gradient.
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Opacity="0.3">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="#00131313" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
To maintain the same width as the parent of the shadow, add them to the same grid and the same column, set the horizontal and vertical alignment to stretch and the shadow will look consistent.
Then I positioned the rectangle in place of the shadow. Seems a little wanky, but works nonetheless.
Edit:
I found another solution which seems way more better, using the ClipToBounds property and the BorderThickness property.
<Border ClipToBounds="True" BorderBrush="White" BorderThickness="0,2,0,0">
<Border.Effect>
<DropShadowEffect ShadowDepth="2" BlurRadius="10"/>
</Border.Effect>
</Border>
Using a border and a drop shadow is easier than using a rectangle and tweaking it till it looks natural.
Usage of grids is advised to position the border perfectly.

Why is the fill of an Ellipse not applied when the Stretch property is set inside a VisualBrush?

I'm working on a custom control and I have a VisualBrush with the Visual property as such:
<VisualBrush.Visual>
<Grid>
<Ellipse Stretch="Uniform" Stroke="OrangeRed" StrokeThickness="1">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5,0.5">
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Orange" Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Stretch="Uniform">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5, 0.05" RadiusX=".7" RadiusY=".5" >
<GradientStop Color="White" Offset=".10" />
<GradientStop Color="Transparent" Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
</VisualBrush.Visual>
When the Stretch property is set to Uniform instead of using the RadialGradientBrush I created, it somehow creates a SolidColorBrush using the Stroke color. However, when I explicitly set the Width and Height properties of the Ellipse instead of using the Stretch property, I get the gradient I expected.
Any ideas of what's going weird here?
EDIT: I just observed the behavior occurs when I enclose the Ellipse inside of a ViewBox.
I'm not sure what you're using the ViewBox for in your above example, but I think some of the strange behaviour you're seeing is a result of not setting the size on the root element for your VisualBrush.
In the documentation for VisualBrush.Visual, there's a paragraph that mentions sizing (added emphasis):
When you define a new Visual for a VisualBrush and that Visual is a
UIElement (such as a panel or control), the layout system runs on the
UIElement and its child elements when the AutoLayoutContent property
is set to true. However, the root UIElement is essentially isolated
from the rest of the system; styles, storyboards, and external layout
dictated by the parent where the brush is applied cannot permeate this
boundary. Therefore, you should explicitly specify the size of the
root UIElement, because its only parent is the VisualBrush and
therefore it cannot automatically size itself to the area being
painted. For more information about layout in Windows Presentation
Foundation (WPF), see the Layout.
If I run your code above, my ellipse is completely filled with the stroke colour, presumably because the the layout is calculated with the smallest value for the ellipse, so the stroke is large enough to cover ellipse content/fill (which is then upscaled to the fill the viewport so it looks like the stroke is the fill colour).
If I give the root element some arbitrary size on which to base the layout <Grid Width="100" Height="100">...</grid> I start to see the stroke and fill colours, rendered with relative sizes.

wpf gradient brush that spans multiple elements

Suppose there is an element with irregular shape composed of other elements with arbitrary nesting:
<Window.Resources>
<RadialGradientBrush x:Key="brush">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="White" Offset="1" />
</RadialGradientBrush>
</Window.Resources>
<StackPanel Name="element">
<StackPanel.Effect>
<DropShadowEffect ShadowDepth="0" BlurRadius="10" />
</StackPanel.Effect>
<Rectangle Name="child1" Height="100" Margin="10" Stroke="Black" Fill="{StaticResource brush}" />
<Grid>
<Rectangle Name="child2" Height="100" Margin="10" Stroke="Black" Fill="{StaticResource brush}" />
</Grid>
</StackPanel>
All parts are interactive (i.e a child can be a real control).
How can I fill backgrounds of children with a single radial gradient that spans all of them (should look like as if it is sized to element).
UPDATE: StackPanel has a shadow which should be drawn around children.
One possible solution is to generate gradients with Radius and Origin/Center bound to element's and child's properties with appropriate conversions, but such approach would be rather complex and expensive.
Finally, I went with transformed gradients route except instead of bindings and converters I was able to elegantly solve it by creating background Transformation in ArrangeOverride of child elements and exposing it via dependency property.
Actual RadialGradientBrush binds its Transform to BackgroundTransform by searching for ancestor with matching type.
This assumes that one can modify/wrap child classes which is true in my case. It can be made more flexible with attached properties and a bit more work.
Actual code is in this gist.

binding the background color of a gradient inside of a WPF listbox datatemplate

So I have a datatemplate and there is board in it, here is what I wan to do.
<Border Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="5">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Transparent"/>
<GradientStop
Color="{Binding Condition, Converter={StaticResourc ConditionTypeToColorConveter}}" Offset="0.541"/>
</LinearGradientBrush>
</Border.Background>
</Border>
I can see the converter being called no problem. but the color it returns isn't displayed. Now if I do this
<Border Grid.Column="0"
Grid.Row="4" Grid.ColumnSpan="5"
Background="{Binding Condition, Converter={StaticResourc ConditionTypeToColorConveter}}">
It works just fine - of course - I don't want it to be all one color I need it to be a gradient.
Anyone have any idea what is wrong with this? it's making me batty...
This can't work because in the first example you bind a Color in the second a Brush. What type your converter returns? You named it 'ToColor' but it is working as a Brush for Background.

Apply Brush to Two Objects At Once

I'd like to apply a Brush (LinearGradientBrush in this case) to two or more objects (TextBlocks) at once. The effect that I'd like is something like this:
Edit
I cannot simply apply the same brush to both objects, as this will make both of them start red and go to blue (instead of the second one starting at a shade of purple).
I'm sure I'm overlooking something quick-n-easy...
Thanks,
wTs
I did it like this:
<Border Height="100" Width="600" >
<Border.OpacityMask>
<VisualBrush>
<VisualBrush.Visual>
<StackPanel>
<TextBlock FontSize="85.333" FontFamily="Calibri" TextAlignment="Right">
The big first line
</TextBlock>
<TextBlock TextWrapping="Wrap" Margin="0,0,8,0" FontSize="32" FontFamily="Calibri" Text="The small second line" TextAlignment="Right" />
</StackPanel>
</VisualBrush.Visual>
</VisualBrush>
</Border.OpacityMask>
<Border.Background>
<LinearGradientBrush EndPoint="0.974,0.49" StartPoint="0,0.457">
<GradientStop Color="#FFFD0202"/>
<GradientStop Color="#FF0234FD" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
</Border>
So, a border whose background is filled with the gradient from blue to red. The only visible part of the gradient is the text in the opacity mask.
Maybe some simpler control than the border would be even better.
The remaining issue is that one has to size the container control explicitly, as it has no content.
What about using an ObjectDataProvider that exposes a method that returns the brush you want based on 3 integers, the starting x position of the brush, the current x position of the brush, and the ending x position of the brush (I could see use cases where you might want four parameters, x start, x end, x current start, x current end, but the 3 parameter will work for the case you have asked for). You can either statically assign these integers based on the layout of your control or use databinding for the parameters.
If you are unfamiliar with ObjectDataProvider and binding to method parameters for this class, i suggest going here and here
Declare the brush into the window (or application) resources and bind the Foreground property of the two textblocks with the brush.

Resources