I am currently working on a Windows Phone 7 application where MVVMLight forms the foundation of the application. In a certain part of the application, I have the following requirements –
- Items in a ListBox to be loaded on demand by the user, with the user having the ability to paginate through the ListBox.
- The page number the user is currently viewing should be displayed in the page.
- The application requires a “First Page”, “Next Page” and a “Previous Page” button.
- While viewing the first page, the “First Page” and “Previous Page” buttons are disabled.
- When the user clicks the “First Page” button & arrives at the first page of the list of items, the “First Page” & “Previous Page” button are disabled.
- While browsing through the list of items with the “Previous Page” button, the “First Page” and “Previous Page” buttons are disabled when the user at the first page of the list of items.
- The application must show an “application is busy” indicator while data is being loaded after a button is clicked.
In the process of implementing these requirements & building the application, I learned a lot about MVVM pattern and its usage in an application with MVVMLight. In this post, I illustrate how these requirements can be implemented using MVVMLight. I will use a sample data source (Netflix OData Catalog) to illustrate how I implemented these requirements. Please feel free to comment on the post and the source code (available for download below). Please note that I have used Laurent Bugnion’s MVVMLight Windows Phone 7 project templates (with the WP7 hotfix applied) to develop this sample. I generated the proxy class for the Netflix OData services and followed the steps in Shawn’s OData post. More Windows Phone 7 development resources are available here.
Initial attempt to implement the requirements
My initial attempt to implement these requirements was to wrap the ListBox in a UserControl and add a panel with three buttons required for pagination through the ListBox. However, after reading the user interface guidelines and going through a bunch of design considerations, I decided to use the ApplicationBar to place the ListBox pagination buttons.I went ahead with the plan and added the icons and the three buttons –
The biggest challenge in implementing this requirement using the ApplicationBar is that it is not a Silverlight element and hence binding its properties and events is not possible through the view’s XAML (For a detailed explanation, read Peter Torr’s post – “Why are ApplicationBar objects not FrameworkElements?“). Laurent Bugnion proposes a neat way of binding RelayCommands in the ViewModel through the click events of the button and menu item of ApplicationBar. This approach fulfilled my requirement of binding the click event of the buttons in the ApplicationBar. However, to implement the requirements #4, #5 & #6 above, I had to bind the “IsEnabled” property of the “ApplicationBarIconButton” to the ViewModel. My first attempt at implementing these three requirements was to check the “PageNumber” property of the ViewModel and set the “IsEnabled” property through the Click event handler of the “ApplicationBarIconButton” –
private void FirstButton_Click(object sender, System.EventArgs e)
{
var vm = DataContext as MainViewModel;
if (vm != null)
{
vm.GetFirstPage.Execute(null);
UpdateMenuButtons(vm);
}
}
public void UpdateMenuButtons(MainViewModel vm)
{
if (vm)
{
if (PageNumber == 1) // ViewModel DependencyProperty
{
((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = false;
((ApplicationBarIconButton)ApplicationBar.Buttons[1]).IsEnabled = false;
}
else
{
((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = true;
((ApplicationBarIconButton)ApplicationBar.Buttons[1]).IsEnabled = true;
}
}
}
A refinement of the above approach
This approach just works fine, however, I didn’t like the fact that the View was tightly coupled to the ViewModel. I spent some time investigating patterns to decouple the View from the ViewModel & Laurent kindly pointed out a nice way to decouple the View from the ViewModel (read his suggestion here). This is how I implemented it –
public MainPage()
{
InitializeComponent();
var vm = DataContext as MainViewModel;
if (vm != null)
{
SetBinding(IsUserInFirstPageProperty, new System.Windows.Data.Binding("IsFirstPage") { Source = vm });
}
}
public bool IsUserInFirstPage
{
get { return (bool)(GetValue(IsUserInFirstPageProperty)); }
set { SetValue(IsUserInFirstPageProperty, value); }
}
public static readonly DependencyProperty IsUserInFirstPageProperty =
DependencyProperty.Register("IsUserInFirstPage", typeof(bool), typeof(MainPage),
new PropertyMetadata(new PropertyChangedCallback(OnIsFirstPreviousButtonEnabledChanged)));
private static void OnIsFirstPreviousButtonEnabledChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
MainPage m = (MainPage)o;
m.UpdateMenuButtons(null);
}
public void UpdateMenuButtons()
{
if (IsUserInFirstPage)
{
((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = false;
((ApplicationBarIconButton)ApplicationBar.Buttons[1]).IsEnabled = false;
}
else
{
((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = true;
((ApplicationBarIconButton)ApplicationBar.Buttons[1]).IsEnabled = true;
}
}
With this approach, the View is no longer tightly coupled with the ViewModel.
Adding a ProgressBar
I decided to use Jeff Wilcox’s PerformanceProgressBar (discussed in detail in this blog post). The progress bar is added in “MainPage.xaml” through the following XAML –
<ProgressBar
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="400"
IsIndeterminate="{Binding IsProgressBarVisible}"
Style="{StaticResource PerformanceProgressBar}"
Visibility="{Binding IsProgressBarVisible, Converter={StaticResource performanceProgressBarVisibilityConverter}}" />
Note that the progress bar style is added to the application resources in “App.xaml”. The progress bar should be visible when the Netflix OData endpoint is queried and must be invisible after data has been loaded. In order to accomplish this, I have used a dependency property on the ViewModel –
// Do we show the progress bar?
public const string IsProgressBarVisiblePropertyName = "IsProgressBarVisible";
private bool _isProgressBarVisible = true;
public bool IsProgressBarVisible
{
get
{
return _isProgressBarVisible;
}
set
{
if (_isProgressBarVisible == value)
{
return;
}
var oldValue = _isProgressBarVisible;
_isProgressBarVisible = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(IsProgressBarVisiblePropertyName, oldValue, value, true);
}
}
This “IsProgressBarVisible” property is updated before the query on the Netflix OData endpoint is issued and after the query completes & the data has been updated in the ListBox –
private void UpdateCatalog()
{
if (IsInDesignMode) // Create design time data for Blendability
{
Titles = new ObservableCollection<Title>();
for (var index = 0; index < 20; index++)
{
var title = new Model.Title
{
Name = "Loboris adiptis",
ShortSynopsis = "Facilisi faucibus habitant inceptos interdum lobortis nascetur pharetra placerat pulvinar sagittis senectus sociosqu"
};
Titles.Add(title);
}
CurrentPage = 1;
}
else
{
IsProgressBarVisible = true;
NetflixCatalog _catalog = new NetflixCatalog(new System.Uri("http://odata.netflix.com/catalog"));
var query = _catalog.Titles.Where(t => t.Rating == "PG").OrderByDescending(t => t.AverageRating)
.Skip((CurrentPage - 1) * 20) // 20 items to skip
.Take(20); // 20 items to fetch
var dsq = query as DataServiceQuery<Title>;
dsq.BeginExecute(
(callback) =>
{
var results = dsq.EndExecute(callback);
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
Titles.Clear();
foreach (var item in results)
{
Titles.Add(item);
}
IsProgressBarVisible = false;
});
}, null);
}
}
Alternative approach
Nicolas Humman recently published a post where he showed how to wrap the ApplicationBar into a control to provide binding through XAML. While this alternative approache may be suitable for my requirements, I didn’t feel confident that wrapping a non-FrameworkElement into a control is considered good practice.
Summary
In this post, I have highlighted a few key points that a Windows Phone 7 developer may encounter while s/he develops an application –
- Data and Event binding through XAML, minimum code-behind approach.
- Blendability – allows designers to work independent of the developer. (Note that in my approach, I have constructed the design-time data in code-behind, an alternative is use XAML provide design-time data.)
- Decoupling of Views and ViewModels.
- Usage of ApplicationBar for navigating through pages in a ListBox.
- Usage of PerformanceProgressBar and applying the IsIndeterminate property.
Resources I have referred while working with MVVM & MVVMLight on Windows Phone 7 –
This post does not illustrate –
- Dependency injection and is usage in ViewModelLocator (@kellabyte has two excellent posts that illustrate how to use NInject and MEF to use dependency injection in ViewModelLocator).
- Data virtualization best practices (Peter Torr’s post & Shawn Oster’s post are good references for this).
- Unit testing and testability of a Windows Phone 7 application.
Source Code
The source code for this sample application is available here. Please rename the file to .zip and uncompress.
Screenshots