The application displays data by binding elements of the view to properties in the view model. For example, the Pivot control on the SurveyListView page binds to the SelectedPivotIndex property in the SurveyListViewModel class.
The view can automatically update the values it displays in response to changes in the underlying view model if the view model implements the INotifyPropertyChanged interface. In the Tailspin mobile client, the abstract ViewModel class inherits from the NotificationObject class in the Prism Library. The Notification Object class implements the INotifyPropertyChanged interface. All the view model classes in the Tailspin mobile client application inherit from the abstract ViewModel class. The application also uses the ObservableCollection class from the System.Collections.Object Model name-space that also implements the INotifyProperty Changed interface.
Note: To learn more about Prism, see the Prism CodePlex site (http://compositewpf.codeplex.com/) and “Prism (Composite Client Application Guidance)” on the MSDN® developer program website (http://msdn.microsoft.com/en-us/library/ff648465.aspx).
Inside the Implementation
The following sections describe examples of data binding in the application. The first section describes a simple scenario on the AppSettingsView page, the next sections describe more complex examples using Pivot and Panorama controls, and the last section describes how Tailspin addressed an issue with focus events on the phone.
Data Binding on the Settings Screen
The AppSettingsView page illustrates a simple scenario for binding a view to a view model. On this screen, the controls on the screen must display property values from the AppSettingsViewModel class, and set the property values in the view model when the user edits the settings values.
The following code example shows the DataContext attribute and some of the control definitions in the AppSettingsView.xaml file. Tailspin chose to use the ToggleSwitch control in place of a standard CheckBox control because it better conveys the idea of switching something on and off instead of selecting something. The Toggle Switch control is part of the Microsoft Silverlight Windows Phone Toolkit available on the Silverlight Toolkit CodePlex site (http://silverlight.codeplex.com).
XAML
<phone:PhoneApplicationPage
x:Class=”TailSpin.PhoneClient.Views.AppSettingsView”
…
DataContext=”{Binding Source={StaticResource ViewModelLocator},
Path=AppSettingsViewModel}”
…>
…
<TextBox Height=”Auto” HorizontalAlignment=”Stretch”
Margin=”0,28,0,0″ Name=”textBoxUsername”
VerticalAlignment=”Top” Width=”Auto”
Text=”{Binding UserName, Mode=TwoWay}” Padding=”0″
BorderThickness=”3″>
<i:Interaction.Behaviors>
<prism:UpdateTextBindingOnPropertyChanged/>
</i:Interaction.Behaviors>
</TextBox>
…
<PasswordBox Height=”Auto” HorizontalAlignment=”Stretch”
Margin=”0,124,0,0″ Name=”passwordBoxPassword”
VerticalAlignment=”Top” Width=”Auto”
Password=”{Binding Password, Mode=TwoWay}”>
<i:Interaction.Behaviors>
<prism:UpdatePasswordBindingOnPropertyChanged/>
</i:Interaction.Behaviors>
</PasswordBox>
…
<toolkit:ToggleSwitch Header=”Subscribe to Push Notifications”
Margin=”0,202,0,0″
IsChecked=”{Binding SubscribeToPushNotifications, Mode=TwoWay}” />
…
<ProgressBar Height=”4″
HorizontalAlignment=”Stretch”Name=”progressBar”
VerticalAlignment=”Bottom”
IsIndeterminate=”{Binding IsSyncing}”/>
The view model class must implement the INotifyProperty Changed interface for automatic updates in the view to work. In the Tailspin client application, this interface is implemented by the View Model class that all the view models inherit from. A view model notifies a view of a changed property value by invoking one of the RaisePropertyChanged methods. The following code example shows how the AppSettingsViewModel view model class notifies the view that it should display the in-progress indicator to the user.
C#
public bool IsSyncing
{
get { return this.isSyncing; }
set
{
this.isSyncing = value;
this.RaisePropertyChanged(() => this.IsSyncing);
}
}
The code for the AppSettingsView page illustrates a solution to a common issue in Silverlight for the Windows Phone 7 platform. By default, the view does not notify the view model of property value changes until the control loses focus. For example, new content in the textBoxUserName control is lost if the user tries to save the settings before moving to another control. The UpdateTextBindingOn PropertyChanged behavior from the Prism Library ensures that the view always notifies the view model of any changes in the TextBox control as soon as they happen. The UpdatePasswordBindingOn PropertyChanged behavior does the same for the PasswordBox control.
Data Binding and the Pivot Control
The application uses the Pivot control to allow the user to view different filtered lists of surveys. Figure 5 shows the components in the mobile client application that relate to the Pivot control as it’s used on the SurveyListView page.
The following code example shows how the Pivot control on the SurveyListView page binds to the SelectedPivotIndex property of the SurveyListViewModel instance. This two-way binding determines which PivotItem control, and therefore which list of surveys, is displayed on the page. Remember, the ViewModelLocator class is responsible for locating the correct view model for a view.
XAML
<phoneControls:Pivot
Title=”TAILSPIN SURVEYS”
Name=”homePivot”
SelectedIndex=”{Binding SelectedPivotIndex, Mode=TwoWay}”
Visibility=”{Binding SettingAreConfi gured,
Converter={StaticResource VisibilityConverter}}”>
…
</phoneControls:PivotControl>
The following code example shows the defi nition of the Pivot Item control that holds a list of new surveys; it also shows how the ListBox control binds to the NewItems property in the view model.
XAML
<phoneControls:PivotItem Header=”new”>
<StackPanel Orientation=”Vertical”>
<ContentControl Template=”{StaticResource NoItemsTextBlock}”
Visibility=”{Binding NewItems.IsEmpty,
Converter={StaticResource VisibilityConverter}}” />
<ListBox ItemsSource=”{Binding NewItems}”
Style=”{StaticResource SurveyTemplateItemStyle}”
Visibility=”{Binding NewItems.IsEmpty,
Converter={StaticResource NegativeVisibilityConverter}}” >
</ListBox>
</StackPanel>
</phoneControls:PivotItem>
Note: In the list, the layout and formatting of each survey’s information is handled by the SurveyTemplateItemStyle style and the SurveyDateTemplate data template in the Styles.xaml file.
The SurveyListViewModel class uses CollectionViewSource objects to hold the list of surveys to display in the list on each Pivot Item control. This allows the view model to notice and to react to changes in the item selected in the view, without needing to know about the view itself. The following code example shows how the SurveyListViewModel class defines the properties that the ListBox controls bind to.
C#
private CollectionViewSource newSurveysViewSource;
private CollectionViewSource byLengthViewSource;
private CollectionViewSource favoritesViewSource;
…
public ICollectionView NewItems { get {
return this.newSurveysViewSource.View; } }
public ICollectionView FavoriteItems { get {
return this.favoritesViewSource.View; } }
public ICollectionView ByLengthItems { get {
return this.byLengthViewSource.View; } }
The following code example shows how the BuildPivot Dimensions method populates the CollectionViewSource objects.
In this example, to save space, only the code that populates the newSurveysViewSource property is shown.
C#
private void BuildPivotDimensions()
{
this.observableSurveyTemplates =
new ObservableCollection<SurveyTemplateViewModel>();
List<SurveyTemplateViewModel> surveyTemplateViewModels =
this.surveyStoreLocator.GetStore().GetSurveyTemplates()
.Select(t => new SurveyTemplateViewModel(t)
{
Completeness = this.surveyStoreLocator.GetStore()
.GetCurrentAnswerForTemplate(t) != null
? this.surveyStoreLocator.GetStore()
.GetCurrentAnswerForTemplate(t).CompletenessPercentage
: 0
}).ToList();
surveyTemplateViewModels.ForEach(
this.observableSurveyTemplates.Add);
…
this.newSurveysViewSource = new CollectionViewSource
{ Source = this.observableSurveyTemplates };
…
this.newSurveysViewSource.Filter +=
(o, e) => e.Accepted =
((SurveyTemplateViewModel)e.Item).Template.IsNew;
…
this.newSurveysViewSource.View.CurrentChanged +=
(o, e) => this.SelectedSurveyTemplate =
(SurveyTemplateViewModel)this.newSurveysViewSource
.View.CurrentItem;
…
// Initialize the selected survey template.
this.HandleCurrentSectionChanged();
}
This method first creates an ObservableCollection collection of SurveyTemplateViewModel objects. Each SurveyTemplateView Model object holds a complete definition of a survey and its questions. The method then assigns this collection to the Source property of each CollectionViewSource object. Next, the method applies a filter or a sort to each CollectionViewSource object so that it displays the correct set of surveys. The method then attaches an event handler to each CollectionViewSource object so that the Selected SurveyTemplate property of the SurveyListViewModel object is updated correctly. Finally, the method calls the HandleCurrent SectionChanged method that causes the view to update and display the currently selected PivotItem control with its list of surveys.
Data Binding and the Panorama Control
The application uses the Panorama control to display survey questions. This enables the user to scroll left and right through the questions as if the questions are all on a single large page of which the phone’s screen shows a small portion.
As in the previous examples, the view uses the ViewModel Locator class to create the view model. In this case, the ViewModel Locator object must also tell the view model which survey to display. The following code example shows how the ViewModelLocator object instantiates a TakeSurveyViewModel object and sets its TemplateViewModel property to identify which survey to display, and it creates a new SurveyAnswer object to hold the survey question definitions and the answers that the user enters.
C#
public TakeSurveyViewModel TakeSurveyViewModel
{
get
{
var templateViewModel =
this.SurveyListViewModel.SelectedSurveyTemplate;
var vm = new TakeSurveyViewModel(this.containerLocator
.Container.Resolve<INavigationService>())
{
SurveyStoreLocator = this.containerLocator.Container
.Resolve<ISurveyStoreLocator>(),
LocationService = this.containerLocator.Container
.Resolve<ILocationService>(),
TemplateViewModel = templateViewModel,
SurveyAnswer = this.containerLocator.Container
.Resolve<ISurveyStoreLocator>().GetStore()
.GetCurrentAnswerForTemplate(templateViewModel.Template)
?? templateViewModel.Template.CreateSurveyAnswers(this
.containerLocator.Container.Resolve<ILocationService>())
};
vm.Initialize();
return vm;
}
}
The Panorama control binds to the Questions property of the TakeSurveyViewModel class and a Panorama.ItemTemplate template controls the display of each question in the survey. However, it’s necessary to display different question types using different layouts. The following code example from the TakeSurveyView.xaml file shows
how the data binding and view layout is defined for the Panorama control using the DataTemplateSelector content selector class from the Prism Library.
XAML
<phoneControls:Panorama
SelectionChanged=”PanoramaSelectionChanged”
Loaded=”PanoramaLoaded”
VerticalAlignment=”Top”
Name=”panorama” Margin=”0,0,0,0″
ItemsSource=”{Binding Questions}”>
<phoneControls:Panorama.ItemTemplate>
<DataTemplate>
<ScrollViewer>
<prism:DataTemplateSelector Content=”{Binding}”
Grid.Row=”0″ VerticalAlignment=”Top”
HorizontalContentAlignment=”Left” IsTabStop=”False”>
<prism:DataTemplateSelector.Resources>
<DataTemplate x:Key=”OpenQuestionViewModel”>
<Views:OpenQuestionView DataContext=”{Binding}”/>
</DataTemplate>
<DataTemplate
x:Key=”MultipleChoiceQuestionViewModel”>
<Views:MultipleChoiceQuestionView
DataContext=”{Binding}”/>
</DataTemplate>
…
</prism:DataTemplateSelector.Resources>
</prism:DataTemplateSelector>
</ScrollViewer>
</DataTemplate>
</phoneControls:Panorama.ItemTemplate>
<phoneControls:Panorama.HeaderTemplate>
<DataTemplate>
<Canvas Width=”0″ Height=”0″/>
</DataTemplate>
</phoneControls:Panorama.HeaderTemplate>
</phoneControls:Panorama>
Note: It is necessary to create a HeaderTemplate data template even if you’re not using it with the Panorama control. You’ ll also notice that the XAML declares handlers for the Selection Changed and Loaded events in the code-behind. The code-behind also contains a workaround method to trim long titles that don’t always display correctly when the user scrolls in the Panorama control.
Each question view on the Panorama control binds to a view model object in the list of questions held in the Questions property of the TakeSurveyViewModel class. For example, an OpenQuestion View view binds to an OpenQuestionViewModel object, and a VoiceQuestionView view binds to a VoiceQuestionViewModel object. The following code example shows how the TakeSurveyView Model class builds this list of question view models.
C#
public IList<QuestionViewModel> Questions { get; set; }
…
public void Initialize(ISurveyStoreLocator surveyStoreLocator)
{
…
this.Questions = this.SurveyAnswer.Answers.Select(
a => Maps[a.QuestionType].Invoke(a)).ToArray();
}
The following code sample shows the definition of the Maps property in the TakeSurveyViewModel. The Maps property maps question types to view models.
C#
private static readonly
Dictionary<QuestionType, Func<QuestionAnswer,
QuestionViewModel>> Maps =
new Dictionary<QuestionType, Func<QuestionAnswer,
QuestionViewModel>>()
{
{ QuestionType.SimpleText,
a => new OpenQuestionViewModel(a) },
{ QuestionType.MultipleChoice,
a => new MultipleChoiceQuestionViewModel(a) },
…
};
Handling Focus Events
The file OpenQuestionView.xaml defines the UI for entering survey results. Tailspin found that when the user clicked the Save button, the last answer wasn’t saved by the view model. This is because Application BarIconButton control is not a FrameworkElement control, so it cannot get the focus. As a result, the lost focus event on the text box wasn’t being raised; as a result, the bindings didn’t tell the view model about the new field contents.
To solve this problem, the developers at Tailspin used a behavior named UpdateTextBindingOnPropertyChanged from the Prism Library. This behavior ensures that the view notifies the view model whenever the user changes the text in the TextBox control, not just when the control loses focus. The following code example shows how this behavior is defined in OpenQuestionView.xaml.
C#
…
xmlns:prism=
“clr-namespace:Microsoft.Practices.Prism.Interactivity;
assembly=Microsoft.Practices.Prism.Interactivity”
…
<TextBox Text=”{Binding AnswerText, Mode=TwoWay}”>
<Interactivity:Interaction.Behaviors>
<prism:UpdateTextBindingOnPropertyChanged/>
</Interactivity:Interaction.Behaviors>
</TextBox>