Unit Converter Starter Kit
March 22, 2012
This Windows Phone 7 Starter Kit is a complete Unit Converter application written in C#. The program provides the user with the ability to convert values from one type of unit to another. The units can be in the following categories: length, temperature, speed, time, volume, angle, weight, or area.
The program consists of two pages. The main page allows the user to enter a number. This number is displayed in one unit and then converted and displayed in another unit. For example, the user can enter 8 inches and the program will then convert that to 20.32 centimeters. If the user has a particular conversion they use frequently, they can save it as a favorite.
The user can navigate to a Category Selection page where they can select from categories of units. Within each category, the user can select a unit to convert From and a unit to convert To. Amongst the categories will be the user’s Favorites. The user can tap and hold a favorite conversion to display a context menu. This context menu enables the user to delete the favorite.
Extending the Unit Converter Application
Note: |
|---|
This documentation assumes that you have a basic knowledge of C# programming concepts and the Windows Phone SDK. You can download the Windows Phone SDK here. The code for this Unit Converter Starter Kit can be downloaded here. You can also read a whitepaper related to the handling of tombstoning in this application by downloading it from here. |
Goals
After reading through this topic, you will understand how the Unit Converter program works. You will also understand a few ways in which you can customize it using the Windows Phone SDK. This starter kit demonstrates:
How to apply the Model-View–ViewModel design pattern in creating your Windows Phone 7 application.
How to optimize your application start up.
How to create a context sensitive menu.
How to dynamically add pages to a pivot control.
Getting Started
To compile and run the Unit Converter Starter Kit:
Download and unzip the Unit Converter Starter Kit.
Open the UnitConverter.sln solution file in Visual Studio.
Build the Unit Converter application and run it in the emulator or deploy it to your registered phone.
The Unit Converter application applies the Model-View-ViewModel (MVVM) design pattern. The files in the Unit Converter solution are divided into Model, View, and ViewModel folders. For more information about MVVM, see WPF Apps With The Model-View-ViewModel Design Pattern, Model-View-ViewModel In Silverlight 2 Apps, and Problems and Solutions with Model-View-ViewModel.
Besides the Model, View, and ViewModel folders, the solution also includes a number of folders that contain helper classes.
Main Program
-
App.xaml.cs - Contains the App() method – the location where the program begins execution and where event handlers are initialized.
Model
The files in the Model folder contain the data for the application.
-
ApplicationState.cs - Holds information state that is used across multiple pages.
-
CategoryInformation.cs - Contains the conversion information for units of a particular category (length, weight, etc).
-
CategoryPageState.cs - Contains the state information specific for the Category Selection page.
-
CommonPageState.cs - Contains state information that is shared between the MainPage and the Category Selection page.
-
CurrentConversion.cs - Contains information about the currently selected conversion.
-
FavoriteCollection.cs - Contains the collection of the user’s favorite conversions.
-
FavoriteData.cs - Contains the data for one of the user’s favorite conversions. These are serialized and deserialized to and from isolated storage.
-
MainPageState.cs - Contains state information for the MainPage.
-
UnitInformation.cs - Contains specific information about each unit of conversion.
View
The files in the View folder control the user interface layout of the application.
-
CategorySelection.xaml and CategorySelection.xaml.cs - Displays information for the Category Selection page.
-
MainPage.xaml and MainPage.xaml.cs - Displays information for the MainPage where conversions are done.
-
PageCommon.cs - Contains functions that are common between the MainPage and the Category Selection page.
-
TiltEffect.cs - Provides attached properties for adding a 'tilt' effect to all controls within a container. To learn more about implementing the ‘tilt’ effect, see Control Tilt Effect for Windows Phone.
-
TwoListBoxes.cs - A control that displays two list boxes together. One list box contains the list of units to convert ‘From’, the other that contains the list of units to convert ‘To’.
ViewModel
The files in the ViewModel folder manipulate the data so that it displays correctly in the user interface of the application.
-
CategorySelectionViewModel.cs - Ties Category Selection data to the Category Selection page.
-
MainPageViewModel.cs - Ties MainPage data to the MainPage.
ContextMenu
-
ContextMenu.cs - Implements a context menu.
-
ContextMenu.generic.xaml - Contains styles and templates for the context menu.
-
ContextMenuService.cs - Helps to attach a context menu onto a control.
Converters
-
DecimalSepartorMarginConverter.cs - Reads the locale settings to determine decimal separation for the current values of the units.
Helpers
-
AppOpenState.cs - An enumeration that determines state of the application. Helps to optimize page loading.
-
ErrorLog.cs - Logs exception information.
-
ErrorLogCollection.cs - A collection of error logs.
-
FileOps.cs - Helps to load data from a content file in the xap, which contains the conversion information.
-
IsolatedStorage.cs - Reads and writes the Favorites information to and from isolated storage.
Themes
-
Generic.xaml - Helps to bring in the ContextMenu.generic.xaml.
Loading the Application
This program demonstrates how to load an application more quickly by loading data on a background thread. From the user’s perspective, the MainPage is loaded quickly. But the Conversions and Add as Favoritebuttons will not be enabled until the available conversions have been read in. When the MainPage is rendered, the PageLayoutUpdated event will be fired. The call to the base class will unsubscribe to this event, so that this method is only called once. Next, the call to DeferStartup will read the available conversions from SupportedUnits.xml and load the saved favorites from isolated storage on a background thread.
protected override void PageLayoutUpdated(object sender, EventArgs e)
{
base.PageLayoutUpdated(sender, e);
if (ApplicationState.ApplicationStartup == AppOpenState.Launching)
{
this.viewModel.DeferStartup(this.SignalFavoritesAreLoaded);
ApplicationState.ApplicationStartup = AppOpenState.None;
}
}
DeferStartup runs the DeferStartupWork method on a background thread. The worker thread is declared like this:
private BackgroundWorker worker = new BackgroundWorker();
The method for the background worker thread to execute is assigned in the MainPageViewModel constructor:
internal MainPageViewModel()
{
this.ConversionSettings = new CurrentConversion();
this.ConversionImageSource = ApplicationState.IsDarkTheme ?
SwitchConversionImageLight : SwitchConversionImageDark;
this.worker.DoWork += new DoWorkEventHandler(DeferStartupWork);
}
DeferStartup then starts the background operation, causing DeferStartupWork to execute.
internal void DeferStartup(Action completed)
{
this.worker.RunWorkerAsync(completed);
}
DeferStartupWork then does the work of loading the conversions from SupportedUnits.xml. The saved favorites are also loaded from isolated storage. The call to DeferStartup in PageLayoutUpdated passed along a method to call when the background thread was completed. This method is SignalFavoritesAreLoaded and it sets a flag that allows the user to select the Conversions and Add as Favorite buttons:
private void SignalFavoritesAreLoaded()
{
Dispatcher.BeginInvoke(() => this.viewModel.AllowNavigation = true);
}
Loading the Conversion Data
The data for the supported units and conversions is included in the SupportedUnits.xml file. This allows additional conversions to be added to the program by just updating SupportedUnits.xml and the string resource files for each language. The information for a category such as length looks like this:
<!--UnitGroup_Length-->
<CategoryInformation>
<Category>UnitGroup_Length</Category>
<Units>
<UnitInformation>
<ResourceName>Units_Length_Inches</ResourceName>
<Multiplier>0.08333333333333333333333333333333</Multiplier>
<Offset>0</Offset>
</UnitInformation>
<UnitInformation>
<ResourceName>Units_Length_Feet</ResourceName>
<Multiplier>1</Multiplier>
<Offset>0</Offset>
</UnitInformation>
<UnitInformation>
<ResourceName>Units_Length_Yards</ResourceName>
<Multiplier>3</Multiplier>
<Offset>0</Offset>
</UnitInformation>
<UnitInformation>
<ResourceName>Units_Length_Miles</ResourceName>
<Multiplier>5280</Multiplier>
<Offset>0</Offset>
</UnitInformation>
<UnitInformation>
<ResourceName>Units_Length_Millimeters</ResourceName>
<Multiplier>.00328083989</Multiplier>
<Offset>0</Offset>
</UnitInformation>
<UnitInformation>
<ResourceName>Units_Length_Centimeters</ResourceName>
<Multiplier>.0328083989</Multiplier>
<Offset>0</Offset>
</UnitInformation>
<UnitInformation>
<ResourceName>Units_Length_Meters</ResourceName>
<Multiplier>3.28083989</Multiplier>
<Offset>0</Offset>
</UnitInformation>
<UnitInformation>
<ResourceName>Units_Length_Kilometers</ResourceName>
<Multiplier>3280.83989</Multiplier>
<Offset>0</Offset>
</UnitInformation>
</Units>
</CategoryInformation>
The method LoadFromFileContent in FileOps.cs will read all the available conversions from SupportedUnits.xml.
public static T LoadFromFileContent<T>(string fileName)
{
T loadedFile = default(T);
try
{
StreamResourceInfo sr =
Application.GetResourceStream(new Uri(fileName, UriKind.Relative));
if (sr != null)
{
XmlSerializer mySerializer = new XmlSerializer(typeof(T));
loadedFile = (T)mySerializer.Deserialize(sr.Stream);
}
}
catch (Exception e)
{
ApplicationState.ErrorLog.Add(new ErrorLog("LoadFromFileContent", e.Message));
}
return loadedFile;
}
The Conversions pivot control is created in CategorySelection.xaml. The first PivotItem for the favorite conversions is created in xaml also. The rest of the PivotItems for the Conversion Selection page are created programmatically, based on conversion information provided in SupportedUnits.xml:
private static void AddPivotItems(CategorySelection view)
{
foreach (CategoryInformation category in ApplicationState.SupportedConversions)
{
PivotItem p = new PivotItem();
TwoListBoxes l = new TwoListBoxes();
l.Name = "pivotItem" + category.CategoryLocalized;
l.FromSelectionChanged += new SelectionChangedEventHandler(view.OnFromSelectionChanged);
l.ToSelectionChanged += new SelectionChangedEventHandler(view.OnToSelectionChanged);
p.Header = category.CategoryLocalized;
p.Content = l;
view.pivot.Items.Add(p);
// Store the TwoListBoxes object in the category for later access
category.PivotUnitSelect = l;
category.PivotUnitSelect.toListView.ItemsSource = null;
category.PivotUnitSelect.fromListView.ItemsSource = null;
}
}
Implementing a Context Menu
When users are viewing their Favorites in the Conversion Selection page, they can press and hold an item in the list to display a context menu, allowing them to delete the selected favorite. ContextMenu.cs implements the functionality of the context menu. When the user first presses the item in the list box, the OnOwnerManipulationStarted event is fired. This method calculates the menu location and starts the tapAndHoldTimer timer.
If the user moves their finger, the OnOwnerManipulationDelta event is fired and the method checks to determine if the user moved their finger or touched the screen with another finger. If either of these events occurred, the timer is stopped.
When the user lifts their finger, then the OnOwnerManipulationCompleted event is fired. This will stop the tapAndHoldTimer timer. If the OnTapAndHoldTimerTick event has not yet fired, that is the user has not held their finger on the item long enough, then the context menu will not be displayed. If the user has held their finger on the item long enough, then the context menu will be displayed, using the following code:
private void Show()
{
if (IsOpen == false)
{
// Apply tilt effect to context menu items - add Border as tiltable
TiltEffect.TiltableItems.Add(typeof(Border));
RaiseOpeningEvent();
// Set this as the current context menu
Current = this;
// Update popup position
if (overlay != null)
{
TiltEffect.SetIsTiltEnabled(overlay, true);
Canvas.SetTop(this, menuPosition);
}
// Show the popup
Popup.IsOpen = true;
IsAppBarVisible = false;
ExecuteAftertemplateApplied(() =>
{
// Change visual state
ChangeVisualState(true);
});
}
}
Extending the Unit Converter Application
Here are some suggested ideas to extend the functionality of the Unit Converter application.
-
Add support for landscape mode.
-
Add support for currency conversion.
Note: