ailon's DevBlog: Development related stuff in my life

Writing WPF/Silverlight compatible code. Part 4: Geometries

11/23/2009 7:26:43 PM

Note for future readers: These series discuss WPF and Silverlight versions that are current stable versions at the time of this writing – WPF 3.5 and Silverlight 3.

These are ongoing series of posts on the subject of WPF/Silverlight compatibility. New posts will be added to the Table of Contents post as they are written so bookmark that post or just subscribe to my RSS feed.

Default Contstructors Only

The first thing you notice when you start working with geometries in Silverlight after you’ve dealt with them in WPF is absence of constructor overloads. So, if you’ve used to write constructs like this in WPF:

RectangleGeometry rectangle = new RectangleGeometry(new Rect(10, 10, 50, 40));


you should forget about them when you start caring about WPF/Silverlight compatibility. Use something like this instead:

   1: RectangleGeometry rectanble = new RectangleGeometry()
   2: {
   3:     Rect = new Rect(10, 10, 50, 40)
   4: };

This works in both and isn’t that much longer.

No PathSegment.IsStroked

IsStroked let’s you specify that a segment of some path shouldn’t be stroked. So, in WPF you can create a path like this

image

with XAML like this:

   1: <Path Stroke="Black" StrokeThickness="3">
   2:     <Path.Data>
   3:         <PathGeometry>
   4:             <PathFigure StartPoint="0,20">
   5:                 <LineSegment Point="20,0" />
   6:                 <LineSegment Point="40,20" />
   7:                 <LineSegment Point="60,20" IsStroked="False" />
   8:                 <LineSegment Point="80,0" />
   9:                 <LineSegment Point="100,20" />
  10:             </PathFigure>
  11:         </PathGeometry>
  12:     </Path.Data>
  13: </Path>

As you have probably guessed, it’s not going to work in Silverlight 3 (and as far as I’ve checked in Siverlight 4 beta either). For simple scenarios like the one above, you can overcome this limitation by simply dividing PathFigure into several figures (one for each continuos segment) like this

   1: <Path Stroke="Black" StrokeThickness="3">
   2:     <Path.Data>
   3:         <PathGeometry>
   4:             <PathFigure StartPoint="0,20">
   5:                 <LineSegment Point="20,0" />
   6:                 <LineSegment Point="40,20" />
   7:             </PathFigure>
   8:             <PathFigure StartPoint="60,20">
   9:                 <LineSegment Point="80,0" />
  10:                 <LineSegment Point="100,20" />
  11:             </PathFigure>
  12:         </PathGeometry>
  13:     </Path.Data>
  14: </Path>

I’ve created a simple helper extension method to do the same trick automatically in code depending on whether it’s being called in WPF or Silverlight:

   1: public static void AddLineSegment(this PathGeometry pathGeometry, Point point, bool isStroked)
   2: {
   3:     PathFigure figure = pathGeometry.Figures[pathGeometry.Figures.Count - 1];
   4:  
   5: #if SILVERLIGHT
   6:     if (isStroked)
   7:     {
   8:         figure.Segments.Add(new LineSegment() { Point = point });
   9:     }
  10:     else
  11:     {
  12:         // silverlight has no IsStroked properto on LineSegment
  13:         // so we create a new PathFigure to imitate non-stroked line segment
  14:         figure = new PathFigure();
  15:         figure.StartPoint = point;
  16:         pathGeometry.Figures.Add(figure);
  17:     }
  18: #else
  19:     figure.Segments.Add(new LineSegment(point, isStroked));
  20: #endif
  21: }

You can use it like this when constructing paths in your code:

myPathGeometry.AddLineSegment(myPoint, true);

The method can be generalized to support other types of PathSegments, but I needed it for lines so there you have lines-only version. You can generalize/adapt it yourself.

This saves us in cases like the one above. But here’s another simple case that above technique fails to solve:

image

In WPF a figure like this can be created with this XAML:

   1: <Path HorizontalAlignment="Center" VerticalAlignment="Center"
   2:     Stroke="Black" Fill="LightGreen" StrokeThickness="3">
   3:     <Path.Data>
   4:         <PathGeometry>
   5:             <PathFigure StartPoint="0,40">
   6:                 <LineSegment Point="0,20" IsStroked="False" />
   7:                 <LineSegment Point="20,0" />
   8:                 <LineSegment Point="40,20" />
   9:                 <LineSegment Point="60,20" IsStroked="False" />
  10:                 <LineSegment Point="80,0" />
  11:                 <LineSegment Point="100,20" />
  12:                 <LineSegment Point="100,40" IsStroked="False" />
  13:             </PathFigure>
  14:         </PathGeometry>
  15:     </Path.Data>
  16: </Path>

Unfortunately, I don’t know a way to achieve this in Silverlight other than overlaying 2 paths – one for the fill and one for the stroke like this:

   1: <Path Fill="LightGreen" StrokeThickness="3">
   2:     <Path.Data>
   3:         <PathGeometry>
   4:             <PathFigure StartPoint="0,40">
   5:                 <LineSegment Point="0,20"/>
   6:                 <LineSegment Point="20,0" />
   7:                 <LineSegment Point="40,20" />
   8:                 <LineSegment Point="60,20"/>
   9:                 <LineSegment Point="80,0" />
  10:                 <LineSegment Point="100,20" />
  11:                 <LineSegment Point="100,40" />
  12:             </PathFigure>
  13:         </PathGeometry>
  14:     </Path.Data>
  15: </Path>
  16:  
  17: <Path Stroke="Black" StrokeThickness="3">
  18:     <Path.Data>
  19:         <PathGeometry>
  20:             <PathFigure StartPoint="0,20">
  21:                 <LineSegment Point="20,0" />
  22:                 <LineSegment Point="40,20" />
  23:             </PathFigure>
  24:             <PathFigure StartPoint="60,20">
  25:                 <LineSegment Point="80,0" />
  26:                 <LineSegment Point="100,20" />
  27:             </PathFigure>
  28:         </PathGeometry>
  29:     </Path.Data>
  30: </Path>

And since we are talking about WPF/Silverlight reusable code here, this is the way we should do it to work in both. Let me know if you happen to know a better way of doing something like this without having to rely on 2 objects.

kick it on DotNetKicks.com Shout it

Tags: , ,

blog comments powered by Disqus
Copyright © 2003 - 2014 Alan Mendelevich
Powered by BlogEngine.NET 2.5.0.6