Click here to Skip to main content
15,881,248 members
Articles / Web Development / XHTML
Article

DrawingBrush for Silverlight 2.0

Rate me:
Please Sign up or sign in to vote.
4.38/5 (5 votes)
12 May 2008CPOL3 min read 57K   442   14   11
This article is about how to build the missing DrawingBrush object for Silverlight. Also, we'll learn about how to deeply clone Silverlight objects.

Introduction

In WPF, we have a number of different brushes to use as background and fills for XAML controls. Unfortunately, in Silverlight, there is only a limited subset of the WPF brushes: some color brushes, ImageBrush, and VideoBrush. There is no DrawingBrush in Silverlight. Also, it looks like Microsoft has no plans to add this type of a brush in future versions of Silverlight.

Are we really in need of a DrawingBrush in Silverlight? The answer is "yes", if we want to paint controls with shapes or create a hatch background. Today, we'll build a basic version of a Silverlight DrawingBrush.

Background

What is, actually, DrawingBrush? This is a deep copy of existing controls in a page, used to paint different areas. So, in order to build a custom DrawingBrush in Silverlight, we need to first learn how to create a deep clone of Sivlerlight objects.

Deep cloning objects in Silverlight

Basically, in order to clone WPF objects, we should use the XamlReader/XamlWriter. The problem is, the XamlWriter is a protected class in Silverlight, and thus we cannot use this approach. So, what do we do? The answer is to use Reflection to deep clone objects.

First, we should create an additional instance of the target class:

