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):

image

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):

image

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>
kick it