WPF: Button template w/ cornerRadius and Bevel looks wrong - wpf

I changed the ControrTemplate of a button, gave it a border with CornerRadius and BevelBitmpaEffect. Looks decent enough for the minimal effort. Put it on a colored background and then the problem becomes apperent.
The corner where the lightangle is coming from there is a white snip with a right angle corner. Possibly coming from the light effect but very obvious with a cornerRadius. I don't suppose I could do anything about it ( aside from the obvious like not using a cornerRadius )?
EDIT:
This code should generate the same problem for you
<Style x:Key="TabButtons" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="8"
SnapsToDevicePixels="True"
Name="BtnBorder"
Width="80"
Height="35"
BorderThickness="1"
BorderBrush="DarkBlue">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="LightBlue" Offset="0" />
<GradientStop Color="DarkBlue" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<Border.BitmapEffect>
<BevelBitmapEffect BevelWidth="5"
EdgeProfile="CurvedOut"
LightAngle="135"
Relief="0.1"
Smoothness="1" />
</Border.BitmapEffect>
<ContentPresenter Name="BtnContent" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Use this button on a LightBlue Background for example
<Border Background="LightBlue" >
<Button Style={StaticResource TabButtons} >Test</Button>
</Border>

I can't imagine what the problem exactly looks like, but here is one suggestion - try setting the SnapsToDevicePixels to true for the affected button or perhaps the entire window.
The problem might stem from the fact that WPF by default wants to do device independent rendering. Which causes control edges to slightly "bleed" to adjacent pixels due to width/height and DPI/pixels not aligning exactly. Setting SnapsToDevicePixels to true, will force WPF to render exactly for the device pixels and the "bleeding" effect will dissapear...
EDIT:
I tried out the code you provided and I guess the "white snip" is the white(ish) pixels around the corners?
Well the problem here stems from antialiasing, which also lightens the pixels inside the corners. Because you use a dark tone, those lighter pixels stand out and the button doesn't look so nice.
I'm not sure if this can be considered a bug. You would think that antialising will do it's magic on the pixels that make up the corner and outside of the corner, but every pixel inside of the corner should be colored with the background color.
Then again this might be by design, that antialiasing has to mess with the pixels inside the corner as well otherwise you don't get the required effect...
Anyway there are two workarounds:
1) Give the border the following property RenderOptions.EdgeMode="Aliased". This will turn off antialiasing (0% white(ish) pixels), but the controll will look very jaggedy.
2) Use two borders. The outer border will be defined just like you have defined your current border, only set the Background property to just DarkBlue. Then for this outer border create another border as a child element. For this inner border set CornerRadius=7, RenderOptions.EdgeMode="Aliased" and define the Background with the LinearGradientBrush. The inner border will contain the ContentPresenter. The code will look like this:
<Border CornerRadius=8 Background=DarkBlue>
<Border CornerRadius=7 RenderOptions.EdgeMode="Aliased">
<Border.Background>
...
</Border.Background>
<ContentPresenter />
</Border>
</Border>
It won't get rid of the white pixels a 100%, but this is the best compromise I think. With the naked eye I couldn't see any white pixels, but when I zoomed in with the magnifier tool I counted only 2 pixels per corner and only at the bottom corners. Before it was something like 8 pixels per corner. At least the outer border is still antialiased and the control doesn't look jaggedy.
The BevelBitmapEffect didn't make any difference to the appearance of the control so I just ommited it.

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.

WPF Custom Border - bracket style

I would like to create a custom border around a content, that the upper and lower side of the border is not continuous, it should have a style like bracket as below. (inside the "bracket border" the content should be placed e.g. grid, stackpanel, etc.)
Note that the height of the right and the left border can be changed depending on the content's height, whereas the top and the bottom should have a standard width.
[ sample content ]
In order to achieve this, I separated the view in a 3 columns grid:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="22px"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="22px"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderBrush="Blue" BorderThickness="6px 6px 0px 6px"></Border>
<Border Grid.Column="1" BorderBrush="Transparent" BorderThickness="6px">
<!--Place for the actual content of the border-->
<TextBlock Text="Test" FontSize="15"></TextBlock>
</Border>
<Border Grid.Column="2" BorderBrush="Blue" BorderThickness="0px 6px 6px 6px"></Border>
</Grid>
Is there another approach for achieving this style?
One possible solution is to write your own Border based on a Decorator.
An implementation (for a different border) can be found in How can I draw a border with squared corners in wpf?
One simple trick is try setting some LinearGradientBrush for the BorderBrush. If your text has a fixed width, it will look best at all time. However when the text's width may change, the horizontal lines (at 2 ends) will shrink/extend at some ratio. That's because we set some Offset for the GradientStop and it's pity that this Offset can only be set based on some ratio (from 0 to 1) with the width of the whole Brush (which is exactly the width of the Border when the background is stretched). Note that the MappingMode cannot change this behavior, it just works for StartPoint and EndPoint.
Here is the pure XAML code:
<Border VerticalAlignment="Center" HorizontalAlignment="Center"
BorderThickness="3" Padding="5,0,5,0">
<TextBlock Text="Square bracket border here" FontSize="30"
HorizontalAlignment="Center"/>
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,1" EndPoint="1,1">
<GradientStop Offset="0.03" Color="Blue"/>
<GradientStop Offset="0.03" Color="Transparent"/>
<GradientStop Offset="0.97" Color="Transparent"/>
<GradientStop Offset="0.97" Color="Blue"/>
</LinearGradientBrush>
</Border.BorderBrush>
</Border>
You can change the Offset of the first 2 GradientStops to what you want, the Offset of the remaining GradentStops should be the subtraction of the first's Offset from 1.
If using some code behind, you can pinpoint exactly the length of the horizontal lines (at the 2 ends). That way we need some Binding between the Offset and the ActualWidth of the Border. Next we need some Converter here, this Converter will convert the ActualWidth and the desired exact length to the correct ratio. So when the text width changes, the length of the horizontal lines will always be some fixed value.
You can try the following XAML code:
<Grid>
<Border BorderBrush="Black" BorderThickness="1"/>
<TextBlock Text="sample content"/>
<Border BorderBrush="White" BorderThickness="0,1,0,1" Margin="8,0,8,0"/>
</Grid>
Second border's color "White" can be replaced with the actual background color. "Transparent" color will not help.
Thanks,
RDV

Aero white glow in WPF?

How do I add the Aero white glow behind a control in WPF?
I mean the glow like in a window's caption bar in Vista/7.
This unrelated question seems to have the answer...
Outer bevel effect on text in WPF
I just needed a rectangle behind my text block...
<Rectangle Height="{Binding ElementName=textBlock1, Path=ActualHeight}" HorizontalAlignment="{Binding ElementName=textBlock1, Path=HorizontalAlignment}" Margin="{Binding ElementName=textBlock1, Path=Margin}" VerticalAlignment="{Binding ElementName=textBlock1, Path=VerticalAlignment}" Width="{Binding ElementName=textBlock1, Path=ActualWidth}" Fill="White" Opacity="0.5">
<Rectangle.Stroke>
<SolidColorBrush />
</Rectangle.Stroke>
<Rectangle.Effect>
<BlurEffect Radius="10" KernelType="Gaussian" />
</Rectangle.Effect>
</Rectangle>
This is gonna do the trick for any control when you replace all the "textBlock"s in the code!
And it looks exactly the same as the aero glow!
Does this help? The effect can be quite subtle but it does look alright.
I tried messing about with Pixel Shaders in WPF using Shazzam but it was a lot of work to achieve the same effect or better. They use a lot of "under the hood" stuff (such as double-pass shaders) of which the techniques aren't available to the WPF api.

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.

blurred opacity

I need to create a transparent blurred background. Lets suppose I have a border with a white blurry transparent background. Everything that is behind the border is blurred.
I'm trying to avoid dependencies; I'm currently using .NET 3.0, and want it to run with XP too.
Mockup image:
A VisualBrush can be used to get close to what you want, but has some limitations.
As long as you only need the glass effect within the window (and not be an effect over other windows) and that the placement of the glass effect border is controlled tightly then you could you something like this:-
<Grid>
<Border x:Name="src" Background="Silver">
<Label HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="50">Hello World</Label>
</Border>
<Border Background="White" Margin="40" >
<Border Opacity="0.5" >
<Border.Effect>
<BlurEffect Radius="10"/>
</Border.Effect>
<Border.Background>
<VisualBrush Visual="{Binding ElementName=src}" Stretch="None" />
</Border.Background>
</Border>
</Border>
</Grid>
I don't think that a child element within the visual tree is able to get the VisualBrush of it's parent so this might be a limitation for you. (i.e. the glass panel cannot be contained by the background panel)
I've used VisualBrushes many times usually with TranslateTransforms to move them around a bit to get the right image in the right place.
Update:
Altered XAML to use Effect and not BitmapEffect that is slower and now depreciated as mentioned in the comments below by Steven Robbins.
I would imagine you will need use an Effect for this, applied to a background rectangle or grid.
There's a decent library of effects here if that floats your boat.
Use Vista glass.

Resources