C#
public static T Clone<T>(this T source)  where T : DependencyObject      
{       
    Type t = source.GetType();
    T no = (T)Activator.CreateInstance(t);

Then, travel recursively through all the DependencyPropertys and DependencyObjects and create a shallow copy of them:

C#
Type wt = t;
while (wt.BaseType != typeof(DependencyObject))
{       
    FieldInfo[] fi = wt.GetFields(BindingFlags.Static | BindingFlags.Public);
    for (int i = 0; i < fi.Length; i++)       
    {       
        {       
            DependencyProperty dp = fi[i].GetValue(source) as DependencyProperty;

When we have all the DependencyPropertys and their values, we can set them within the new instance of the object:

C#
if (dp != null && fi[i].Name != "NameProperty")      
{       
    DependencyObject obj = source.GetValue(dp) as DependencyObject;
    if (obj != null)       
    {       
        object o = obj.Clone();
        no.SetValue(dp, o);
    }
    else
    {

There are some read-only DependencyPropertys. We have no information about them when using Reflection, thus the only way to avoid setting their values is by using hard-coded expressions:

C#
{       
    if(fi[i].Name != "CountProperty" &&
        fi[i].Name != "GeometryTransformProperty" &&
        fi[i].Name != "ActualWidthProperty" &&       
        fi[i].Name != "ActualHeightProperty" &&
        fi[i].Name != "MaxWidthProperty" &&
        fi[i].Name != "MaxHeightProperty" &&
        fi[i].Name != "StyleProperty")
    {
        no.SetValue(dp, source.GetValue(dp));
    }
}

We are done with DependencyPropertys and DependencyObjects, and our next step is to get the CLR properties.

C#
PropertyInfo[] pis = t.GetProperties();      
for (int i = 0; i < pis.Length; i++)       
{ 
    if (pis[i].Name != "Name" &&
        pis[i].Name != "Parent" &&
        pis[i].CanRead && pis[i].CanWrite &&
        !pis[i].PropertyType.IsArray &&
        !pis[i].PropertyType.IsSubclassOf(typeof(DependencyObject)) &&
        pis[i].GetIndexParameters().Length == 0 &&
        pis[i].GetValue(source, null) != null &&
        pis[i].GetValue(source,null) == (object)default(int) &&
        pis[i].GetValue(source, null) == (object)default(double) &&
        pis[i].GetValue(source, null) == (object)default(float)
        )
          pis[i].SetValue(no, pis[i].GetValue(source, null), null);

This works fine for regular properties, but what about arrays? There are methods to set the array values.

C#
else if (pis[i].PropertyType.GetInterface("IList", true) != null)
{       
    int cnt = (int)pis[i].PropertyType.InvokeMember("get_Count",
               BindingFlags.InvokeMethod, null, pis[i].GetValue(source, null), null); 
    for (int c = 0; c < cnt; c++)
    {       
        object val = pis[i].PropertyType.InvokeMember("get_Item",
                     BindingFlags.InvokeMethod, null, pis[i].GetValue(source, null), 
                     new object[] { c }); 
        object nVal = val;      
        DependencyObject v = val as DependencyObject;       
        if(v != null)       
            nVal = v.Clone();
        pis[i].PropertyType.InvokeMember("Add", 
               BindingFlags.InvokeMethod, null, 
               pis[i].GetValue(no, null), new object[] { nVal }); 
    }       
}

By now, we are done with cloning; our next step is to use the cloned objects to create a DrawingBrush.

Using cloned objects to implement a DrawingBrush

All we have to do is add new objects to the visual and logical trees of our application. In order to do this, we'll create a new object, named DrawingBrush, with some properties: Pattern, Tile (inherited from TileBrush), and a Panel that will hold our cloned objects. I'm using a WrapPanel (that is also missing in Silverlight) in order to hold the new objects and tile them across the brush. You can get the source code and a description of how to implement it, from my blog.

C#
void SetPatternImpl(double width, double height)
{       
    Pattern = new WrapPanel();
    Pattern.Width = width;
    Pattern.Height = height;
    Pattern.HorizontalAlignment = HorizontalAlignment.Stretch;
    Pattern.VerticalAlignment = VerticalAlignment.Stretch;
    double xObj = (1 / this.Viewport.Width);
    double yObj = (1 / this.Viewport.Height);
    for (int i = 0; i < Math.Ceiling(xObj*yObj); i++)
    {       
        Shape ns = this.Drawing.Clone();
        ns.Stretch = this.TileMode == TileMode.None?Stretch.None:Stretch.Fill;
        ns.Width = Pattern.Width / xObj;
        ns.Height = Pattern.Height / yObj;
        ScaleTransform st = new ScaleTransform();
        st.ScaleX = this.TileMode == TileMode.FlipX | 
                    this.TileMode == TileMode.FlipXY ? -1 : 1;
        st.ScaleY = this.TileMode == TileMode.FlipY | 
                    this.TileMode == TileMode.FlipXY ? -1 : 1;
        ns.RenderTransform = st;       
        Pattern.Children.Add(ns);       
    }
}

We are done; the only thing we have to do now is to use it within our XAML code. I tried to keep the syntax as simple as possible.

XML
<Grid x:Name="LayoutRoot" Width="300" Height="300">      
    <Grid.Background>       
        <l:DrawingBrush Viewport="0,0,0.25,0.25" TileMode="Tile">
            <l:DrawingBrush.Drawing>
                <Path Stroke="Black" Fill="Red" StrokeThickness="3">
                    <Path.Data>
                        <GeometryGroup>
                            <EllipseGeometry RadiusX="20" RadiusY="45" Center="50,50" />
                            <EllipseGeometry RadiusX="45" RadiusY="20" Center="50,50" />
                        </GeometryGroup>
                    </Path.Data>
                </Path>
            </l:DrawingBrush.Drawing>
        </l:DrawingBrush>
    </Grid.Background>
    <Canvas Width="150" Height="150" x:Name="canvas">
        <Canvas.Background>
            <l:DrawingBrush Viewport="0,0,0.1,0.1" TileMode="FlipX">
                <l:DrawingBrush.Drawing>
                    <Polygon Fill="Blue" Points="0,0 1,1 1,0 0,1"/>
                </l:DrawingBrush.Drawing>
            </l:DrawingBrush>
        </Canvas.Background>
        <TextBox Foreground="Yellow" Background="#AA000000" 
                 Text="Hello, World!" Height="30"/>
    </Canvas>
</Grid>

Points of Interest

There are some limitations for this implementation of the DrawingBrush:

  • It works with Panels only
  • It does not layout (thus you cannot use it with a StackPanel, for example)
  • You should name the hosting control (I explained why)
  • For drawings inside the DrawingBrush, you can use only Shape derived classes (e.g., Line, Polygon, Ellipse, Path, etc.)

For more information and a detailed description of this control, visit my blog. Also, you are more than welcome to enhance this control and contribute it in order to provide quality solutions for other developers' needs.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Better Place
Israel Israel
Hello! My name is Tamir Khason, and I am software architect, project manager, system analyst and [of course] programmer. In addition to writing big amount of documentation, I also write code, a lot of code. I used to work as a freelance architect, project manager, trainer, and consultant here, in Israel, but recently join the company with extremely persuasive idea - to make a world better place. I have very pretty wife and 3 charming kids, but unfortunately almost no time for them.

To be updated within articles, I publishing, visit my blog or subscribe RSS feed. Also you can follow me on Twitter to be up to date about my everyday life.

Comments and Discussions

 
GeneralNEEDS UPDATING!!! Pin
Zachary Bauer7-Oct-10 4:14
Zachary Bauer7-Oct-10 4:14 
Generalsome issues Pin
snowcaps20079-Jun-10 6:33
snowcaps20079-Jun-10 6:33 
QuestionCannot find GeometryDrawing, DrawingGroup, DrawingBrush classes in silverlight 3.0. Whats an alternative option? Pin
Dinesh Patel12-Oct-09 4:44
Dinesh Patel12-Oct-09 4:44 
GeneralGotta love it... Pin
TacosAndBananas27-Jul-09 16:23
TacosAndBananas27-Jul-09 16:23 
Generalnot working Pin
vocrab21-Jun-09 19:02
vocrab21-Jun-09 19:02 
GeneralSL2 RC1 Pin
Giancarlo Aguilera17-Dec-08 5:42
Giancarlo Aguilera17-Dec-08 5:42 
General[Message Removed] Pin
Mojtaba Vali13-May-08 19:19
Mojtaba Vali13-May-08 19:19 
GeneralRe: Blackboard system Pin
Tamir Khason13-May-08 19:23
Tamir Khason13-May-08 19:23 
General[Message Removed] Pin
Mojtaba Vali13-May-08 19:56
Mojtaba Vali13-May-08 19:56 
General[Message Removed] Pin
Mojtaba Vali13-May-08 20:04
Mojtaba Vali13-May-08 20:04 
GeneralRe: Blackboard system Pin
Tamir Khason13-May-08 20:06
Tamir Khason13-May-08 20:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.