One of the things missing from Silverlight 3 is WPF’s Visualbrush. Visualbrush basically allows you to create a brush based on a visual element (including its visual changes), and use it for painting other elements. A common use is to create the fashionable reflection effect:
The above image is shamelessly stolen from Stefan Wick’s blog post.
Here is what the demo project looks like (sample requires Silverlight 3 Beta).
As you can see, a Visualbrush can be used not only to create reflections, but also to fill text, and other interesting things, too.
A key goal of mine was to create a good design-time experience. Unfortunately, I could only accomplish this goal partially: you will not get WYSIWYG preview in Blend, neither is databinding ideal. We also need some additional code in the codebehind - but it works, and requires only minimal XAML.
So, how does it work?
In Silverlight 3, you have Videobrush and Imagebrush. Unfortunately, these are sealed classes, and there doesn’t seem to be any way to create your own Brush (you can inherit from Brush or TileBrush, but what do do with it? Documentation is very sparse…). So, I could not just go ahead and create a custom brush.
Instead, I created a new Behavior. Behaviors and triggers are excellent new features that the Blend team is integrating into Blend 3. Basically, behaviors allow the interactive designer to add interactivity to UI elements from a pre-coded library, without writing any code. To really appreciate the power of Behaviors, check out this amazing Mix presentation: Creating Interactivity with Microsoft Expression Blend.
I am using Silverlight’s new Writablebitmap class to periodically (at every frame) render the Canvas into memory. Then, the source of an Imagebrush resource is changed to the Writablebitmap, and that’s it.
Here is the relevant code:
positionTransform.X = - AssociatedObject.Margin.Left - (double) AssociatedObject.GetValue(Canvas.LeftProperty);
positionTransform.Y = - AssociatedObject.Margin.Top - (double) AssociatedObject.GetValue(Canvas.TopProperty);
wb.Render(AssociatedObject, positionTransform);
Brush.ImageSource = wb;
Unfortunately, we have a big problem: It seems that binding to behaviors from XAML does not work in the Beta. To overcome this obstacle, I created the Brush that is used by the text and the reflection in the resources:
<ImageBrush x:Key="VisBrush" />
You can add the behavior to the Canvas (called vbSource here) by dragging it from the Assets panel onto the vbSource. By the way, the behaviors are a bit too difficult to find and use, maybe Blend should expose them more, at least in the Animation Workspace?
Now the only thing you need to do is set the BrushResourceName property to the resource key of the ImageBrush defined earlier, and the PoormansVisualBrush should be working properly. To save CPU cycles, you can also turn it off using the IsActive property.
note: using PoorMansVisualBrush can consume a lot of resources as the rendering of the brushed object is done twice: once to the screen, and once into the WritableBitmap. Use it sparingly and with caution!
Warning: this is just a proof-of-concept solution, and thus is sub-optimal and not fault tolerant and definitely not production quality. There are multiple ways to enhance it, such as the ability to render only every second, third, etc. frame thus saving a lot of CPU cycles.
You can download the source code of the entire solution here.
Posted
Mar 26 2009, 12:43 AM
by
vbandi