|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIn 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, 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.
BackgroundWhat 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 SilverlightBasically, in order to clone WPF objects, we should use the First, we should create an additional instance of the target class: 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 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 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 {
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 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. 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 DrawingBrushAll 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 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. <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 InterestThere are some limitations for this implementation of the DrawingBrush:
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.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||