Whoa! Insurrection on this series! Fair enough, it is based on the quite legitimate complaint that the mini-tutorials were too mini and not enough tutorial. To make up for this deficiency, this mini-tutorial will cover steps 2 and 3 described in the previous mini-tutorial: creating the form and basic data binding.
Let’s start by creating the form. Based on the work you did earlier in the series, you should feel reasonably comfortable opening Blend and creating 6 rows and two columns in the content grid, and dragging on the appropriate input controls. Here’s the Xaml for those of you who would just as soon work in Visual Studio,
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="24,0,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.384*" /> <ColumnDefinition Width="0.616*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.2*" /> </Grid.RowDefinitions> <TextBlock x:Name="NamePrompt" TextWrapping="Wrap" Text="Name" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Center" /> <TextBlock x:Name="SexPrompt" Grid.Row="2" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" Text="Sex" /> <TextBlock x:Name="HeightPrompt" TextWrapping="Wrap" Text="Height" HorizontalAlignment="Left" Grid.Row="3" d:LayoutOverrides="Height" VerticalAlignment="Center" /> <TextBlock x:Name="FavoritePrompt" TextWrapping="Wrap" Text="Favorite" HorizontalAlignment="Left" Grid.Row="4" d:LayoutOverrides="Height" VerticalAlignment="Center" /> <TextBox x:Name="Name" TextWrapping="Wrap" d:LayoutOverrides="Height" Grid.Column="1" HorizontalAlignment="Left" Width="200" VerticalAlignment="Center" Text="{Binding Name}" /> <StackPanel x:Name="BeardStackPanel" Grid.ColumnSpan="2" Grid.Row="1" Orientation="Horizontal"> <CheckBox x:Name="Moustache" Content="Moustache" HorizontalAlignment="Left" VerticalAlignment="Center" IsChecked="{Binding Moustache}" /> <CheckBox x:Name="Goatee" Content="Goatee" IsChecked="{Binding Goatee}" /> <CheckBox Content="Beard" IsChecked="{Binding Beard}"/> </StackPanel> <StackPanel x:Name="SexStackPanel" Grid.Column="1" Grid.Row="2" Orientation="Horizontal"> <RadioButton x:Name="Male" Content="Male" IsChecked="True" GroupName="Sex" /> <RadioButton x:Name="Female" Content="Female" GroupName="Sex" /> </StackPanel> <StackPanel x:Name="HeightStackPanel" Grid.Column="1" Grid.Row="3" Orientation="Horizontal"> <TextBlock TextWrapping="Wrap" Text="{Binding Height}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,0,0" /> <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="5,0,0,0" Text="meters" /> </StackPanel> <ToggleButton x:Name="Favorite" Content="Favorite" Grid.Column="1" Grid.Row="4" d:LayoutOverrides="Width, Height" HorizontalAlignment="Left" VerticalAlignment="Center" IsChecked="{Binding Favorite}" /> </Grid>
Notice that there are a few simplifications made for this tutorial. The slider has been removed from Height, and the Birthdate is not being shown. Both of these omissions will be addressed in the next mini-tutorial.
Binding
Each of the text entry fields now has its value set using the Binding syntax. For example, to tell the TextBox to bind, we identify which of its attributes will require the data; in this case the Text attribute, and we use the binding syntax, as shown here,
<TextBox x:Name="Name" TextWrapping="Wrap" d:LayoutOverrides="Height" Grid.Column="1" HorizontalAlignment="Left" Width="200" VerticalAlignment="Center" Text="{Binding Name}"/>
Bindings are within curly braces, and use the keyword Binding, typically followed by the name of the property to which you are binding the attribute. For example, this Xaml states that the Text for the text box will be obtained from a public property named Name.
We don’t yet know what object that property (Name) will be on. The object that contains the bindable property is known as the DataContext. It can be just about anything, but in our case, we’re going to create a person object, and then we’re going to set that person object to be the data context.
We can instantiate the person in the Loaded event handler of the code behind page. The Loaded even is called once the page is loaded and the controls are initialized.
public MainPage() { InitializeComponent(); Loaded += MainPage_Loaded; } void MainPage_Loaded( object sender, RoutedEventArgs e ) { _currentPerson = new Person { Beard = false, Favorite = true, Goatee = false, Height = 1.86, Moustache = true, Name = "Jesse", WhichSex = Person.Sex.Male }; }
We can now set the data context for ever control in the ContentPanel to be the _currentPerson object we just instantiated
ContentPanel.DataContext = _currentPerson;
Once it knows its datacontext, the TextBox can resolve the Name property and obtain the value (“Jesse”) to display.
We do the same with each of the other controls, setting their contents based on properties in the _currentPerson object.
Binding to Different Objects
To drive home the relationship between the binding and the display, let’s create a number of Person objects, and display them one by one. To do this we’ll modify MainPage.xaml.cs to create a list of (randomly created) Persons and then we’ll iterate through the list with a new “Next” button on the UI.
A few issues arise right away that will force us to simplify for this mini-tutorial and to pick up the complexity in the next. Both the person’s BirthDate and sex require a bit of manipulation to work with this simple approach (e.g., how do you translate a boolean into which CheckBox is checked?) It isn’t hard to handle this, and data binding provides a mechanism, but let’s save that for the next mini-tutorial.
We can now make all the changes in the code behind. First, we’ll stop hardwiring the current person, and instead we’ll set it by calling the GeneratePerson method (that we’ll review in a moment).
void MainPage_Loaded( object sender, RoutedEventArgs e ) { SetDataContext(); Next.Click += Next_Click; } private void SetDataContext() { ContentPanel.DataContext = GeneratePerson(); } void Next_Click( object sender, RoutedEventArgs e ) { SetDataContext(); }
Notice that both the page loaded event and the click event handler for the Next button must both set the data context, and so I’ve factored that out into a separate method: SetDataContext. That method, in turn calls our GeneratePerson method, whose job is to create a person at random.
Generating A Random Person
Here is the entire GenerateNewPerson method; you’ll see that I’ve factored out the task of choosing true vs. false into a method called FlipCoin.
private Person GeneratePerson() { var newPerson = new Person { Beard = FlipCoin(), Favorite = FlipCoin(), Goatee = FlipCoin(), Height = rand.NextDouble() + 1, Moustache = FlipCoin(), Name = names[rand.Next(0, names.Count - 1)] }; return newPerson; }
FlipCoin just uses the random number generator to return true 50% of the time:
private bool FlipCoin() { return rand.Next( 1, 3 ) % 2 == 0; }
Finally, to pick a name, I created a list of half a dozen names that can be assigned to men or women, and we use the random number generator to pick an offset into the list,
private readonly List<string> names = new List<string>() { "Stacey", "Robbie", "Jess", "Robin", "Syd", "J.J.", "Terri", "Moonunit", };
The net effect is that each time the Next button is pressed a new Person is generated and immediately bound to the fields.
14 Responses to Windows Phone From Scratch #6 – Data Binding (Really)