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

Comments

12/11/2009 1:54:15 PM

Mayur

Hi Ailon,
The workaround given here for isStroked property is excellent and works great. Thanks a lot for providing this work around.
However i am facing a difficulty when i try to fill the path. Let me explain the problem that i am facing.
Here is my original code..

private void DrawPath()
        {
            PathGeometry pathGeometry = new PathGeometry();
            PathFigure pathFigure = new PathFigure();
            pathFigure.IsClosed = false;
            pathGeometry.Figures.Add(pathFigure);
            pathFigure.StartPoint = new Point(93, 151);
            pathFigure.Segments.Add(GetSegment(new Point(93, 115)));
            pathFigure.Segments.Add(GetSegment(new Point(312, 115)));
            pathFigure.Segments.Add(GetSegment(new Point(276, 151))); // In need the isStroked property to be false for this segment.          
            pathFigure.Segments.Add(GetSegment(new Point(93, 151)));

            PathFigure pathFigure1 = new PathFigure();
            pathFigure1.IsClosed = false;
            pathFigure1.StartPoint = new Point(312, 115);
            pathFigure1.Segments.Add(GetSegment(new Point(312, 284)));
            pathFigure1.Segments.Add(GetSegment(new Point(276, 284)));
            pathFigure1.Segments.Add(GetSegment(new Point(276, 151)));
            pathGeometry.Figures.Add(pathFigure1);

            Path path = new Path();
            path.Stroke = new SolidColorBrush(Colors.Black);
            path.StrokeThickness = 1;
            path.Fill = new SolidColorBrush(Colors.LightGray);
            path.Opacity = 0.5;
            path.Data = pathGeometry;
            myCanvas.Children.Add(path);
        }      
private LineSegment GetSegment(Point p)
        {
            TextBlock tb = new TextBlock();
            tb.Text = "{" + p.X.ToString()+ "," + p.Y.ToString() + "}" ;
            tb.SetValue(Canvas.LeftProperty, p.X);
            tb.SetValue(Canvas.TopProperty, p.Y);
            myCanvas.Children.Add(tb);
            LineSegment ls = new LineSegment();
            ls.Point = p;
            return ls;
        }

The above code draws a figure similar to 7. There is a segment for which i need to set the isStroked property to false.
So i used the solution provided by you. So my modified code is

private void DrawPath()
        {
            PathGeometry pathGeometry = new PathGeometry();
            PathFigure pathFigure = new PathFigure();
            pathFigure.IsClosed = false;
            pathGeometry.Figures.Add(pathFigure);
            pathFigure.StartPoint = new Point(93, 151);
            pathFigure.Segments.Add(GetSegment(new Point(93, 115)));
            pathFigure.Segments.Add(GetSegment(new Point(312, 115)));
            //pathFigure.Segments.Add(GetSegment(new Point(276, 151)));
            AddLineSegment(pathGeometry, new Point(276, 151), false);
            PathFigure pathfigureNew = pathGeometry.Figures[pathGeometry.Figures.Count - 1];
            pathfigureNew.Segments.Add(GetSegment(new Point(93, 151)));

            PathFigure pathFigure1 = new PathFigure();
            pathFigure1.IsClosed = false;
            pathFigure1.StartPoint = new Point(312, 115);
            pathFigure1.Segments.Add(GetSegment(new Point(312, 284)));
            pathFigure1.Segments.Add(GetSegment(new Point(276, 284)));
            pathFigure1.Segments.Add(GetSegment(new Point(276, 151)));
            pathGeometry.Figures.Add(pathFigure1);

            Path path = new Path();
            path.Stroke = new SolidColorBrush(Colors.Black);
            path.StrokeThickness = 1;
            path.Fill = new SolidColorBrush(Colors.LightGray);
            path.Opacity = 0.5;
            path.Data = pathGeometry;
            myCanvas.Children.Add(path);
        }

This above code hides the segment i wanted to hide but does fill the entire geometry with light gray color.
Please let me know the solution to this.

Mayur India

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading



Copyright © 2003 - 2010 Alan Mendelevich
Powered by BlogEngine.NET 1.5.0.7