This chapter
is taken from book "Programming Windows Phone 7" by Charles Petzold published by
Microsoft press.
http://www.charlespetzold.com/phone/index.html
When a property backed by a dependency property is changed at runtime, the
element with that property changes to reflect that change. This is a result of
the support for a property-changed handler built into dependency properties.
At runtime, you can dynamically add
Point objects to the PointCollection, or remove them from the PointCollection, and a Polyline or Polygon will change. The GrowingPolygons project has a MainPage.xaml file that instantiates a Polygon element and gives it a couple properties:
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<Polygon
Name="polygon"
Stroke="{StaticResource
PhoneForegroundBrush}"
StrokeThickness="{StaticResource
PhoneStrokeThickness}" />
</Grid>
The code-behind file waits until the Loaded event is fired before determining
the size of the content panel (just as in the Spiral program) and it begins by
obtaining similar information. But the OnLoaded handler just adds two points to
the Points collection of the Polygon to define a vertical line; everything else
happens during Tick events of a DispatcherTimer (which of course requires a
using directive for System.Windows.Threading):
namespace GrowingPolygons
{
public partial
class MainPage
: PhoneApplicationPage
{
Point center;
double radius;
int numSides = 2;
public MainPage()
{
InitializeComponent();
Loaded += OnLoaded;
}
void OnLoaded(object
sender, RoutedEventArgs args)
{
center = new
Point(ContentPanel.ActualWidth / 2 - 1,
ContentPanel.ActualHeight / 2 - 1);
radius = Math.Min(center.X,
center.Y);
polygon.Points.Add(new
Point(center.X, center.Y - radius));
polygon.Points.Add(new
Point(center.X, center.Y + radius));
DispatcherTimer tmr =
new DispatcherTimer();
tmr.Interval = TimeSpan.FromSeconds(1);
tmr.Tick += OnTimerTick;
tmr.Start();
}
void OnTimerTick(object
sender, EventArgs args)
{
numSides += 1;
for (int
vertex = 1; vertex < numSides; vertex++)
{
double radians = vertex * 2 *
Math.PI / numSides;
double x = center.X + radius *
Math.Sin(radians);
double y = center.Y - radius *
Math.Cos(radians);
Point point =
new Point(x,
y);
if (vertex < numSides - 1)
polygon.Points[vertex] = point;
else
polygon.Points.Add(point);
}
PageTitle.Text = "" + numSides +
" sides";
}
}
}
Every second, the program replaces all but one of the
Point objects in the Points
collection of the Polygon. The first Point in the collection—which is the Point
at the top center of the content area-is the only one that remains the same. In
addition, the Tick handler adds a new
Point object at the end of the collection.
The result is a polygon that gains one new side every second:
The Path Element
The Path class defines just one property of its own named Data of
type Geometry,
but geometries are a very important concept in Silverlight vector graphics.
It's important to recognize that a Geometry object is nothing but naked coordinate points. There is no concept of brushes or line thickness or styles with a geometry. That's why you need to combine a Geometry with a Path element to actually render something on the screen. The Geometry defines the coordinate points; thePath defines the stroke brush and fill brush.
Geometry fits into the Silverlight class hierarchy like so:
Object
DependencyObject (abstract)
Geometry (abstract)
LineGeometry (sealed)
RectangleGeometry (sealed)
EllipseGeometry (sealed)
GeometryGroup (sealed)
PathGeometry (sealed)
LineGeometry defines two
properties of type Point named
StartPoint and EndPoint:
RectangleGeometry defines a
property named Rect of type Rect,
a structure that defines a rectangle with four numbers: two numbers indicate the
coordinate point of the upper-left corner and two more numbers for the
rectangle's size. In XAML you specify these four numbers sequentially: the x and
y
coordinates of the upper-left corner, followed by the width and then the height:
The EllipseGeometry also defines
RadiusX and RadiusY
properties, but these are for defining the lengths of the two axes. The center
of the EllipseGeometry is provided by a property named Center of type
Point:
Geometries and Transforms
If you're using
EllipseGeometry and you don't want the axes of the ellipse
to be aligned on the horizontal and vertical, you can apply a RotateTransform to it. And you have a choice. Because Path derives
from UIElement,
you can set this
RotateTransform to the RenderTransform property of the Path:
Notice that the CenterX
and
CenterY properties of RotateTransform are set to the same values as the Center point of
the EllipseGeometry itself so that the ellipse is rotated around its center. When
working with Path and Geometry
objects, it's usually easier to specify actual transform
centers rather than to use
RenderTransformOrigin.
The RenderTransform property has no effect on how the element is perceived in the
layout system, but the
Transform property of the Geometry
affects the perceived dimensions. To see this difference, enclose a Path with an
EllipseGeometry in a centered
Border:
Grouping Geometries
One of the descendent classes of Geometry is GeometryGroup. This allows you to combine one or more Geometryobjects in a composite. Notice how theFillRule applies to this combination.
<Grid Background="LightCyan">
<Path Stroke="Maroon"
StrokeThickness="4"
Fill="Green">
<Path.Data>
<GeometryGroup>
<RectangleGeometryRect=" 40 40 200 200" />
<RectangleGeometryRect=" 90 90 200 200" />
<RectangleGeometryRect="140 140 200 200" />
<RectangleGeometryRect="190 190 200 200" />
<RectangleGeometryRect="240 240 200 200" />
</GeometryGroup>
</Path.Data>
</Path>
</Grid>
The ArcSegment
Things start getting tricky with ArcSegment. An arc is just a partial circumference of an ellipse, but because theArcSegment must fit in with the paradigm of start points and end points, the arc must be specified with two points on the circumference of some ellipse. But if you define an ellipse with a particular center and radii, how do you specify a point on that ellipse circumference exactly without doing some trigonometry?
The solution is to define only the
size of
this ellipse and not where the ellipse is positioned. The actual location of the
ellipse is defined by the two points.
In code, you can determine algorithmically the points on a circle where you
want the arc to begin and end. That's the idea behind the SunnyDay program,
which combines LineSegment and ArcSegment objects into a composite image. The
MainPage.xaml file instantiates the Path element: and assigns all the properties
except the actual segments:
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<Path
Fill="Yellow">
<Path.Data>
<PathGeometry>
<PathFigure
x:Name="pathFigure"
IsClosed="True" />
</PathGeometry>
</Path.Data>
</Path>
</Grid>
The Loaded event handler is then responsible for obtaining the size of the
content area and calculating all the coordinates for the various path segments:
namespace
SunnyDay
{
public partial
class MainPage
: PhoneApplicationPage
{
const int
BEAMCOUNT = 24;
const double
INCREMENT = Math.PI / BEAMCOUNT;
public MainPage()
{
InitializeComponent();
Loaded += OnLoaded;
}
void OnLoaded(object
sender, RoutedEventArgs args)
{
Point center =
new Point(ContentPanel.ActualWidth
/ 2,
ContentPanel.ActualHeight / 2);
double radius = 0.45 *
Math.Min(ContentPanel.ActualWidth,
ContentPanel.ActualHeight);
double innerRadius = 0.8 *
radius;
for (int
i = 0; i < BEAMCOUNT; i++)
{
double radians = 2 *
Math.PI * i / BEAMCOUNT;
if (i == 0)
{
pathFigure.StartPoint = new
Point(center.X, center.Y - radius);
}
LineSegment lineSeg =
new LineSegment();
lineSeg.Point = new
Point(
center.X + innerRadius * Math.Sin(radians
+ INCREMENT / 2),
center.Y - innerRadius * Math.Cos(radians
+ INCREMENT / 2));
pathFigure.Segments.Add(lineSeg);
ArcSegment arcSeg =
new ArcSegment();
arcSeg.Point = new
Point(
center.X + innerRadius * Math.Sin(radians
+ 3 * INCREMENT / 2),
center.Y - innerRadius * Math.Cos(radians
+ 3 * INCREMENT / 2));
pathFigure.Segments.Add(arcSeg);
lineSeg = new
LineSegment();
lineSeg.Point = new
Point(
center.X + radius * Math.Sin(radians
+ 2 * INCREMENT),
center.Y - radius * Math.Cos(radians
+ 2 * INCREMENT));
pathFigure.Segments.Add(lineSeg);
}
}
}
}
The result is a rather cartoonish sun: