Where We Left Off
Here's a quick reminder of what we did last time:
- We created a WCF Service that is Silverlight 2 compatible.
- We added a reference to the service in a Silverlight application.
- We created a UI (Button and ListBox) to call the service and show the results.
- We called the WCF Service and hooked up the Completed event to an event handler.
The Page.xaml.cs file we finished with is as follows:
And the UI:
A Problem
There is a small problem with the UI. After calling the service (by clicking the button), the list gets populated with the names. You can select a name in the list by clicking on it. But if you click the button again, the list refreshes and the selection goes away. We want to update this so that the selected item is maintained when the service is called again.
Solution #1 - Using the Event Handler
The SelectedItem of the ListBox gets cleared when the service is called because we are re-binding the data. From the ListBox's point of view, it is getting a new object to bind to. When the old object is un-bound, the selected item goes away and doesn't come back.
We'll solve this by simply saving off the SelectedIndex of the ListBox before we make the service call. Then after the service call returns, we will reassign the SelectedIndex. (This is assuming that the order of our items is not changing, and we're not getting any new items -- you can extend this basic functionality for more complex situations).
Since we need to save off the SelectedIndex in one method (the Button Click event handler) and then use it in a different method (the Service Completed event handler), we'll need to create a class-level variable to hold this information. See the code below:(Click image to enlarge)
You'll see 3 new lines of code to get this to work:
- private int ehListIndex;
The class-level variable - ehListIndex = EHList.SelectedIndex;
Saving the index - EHList.SelectedIndex = ehListIndex;
Re-assigning the index to the list box
If you re-run the application, you will see that if you make a selection in the list box, that selection is maintained when you call the service multiple times.
Solution #2 - Using an Anonymous Delegate
Now, we'll try this again using an anonymous delegate instead of an explicit event handler. You'll see why this is an advantage in just a bit.
We'll update our XAML by adding another StackPanel to our Grid.
You'll notice that this section looks almost identical to the EventHandler UI section. Here's a summary of the differences:
- The 2nd column of the Grid is specified in the StackPanel.
- The Names of the ListBox and Button start with AD (for anonymous delegate) instead of EH.
- The ListBox border is blue (just to make it different).
- The button Content is "Anonymous Delegate".
- There is a new Click handler for the button (you can look at the hint in Part 1 about letting IntelliSense create the handler for you).
So, what is an anonymous delegate? In our case, it is an in-line definition of an event handler (for the service callback). This is defined by using the "delegate" keyword, then a set of parentheses with the appropriate parameters, then a set of curly braces with the method implementation. The implementation will look just like in our callback when using the EventHandler.
Here's the initial implementation:
What you will notice is that that the first and last lines are the same as our previous button click event. You'll also notice that the parameters for the anonymous delegate ("object" and "GetPeopleCompletedEventArgs") matches the parameters in the explicit event handler we implemented earlier. Finally, the body of the delegate matches the body of the event handler callback.I know what you're thinking: big deal. Other than "in-lining" some code, exactly what did we gain here? We're about to find out.
We still have the same issue we had earlier with the ListBox SelectedIndex. The advantage to using an anonymous delegate is that any variables in the outer method can be accessed in the delegate implementation. This means that instead of using a class-level variable to store the index, we can use a local variable. Check out the completed code below:
(Click image to enlarge)
You'll see that we now have a local variable named "adListIndex" that is assigned outside of the delegate and then used inside of the delegate. We've just eliminated the need to have a class-level variable.
Solution #3 - Using a Lambda Expression
Let's take this one step further. Instead of using an anonymous delegate, we'll use a lambda expression. First, update the UI. This XAML is very similar to the anonymous delegate section:
(Click image to enlarge)
The same items are updated as above: grid column, element names, border color, button content, and click event.
So, now lets take a look at lambda expressions. There are 2 types of lambda expressions: statement lambdas and expression lambdas. Both use the same syntax (consisting of 3 parts):
- Parameters
These are normally enclosed by parentheses (although, parentheses can be excluded if there is only a single parameter).
The parameter types may be excluded if the compiler can determine the correct types. - =>
This is known as the "goes to" operator and denotes that this is a lambda. - Statement(s) or Expression(s)
A statement lambda has one or more operations wrapped in curly braces. The statement(s) denotes some type of work to be done.
An expression lambda has one or more expressions (that return true or false) wrapped in curly braces. These are used extensively in LINQ.
If there is only a single statement or expression, then the curly braces can be excluded.
(Click image to enlarge)
But, as we noted above, if the compiler can determine the parameter types, then we don't need to include them in our code:
(Click image to enlarge)
If you hover over the "s" and "ea" parameters, you will see that Visual Studio recognizes these as being of type "object" and "GetPeopleCompletedEventArgs" respectively. This means that we still have strongly-typed objects. The same is true of "ea.Result". This is the strongly-typed collection that is returned from our WCF Service.
As a final step, we can add the SelectedIndex handling code into the mix:
(Click image to enlarge)