Page view counter

February 2009 - Posts

Hypervideo put to work


 

Eric Mork (of Sparkling Client) SilverBayhas created a site with videos using Joel Neubeck & Tim Heuer's video player that makes terrific use of Hypervideo.

Eric creates short teaching videos, and embeds markers in each.  When the video hits each marker, the table of contents is updated in a separate but related window,  and the code associated with what Eric is teaching is shown in a third window. The integration is a work of art.

MorkHyperVideo 

For more on Hypervideo check out these resources:


This work is licensed under a Creative Commons Attribution By license.

Better Navigation

I was pretty happy with the threading navigation I had introduced…

OldNav

 

Just to double check, I asked my buddy David Platt, author of the absolutely essential Why Software Sucks, to take a look. He was quite gentle in ripping my guts out.

He pointed out that the juxtaposition of the word More with a left pointing arrow is so jarring it just forces you to stop and think (Don't Make Me Think!"). He also noted that putting one link at the top of the article, and the other at the bottom was odd (not clear you want a link to another article before you start reading) fights the standard used throughout the industry. 

PrevNext2

Once he pointed it out, it was painfully obvious… as was the fix.

 

NewNav

Navigation all at the bottom, and no ambiguity. Much nicer.  Thank you David. I owe you yet another one.


This work is licensed under a Creative Commons Attribution By license.

We love to measure everything

I just received a note from a colleague about the "Twitter Influence Calculator."  Not being able to resist, I immediately put in my name and out popped more statistics than you can shake a stick at…

influence

I chopped out the actual numbers because they are beside the point (and tiny).

Is this a good thing?

There are so many ways to respond (I'm of two minds about my ambivalence).

Is this site and others like it, a reflection of our endless self-absorption or is this another useful tool for measuring our interaction and effectiveness with the developer community? 

More generally, do sites provide these numbers because they are meaningful or because they can?  Are they meaningful?  I'm not enough of a statistician to know whether the values make sense, though the person who created this site clearly has a great deal of expertise

But we do this a lot.  There is also Twitter Grader which happily assures me that although my overall rank is 17,705, that is out of 1.4 million Twitter users, giving me a rank of 98.8 which they pleasantly round up to 99. 99 is good. I like Twitter Grader better, cause I have better numbers there.

Side note: my one good number on the Influence calculator is the Twitter Ratio, where I scored 11.3.  The explanation you receive when you click on the term (see the last line in the figure above) is quite detailed but it includes this: "A …ratio of 10 or higher indicates that you're either a Rock Star in your field or you are an elitist… You like to hear yourself talk. Luckily others like to hear you talk, too. You may be an ass."

Take that!

Figures Lie and Liars Figure

Microsoft is big on numbers, we spend a lot of time measuring influence and customer happiness (remember to fill out your eval forms after each talk) and I think that is great, because what you measure you'll get more of, but sometimes we split things a little fine.

salt shakerWe measure everything, and then we set our pay and rewards based on those decimals and infinitesimals. Overall, who can argue? It makes for a highly motivated group, and weeds out the dead weight, but one does worry a bit about how stochastic some of the results are. 

Qualitative != Quantitative

At another company I worked for, we used to hold focus groups with 20 people, ask their opinions, and then come back and say "83.7% of the people we talked to said…"  It made my head hurt.  When n = 20 (or 50!) you really don't want to be using quantitative values finer than, "some" or a lot" – and even then with a shaker of salt.

Sour Grapes

Don't mind me, I'm just jealous that Scott Hanselman and I set out to Twitter around the same time and a week or so later he has, according to  the Twitter Influence Calculator, over 8 thousand followers with a visibility that they characterize as "Astonishingly High." What can I say? He's a rock star. I'm a roadie.  Life is good.

 Next: Using Statistics Well


This work is licensed under a Creative Commons Attribution By license.

And now for something completely different

I'm not sure it is useful,  but it is one of the more creative approaches to searching I've seen. Amaztype takes an entirely different approach to finding things on Amazon.  They start by presenting you with a sparse (beta?) window

amazTypeSearch

 

 

I've put the word Silverlight into the title. I then click Start Search and it begins beeping and fizzing as it gathers data.

 

 

 

 

 

 

 

 

 

 

 

 

Books begin to appear;  small, sparse at first…

AmazType1

If you move the browser, it starts over with a "boop"  Soon small piles form

AmazType2

Then larger piles, one on top of another…

Books

 

Click for Details

If you click on a book, you zoom to that pile and a second click gets you quick box with essential info (author, price, etc.)

AmazType3

Another click takes you to the web page.

 

But if you are patient….

 

If you hold back, and just let it cook for a while, something interesting happens with the piles of books….

 

AmazType4

 

( Yes, it's flash, not silverlight, but it's RIA and an interesting approach. )


This work is licensed under a Creative Commons Attribution By license.
Posted by jesseliberty | 4 comment(s)
Filed under: , ,

More About the Layout System

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 = ChildrenIdea;

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,

CarouselRect

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

PointConstructor

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.

MidPointApproach1

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:

OffCenterObject

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!

ObjectCentered

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).

AngleToPositionComplete

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

This work is licensed under a Creative Commons Attribution By license.

Tech Ed Connect

Interesting social / community approach at tech ed. You fill out your community quick card (here's mine):

TechEdConnect

and you then fill in a quick survey of your interests and the types of folks (designer, developer, etc.) you are interested in meeting at Tech Ed.  Click on PeopleMap and hey! presto! a map with you at the center and others positioned so that proximity = closeness of match in interest. Mouse over tells you more, click on the individual to see their profile.

PeopleMap

Neat way to make contact with folks.


This work is licensed under a Creative Commons Attribution By license.
Posted by jesseliberty | with no comments
Filed under: ,

Carousel Video Posted – Parts 1 and 2

I'm very pleased to be able to announce that the videos Creating a Carousel Part 1 and Creating a Carousel Part 2 are now available.  These videos cover the material that is also discussed in my two part blog entry (beginning here) including such advanced topics as

  • The Silverlight Layout System
  • Overriding MeasureOverride and ArrangeOverride
  • Matrix Transforms
  • Adding Attached Dependency Properties
  • Programmatic Animation

This work is licensed under a Creative Commons Attribution By license.

Putting the Silverlight Layout System to Work

In my previous blog entry I described the fundamentals of the Silverlight Layout System (SLS). Today, I'd like to build a simplified version of the custom Carousel control that I create in greater detail in a forthcoming video, and examine the role of the SLS in laying out and animating the objects in the Carousel.

We'll start by creating a new Silverlight solution, and immediately adding a Silverlight library (Add New Project –>Silverlight Class Library) named SimpleCarouselControl. Within that library we'll add a single class: CarouselPanel.cs, and we'll delete Class1.cs that Visual Studio created.

We're going to animate the carousel programmatically using a DispatchTimer, so be sure to add

using System.Windows.Threading;

at the top of the page. Within the class, let's add a pair of private member variables,

protected DispatcherTimer timer;
public double ItemSize { get; set; }

The latter will be used to hold the size of the items we'll be adding to the carousel (and to keep things absurdly simple, we'll set a single size for all the items).

Attached Dependency Property

We want to enable each child to set its Angle Property, but of course each child won't have such a property; the angle property only makes sense in the context of a carousel.  This is exactly analogous to allowing each item in a grid to set the grid.row and grid.column and the solution is the same: we create an attached dependency property:

 public static readonly DependencyProperty AngleProperty =
       DependencyProperty.RegisterAttached(
       "Angle",
       typeof( double ),
       typeof( CarouselPanel ),
       null );

If you are familiar with the syntax for registering regular dependency properties, you'll notice this is identical except that the keyword Property is changed to DependencyProperty.  The first parameter is the name of the Dependency property, the second is its type, the third is the type of its parent, and the fourth is a reference to its metadata (almost always a delegate used as a callback for when the property changes).

We'll also declare a static get and set method for the DP:

 public static double GetAngle( DependencyObject obj )
 {
    return (double) obj.GetValue( AngleProperty );
 }

 public static void SetAngle( DependencyObject obj, double value )
 {
    obj.SetValue( AngleProperty, value );
 }

 

With the properties in place, we're ready to implement the methods needed to handle layout (MeasureOverride and ArrangeOverride).  We'll use a helper method for the latter, which will come in handy in animating the carousel, which, after all, is just repeatedly laying out the controls, changing their angle and then laying them out again.

MeasureOverride

We'll make our override of MeasureOverride simple. Rather than asking each object for its size, and then deciding on a total size needed, we'll get the size of the largest item, and then multiply that by the number of items. Quick, sleazy and effective.

protected override Size MeasureOverride( Size availableSize )
{
   double maxSize = ItemSize;
   int numChildren = 0;
   if ( ItemSize == 0.0 )
   {
      foreach ( UIElement element in Children )
      {
         element.Measure( availableSize );
         maxSize = Math.Max( element.DesiredSize.Width, maxSize );
         maxSize = Math.Max( element.DesiredSize.Height, maxSize );
         ++numChildren;
      }
      ItemSize = maxSize;
   }
   return new Size( numChildren * maxSize, numChildren * maxSize );
}

ArrangeOverride checks that the panel has chidlren, and iterating through the children sets each one's angle property for even spacing around the circle that represents the carousel. (Note that the member variables Width and Height are inherited from FrameworkElement).

protected override System.Windows.Size ArrangeOverride( 
System.Windows.Size finalSize ) { if ( Children.Count == 0 ) return new Size( Width, Height ); for ( int i = 0; i < Children.Count; i++ ) { Children[ i ].SetValue( CarouselPanel.AngleProperty, ( Math.PI * 2 ) * i / Children.Count ); } PreArrange(); return new Size( Width, Height ); }

Setting the Angles

The key to this math is that a circle is 360 degrees or 2pi radians. Thus, you are setting the angle to each child's fraction of the total number of children times the circumference  number of radians in a circle [corrected 2/21/2009] (e.g., if there are 6 children and this is child 5 you are setting the angle to 5/6 of the circumference. Each of the 6 children will be its fraction of the way around (1/6th, 2/6th etc.).  If there are 8 children, they will be 1/8, 2/8, etc. Sweet.

As an aside, when I first saw this, it was written:

Children[ i ].SetValue( 
         CarouselPanel.AngleProperty, 
          i * ( Math.PI * 2 ) / Children.Count );

The result is identical, but the reasoning is harder to discern.

PreArrange

The helper method PreArrange finds the center of the panel, and from that, the X and Y coordinates of the center.

It then iterates through the children of the panel and uses the Angle of each element to find the distance from the center at which to place the object. It does so by setting a Point (P) as the run (multiplying the sine and cosine by the radius) from the center.

double radians = (double) item.GetValue( CarouselPanel.AngleProperty );

Point p = new Point(
                    ( Math.Cos( radians ) * radiusX ) + center.X,
                    ( Math.Sin( radians ) * radiusY ) + center.Y
                   );

Matrix Transform

In other columns and videos we discuss various transforms that can be made directly on shapes and objects such as scale transforms, skew transforms and so forth.  All of these and more can be made directly using a Matrix transform.

While matrices are powerful and have many applications the Matrix we care about here is called an affine matrix which is used to manipulate a coordinate system on a two dimensional plane.

You're not going crazy

I've chased down pages and pages of documentation, and this is what I've found. You are told repeatedly that the Matrix we use is a 3x3 structure in which you can safely ignore the third column. The matrix looks like this

    Ignore this column
M11 M12 0
M21 M22 0
OffsetX OffsetY 1

You are told that the OffsetX and OffsetY represent translation values which their name more or less tells you and you are told that the other four can be used for any kind of transform. Great, which does what?  Aha!  That you are not told.  Most of the documentation teases wonderfully, with sentences like this: "M11, the numeric value in the first row and first column of the matrix. for more information see the M11 property.   You follow that link with eager anticipation where you find an entire page of documentation that tells you that this attribute or property sets or retrieves the first row and first column of the matrix (!). Yikes!

So, because I honestly don't think it is a corporate secret, here is what they actually do: (The default values are in parentheses)

Secrets Revealed!  
M11  X Scale (1.0) M12  Y Skew (0.0)
M21  X Skew (0.0) M22  Y Scale (1.0)
OffsetX  (0.0) OffsetY  (0.0)

For our carousel we want to scale the object based on where it is on the Y scale as in a two dimensional plane, as it moves towards higher values on the Y scale it should appear to move closer to you and thus appear larger.

To compute that value, we return to the point P we computed earlier (the placement for our object as a distance from the center).  Since we know the distance from the center we need only set the apparent perspective by dividing that distance by the sum of the center and radius values plus a small constant found by the incredibly scientific method of trial and error.

 double scaleMinusRounding = p.Y / ( center.Y + radiusY ) +0.2;

We then ensure that we use the value we just computed or the value 1, whichever is less,

double scaleY = Math.Min( scaleMinusRounding, 1.0 );
double scaleX = Math.Min( scaleMinusRounding, 1.0 );

Note carefully that we set the scaleX adn scaleY to the scaling factor we derived based on the Y axis. Objects appear larger as they approach, but not as they move from side to side, and they appear larger both in height and in breadth.

Using the Matrix to implement the scaling up

With the scale values in hand, we retrieve the MatrixTransform object from each item in the carousel and we create a new Matrix to provide to it. The Matrix constructor takes six values (as you would expect) as shown,

MatrixConstructor

Here's the complete block of code,

MatrixTransform mt = item.RenderTransform as MatrixTransform;
double scaleMinusRounding = p.Y / ( center.Y + radiusY ) +0.2;
double scaleY = Math.Min( scaleMinusRounding, 1.0 );
double scaleX = Math.Min( scaleMinusRounding, 1.0 );
Matrix mx = new Matrix( scaleX, 0.0, 0.0, scaleY, 0.0, 0.0 );
mt.Matrix = mx;
item.RenderTransform = mt;

All that is left to do is to ensure that the items in front are not only larger, but are on top of the items that are behind, which we do by hacking the zIndex,

int zIndex = (int) ( ( p.Y / base.Height ) * 50 );
item.SetValue( Canvas.ZIndexProperty, zIndex );

We can now compute the bounding rectangle for each item, and call Arrange on the item, passing in that rectangle,

Rect r = new Rect( p.X, p.Y, ItemSize, ItemSize );
item.Arrange( r );

Starting  The Animation

All that is left is to start the animation, which we can do by creating and starting the DispatcherTimer:

public CarouselPanel()
    : base()
{
    Loaded += new RoutedEventHandler( CarouselPanel_Loaded );
}

void CarouselPanel_Loaded( object sender, RoutedEventArgs e )
{
    if ( timer == null )
    {
       timer = new DispatcherTimer();
       timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
       timer.Tick += new EventHandler( timer_Tick );
       timer.Start();
    }
}

The DispatcherTimer's interval property is set with a TimeSpan, the constructor for the TimeSpan used here takes days, hours, minutes, seconds and milliseconds. We have instructed the timer to fire its Tick event every 10 milliseconds or 100 times per second.

The timer Tick is registered with an event handler and then the Timer is started with the cleverly named Start method (no extra points for guessing how it is stopped).

Each time the event fires, our event handler iterates through the children, and moves each child's angle by a small increment,

void timer_Tick( object sender, EventArgs e )
{
   foreach ( UIElement uie in Children )
   {
      double current = (double) ( uie.GetValue( CarouselPanel.AngleProperty ) );
      uie.SetValue( CarouselPanel.AngleProperty,
         current + ( .0016 * (2 * Math.PI ) ) );  
   }
   PreArrange();
}

That's it for the custom control. There is no need to create a default appearance (e.g., generic.xaml) as this control derives from Panel. The next step is to use your new SimpleCarousel in your xaml file to hold and display the items in the Carousel.

 

Page.xaml

The very first thing you'll need is to make your main project (SimpleCarousel) aware of the control you just created, by adding a reference to the Control Library project,

AddReferenceToCarousel

Once you've done this you can add a namespace identifier so that you can add an instance of the control to the page.

xmlns:custom="clr-namespace:SimpleCarouselControl;assembly=SimpleCarouselControl"

 

 

 

From there, you just add the panel as you would any other container,

<custom:CarouselPanel x:Name="cPanel"
                          Width="500"
                          Height="400"
                          Background="Bisque">

</custom:CarouselPanel>

Between the open and close tags you may place as many UI elements as you like,

<custom:CarouselPanel x:Name="cPanel"
                      Width="500"
                      Height="400"
                      Background="Bisque">
  <Ellipse Width="15"
           Height="15"
           Fill="Orange" />
  <Ellipse Width="75"
           Height="40"
           Fill="Blue" />
  <Rectangle Height="60"
             Width="30"
             Stroke="Black"
             StrokeThickness="3" />
  <Ellipse Width="50"
           Height="50"
           Fill="Red" />
  <Rectangle Height="40"
             Width="40"
             Fill="Green" />
  <TextBlock Text="Hello!"
             FontFamily="Georgia"
             FontSize="24" />
  <ListBox Height="70"
           Width="75">
    <ListBoxItem Content="George" />
    <ListBoxItem Content="Paul" />
    <ListBoxItem Content="John" />
    <ListBoxItem Content="Ringo" />
  </ListBox>
</custom:CarouselPanel>

The Sequence Of Events

Page.xaml will load, and your panel will be initialized. Your class will be constructed, and then when Page.xaml loads the Carousel will load firing the CarouselPanel_Loaded event.

As part of loading the page, MeasureOverride and ArrangeOverride are called and initial sizing and placement of each object is accomplished. The Carousel_Loaded event handler also creates the timer, sets its interval and starts it. 

10 milliseconds later the event will fire and be caught by timer_Tick which will iterate through all the Panel's children, getting their angleProperty and incrementing them slightly. Timer_Tick then calls PreArrange which re-scales each object depending on its position on the y axis and calls arrange on the object, which in turn triggers a call to ArrangeOverride on each object (but not on the panel.

Note that the overrides of MeasureOverride and ArrangeOverride in panel are each called only once; after that the values are scaled and incremented as part of the animation but not as part of the layout system.

Streaming Example

-- Begin streaming application

-- End streaming application

 

    Previous: The Layout Model       Next: More about Layout


This work is licensed under a Creative Commons Attribution By license.

The Layout Model

 

 

I have finished a video, to be posted soon, on how to build a Carousel control. Along the way, I had the opportunity to explore the Silverlight Layout System (SLS) and will describe this fascinating corner of Silverlight 2 in this and future blog entries.

The first thing to know about the SLS is that most of the time you can ignore it! It is possible to become quite proficient in Silverlight programming without even knowing explicitly that the system exists, much less having to override any of its methods. For most developers, most of the time, the layout system is implicit and mediated for you by layout controls such as the GridPanel and StackPanel, and more recently by the Silverlight Toolkit layout controls such as the DockPanel and the WrapPanel.

That said, there are times when you want to do something the existing controls just don't provide, and familiarity with how Silverlight lays out controls can be both fascinating and essential.

Page Fundamentals

When you ask Visual Studio to create an application, it creates Page.xaml. Pages consist of a UserControl that contains a single element, typically a panel which in turn has any number of child elements.  If you create additional pages  the same pattern is repeated (Perhaps confusingly, you create pages by selecting "Add User Control" )

Because you can put a UserControl inside another user control (see my video on multiple-page applications and also my video on reusable user controls) we distinguish between a page (a .xaml file with a user control and its contents) on the one hand and a Custom UserControl on the other, but this is a matter of convention.

In any case, the key point is that the outermost user control can only have one child, and that child is almost always a type derived from Panel.

About Panels

Panel itself is abstract, meaning that it was created to provide shared functionality for types derived from it, but it is not possible to instantiate a Panel per se.

The Panel Class derives from FrameworkElement, which in turn derives from UIElement.

PanelControl

UIElement

The UIElement class provides the common features for most of the objects that have a visual appearance in Silverlight, and lays the ground work for layout. It is uncommon to derive directly from Framework element, and almost unheard of to derive directly from UIElement (both should be though of as infrastructure) but it is not illegal. Probably the key UIElement method for layout is InvalidateArrange (described below) and the key property is RenderTransform.

FrameworkElement

Framework element (which derives from UIElement) provides the API for any object that participates in the Silverlight Layout System (as well as APIs for data binding and object lifetime). It is here that you find the two essential methods for taking programmatic control of layout

  • MeasureOverride
  • ArrangeOverride

Along with various important properties and events.

A Two Step Process (Measure twice, cut once)

Laying out a Silverlight application is a two-step process. This allows the Silverlight Layout System to first measure all the objects you wish to display and then to lay them out given the available space balanced against each objects' desired size. Compromises are made, and to some degree the size allocated one object is determined both by the total of the sizes requested and the total size available (and (in some cases) who asks first!)

As you can imagine, this can get quite complicated, but we can keep it relatively simple, at least at first. 

Let's start by making the assumption that what we will have is a user control with a class derived from Panel.

MeasureOverride

iStock_TapeMeasureXSmall

Your main job in MeasureOverride is to loop through the Panel's children and call Measure() on each child. The parameter you pass in to each child's Measure() method is of type Size and tells the child object the maximum size it can have.

Note that you can say to the child "take all the size you want," by passing in a Size object with Double.PositiveInfinity for both dimensions.

Measure() does not return a value. Instead it sets its own internal property DesiredSize to the size it wants or the size it can have, whichever is smaller.

Note that it is essential to call Measure on every child even if you don't need its size. A critical side-effect of calling measure is to set the "yes I want you to display" bit.

MeasureOverride() returns the desired size of the layout container (often the sum of the desired sizes of the children).

ArrangeOverride

iStock_Parts_XSmall

The Silverlight Layout System calls ArrangeOverride() on the panel, and again, the panel iterates over every child, this time calling Arrange() on each one, telling it how much space it has been allocated.

Interestingly, each time you call Arrange on a child element, it calls ArrangeOverride as the first step. For that matter, each time MeasureOverride calls Measure on a child, the child calls MeasureOverride on itself!  We'll examine this bit of recursion in a future blog entry.

You remember that the parameter to MeasureOverride was a Size object. The parameter to ArrangeOverride is a System.Windows.Rect object which describes not only the size but the location of the object.

In my next blog entry, I'll work through a simplified Carousel example that puts all this theory into practice.


Next: Putting the layout system to work


This work is licensed under a Creative Commons Attribution By license.

Help! The Silverlight Toolkit Ate My PC!

Dear Jesse,

"I loaded the Silverlight Toolkit from December 1988 on my CPM machine and it broke my Apple IIe in my vacation home in Maine. What should I do?" – Toolkit Lover.

Dear Toolkit Lover,

We have a dedicated forum for posting questions about the Silverlight Toolkit where you will be able to get very fast answers. The forum is watched by the Toolkit team as well as many community experts.

To get there, please click here or enter http://silverlight.net/forums/35.aspx in your browser.  You can also reach the forum through the Toolkit page on Codeplex).

To get the best results, please be sure your message includes:

Note that screen captures can be very helpful:

SilverlightVer
To get to this image, navigate to any Silverlight application and right click. You will see a small box that says "Silverlight Configuration." Left click on that box and a tabbed dialog box will open with this information on the About tab.

SystemInfo

To get this information from Vista, Click on Start –> Control Panel –> Welcome Center and then in the upper right hand corner: Show More Details.

ToolKitControlVer

To get to this information about the Toolkit dll's, open a Silverlight application, click on references and navigate to wherever you installed the Toolkit. Hover over the appropriate Silvelight Toolkit DLL (in the illustration above I hovered over the Controls.DLL file) to see the File version adn Date Created.

 

The Subject Line Is Critical

One great tip on getting help is to make sure the subject of your message summarizes the problem. You'll get a faster response with Crashes on binding to XML File  than with Help! Urgent!

Small Programs Get Tested, Big Programs Get Ignored

Boil your problem down to the smallest possible program that reproduces the error/bug/issue. I find that when I do so, 50% of the time I solve the problem my self, and in any case I greatly increase the likelihood that someone will bother to load my problem and find out what is wrong.

Comments Can Help or Kill

Also be sure to comment your program wisely: don't fill the program with self-evident comments

x = 5   // assign 5 to x

they are just clutter.

On the other hand, do explain anything you are doing that is less than self-evident

// get the collection from the
// isolated storage reference variable
foo.itemSource = bar; 

Short Paragraphs

Finally, you want folks to read your message; so make it short, and make each paragraph short. Make it easy and inviting and more folks will read and help.

 

Best of luck!

 

-jesse

 

 

 


This work is licensed under a Creative Commons Attribution By license.
Posted by jesseliberty | 2 comment(s)
Filed under: ,

Creating Extension Methods in VB

 

I received a question about how to create extension methods in VB. It turns out that the key difference is that the extension must be defined in a Module and not in a class.

Thus, building on the code from the previous example, we end up with the same Page.xaml and a very similar Page.xaml.vb and a quite different Extension.vb

Here is Extension.vb

Imports Microsoft.VisualBasic
Imports System
Namespace GetLastNFromString
   Module ExtensionModule
      <System.Runtime.CompilerServices.Extension()> _
      Public Function Right(ByVal s As String, _
ByVal howMany As Integer) As String If howMany <= s.Length Then Return s.Substring(s.Length - howMany, howMany) Else Return s End If End Function End Module End Namespace



And here is Page.xaml.vb

Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Net
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes

Namespace GetLastNFromString
   Partial Public Class Page
       Inherits UserControl
      Public Sub New()
         InitializeComponent()
         AddHandler GoButton.Click, AddressOf GoButton_Click
      End Sub

      Private Sub GoButton_Click( _
            ByVal sender As Object, _
            ByVal e As RoutedEventArgs)
         ResultBox.Text = _
StringToParse.Text.Right(Convert.ToInt32 (NumberToGet.Text)) End Sub End Class End Namespace

For completeness, here is Page.xaml

<UserControl x:Class="GetLastNFromString.Page"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   Width="650"
   Height="200">
  <Grid x:Name="LayoutRoot"
        Background="Bisque">
    <Grid.RowDefinitions>
      <RowDefinition Height="1.5*" />
      <RowDefinition Height="1*" />
      <RowDefinition Height="1*" />
      <RowDefinition Height="1*" />
      <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="1*" />
      <ColumnDefinition Width="2*" />
    </Grid.ColumnDefinitions>
    <TextBlock HorizontalAlignment="Center"
               Margin="0,0,0,0"
               x:Name="Title"
               Grid.ColumnSpan="2"
               FontFamily="Georgia"
               FontSize="36"
               Foreground="#FF0C42EE"
               Text="Last n Traditional (VB)"
               TextWrapping="Wrap" />
    <TextBlock x:Name="String_Prompt"
               Text="String"
               Width="Auto"
               Grid.Row="1"
               Margin="0,0,10,0"
               Foreground="Blue"
               FontFamily="Georgia"
               FontSize="24"
               VerticalAlignment="Bottom"
               HorizontalAlignment="Right" />
    <TextBlock x:Name="NumChars_Prompt"
               Text="Number To  Get"
               Grid.Row="2"
               Margin="0,0,10,0"
               Foreground="Blue"
               FontFamily="Georgia"
               FontSize="24"
               VerticalAlignment="Bottom"
               HorizontalAlignment="Right" />
    <TextBlock x:Name="Result"
               Text="Result"
               Grid.Row="3"
               Margin="0,0,10,0"
               Foreground="Blue"
               FontFamily="Georgia"
               FontSize="24"
               VerticalAlignment="Bottom"
               HorizontalAlignment="Right" />
    <TextBox FontFamily="Georgia"
             FontSize="24"
             HorizontalAlignment="Left"
             Margin="5,0,0,2"
             x:Name="StringToParse"
             VerticalAlignment="Bottom"
             Width="400"
             Grid.Column="1"
             Grid.Row="1"
             Text="" />
    <TextBox  x:Name="NumberToGet"
              FontFamily="Georgia"
              FontSize="24"
              HorizontalAlignment="Left"
              Margin="5,0,0,0"
              VerticalAlignment="Bottom"
              Width="Auto"
              Grid.Column="1"
              Grid.Row="2"
              Text="?" />
    <TextBlock x:Name="ResultBox"
               HorizontalAlignment="Left"
               Margin="5,0,0,0"
               VerticalAlignment="Bottom"
               Grid.Column="1"
               Grid.Row="3"
               FontFamily="Georgia"
               FontSize="24"
               Text="" />
    <Button x:Name="GoButton"
            Content=" Go! "
            Grid.Row="4"
            Grid.Column="0"
            FontFamily="Georgia"
            FontSize="18"
            Foreground="Blue"
            HorizontalAlignment="Right"
            Margin="5"
            Width="Auto" />
  </Grid>
</UserControl>
 

This work is licensed under a Creative Commons Attribution By license.
Posted by jesseliberty | 3 comment(s)
Filed under:

Extension Methods Part 2 – Lambda Expressions in Linq

 



 

In my previous entry on this topic I demonstrated how you can use Extension methods to add what appear to be new methods to existing classes. In this entry I will demonstrate how Extension methods enhance Linq by adding “method queries” using lambda expressions.

To see how this works, let’s set up a simple query,  with a display that will show the two ways we might execute that query using Linq.

We’ll start by creating a new Silverlight application and within the application a simple data class, Person. Here is the code for Person.cs

using System.Collections.Generic;

namespace ExtensionPart2
{
   public class Person
   {
      public string FirstName { get; set; }
      public string LastName { get; set; }
      public string EmailAddress { get; set; }

      public override string ToString()
      {
         return string.Format( "{0} {1}\n{2}",
                     FirstName, LastName, EmailAddress );
      }

      public static List<Person> GetPeople()
      {
         List<Person> people = new List<Person>();
         people.Add( new Person() 
         { 
            FirstName = "Jesse", 
            LastName = "Liberty", 
            EmailAddress = "jliberty@microsoft.com" 
         } );

         people.Add( new Person() 
         { 
            FirstName = "George", 
            LastName = "Washington", 
            EmailAddress = "gWashington@whitehouse.gov" 
         } );



// also added John Adams, Thomas Jefferson
// James Madison, James Monroe, John Q. Adams return people; } } }

(I've elided the initialization of 5 of the presidents to save room)

 

We will issue two queries against this data. The first is a traditional Linq query,

from person in people
where person.LastName.StartsWith( "M" )
select person;

While this works fine, taking advantage of extension methods allows us (or more accurately the authors of the library) to extend the List<t> class to add the Where method, which in turn allows us to collapse these three statements to a single line of code using a lambda expression,

people.Where( person => person.LastName.StartsWith( "M" ) );

Examining the Extension Method

The tooltip for people.Where give great insight into what is actually going on here

whereExtension

Taking this apart, we see that Where is defined to be an extension method that returns an IEnumerable<Person> and takes one argument: a function (named predicate) that takes two arguments: a Person object and a boolean.

Rather than passing in a method, or a delegate to a method, or even an anonymous method, we go one step further and pass in a lambda expression. As you know from previous articles, the lambda expression "are a way to write short in-line substitutions for the methods that delegates refer to."

You can read the lambda expression above as

"There is a method that takes one parameter, a list of person objects named people and that returns  each person whose last name starts with the letter m in an IEnumberable collection."

or, more conventionally you can read it as

"person goes to each person whose last name starts with m"

For a lengthy review of how to read this aloud see this article.

The result for both queries is identical, but I would argue the second query is far more readable, as shown in the running program,

Extension2Running

The code for Page.xaml is shown here, and below it the code for Page.xaml.cs

Page.xaml

<UserControl x:Class="ExtensionPart2.Page"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d"
  HorizontalAlignment="Left"
  VerticalAlignment="Center"
  FontFamily="Georgia"
  FontSize="14"
  Width="650"
  Height="553"
  Margin="5">
  <Grid x:Name="LayoutRoot"
        Background="Bisque"
        Margin="20">
    <Grid.RowDefinitions>
      <RowDefinition Height="1*" />
      <RowDefinition Height="5*" />
      <RowDefinition Height="3*" />
      <RowDefinition Height="3*" />
      <RowDefinition Height="1.5*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="6*" />
      <ColumnDefinition Width="4.5*" />
    </Grid.ColumnDefinitions>
    <TextBlock Height="Auto"
               Width="Auto"
               Grid.Row="0"
               Text="Where last name begins with M"
               TextWrapping="Wrap"
               FontFamily="Georgia"
               FontSize="24"
               Margin="0"
               Grid.RowSpan="1"
               Grid.ColumnSpan="2"
               HorizontalAlignment="Center" />
    <TextBlock Text="Presidents"
               TextWrapping="NoWrap"
               FontFamily="Georgia"
               FontSize="18"
               HorizontalAlignment="Left"
               VerticalAlignment="Top"
               Margin="5,5,5,5"
               d:LayoutOverrides="Height"
               Grid.Row="1" />
    <ListBox x:Name="PresidentsListBox"
             Margin="5"
             Grid.Row="1"
             Grid.Column="1" />
    <TextBlock HorizontalAlignment="Left"
               VerticalAlignment="Bottom"
               Margin="5,4,5,5"
               Grid.Row="2"
               FontFamily="Georgia"
               FontSize="14"
               Text=""
               TextWrapping="Wrap" />
    <ListBox x:Name="LinqResults"
             Margin="5"
             Grid.Row="2"
             Grid.RowSpan="1"
             Grid.Column="1" />
    <ListBox x:Name="QOEM_Results"
             Margin="5"
             Grid.Column="1"
             Grid.Row="3"
             Grid.RowSpan="1" />
    <StackPanel Margin="0"
                Grid.Row="2">
      <TextBlock Height="Auto"
                 Width="Auto"
                 RenderTransformOrigin="0.5,0.5"
                 FontFamily="Georgia"
                 FontSize="18"
                 Text="from person in people"
                 TextWrapping="NoWrap"
                 HorizontalAlignment="Left" />
      <TextBlock Height="Auto"
                 RenderTransformOrigin="0.5,0.5"
                 FontFamily="Georgia"
                 FontSize="18"
                 Text="where person.LastName.StartsWith
                     (&quot;M&quot;)"
                 TextWrapping="NoWrap"
                 Width="Auto"
                 HorizontalAlignment="Left" />
      <TextBlock RenderTransformOrigin="0.5,0.5"
                 FontFamily="Georgia"
                 FontSize="18"
                 Text="select person"
                 TextWrapping="NoWrap"
                 Width="334"
                 Height="20"
                 HorizontalAlignment="Left" />
    </StackPanel>
    <StackPanel Margin="0,0,0,0"
                Grid.Row="3">
      <TextBlock x:Name="QOEM"
                 FontFamily="Georgia"
                 FontSize="18"
                 Text="people.Where ( 
    person =&gt; person.LastName.StartsWith ( &quot;M&quot; );"
                 TextWrapping="Wrap"/>
    </StackPanel>
    <Button x:Name="Go"
            Background="#FF00FF00"
            FontFamily="Georgia"
            FontSize="24"
            Foreground="#FF0000FF"
            Margin="5"
            Grid.Column="1"
            Grid.Row="5"
            Content="Go!" />
  </Grid>
</UserControl>

Page.xaml.cs

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace ExtensionPart2
{
   public partial class Page : UserControl
   {
      public Page()
      {
         InitializeComponent();
         Go.Click += new RoutedEventHandler( Go_Click );
      }

      void Go_Click( object sender, RoutedEventArgs e )
      {
         List<Person> people = Person.GetPeople();

         // bind entire collection
         PresidentsListBox.ItemsSource = people;

         // traditional linq statement
         LinqResults.ItemsSource = from person in people
                            where person.LastName.StartsWith( "M" )
                            select person;

         // extended method
         QOEM_Results.ItemsSource = people.Where(
person => person.LastName.StartsWith( "M" ) ); } // end go button event handler } // end class } // end namespace
 

This work is licensed under a Creative Commons Attribution By license.

Background: Extension Methods, Silverlight & Linq

 

 

A number of articles I've read recently have mentioned "Extension methods" in passing, yet it is difficult to find out what this is, if you don't already know.

A few searches, however, did turn up three good sources: MSDN provides this reference page, then I found a classic "Gu" blog post from March of '07, and finally, the incredible Pete Brown tackles Extension methods as well.

This mini-tutorial will attempt to fill in the details and focus on two key uses of Extension Methods:

  • To add functionality to a class you don't own
  • To enable method-based queries (using lambda expressions) in Linq

Adding Functionality to a Class You Don't Own

Programmers are often faced with wishing that a given class had a method that the designer of the class did not supply. If the class is not sealed, you can derive a specialized version that has your method, but many framework classes are sealed. You can write around this problem, but it would be nice to create a method that looks like it is part of the original class, and Extension methods let you do just that.

Adding LastN to String

It is certainly possible to get the last n characters from a string, but it is a little tedious.  The traditional way to do so (broken out into fragments so that the steps are obvious) is shown in this very small Silverlight application,

lastnTraditional

Here is the click handler for GoButton (you'll be able to deduce the names of the other controls),

void GoButton_Click( object sender, RoutedEventArgs e )
{
   string target = StringToParse.Text;
   int howMany = Convert.ToInt32( NumberToGet.Text ); 
   string retString = string.Empty;
   if ( howMany <= target.Length )
   retString = target.Substring( target.Length - howMany, howMany);
   ResultBox.Text = retString;
}

Extending String

It would be nice, especially if we are going to be doing this a lot, if the String  class had a Right() method so that we could shorten this listing to

void GoButton_Click( object sender, RoutedEventArgs e )
{
   string target = StringToParse.Text;
   int howMany = Convert.ToInt32( NumberToGet.Text );
   ResultBox.Text = target.Right( howMany );
}

While the actual savings may not be that great, the code is far easier to understand (and thus maintain). 

Some Like It Terse

I must confess the old C programmer in me wants to rewrite this as

void GoButton_Click( object sender, RoutedEventArgs e )
{
  ResultBox.Text = StringToParse.Text.Right( 
Convert.ToInt32 (NumberToGet.Text )); }

While I would have eschewed such a compressed style in the past as forfeiting any chance to see the interim variables, the debugger is now quite sophisticated, and has no trouble providing the information needed,

InterimVariables

Creating the Extended Method

The example above works because I created an extended method for the String class. This is done by creating a static method, typically in a static class. That method must have as its first variable a parameter declared in three parts:

  1. the key word this
  2. the type you are extending
  3. an instance identifier

It can then have any other "normal" parameters you care to give it.  The extended method Right was thus created (in a file named Extensions.cs as follows:

namespace GetLastNFromString
{
   public static class Extensions
   {
      public static string Right( this string s, int howMany )
      {
         if ( howMany <=  s.Length )
            return s.Substring( s.Length - howMany, howMany );
         else
            return s;
      }
   }
}

The "else" statement is arbitrary; here I choose to return the original string if I can't return the requested portion (e.g., the user asks for the right most 10 characters of an 8 character string. Alternative solutions are to return string.empty, null or, if you insist, to throw an exception.

(I normally would put the if and the else in braces as well (you never know when you'll add more code) but I've left them out here to save space.

Extension Methods and LINQ

In the next column I'll tackle the second primary use of Extension methods, enabling method-based LINQ queries using Lambda expressions.

 



Next  Extension Methods and Lambda Expressions


This work is licensed under a Creative Commons Attribution By license.
Posted by jesseliberty | 8 comment(s)
Filed under:

New Tutorial: DataGrid Binding via Data Entities & WCF

 

DataTutorialIcon

 

I'm very pleased to announce the posting of my latest Silverlight 2 Tutorial: ADO.NET Data Entities and WCF Feeding A Silverlight Datagrid, available in both C# and VB in HTML and pdf.

This is the first (but not the last) in an occasional series on how Silverlight can be combined with other aspects of .NET 3.5 to create complete applications. This tutorial touches on WCF, Data Entities, LINQ and SqlServer.

Complete code is available for download on the html pages as well.


This work is licensed under a Creative Commons Attribution By license.
Posted by jesseliberty | 6 comment(s)
Filed under:

Permanent Up-to-date Get Started Link

I have added a link on my sidebar to a permanent page, that I will be keeping up to date, on Getting Started with Silverlight.



EssentialLinks (clicking the image will take you to the page)

Please feel free to direct “newbies” to this link and to let me know what you think should be added to this page.

Thanks!

Posted by jesseliberty | 2 comment(s)
Filed under:
Next