This article is part 3 in a 12-part article series on Windows Phone 7 for iPhone and Android Developers.
So far in this article series I’ve provided a brief introduction to the C# language, including object-oriented programming concepts like interfaces, inheritance, and even polymorphism. Next we took a quick tour of Xaml and hopefully learned just how powerful and underrated a language it is in the hands of skilled developers.
Now we’re going to expand on our newfound Xaml and Silverlight knowledge and play with some incredibly cool and powerful controls like the Panorama and the Pivot. Next, I’ll show you the basics of data binding and this is where you’ll really start to see one of the areas where, in my opinion, Silverlight truly outshines iOS and UIKit.
Using the Panorama Control
The Panorama control is a great control and anytime you use this control, your users will immediately feel like they are using a Windows Phone 7 application because of it’s prevalence throughout the application’s stock applications. You can see this control used in the people hub, the Xbox live hub, the music hub, and even the Marketplace.
It works by providing, as its name implies, a Panorama of content. The name of the application or the theme of the built-in hub appears in large letters on top while the user can swipe between various views all within the same panorama. As the user swipes between views, the background of the panorama scrolls accordingly.
This is control provides a really rich, immersive experience and lets users select between different interactive views that all correspond to a single shared theme or parent data set. To see a sample of the Panorama control in action (like the screenshot above) just fire up Visual Studio and create a new Windows Phone 7 project and select the Windows Phone Panorama Application template. Run the application right away and you’ll see a Panorama with two lists of data and a nice background that illustrates the scrolling action as the user swipes.
Here’s the Xaml for the sample panorama control (with some of the comments removed for brevity):
1: <controls:Panorama Title="my application">
2: <controls:Panorama.Background>
3: <ImageBrush ImageSource="PanoramaBackground.png"/>
4: </controls:Panorama.Background>
5: <controls:PanoramaItem Header="first item">
6: <ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}">
7: <ListBox.ItemTemplate>
8: <DataTemplate>
9: <StackPanel Margin="0,0,0,17" Width="432">
10: <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
11: <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
12: </StackPanel>
13: </DataTemplate>
14: </ListBox.ItemTemplate>
15: </ListBox>
16: </controls:PanoramaItem>
17: <controls:PanoramaItem Header="second item">
18: <ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}">
19: <ListBox.ItemTemplate>
20: <DataTemplate>
21: <StackPanel Orientation="Horizontal" Margin="0,0,0,17">
22: <Rectangle Height="100" Width="100" Fill="#FFE5001b" Margin="12,0,9,0"/>
23: <StackPanel Width="311">
24: <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
25: <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
26: </StackPanel>
27: </StackPanel>
28: </DataTemplate>
29: </ListBox.ItemTemplate>
30: </ListBox>
31: </controls:PanoramaItem>
32: </conrols:Panorama>
Skip past some of the statements that include the word Binding because we’ll be covering that at the end of the article. The main thing to note here is that the Xaml for the Panorama control shows you that each of the sections of content to which users can swipe are contained within an <controls:PanoramaItem> element. The PanoramaItem control, like many other containers such as Border can contain a single child control which is almost always either a layout container like Grid or StackPanel or, as in the preceding sample, a ListBox.
There are two caveats that I feel like I need to point out while discussing the Panorama control:
- The background will snap back to the beginning when the user scrolls from the last panorama item to the first (or from the first to the last if they’re swiping to the left). This means you are going to want to invest some time in making sure that the start and end of your panorama background matches up closely either in color tone or even in a pixel-perfect fashion like you would if you were making a tile-able background image. The reason for this is that the more jarring the switch from the left side of your background to the right side, the more unpleasant the panorama wrap-around effect will be. A little effort here will go a long way toward improving the user experience.
- You also need to keep in mind that even though a panorama item might not be visible to the user, it is already loaded into memory. This means that you need to keep a very close eye on the complexity (in terms of processor usage during load) of your panorama items as well as the number of them and the size of the content contained within. A small first panorama item that takes no time to load at all can still be slowed down by something going on while the tenth panorama item is binding. Either keep your panorama items small and few in number or go the extra mile to make sure that item contents aren’t loaded until the item comes into view. If you don’t want to take my word for it, add a Loaded event handler to a panorama item that starts off its life off-screen. You’ll see that event gets fired right when the containing panorama also loads.
If you want to defer some of that extra work until the panorama item comes into view, you can write an event handler for the Panorama control’s SelectionChanged event. For example, if you add an event handler to the panorama control from the previous sample, you can see how you have easy access to the panorama item at runtime:
1: private void mainPanorama_SelectionChanged(object sender, SelectionChangedEventArgs e)
2: {
3: Debug.WriteLine("new panorama item: " + (mainPanorama.SelectedItem as PanoramaItem).Header);
4: }
As you get to the end of this article and start seeing how to use data binding, then the world of possibilities available to you with the Panorama control will start to become clear.
While it is possible to create panorama-like views manually both in Android and iOS, neither platform has a stock control like Panorama that ships with the SDK. iOS can provide you with similar functionality using a UITabBarController to segment discrete views but it doesn’t give you the same “hub” feel that you get with the WP7 control.
As a side note – I have several issues with the organization and structure of the stock WP7 application templates so please do not use them as examples of the best real-world ways to set up applications. I’ll show you some of my favorite ways to create applications when I get to the article on MVC and MVVM.
Using the Pivot Control
The Pivot control is related to the Panorama control but with a slightly different feel and purpose. The first difference you’ll notice is that, while it is possible to set a background picture on the PIvot control, it doesn’t scroll when the user switches between PivotItems. The other thing you’ll notice is that the header of the pivot items appear close to each other, allowing you to see the header of the next item without seeing the next item’s content.
This control functions much more like an iOS/UIKit UITabBarController than the panorama. You use it to separate discrete views of information into multiple pivot items (you can think of these as tabs if it makes it easier). These items are often unrelated or loosely related, whereas the items within a Panorama control are often related along some common thread (hence the “hub” paradigm used in WP7’s nomenclature).
Just like with the Panorama, Pivot items are loaded when the main pivot control is loaded, along with all of their contents. You can use similar tricks like using SelectionChanged to defer work and avoid any unnecessary processing and memory consumption.
Both the Panorama and the PIvot controls are templatable. This means you can change nearly all aspects of their appearance without actually re-writing your own control. For example, if you want to modify the Pivot control so that instead of seeing the header text belonging to other pivot items, you actually see left and right arrows or each header includes it’s own icon – you just need to modify the PIvot’s HeaderTemplate property. You can customize things even further by manually modifying the Header property of the PivotItem control.
Here’s a screenshot of a Pivot control that has been modified so that the first pivot item has a “thumbs up” icon next to the title:
And here’s the easy bit of Xaml that makes this possible:
1: <controls:Pivot Title="MY APPLICATION">
2: <!--Pivot item one-->
3: <controls:PivotItem>
4: <controls:PivotItem.Header>
5: <StackPanel Orientation="Horizontal">
6: <Image Height="32" Width="32" Margin="5" Source="/WindowsPhonePivotApplication1;component/017-ThumbsUp.png" />
7: <TextBlock Text="first"/>
8: </StackPanel>
9: </controls:PivotItem.Header>
10: ...
11: </controls.PivotItem>
12: ... and so on ...
13: </controls:Pivot>
The Pivot and Panorama controls are two of the most powerful controls available to WP7 developers and they are also two controls that give developers a head start in creating compelling user interfaces that feel at home on a Windows Phone 7 device. Just like the UITableViewController creates a default UI that feels classically “iPhone-ish”, these two controls have the same power for a Windows Phone 7 application.
Introduction to Data Binding
Data binding is the glue that holds some of the most powerful and useful WP7 applications together. The ability to perform real data binding declaratively using Xaml is, in my opinion, one of the biggest differentiators between WP7 and iPhone development. Android has declarative data binding capabilities as well so this section should be fairly easy to absorb for Android developers.
Put simply, data binding consists of connecting (or binding) a specific property of a UI element to an instance of some C# class. This allows you to do everything from bind the contents of a text box to an object’s string property to binding the source of items of a list box to an array of objects.
Simple Binding
Declarative binding, in its simplest form, is a simple expression wrapped within curly braces (the ‘}’ and ‘{‘ characters). In a previous article I showed you how to create an instance of my Zombie class within a page’s ResourceDictionary property. What I skimmed over is that the presence of this instance in the resource dictionary makes it available for binding. In other words, I could bind a text box’s Text property to the Name property of my zombie object like this:
1: <TextBlock Text="{Binding Path=Name, Source={StaticResource mainZombie}}"
2: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
Using this new syntax, note that there is a Binding keyword. Remembering that Xaml is, at its core, an instantiation language, it becomes clear that we’re actually setting a property called Binding to a new instance of a binding object and that binding object has two key properties: Path and Source. Source is the source of the binding. This is a really powerful property and I will skip over a lot of the details of how this works but you should remember that source can be a “nearby” Xaml element, it can refer to something relative to the current data context, or it can be a directly referenced instance of an object like the one in the preceding sample. In the preceding sample I’m binding the Name property of the resource dictionary instance of my Zombie object to the Text property of the text block.
Using this new declarative syntax for data binding I can quickly and easy create a UI around my Zombie binding, as the following screenshot shows:
The preceding screenshot was generated by the following Xaml (the markup for the default page of a nearly empty WP7 application):
1: <phone:PhoneApplicationPage
2: x:Class="SimpleDataBinding.MainPage"
3: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5: xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
6: xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
7: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
9: mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
10: FontFamily="{StaticResource PhoneFontFamilyNormal}"
11: FontSize="{StaticResource PhoneFontSizeNormal}"
12: Foreground="{StaticResource PhoneForegroundBrush}"
13: SupportedOrientations="Portrait" Orientation="Portrait"
14: xmlns:xamlintro="clr-namespace:IntroToXaml"
15: shell:SystemTray.IsVisible="True">
16: <phone:PhoneApplicationPage.Resources>
17: <xamlintro:Zombie x:Key="mainZombie" Name="Harold" Hitpoints="12" OozeColor="Purple">
18: <xamlintro:Zombie.Weapon>
19: <xamlintro:Shotgun DamageClass="12" RemainingAmmo="50"/>
20: </xamlintro:Zombie.Weapon>
21: </xamlintro:Zombie>
22: </phone:PhoneApplicationPage.Resources>
23:
24: <Grid x:Name="LayoutRoot" Background="Transparent">
25: <Grid.RowDefinitions>
26: <RowDefinition Height="Auto"/>
27: <RowDefinition Height="*"/>
28: </Grid.RowDefinitions>
29:
30: <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
31: <TextBlock x:Name="ApplicationTitle"
32: Text="{Binding Path=Name, Source={StaticResource mainZombie}}"
33: Style="{StaticResource PhoneTextNormalStyle}"/>
34: <TextBlock x:Name="PageTitle"
35: Text="Zombie" Margin="9,-7,0,0"
36: Style="{StaticResource PhoneTextTitle1Style}"/>
37: </StackPanel>
38:
39: <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
40: <StackPanel>
41: <StackPanel Orientation="Horizontal">
42: <TextBlock Text="Zombie Name:" Margin="5"/>
43: <TextBlock Text="{Binding Path=Name, Source={StaticResource mainZombie}}"
44: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
45: </StackPanel>
46: <StackPanel Orientation="Horizontal">
47: <TextBlock Text="Weapon Ammo Remaining:" Margin="5"/>
48: <TextBlock Text="{Binding Path=Weapon.RemainingAmmo, Source={StaticResource mainZombie}}"
49: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
50: </StackPanel>
51: </StackPanel>
52: </Grid>
53: </Grid>
54: </phone:PhoneApplicationPage>
There are a few things worth pointing out here that I’ll show you how to deal with in the next few sections. The first is that if you are able to get ahold of the instance of the Zombie object to which your GUI is bound and make a bunch of property changes, the UI will not change. There is a special set of steps that you need to perform on any C# object to which Xaml GUI is bound in order to make changes to the object reflected in the UI. Second, we also want to be able to deal with lists and collections and we will have the same problem with those – if we bind to a traditional array or a regular List object then the GUI will not be aware of changes made to the collection.
Using INotifyPropertyChanged
By default, data binding takes place once at the time of binding. This means that the values that show up in your GUI will be whatever values were on the bound object at the time it became the data context. This could be right at application startup for declarative binding like I’ve showed you so far, or you can programmatically set the data context of individual UI elements at runtime if you wish:
1: Zombie z = LoadZombie();
2: myTextBlock.DataContext = z;
If you change the properties on this object after it is initially bound, the UI has no way of knowing whether it should go look at the object for new or updated values. The only way the UI can know when important changes take place on bound objects is if you tell it. This is where the INotifyPropertyChanged interface comes in.
When you bind a Xaml element to an object that implements the INotifyPropertyChanged interface, the UI subscribes to a special event called PropertyChanged. Whenever this event is received, if a UI element is bound to a Path that includes that property (or it’s sub-properties!) then that element will re-bind.
To illustrate this, let’s modify the Zombie object from the Introduction to Xaml article earlier in this series so that it implements this interface and fires the appropriate event when its properties change:
1: using System.ComponentModel;
2:
3: namespace IntroToXaml
4: {
5: public class Zombie : INotifyPropertyChanged
6: {
7: private string name;
8: private int hitPoints;
9: private string oozeColor;
10: private Weapon weapon;
11:
12: public string Name
13: {
14: get { return name; }
15: set { name = value; NotifyPropertyChanged("Name"); }
16: }
17:
18: public int Hitpoints
19: {
20: get { return hitPoints; }
21: set { hitPoints = value; NotifyPropertyChanged("Hitpoints"); }
22: }
23:
24: public string OozeColor
25: {
26: get { return oozeColor; }
27: set { oozeColor = value; NotifyPropertyChanged("OozeColor"); }
28: }
29:
30: public Weapon Weapon
31: {
32: get { return weapon; }
33: set { weapon = value; NotifyPropertyChanged("Weapon"); }
34: }
35:
36: public event PropertyChangedEventHandler PropertyChanged;
37:
38: protected void NotifyPropertyChanged(string propertyName)
39: {
40: if (PropertyChanged != null)
41: PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
42: }
43: }
44: }
Now, to prove that we can modify the value of a property on the Zombie object and have that value automatically reflected in the GUI (the essence of data binding), let’s put a “Punch Zombie” button on the main page and also display the zombie’s current hitpoints. Here’s a modified MainPage.xaml file containing the new UI elements:
1: <phone:PhoneApplicationPage
2: x:Class="SimpleDataBinding.MainPage"
3: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5: xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
6: xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
7: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
9: mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
10: FontFamily="{StaticResource PhoneFontFamilyNormal}"
11: FontSize="{StaticResource PhoneFontSizeNormal}"
12: Foreground="{StaticResource PhoneForegroundBrush}"
13: SupportedOrientations="Portrait" Orientation="Portrait"
14: xmlns:xamlintro="clr-namespace:IntroToXaml"
15: shell:SystemTray.IsVisible="True">
16: <phone:PhoneApplicationPage.Resources>
17: <xamlintro:Zombie x:Key="mainZombie" Name="Harold" Hitpoints="100" OozeColor="Purple">
18: <xamlintro:Zombie.Weapon>
19: <xamlintro:Shotgun DamageClass="12" RemainingAmmo="50"/>
20: </xamlintro:Zombie.Weapon>
21: </xamlintro:Zombie>
22: </phone:PhoneApplicationPage.Resources>
23:
24: <Grid x:Name="LayoutRoot" Background="Transparent">
25: <Grid.RowDefinitions>
26: <RowDefinition Height="Auto"/>
27: <RowDefinition Height="*"/>
28: </Grid.RowDefinitions>
29:
30: <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
31: <TextBlock x:Name="ApplicationTitle"
32: Text="{Binding Path=Name, Source={StaticResource mainZombie}}"
33: Style="{StaticResource PhoneTextNormalStyle}"/>
34: <TextBlock x:Name="PageTitle"
35: Text="Zombie" Margin="9,-7,0,0"
36: Style="{StaticResource PhoneTextTitle1Style}"/>
37: </StackPanel>
38:
39: <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
40: <StackPanel>
41: <StackPanel Orientation="Horizontal">
42: <TextBlock Text="Zombie Name:" Margin="5"/>
43: <TextBlock Text="{Binding Path=Name, Source={StaticResource mainZombie}}"
44: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
45: </StackPanel>
46: <StackPanel Orientation="Horizontal">
47: <TextBlock Text="Zombie Hitpoints:" Margin="5"/>
48: <TextBlock Text="{Binding Path=Hitpoints, Source={StaticResource mainZombie}}"
49: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
50: </StackPanel>
51: <StackPanel Orientation="Horizontal">
52: <TextBlock Text="Weapon Ammo Remaining:" Margin="5"/>
53: <TextBlock Text="{Binding Path=Weapon.RemainingAmmo, Source={StaticResource mainZombie}}"
54: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
55: </StackPanel>
56: <Button x:Name="punchZombieButton" Click="punchZombieButton_Click">
57: <TextBlock Text="Punch Zombie"/>
58: </Button>
59: </StackPanel>
60: </Grid>
61: </Grid>
62: </phone:PhoneApplicationPage>
And here’s the C# event handler for the punch button’s click event:
1: private void punchZombieButton_Click(object sender, RoutedEventArgs e)
2: {
3: Zombie z = this.Resources["mainZombie"] as Zombie;
4: if (z != null)
5: {
6: z.Hitpoints -= 1;
7: }
8:
9: }
Run the application now and click the “Punch Zombie” button. You should see the zombie’s remaining hit points go down every time you tap the button. While Cocoa developers have long had a powerful, robust bindings feature there is no such feature in iOS. In other words, to accomplish this on the iPhone a developer would have to manually change the text property of a label every time the underlying data changes. While it’s certainly possible and iOS developers get used to this kind of thing, being able to set up the relationships between view and data declaratively and have those relationships automatically handle UI updates is quite a convenience.
Using Observable Collections
Now that we know we can bind single elements of our UI to single instances of C# objects, the next thing we’re going to want to do is be able to bind a list of objects to a list-type control like the ListBox. To do this, we’ll need to use a special kind of collection called an ObservableCollection. These collections implement the INotifyCollectionChanged interface. In the same way that INotifyPropertyChanged tells the UI when a single property has been modified and requires re-binding, objects that implement the INotifyCollectionChanged interface can tell the UI when one or more items have been added, deleted, or moved around within the collection.
To see how this works, let’s create a ListBox that is bound to a list of Zombies and we’ll make a button that creates new Zombie instances and adds them to an observable collection so that we can watch the Zombies appear within the UI.
If you’re familiar with Cocoa programming, then the use of Observable Collections should seem very familiar to you – it is a lot like the use of array controllers which you can use in conjunction with the Cocoa bindings facility to the same effect as Xaml bound to an Observable Collection. Android uses a concept called adapters which serve as “plugs” that attach UI elements with data associated with the adapter. Again, iOS has no native binding concepts so this all may appear very new (and hopefully very appealing!) to iPhone developers.
To start, let’s create a LIstBox. We can use a feature called a DataTemplate that defines the UI elements that will be produced for each data item in the bound list. I won’t show it in this article but you can even use something called a template selector to programmatically determine which data template to use for a given bound item at runtime. For example, if you were displaying a ledger list and you wanted different data templates for debits and credits, you would use a template selector.
Below is the Xaml for the zombie-bound list box. Take a close look at the data template and the binding syntax. What you should notice here is that the Path of each binding expression has remained exactly the same as in the previous sample but I’ve left off the Source property of the binding expression. This is because it is a data template. Every time a bound UI element is needed, the data template is copied and converted into real Xaml and the source of any binding expression automatically defaults to the currently bound item (though you can override this behavior if you want).
1: <ListBox x:Name="ZombieList">
2: <ListBox.ItemTemplate>
3: <DataTemplate>
4: <StackPanel>
5: <StackPanel Orientation="Horizontal">
6: <TextBlock Text="Zombie Name:" Margin="5"/>
7: <TextBlock Text="{Binding Name}"
8: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
9: </StackPanel>
10: <StackPanel Orientation="Horizontal">
11: <TextBlock Text="Zombie Hitpoints:" Margin="5"/>
12: <TextBlock Text="{Binding Hitpoints}"
13: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
14: </StackPanel>
15: <StackPanel Orientation="Horizontal">
16: <TextBlock Text="Weapon Ammo Remaining:" Margin="5"/>
17: <TextBlock Text="{Binding Path=Weapon.RemainingAmmo}"
18: Style="{StaticResource PhoneTextAccentStyle}" Margin="5"/>
19: </StackPanel>
20: </StackPanel>
21: </DataTemplate>
22: </ListBox.ItemTemplate>
23: </ListBox>
Another handy thing to notice here is that if the only part of the binding expression you’re specifying is the Path, then you don’t need to use “Path=”, you can just use expressions like {Binding Name} or {Binding Hitpoints}. Now I am going to take the “punch” button we created earlier and make it punch the currently selected zombie in the list. In addition, I’m also creating a delete and an add button that will add new zombies to the end of the list and delete zombies from the front. The event handlers for those click events are shown here:
1: private void punchZombieButton_Click(object sender, RoutedEventArgs e)
2: {
3: //Zombie z = this.Resources["mainZombie"] as Zombie;
4: if (ZombieList.SelectedItem != null)
5: {
6: Zombie z = ZombieList.SelectedItem as Zombie;
7: if (z != null)
8: {
9: z.Hitpoints -= 1;
10: }
11: }
12: }
13:
14: private void addZombieButton_Click(object sender, RoutedEventArgs e)
15: {
16: Zombie z = new Zombie()
17: {
18: Hitpoints = 100,
19: Name = "Test Zombie",
20: OozeColor = "purple",
21: Weapon = new Shotgun() { DamageClass = 10, RemainingAmmo = 100 }
22: };
23: zombies.Add(z);
24:
25: }
26:
27: private void delZombieButton_Click(object sender, RoutedEventArgs e)
28: {
29: if (zombies.Count > 0)
30: zombies.RemoveAt(0);
31: }
If you’re not all that familiar with C# then the syntax I’m using to create a new zombie might look a bit strange. C# allows me to supply values for properties and nested properties all within the constructor of the class. This gives me a nice and very readable way of creating a new instance of an object without the tedium of writing “object.property = value;” over and over.
Now when you run the application, you’ll be able to click the “Add” button to create new zombies and observe the change that has on the UI. Deleting zombies will remove the first zombie from the list and you’ll see the list box contents shrink, moving all the other zombies up higher in the list. Finally, clicking the “punch zombie” button will let you affect a single property of one data-bound item within an observable collection. Here’s what the UI looks like after I’ve punched a couple zombies a few times:
Using Converters
Converters give us even more flexibility when we’re data binding: they let us convert between two different data types for any given bound field. What if I wanted to bind an icon to difficulty level? In other words, I want to show a baby sucking on a lollipop for an easy game level and I want to show a scar-riddled bloody angry face for the hardest game level. Difficulty levels are usually stored on model objects as an integer or an enumerated type and icons are images. Using a converter, we can bind the source of an image to the integer difficulty level and have the image display the right icon.
In this next sample, I’m going to bind the color of the zombie’s remaining hitpoints to its remaining hitpoints. In other words, I want its hitpoints to show up as green when it has more than 50 points remaining, yellow when below 50, and red at 10 or under. Using a converter is actually a fairly easy 3-step process:
- Create the converter
- Add an instance of the converter to a resource dictionary
- Refer to the converter in the binding expression for the bound element.
So to get started, let’s create a class (all converters must implement the IValueConverter interface) that converts from hitpoints to a color:
1: using System;
2: using System.Windows.Media;
3: using System.Windows.Data;
4:
5: namespace SimpleDataBinding
6: {
7: public class HitpointsToColorConverter : IValueConverter
8: {
9:
10: public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
11: {
12: int hitpoints = (int)value;
13:
14: Color outColor = Colors.Green;
15: if (hitpoints < 50)
16: outColor = Colors.Yellow;
17: if (hitpoints <= 10)
18: outColor = Colors.Red;
19:
20: return new SolidColorBrush(outColor);
21: }
22:
23: public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
24: {
25: throw new NotImplementedException();
26: }
27: }
28: }
Step 2 is to add the converter to the resource dictionary so we can refer to it from a binding expression:
1: <phone:PhoneApplicationPage
2: <!-- cut a bunch of stuff here for clarity -->
3: xmlns:local="clr-namespace:SimpleDataBinding"
4: shell:SystemTray.IsVisible="True">
5: <phone:PhoneApplicationPage.Resources>
6: <local:HitpointsToColorConverter x:Key="hitpointsToColor" />
7: </phone:PhoneApplicationPage.Resources>
8:
9: ... and so on ...
10:
11: </phone:PhoneApplicationPage>
And finally, step 3 is to bind the foreground color of the text block to the hitpoints property using the new converter:
1: <StackPanel Orientation="Horizontal">
2: <TextBlock Text="Zombie Hitpoints:" Margin="5"/>
3: <TextBlock Text="{Binding Hitpoints}"
4: Foreground="{Binding Hitpoints, Converter={StaticResource hitpointsToColor}}"
5: Margin="5"/>
6: </StackPanel>
Now when you run the application and you pick an unsuspecting zombie and start punching it, you’ll see the color of its hitpoints label go from green to yellow at 50 and from yellow to red at 10.
Summary
So far in this series of articles, we’ve taken a look at the C# language and moved on from that to an exploration of Xaml and how you can use it to declaratively build user interfaces. In this article, we took a look at two of the more powerful and visually pleasing controls available to WP7 developers, the Pivot and Panorama controls.
With all of these new tools in our toolbox, we moved on to exploring the basics of data binding to really start moving our WP7 development skills out of the realm of “hello world” and into the real world where we can build real, powerful, useful applications. As we continue throughout this article series, we will see progressively more powerful aspects of WP7 development that build on what we’ve covered so far.
About the Author
Kevin Hoffman (http://www.kotancode.com/) is a Systems Architect for Oakleaf Waste Management (http://www.oakleafwaste.com/), freelance developer, and author of multiple books including the upcoming WP7 for iPhone Developers and co-author of books such as ASP.NET 4 Unleashed and SharePoint 2007 Development Unleashed. He is the author of the Kotan Code blog and has presented at Apple's WWDC twice and guest lectured at Columbia University on iPhone development.