As you know Silverlight 3 doesn’t support VisualBrush, which can make things like this pretty tricky.
Here’s my attempt at a workaround 🙂 It’s a control called VisualImage which can be pointed at any element and exposes it as a WriteableBitmap. You could bind an Image to this to create a reflection effect like this (don’t forget to look at Jeff Prosise’s sample):
You could even bind it to an ImageBrush… if it supported binding. To work around this, VisualImage can be bound to the ImageBrush instead. One application of this is for clipped, rounded corners on any element (here’s the WPF way):
You can try a live sample here (source on CodePlex).
Although VisualImage is a Control, it doesn’t render anything itself – it just acts as an intermediary between your visual and whatever you want to bind it to. Here’s everything you need (also available here):
/// <summary> /// VisualImage /// </summary> public class VisualImage : Control { #region Visual DependencyProperty public static readonly DependencyProperty VisualProperty = DependencyProperty.Register( "Visual", typeof( FrameworkElement ), typeof( VisualImage ), new PropertyMetadata( OnVisualChanged ) ); public FrameworkElement Visual { get { return (FrameworkElement)GetValue( VisualProperty ); } set { SetValue( VisualProperty, value ); } } private static void OnVisualChanged( DependencyObject obj, DependencyPropertyChangedEventArgs args ) { var visualImage = obj as VisualImage; visualImage.OnVisualChanged( args ); } private void OnVisualChanged( DependencyPropertyChangedEventArgs args ) { if ( args.OldValue != null ) ( (FrameworkElement)args.OldValue ).SizeChanged -= VisualImage_SizeChanged; if ( args.NewValue != null ) { var visual = (FrameworkElement)args.NewValue; visual.SizeChanged += VisualImage_SizeChanged; PrepareBitmap( (int)visual.RenderSize.Width, (int)visual.RenderSize.Height ); } } private void VisualImage_SizeChanged( object sender, SizeChangedEventArgs e ) { PrepareBitmap( (int)e.NewSize.Width, (int)e.NewSize.Height ); } #endregion // Visual DependencyProperty #region Bitmap DependencyProperty public static readonly DependencyProperty BitmapProperty = DependencyProperty.Register( "Bitmap", typeof( WriteableBitmap ), typeof( VisualImage ), null ); public WriteableBitmap Bitmap { get { return (WriteableBitmap)GetValue( BitmapProperty ); } set { SetValue( BitmapProperty, value ); } } #endregion // Bitmap DependencyProperty #region ImageBrush DependencyProperty public static readonly DependencyProperty ImageBrushProperty = DependencyProperty.Register( "ImageBrush", typeof( ImageBrush ), typeof( VisualImage ), null ); public ImageBrush ImageBrush { get { return (ImageBrush)GetValue( ImageBrushProperty ); } set { SetValue( ImageBrushProperty, value ); } } #endregion // VisualBrush DependencyProperty /// <summary> /// Initializes a new instance of the <see cref="VisualImage"/> class. /// </summary> public VisualImage() { } /// <summary> /// Prepares the bitmap. /// </summary> /// <param name="width">The width.</param> /// <param name="height">The height.</param> private void PrepareBitmap( int width, int height ) { Bitmap = new WriteableBitmap( width, height ); Invalidate(); } /// <summary> /// Invalidates the VisualImage and causes WriteableBitmap to be refreshed. /// </summary> public void Invalidate() { if ( Bitmap != null && Visual != null ) { Array.Clear( Bitmap.Pixels, 0, Bitmap.Pixels.Length ); Bitmap.Render( Visual, this.RenderTransform ); Bitmap.Invalidate(); if ( ImageBrush != null && ImageBrush.ImageSource != Bitmap ) { ImageBrush.ImageSource = Bitmap; } } } }
For performance reasons it only refreshes the WriteableBitmap when the target Visual’s size changes. You can call the Invalidate() method to force a refresh (consider calling it from CompositionTarget.Rendering if you want it to refresh every frame).
Here’s how to get rounded corners on anything (similar to WPF technique, with added VisualImage and named ImageBrush):
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Border x:Name="mask" Background="White" CornerRadius="20" Padding="10"/> <local:VisualImage Name="visualImage" Visual="{Binding ElementName=mask}" ImageBrush="{Binding ElementName=brush}"/> <Image Source="http://farm2.static.flickr.com/1429/1430528819_edb63b79a6.jpg"> <Image.OpacityMask> <ImageBrush x:Name="brush"/> </Image.OpacityMask> </Image> </Grid>
And here’s a reflection:
<TextBlock x:Name="myText" FontSize="96">Hello</TextBlock> <local:VisualImage Name="reflectImage" Visual="{Binding ElementName=myText}"/> <Image Source="{Binding Bitmap, ElementName=reflectImage}" RenderTransformOrigin="0.5,0.2"> <Image.RenderTransform> <ScaleTransform ScaleY="-0.8"/> </Image.RenderTransform> <Image.OpacityMask> <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Offset="0" Color="#00FFFFFF"/> <GradientStop Offset="1" Color="#80FFFFFF"/> </LinearGradientBrush> </Image.OpacityMask> </Image>
[…] Silverlight VisualBrush and rounded corners « Chris Cavanagh’s Blog // September 24, 2009 at 8:23 pm | Reply […]
[…] March 16, 2009 · 2 Comments UPDATE: Silverlight 3 joins in the fun! […]
[…] Silverlight VisualBrush and rounded corners (Chris Cavanagh) […]
[…] is a very useful features in RIA. However, it’s only available in WPF. Chris Cavanagh has a work around to simulate VisualBrush using WriteableBitmap. You are able to apply this […]
Chris
Can this be used to put rounded corners on ScrollViewers too?
Tushar – You should be able to apply it to any element 🙂
Thanks!
I only asked cos all the examples included Images or ImageBrushes.
Hi there,
very good feature, but how can I set CornerRadius in code?
It does not work for me if I set cornerRadius without binding or in code.
Thank you.
If I try to apply a drop shadow effect to the control, it looks right in the preview window, however when I run it in browser it has weird effects along the right edge (code included below). If I comment out the Effect code then the weirdness goes away. If I instead comment out the VisualImage code it also fixes the issue. Can VisualImage and DropShadowEffect not co-exist?
<Grid
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:l=”clr-namespace:SLTest”
x:Class=”SLTest.MainPage”
Width=”180″ Height=”180″>
<Grid.Effect>
<DropShadowEffect BlurRadius=”5″ ShadowDepth=”5″/>
</Grid.Effect>
<Border Name=”mask” CornerRadius=”5″ BorderBrush=”Black” Background=”Black”/>
<l:VisualImage x:Name=”myRoundedImage” Visual=”{Binding ElementName=mask}” ImageBrush=”{Binding ElementName=myRoundedBrush}”/>
<Grid Background=”Gray” HorizontalAlignment=”Stretch”>
<Grid.OpacityMask>
<ImageBrush x:Name=”myRoundedBrush”/>
</Grid.OpacityMask>
<TextBlock Text=”Hello”/>
</Grid>
</Grid>
Ryan – Could you send me some screenshots that show the problem? (silverlight at chriscavanagh.com).
Ryan – Not sure if it’ll help, but did you try putting the DropShadowEffect on the “mask” border instead? Or maybe wrap the outer Grid with another grid, and give the outer its own Border with a DropShadowEffect? Thinking something like this (the outer border will stretch itself to the same size as its content)…
Grid
..Border
….DropShadowEffect
..Grid
….Border (“mask”)
….Visualmage
….Grid
……OpacityMask
……Content…
I did try putting it on the mask before I posted and it caused the grid that i was attempting to mask with VisualImage to bleed through into the shadow (shadow area became part of the visible area of grid). I just tried your other suggestion though and it seems to be working. Placing a border in parallel with the grid containing the mask seems to fix the issue.
Hello Chris Cavanagh,
i want to implement the VisualBrush in SL, where, I am not using reflection. I just want to create a wavy line using a path. I will show you the requirement.
I want to paint the above visual brush repeatedly. so, that it forms a wavy line. Could please help me on this?
I have attached the Code here
https://skydrive.live.com/redir?resid=C35534687298D6CE!167&authkey=!APjaLiaTvxrO3r0
Salam – I’ll take a look at this; it’s been a while since I looked at this VisualBrush stuff though! 🙂
Salam – Sorry got distracted a couple months… Let me know if you didn’t get it figured out and I’ll take a look 🙂