I recently posted about the Silverlight Layout System (and now have posted videos on the subject here and here). But this can get quite confusing and a reader posted this question:
You mention PreArrange() and it is called but you are not clear on which of the functions should be used in it. I am also not clear on where “item” and “center” are defined.
This gives me an opportunity to do a better job explaining how the location of each object as it moves around the carousel, is determined.
Please be sure to read the two blog postings before reading this response. The best bet would be to download the complete source (which is available with the video), but let’s tackle your specific questions.
Also, please note, in the code associated with the video, PreArrange is called DoArrange – these are the same method. |
Answering your three questions, first.
PreArrange() is not part of the Silverlight Layout System, it is a method that I wrote (or, more accurately, “borrowed” from the original author of the code I based my approach on).
I’m not quite sure what you mean when you write “you are not clear on which of the functions should be used in it,” but the goal of this method is explained in the second article under the heading PreArrange.
Center and Item
Both center and item are defined within the DoArrange method as local variables.
Item is defined in the for loop (about four lines into the method) to hold each member of the Children collection of the panel, and is defined to be of type UIElement,
UIElement item = Children;
Essentially, what this method does is to distribute the objects at the appropriate distance from the center of your panel.
To do that, it needs a working definition of “center” and by center we mean half the width and half the height, but we want to center the item and so must allow for the width and height of the item as well.
Picture, for example, that we are working in a panel that is 500 x 300,
The center of this rect could be found as follows
Point center = new Point( this.Width / 2, this.Height / 2;
As you can see, center is defined to be of type Point. A Point’s constructor takes two doubles, as shown in the Silverlight documentation
In this case, we find the X coordinate by taking the width of the panel (this) and dividing it in half. We do the same for the y coordinate.
Centering an Object
If we had an object that was 60 x 40 and wanted to place it dead center, you might think that we would set its coordinates to 250,150, but the coordinate for an object is upper left hand corner of the bounding rectangle of the object, and we’d end up with something like this:
You will remember that we decided earlier to set all our objects to the same size, so we have two ways of fixing this problem. We can offset each object’s center by 1/2 its width and 1/2 its height, or we can offset the center by 1/2 the width and height of the ItemSize, which is what we do:
Point center = new Point( ( this.Width - ItemSize ) / 2, ( this.Height - ItemSize ) / 2 );
In the original computation, the X coordinate of the center was 1/2 of the width (or 500 /2 or 250). In this formula it is (500 – 60) /2 or 440 /2 = 220. Similarly the Y coordinate is now (300-40)/2 = 130. Thus the center moves from 250,150 to 220,130. When we place the object at 220,130 we center the object perfectly!
Finding The Distance from the Center
To place each object, we need to know the coordinates for that object, and we need to know it in terms of distance from the center and then we need to translate that into distance from the (0,0) coordinates of the upper left hand corner.
To accomplish this, we start with the mathematics of a triangle to find the distance along the x axis and the distance along the y axis, given the two facts we know: the radius and the angle.
The distance from the center on the X axis is equal to the radius times the cosine of the angle. The distance from the center on the Y axis is equal to the radius times the sine of the angle |
How Do We Find the Radius?
Let’s assume that we redraw our rectangle to be a square, 150 pixels on a side as shown in the figure. If we draw our carousel as a circle within the square, and drawing lines through the center, make our circle tangential to the square at the X and Y axis, we can then take advantage of the fact that the width of the square will be equal to the diameter of the circle (or 1/2 the width will be equal to the radius).
If we then stretch our square to a rectangle, the circle will stretch to an ellipse, but the principle will hold, so long as we differentiate between the X and Y “radius”
This is why we you see this in the code:
double radiusX = center.X;
double radiusY = center.Y;
You can read this as “the variable radiusX is set equal to the distance on the X axis from the edge to the center, which serves as the radius of a circle circumscribed within the rectangle, if that radius is drawn on the x axis. and the variable radiusY….”
How Do We Find the Angle?
The angle was stored in the angle property of the CarouselPanel, in the ArrangeOverride method. It was set by iterating through the children, and for each child multiplying that child’s fractional part of the total number of children by 2 pi (the number of radians equal to 360 degrees, or the number of radians in a full circle).
for ( int i = 0; i < Children.Count; i++ ) { Children[ i ].SetValue( CarouselPanel.AngleProperty, Math.PI * 2 * i / Children.Count ); }
Computing the X, Y Coordinate
We are now ready to compute the x,y coordinate of the object. We know that the radius is the same as the distance to the center. We know that the AngleProperty is a representation of the angle in radians. We know the formula.
for ( int i = 0; i < Children.Count; i++ ) { UIElement item = Children[ i ]; double radians = (double) item.GetValue( CarouselPanel.AngleProperty ); Point p = new Point( ( Math.Cos( radians ) * radiusX ) + center.X, ( Math.Sin( radians ) * radiusY ) + center.Y );
Why Do we add center.X and Center.Y?
While the formula tells us the distance the object should be located from the center, our coordinate system doesn’t have (0,0) at the center. Instead (0,0) is at the upper left corner, counting up on the X axis as you move to the right, and up on the y axis as you move down. To compensate, we must move to the center by adding back the distance from (0,0) to the center, which is to say we must move center.X on the X axis and center.Y on the Y axis.
This might be slightly clearer if we were to reverse the order of the operations:
Point p = new Point(
center.X + ( Math.Cos( radians ) * radiusX ),
center.Y + ( Math.Sin( radians ) * radiusY )
);
Thus, you would read this: “move to the center, and then move the computed distance.”
Previous: Putting the Silverlight Layout System To Work