One of the more popular eye-candy effects is reflection, and WPF can do really good reflections, this post on Xamlog shows a really nice technique to add reflection to just about anything.
When I was showing this technique to a co-worker he asked me if it can be encapsulated in a control, well, why not?
So I quickly wrote this control, the control is a "Decorator" like a Border, that means you can add one child to it and it draws itself around that child (obviously this child can be a panel with multiple children).
This control is divided into two halves the upper half displays the child control and the lower half the reflection.
Here is an example of a window with a reflected login box:
Notice that the reflection is "live", every change in the controls (including the blinking caret is immediately automatically updated in the reflection).
Here is the XAML for the window, the parts that add the reflection are marked in yellow:
<Window x:Class="ReflectionControlApp2.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:ReflectionControlApp2"
Title="ReflectionControlApp2" Height="300" Width="300"
>
<Window.Background>
<LinearGradientBrush StartPoint="0,0.3" EndPoint="1,0">
<GradientStop Color="#C4CBD8" Offset="0" />
<GradientStop Color="#E0E4F0" Offset="0.3" />
<GradientStop Color="#E6EAF5" Offset="0.5" />
<GradientStop Color="#CFD7E2" Offset="0.9" />
<GradientStop Color="#C4CBD8" Offset="1" />
</LinearGradientBrush>
</Window.Background>
<r:ReflectionControl>
<Border BorderBrush="DarkGray" BorderThickness="3"
CornerRadius="5" Background="#CFD7E2">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Welcome" FontSize="18" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="2"/>
<Label Content="User Name:" Grid.Row="1" Margin="2"/>
<TextBox Grid.Row="1" Grid.Column="1" Margin="2" />
<Label Content="Password:" Grid.Row="2" Margin="2"/>
<PasswordBox Grid.Row="2" Grid.Column="1" Margin="2"/>
<Button Grid.Row="3" Grid.ColumnSpan="2" Content="Login" Margin="2" Width="80"/>
</Grid>
</Border>
</r:ReflectionControl>
</Window>
And here is the complete code for the reflection control itself:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ReflectionControlApp2
{
class ReflectionControl : Decorator
{
private VisualBrush _reflection;
private LinearGradientBrush _opacityMask;
public ReflectionControl()
{
// Set defaults for this control
VerticalAlignment = VerticalAlignment.Center;
HorizontalAlignment = HorizontalAlignment.Center;
// Create brushes were going to use
_opacityMask = new LinearGradientBrush();
_opacityMask.StartPoint = new Point(0, 0);
_opacityMask.EndPoint = new Point(0, 1);
_opacityMask.GradientStops.Add(new GradientStop(Colors.Black, 0));
_opacityMask.GradientStops.Add(new GradientStop(Colors.Black, 0.5));
_opacityMask.GradientStops.Add(new GradientStop(Colors.Transparent, 0.8));
_opacityMask.GradientStops.Add(new GradientStop(Colors.Transparent, 1));
_reflection = new VisualBrush();
_reflection.Stretch = Stretch.None;
_reflection.TileMode = TileMode.None;
_reflection.Transform = new ScaleTransform(1, -1);
}
protected override Size MeasureOverride(Size constraint)
{
// We need twice the space that our content needs
if (Child == null)
{
return new Size(0, 0);
}
Child.Measure(constraint);
return new Size(Child.DesiredSize.Width, Child.DesiredSize.Height * 2);
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
// always put out content at the upper half of the control
if (Child == null)
{
return new Size(0, 0);
}
Child.Arrange(new Rect(0, 0, arrangeBounds.Width, arrangeBounds.Height / 2));
return arrangeBounds;
}
protected override void OnRender(DrawingContext drawingContext)
{
// draw everything except the reflection
base.OnRender(drawingContext);
// set opacity
drawingContext.PushOpacityMask(_opacityMask);
drawingContext.PushOpacity(0.3);
// set reflection parameters based on content size
_reflection.Visual = Child;
((ScaleTransform)_reflection.Transform).CenterY = 3 * ActualHeight / 4;
((ScaleTransform)_reflection.Transform).CenterX = ActualWidth / 2;
// draw the reflection
drawingContext.DrawRectangle(
_reflection, null,
new Rect(0, ActualHeight / 2, ActualWidth, ActualHeight / 2));
// cleanup
drawingContext.Pop();
drawingContext.Pop();
}
}
}
posted @ Wednesday, November 21, 2007 11:36 AM