Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / XAML

MoneyPit

Rate me:
Please Sign up or sign in to vote.
4.97/5 (23 votes)
18 Jan 2014CPOL22 min read 36.4K   590   33   12
A fuel logging app for Windows Phone

Image 1

Introduction

MoneyPit was started as a learning exercise. It is nothing earth-shattering, it is not even something that has not been done before. It is, however, something which has given me the opportunity to work on a mobile platform using the power of Visual Studio. It also gave me an opportunity to (finally) learn some XAML and MVVM. I have been an Android user and I have, in fact, tried to do some app development for this platform. For some reason, this never went past some small attempts at actually creating something I could use myself. Since I have switched over to the Windows Phone platform, I decided to target my app development at this platform.

What is MoneyPit?

MoneyPit is a simple fuel log app. It is designed to enable you to keep track of fuel costs of your car. It will allow you to enter one or more cars and then keep track of refueling the car(s) by simply entering the fill up information when you pump the gas.

Background

It wasn't until I got my Nokia Lumia 920 that I got really interested in creating something from start to finish. Microsoft offers great tooling for creating Windows Phone 8 apps with their Windows Phone 8 SDK [^]. It even includes Visual Studio 2012 Express edition which is a great starting point for app development. MoneyPit was developed using Visual Studio 2012/2013 Premium but the solution included in this article should work just fine with the Visual Studio Express edition included in the Windows Phone 8 SDK although I have not tried it.

This article tries to explain the development and release of MoneyPit from concept to design to coding to certification. It has not been smooth sailing all the way.

Functional Requirements

I had set myself a simple set of requirements for the app:

  • The app should follow the Windows Phone design guidelines [^]
  • The app must support the standard WP8 dark and light themes.
  • There must be support for multiple cars.
  • The app must be designed/coded using the MVVM pattern.
  • The app must support any currency, distance unit and volume unit (no unit conversion).
  • There must be support for export to, and import from SkyDrive.
  • It must be possible to store the location of a fill up.
  • It must be possible to add notes to a fill up.
  • The app must enable the user to quickly get a historical overview of the fuel cost and economy of a car.
  • The app should protect the user as much as possible against entering false data.
  • The app must support multiple languages (English and Dutch to begin with).
  • The app should use SQLite because SQL CE is not supported on RT (in case the app will ever be ported).

The Tools

MoneyPit was created using a set of tools which I discovered during development of the app. The following list (in no particular order) contains the links to the tools. During the article, I will explain the reasons for using these tools:

App Design

Microsoft has created a comprehensive set of design rules and guidelines [^] for Windows Phone 8 development. After experimenting with several UI designs for MoneyPit, I have chosen to base the UI design on the "List with details drill-down [^]" and the "App Tabs [^]" designs.

The main page of the UI is a simple list of cars in the database. The user can tap on a car from the list to drill down into the details of that car.

The details of the car consist of a pivot containing four pages of detailed information relating to the selected car. The first pivot contains the fill up log of the car. The second pivot contains charts with fuel costs of the car. The third pivot contains charts with the distances driven with the car and the fuel economy. The fourth and last pivot contains summary information about cost, fuel economy, etc.

The information shown in the details pivots show at maximum a single year of data. The menu in the application bar allows for selecting the year to show. By default, the current year is shown.

The application bar is mostly used throughout the app for accessing functionality that is not needed on a day to day basis. Things like the Settings page, the About page, Import/Export, etc. are accessed using an application bar button.

Adding a fill up for a car is also accessed through an application bar button on the car details page. Since this is what the app is mostly about, logging fill ups for a car, this functionality is also available by "pinning" the car to the start screen. This creates a tile on the start screen which will act as a shortcut for getting to the "add fill up" page for the car.

The Application Bar

The application bar is an important part of a Windows Phone app. It does, however, have its quirks which can make it a bit challenging to work with.

One issue with the application bar is that it will not take the focus of a control when the user clicks on a button or a menu item. Now you may say that this is not a big issue but it can be when using the application bar on a page which allows for user input. The TextBox and PasswordBox controls are designed to update their binding source when they lose focus. This means that when a user is editing the control contents and then taps on an application bar button or menu-item, the edited information is not stored in the view model.

To circumvent this problem, MoneyPit uses a class based on the ApplicationBarIconButton class. This class will handle the Click event of its base class to find out the object type of the currently focused element. If this is either a TextBox, a PhoneTextBox or a PasswordBox, it will find the object its binding expression and update it.

C#
/// <summary>
/// Extended ApplicationBarIconButton class which will make sure the
/// binding source is updated when either a TextBox, a PhoneTextBox or
/// a PasswordBox currently has the focus.
/// </summary>
public class ApplicationBarIconButtonEx : ApplicationBarIconButton
{
    /// <summary>
    /// Constructor. Initializes an instance of the object.
    /// </summary>
    public ApplicationBarIconButtonEx()
    {
        // When the button is clicked we evaluate the currently
        // focused element.
        Click += (s, e) =>
        {
            BindingExpression be = null;
            object focused = FocusManager.GetFocusedElement();
            if(focused == null)
            {
                return;
            }

            // When it is either a PhoneTextBox, a TextBox or a PasswordBox we
            // get it's binding expression.
            var phonetextbox = focused as PhoneTextBox;
            if(phonetextbox != null)
            {
                be = phonetextbox.GetBindingExpression(PhoneTextBox.TextProperty);
            }
            else
            {
                var passwordbox = focused as PasswordBox;
                if(passwordbox != null)
                {
                    be = passwordbox.GetBindingExpression(PasswordBox.PasswordProperty);
                }
                else 
                {
                    var textbox = focused as TextBox;
                    if(textbox != null)
                    {
                        be = textbox.GetBindingExpression(TextBox.TextProperty);
                    }
                }
            }

            // Update the binding expression if it is valid.
            if(be != null)
            {
                be.UpdateSource();
            }
        };
    }
}

Another issue with using the application bar and wanting to use an MVVM approach is that the buttons and menus of an application bar do not allow for command binding. AppBarUtils [^] offers a solution for, among other things, this issue. It allows you to use command binding on application bar buttons and menus. One of the great things about it is that it will also enable/disable the application bar button or menu item depending on the CanExecute state of the bound command.

XML
<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True">
        <mpctrl:ApplicationBarIconButtonEx IconUri="/Assets/AppBar/add.png"
                                        Text="new" />
        ...
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<i:Interaction.Behaviors>
    <abu:AppBarItemCommand Id="new"
                           Text="{Binding Path=LocalizedResources.CreateCarButtonText,
                           Source={StaticResource LocalizedStrings}}"
                           IconUri="/Assets/AppBar/add.png"
                           Command="{Binding NewCarCommand}" />
     ...
</i:Interaction.Behaviors>

You declare your application bar buttons as per usual. The interesting part happens in the behaviors section. Here, we use the AppBarUtils to link a command to an application bar button. As you may have noticed, the Id property of the AppBarItemCommand is the same as the Text property of the ApplicationBarIconButton. The Text property of the original ApplicationBarIconButton is used as a key to find it by AppBarUtils. In the AppBarItemCommand, the actual text of the button can be set using its Text property. Its Command property can be used to bind it to a command in your view model.

MVVM Light

As mentioned earlier, MoneyPit uses MVVM Light [^] as MVVM framework. It is, as the name implies, a lightweight framework which makes implementing an MVVM architecture a lot easier. It offers among other things, features like a simple IoC container, a messenger service and design time data.

ViewModels

All view models in MoneyPit are derived from a class called BaseViewModel. This class is derived from the MVVM Light class ViewModelBase. The BaseViewModel class contains methods for setting properties and handling property related events.

C#
/// <summary>
/// Base view model class. All view models and database entity
/// models inherit from this class.
/// </summary>
public class BaseViewModel : ViewModelBase
{
    /// <summary>
    /// Gets or sets the dirty flag for the object. This is only used for
    /// objects that need to be persisted to the database. Since they also derive
    /// from this class this flag is put here.
    /// <remarks>This property is decorated with the SQLite [Ignore] attribute
    /// because it should never be persisted to the database.</remarks>
    /// </summary>
    [Ignore]
    public bool IsDirty { get; set; }

    /// <summary>
    /// Simple SetProperty method version which does nothing other than firing the
    /// OnPropertyChanged event.
    /// </summary>
    /// <typeparam name="T">The type of the property that must change.</typeparam>
    /// <param name="value">The new value to store.</param>
    /// <param name="propertyName">The name of the property to set 
    /// (is set automagically by [CallerMemberName])</param>
    protected void SetProperty<t>(T value, [CallerMemberName] string propertyName = null)
    {
        RaisePropertyChanged(propertyName);
    }

    /// <summary>
    /// Checks if the value of the property really did change. If it did not false
    /// is returned. If the value did change the following happens:
    /// 
    /// 1) OnPropertyChanging event is fired.
    /// 2) The value is written to the storage field.
    /// 3) OnPropertyChanged event is fired.
    /// 4) The IsDirty flag is set to true.
    /// </summary>
    /// <typeparam name="T">The type of the property that must change.</typeparam>
    /// <param name="storage">Storage in which the changed value is stored.</param>
    /// <param name="value">The new value to store.</param>
    /// <param name="propertyName">The name of the property to set 
    /// (is set automagically by [CallerMemberName])</param>
    /// <returns>True if the property changed, false if it did not.</returns>
    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;
        RaisePropertyChanging(propertyName);
        storage = value;
        RaisePropertyChanged(propertyName);
        IsDirty = true;
        return true;
    }
}</t>

The view models are created and injected into the views using the MVVM Light SimpleIoc container. The ViewModelLocator class creates all view models and registers them into the SimpleIoc container. The ViewModelLocator class will then expose the instances as properties which will in turn return the instances from the SimpleIoc container.

Normally, the SimpleIoc container will create one instance of the view model and return this same instance each time the view model property is referenced. However, sometimes, we want to create a new instance each time a view model property is referenced. In this case, SimpleIoc offers the possibility to get a instance using a key. However, the instances created with a key are internally cached by SimpleIoc. What I wanted was to create a new instance each time the view model property is referenced but to destroy each previously created instance when a new one is created. To do this, a view model is created using a key. However, before it is created, a check is made to see if SimpleIoc contains an instance created with the previous key. If so, this one is unregistered before the new instance is created.

C#
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// <para>
/// See http://www.galasoft.ch/mvvm
/// </para>
/// </summary>
public class ViewModelLocator
{
    static readonly string _editModelKey = "798829F7-0753-4450-AC72-F3D0013D048E";
    ...

    static ViewModelLocator()
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
    ...

        SimpleIoc.Default.Register<EditCarViewModel>();
        ...
    }

...

    /// <summary>
    /// Gets the EditCar property.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
        "CA1822:MarkMembersAsStatic",
        Justification = "This non-static member is needed for data binding purposes.")]
    public EditCarViewModel EditCar
    {
        get
        {
            // I want a new instance each time this view model is requested but
            // I do not want the previous instance to be cached. What we do here
            // is to keep track of the last requested instance and, when present,
            // unregister this instance before creating a new one.
            if (!String.IsNullOrEmpty(_editModelKey))
            {
                SimpleIoc.Default.Unregister<EditCarViewModel>(_editModelKey);
            }
            return SimpleIoc.Default.GetInstance<EditCarViewModel>(_editModelKey);
        }
    }
    ...
}

Design Time Data

MVVM Light has support for design time data. In the ViewModelLocator class constructor, a check is made to see whether or not it is running in the designer. If it is running under the designer, an instance of the DesignMoneyPitRepository class is registered instead of an instance of the MoneyPitRepository class. This DesignMoneyPitRepository class takes care of providing the design time data.

C#
static ViewModelLocator()
{
    ...
	    
    if (ViewModelBase.IsInDesignModeStatic)
    {
        SimpleIoc.Default.Register<IMoneyPitRepository, Design.DesignMoneyPitRepository>();
    }
    else
    {
        SimpleIoc.Default.Register<IMoneyPitRepository, MoneyPitRepository>();
    }
    ...
}

This will result in something like you see below in the Visual Studio/Blend designer.

Designer

This can be a real big help with designing your UI.

One word of caution when using design time data is that you cannot do much of anything in your view model when running under the designer. It is not possible to access a database or something similar when running under the designer. You can use MVVM Light's ViewModelBase.IsInDesignModeStatic property to check if you are running under the designer.

Problems doing things that are not allowed under the designer manifest themselves by designer data not showing up in the designer. That's about it. There are no more clues as to why the data is not showing. This can be a real pain to track down. A great help with issues concerning design time data is running Blend attaching the Visual Studio debugger to this instance of Blend and then opening the project and XAML page giving you trouble. With a little luck, the Visual Studio will break at the code that is causing the issue.

EventToCommand

When using MVVM to design your application, you want to be able to link any type of event on a UI control to a command in your view model. Some controls offer a Command property but that is not nearly enough. Luckily, MVVM Light has EventToCommand. This is a TriggerAction derived class which will allow for mapping any type of event to a command in a view model.

There is an issue with MVVM Light's implementation of EventToCommand and that is that it will not invoke the command when the UI element the trigger is attached to is disabled. There is a lot to be said for this behavior but wouldn't you know, I needed it to invoke the command also when the attached UI element is disabled.

For this reason, I created my own version of EventToCommand based on the original MVVM Light source code (long live open source). It has an added DependancyProperty called InvokeWhenSourceDisabled which will, when set to true, tell the trigger to also call the command when the source UI element is disabled.

The Messenger

In MoneyPit, the MVVM Light Messenger is used for communication between view models and for displaying dialogs from the view model. The MVVM Light Messenger is a simple register and send system which allows for registering for a message type at one point and sending the message at another point without having any knowledge of each other other than the interface the message defines. This makes it possible to keep things loosely coupled.

C#
// At the receiver side...
Messenger.Default.Register<BitmapImage>(this, "PictureTaken", (image) => { CarPhoto = image; });
C#
// At the sender side...
Messenger.Default.Send<BitmapImage>(Util.ScaleImage(e.ImageStream), "PictureTaken");

As mentioned, the Messenger is also used to open dialogs. The method I used for this is not my own. Searching for a good way to display dialog boxes from the view model I came along this blog [^] which shows a nice way to do just that. Basically, it consists of a DialogBehaviour class which can be instantiated in your XAML like this:

XML
<i:Interaction.Behaviors>
    <mpb:DialogBehavior Caption="{Binding LocalizedResources.MessageDialogTitle, Mode=OneWay, 
                        Source={StaticResource LocalizedStrings}}"
                        Text="{Binding LocalizedResources.OdometerFault, 
                        Source={StaticResource LocalizedStrings}}"
                        Buttons="Ok"
                        Identifier="odometerFault" />
</i:Interaction.Behaviors>

Then, in your view model, you can show the dialog simply as follows:

C#
Messenger.Default.Send(new DialogMessage(String.Empty, null), "odometerFault");

In the DialogMessage, you can pass in an Action<MessageBoxResult> to handle the button clicks in the dialog if necessary.

The Database

I started off developing MoneyPit with SQL CE as database engine. Soon, however, I came to realize that was not a good choice if ever I wanted to port the app to RT. SQL CE is not supported on Windows RT. Luckily SQLite [^] is supported on both platforms so a switch was made to using this embedded database engine.

SQLite for Windows Phone can simply be installed as a Visual Studio Extension. To use SQLite, I have also used the SQLite-net [^] wrapper. This is a .NET wrapper for the SQLite native DLL which enables to use SQLite in a managed environment and also enables some Linq as well. There is a problem with this wrapper however and that is the fact that it is not in sync with the official SQLite releases. As of this writing, the wrapper supports version 3.8.0.2 of SQLite for Windows Phone whereas SQLite for Windows Phone is up to version 3.8.2

Because the SQLite-net wrapper is a C++ project that compiles against a specific version of the SQLite runtime, upgrading the Visual Studio SQLite Extension will get you into trouble. I have so far managed to keep using the SQLite-net wrapper against the latest SQLite version by ... manually editing the Sqlite.vcxproj project file. Now this is far from ideal, but so far it has enabled me to use the latest SQLite version.

To "update" the Sqlite.vcxproj, you should change the SQLite version number in the ImportGroup sections of the project file. If for example, you want to update the version from 3.8.0.2 to 3.8.2, you need to make the following changes:

XML
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Import Project="$(MSBuildProgramFiles32)\Microsoft SDKs\Windows Phone\v8.0\ExtensionSDKs\
     SQLite.WP80\3.8.0.2\DesignTime\CommonConfiguration\Neutral\SQLite.WP80.props" />
</ImportGroup>

Should be changed to:

XML
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Import Project="$(MSBuildProgramFiles32)\Microsoft SDKs\Windows Phone\v8.0\ExtensionSDKs\
    SQLite.WP80\3.8.2\DesignTime\CommonConfiguration\Neutral\SQLite.WP80.props" />
</ImportGroup>

Simply replacing all occurrences of "3.8.0.2" to "3.8.2" should suffice. This method will of course go "poof" when the native API of SQLite changes, but that is not likely to happen.

Another issue with the SQLite-net wrapper is that it does not support foreign keys and cascading deletes using the "normal" CreateTable<Type> method of creating tables. Since I needed both foreign keys and cascading deletes in my data model tables are created using the Execute method of the Connection class. Using the Execute method, you can run standard SQL statements for table creation allowing you to have more control over the created data model.

C#
Connection.Execute("CREATE TABLE IF NOT EXISTS [SchemaVersion] ( [Version] INT );");
Connection.Execute("INSERT INTO [SchemaVersion] ([Version]) VALUES(?);", 1);

Connection.Execute("CREATE TABLE IF NOT EXISTS [Car] ( " +
                    "[Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " +
                    "[Make] NTEXT(50) NOT NULL, " +
                    "[Model] NTEXT(50) NOT NULL, " +
                    "[Picture] IMAGE, " +
                    "[LicensePlate] NTEXT(15) NOT NULL, " +
                    "[BuildYear] INT, " +
                    "[Currency] NTEXT(3) NOT NULL, " +
                    "[VolumeUnit] SMALLINT NOT NULL DEFAULT 1, " + 
                    "[DistanceUnit] SMALLINT NOT NULL DEFAULT 1);");

Connection.Execute("CREATE TABLE IF NOT EXISTS [Fillup] ( " +
                    "[Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " +
                    "[CarId] INTEGER NOT NULL CONSTRAINT [FillupCar] 
                     REFERENCES [Car]([Id]) ON DELETE CASCADE, " +
                    "[Date] DATETIME NOT NULL, " +
                    "[Odometer] DOUBLE NOT NULL, " +
                    "[Volume] DOUBLE NOT NULL, " +
                    "[Price] DOUBLE NOT NULL, " +
                    "[FullTank] BOOL NOT NULL, " +
                    "[Note] NTEXT, " +
                    "[Longitude] DOUBLE, " +
                    "[Latitude] DOUBLE);");

Connection.Execute("CREATE TABLE IF NOT EXISTS [CrashLog] ( " +
                   "[Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " +
                   "[Message] NTEXT NOT NULL, " +
                   "[Stacktrace] NTEXT NOT NULL, " +
                   "[Stamp] DATETIME NOT NULL);");

As you can see, the data model of MoneyPit is very simple. There is a Car table for storing information about the cars. There is a Fillup table for storing fill up information which has a foreign key constraint to the Car table. It also defines a cascading delete so that fill ups that are linked to a car are automatically deleted when the car is deleted. The SchemaVersion table will be used when updating an existing model is necessary. Lastly, there is the CrashLog table. In that table, unhanded exceptions are stored. If this happens and the user restarts the app, (s)he will be given the option to email the exception to the developer. A feature which hopefully is not needed too often ...

The UI

Windows Phone 8 offers a broad range of standard controls, however more is needed to really create a rich user experience. For this reason, Microsoft has created the Windows Phone Toolkit [^] which offers a truck load of new controls and effects which makes it so much easier to create a good user experience. MoneyPit also uses the Windows Phone Toolkit to enhance the user interface of the app.

Page Transitions

MoneyPit uses SlideTransition for page navigation. When navigation to a page, it slides in the screen right to left while the page you navigate from slides out of the screen right to left. Creating these transitions is very simple. I simply added the following code to App.xaml:

XML
<Style x:Key="TransitionPageStyle"
       TargetType="phone:PhoneApplicationPage">
    <Setter Property="toolkit:TransitionService.NavigationInTransition">
        <Setter.Value>
            <toolkit:NavigationInTransition>
                <toolkit:NavigationInTransition.Backward>
                    <toolkit:SlideTransition Mode="SlideRightFadeIn" />
                </toolkit:NavigationInTransition.Backward>
                <toolkit:NavigationInTransition.Forward>
                    <toolkit:SlideTransition Mode="SlideLeftFadeIn" />
                </toolkit:NavigationInTransition.Forward>
            </toolkit:NavigationInTransition>
        </Setter.Value>
    </Setter>
    <Setter Property="toolkit:TransitionService.NavigationOutTransition">
        <Setter.Value>
            <toolkit:NavigationOutTransition>
                <toolkit:NavigationOutTransition.Backward>
                    <toolkit:SlideTransition Mode="SlideRightFadeOut" />
                </toolkit:NavigationOutTransition.Backward>
                <toolkit:NavigationOutTransition.Forward>
                    <toolkit:SlideTransition Mode="SlideLeftFadeOut" />
                </toolkit:NavigationOutTransition.Forward>
            </toolkit:NavigationOutTransition>
        </Setter.Value>
    </Setter>
</Style>

After that, it is a simple matter of adding the following code to the initialization of the XAML page:

XML
<phone:PhoneApplicationPage ...
                            Style="{StaticResource TransitionPageStyle}"
                            toolkit:TiltEffect.IsTiltEnabled="True">

The toolkit:TiltEffect.IsTiltEnabled="True" is there to enable the tilting effect of controls like the ListPicker or Button when the user taps on it. Another nice feature of the Windows Phone Toolkit.

Charts

Unfortunately, neither the default Windows Phone 8 controls or the Windows Phone Toolkit includes charting controls. Because I did need to have charting capabilities in the app, I had a search for a free solution. There are not too many choices (amCharts [^] and Sparrow Toolkit [^] are the ones I found). I chose to go with the Sparrow Toolkit simply because it gave me quick results without too much effort. I need to admit I never gave amCharts a fair go simply because Sparrow Toolkit did everything I needed it to and I tried that one first.

I did, however, run into one very annoying "feature" when using the Sparrow Toolkit. In the app, I create lists of CategoryPoint data. The CategoryPoint object is a simple structure that defines a value and the category the value belongs to. Nothing shocking you would think if it weren't for the fact that the CategoryPoint class depends on some static fields in the SparrowChart class being setup properly. These fields are setup when creating a chart for the first time. Not a very nice design. The problem is that I create the list of CategoryPoint objects before any chart is created resulting in an exception. I have worked around this as follows:

C#
class MoneyPitRepository
{
    private SparrowChart _stupidWorkAround;
    
    ...
    
    /// <summary>
    /// Converts a List of MonthlyChartData into a List of CategoryPoint objects. 
    /// Months for which there is not data in the chart data will have a value of 0.
    /// The result will always have 12 CategoryPoint objects regardless whether or not the
    /// MonthlyChartData has values for a specific month or not.
    /// </summary>
    /// <param name="data">The monthly data to convert </param>
    /// <returns>A list of 12 CategoryPoint objects. The Category field is represented by
    /// the abbreviated month name. The Value field contains the summarized values from the
    /// MonthlyChartData input.</returns>
    private List<CategoryPoint> GetFullYearAsMonthCategory(List<MonthlyChartData> data)
    {
        // This sucks...
        // The SparrowChart CategoryPoint object is dependent on some static fields
        // in SparrowChart being setup properly. This setup is done in the SparrowChart
        // constructor. Since this code can be called before any SparrowChart control has
        // been created we need to instantiate a dummy for the fields to be setup properly.
        if (_stupidWorkAround == null)
        {
            DispatcherHelper.CheckBeginInvokeOnUI(() =>
            {
                _stupidWorkAround = new SparrowChart();
            });
        }

        var yearData = new List<CategoryPoint>();
        for (int i = 1; i <= 12; i++)
        {
            var dataPoint = new CategoryPoint 
            { 
                Value = data.Where(m => m.Month == i).Select(m => m.Value).SingleOrDefault(),
                Category = CultureInfo.CurrentUICulture.DateTimeFormat.GetAbbreviatedMonthName(i)
            };
            if (Double.IsInfinity(dataPoint.Value))
            {
                dataPoint.Value = 0.0;
            }
            yearData.Add(dataPoint);
        }
        return yearData;
    }
}

This will not win any prizes, but it did solve my problem. Maybe, someday, I will revisit this issue and come up with a nicer solution, but for now, I'll leave it as it is.

The Map Control

MoneyPit has the possibility for the end user to use the location services to log the position where a fill up has taken place. When the Phone location services are activated and the user consented in MoneyPit using the location services, the location for a new fill up is logged. When a location is logged, the edit fill up page will get an extra pivot item containing a Map control which shows the location with a Pin.

Now the view model for the edit fill up page contains two GeoCoordinate properties which will be set to the same position initially. One position for the center coordinate of the Map and one for the position of the Pin. Now normally, you would say well it is the same position so just use one property and bind to that OneWay from the Map and the Pin and be done with it. That unfortunately does not work. You must bind the Center property of the Map control TwoWay or else binding will not work. That means that when the user scrolls the map, the CenterPosition property in the view model will also be updated. It is after all TwoWay binding that must be used. Now if this property was being used for both the Map center position and the Pin position, it would mean that the pin would be moved when the user scrolls the map. We do not want that and that is why we have a CenterPosition property for the Map and a PinPosition property for the Pin.

The App Icons

MoneyPit was started using the standard app icons the Windows Phone SDK offers. This soon turned out to be insufficient so I went on the prowl to find icons that I could use for the app. Soon, I found the Modern UI Icons [^] site from which you can download an icon package which should be more than enough for any kind of app. It even includes XAML path and Blend designer files. A very comprehensive piece of work.

Simply read the license (it is included in the Assets folder of the source code download of this article) to understand how you can use these icons.

SkyDrive

MoneyPit has the possibility to export information to your SkyDrive account and to import that information again. Microsoft has created the Live SDK [^] which allows, among other things, communication with SkyDrive. While working on the SkyDrive code, I found that when the app is deactivated while a call to SkyDrive is running sometimes, not always, will throw a TaskCanceledException when the app is resumed again. To make the behavior consistent, I have created a solution which will cancel any running SkyDrive task before the app is deactivated. That way, I can be sure that when the app is resumed, I get a TaskCanceledException.

I have done this by creating a CancellationTokenSource each time a SkyDrive function is executed. This CancellationTokenSource is passed to all async Live SDK methods used for the SkyDrive function. The CancellationTokenSource is also stored in a list while it is being used.

C#
public static class SkyDrive
{
    ...
    
    /// <summary>
    /// Creates a new CancellationTokenSource and adds it to the internal list.
    /// The tokens are used to cancel outgoing SkyDrive requests when the application
    /// is deactivated or tombstoned.
    /// </summary>
    /// <returns>The newly created CancellationTokeSource.</returns>
    private static CancellationTokenSource GetCancellationTokenSource()
    {
        var result = new CancellationTokenSource();
        lock (_tokenLock)
        {
            _tokens.Add(result);
        }
        return result;
    }

    /// <summary>
    /// Get a list of files from the MoneyPit folder on SkyDrive.
    /// </summary>
    /// <returns>A list of files which were found in the MoneyPit folder
    /// on SkyDrive.</returns>
    public static async Task<SkyDriveData> GetFilesInMoneyPitFolder()
    {
        var result = new SkyDriveData { Result = SkyDriveResult.NotConnected, Data = null };
        var cts = GetCancellationTokenSource();
        if (cts != null)
        {
            try
            {
                result.Data = await GetFilesFromFolder(cts.Token);
                result.Result = SkyDriveResult.ImportOk;
            }
            catch (LiveConnectException)
            {
                result.Result = SkyDriveResult.LiveConnectError;
            }
            catch (TaskCanceledException)
            {
                result.Result = SkyDriveResult.TaskCanceledError;
            }
            finally
            {
                ClearCancellationTokenSource(cts);
            }
        }
        return result;
    }

    /// <summary>
    /// Cancels all running tasks by canceling all the active
    /// CancellationTokenSource objects.
    /// </summary>
    public static void CancelAllRunningTasks()
    {
        lock (_tokenLock)
        {
            foreach (var cts in _tokens)
            {
                cts.Cancel();
            }
            _tokens.Clear();
        }
    }
    
    ...
}

When the app gets deactivated while one of the SkyDrive functions is still running, the CancellationTokenSource associated with the SkyDrive function is canceled before the app is deactivated. This way, I can be sure I get a TaskCanceledException after app resume and I can act accordingly.

C#
public partial class App : Application
{
    ...
    
    // Code to execute when the application is deactivated (sent to background)
    // This code will not execute when the application is closing
    private void Application_Deactivated(object sender, DeactivatedEventArgs e)
    {
        // Cancel any running SkyDrive tasks.
        SkyDrive.CancelAllRunningTasks();
        IsolatedStorageSettings.ApplicationSettings.Save();
    }
    
    ...
}

The Visual Studio Project

I have enabled NuGet package restore in the solution which should download the necessary packages for you when you first build the solution. It is, however, possible that you need to install the SQLite extension before the solution will compile. Also make sure you make the necessary changes to the SQLite.vcxproj project file to reflect the version of SQLite you are using. The solution included in this article uses version 3.8.2 of SQLite.

The application uses SkyDrive and Maps. This means it uses a Live Connect Client ID and a Map application ID and authentication token. These are located in the Tokens class in the Helpers source code folder. The posted source does not include the application ids and tokens used by MoneyPit. You will need to get these yourself for the functionality to work properly on your phone.

To deploy the app to a Phone, it needs to be Developer Unlocked [^]. It will not deploy to a locked Phone.

If you are going to deploy to a phone, remember that you should switch to an ARM build for both projects in the solution (are there x86 based phones out there?). When you are going to deploy to the emulator, use a x86 build.

I will do my best to keep this article updated but it is possible that the article is out of sync with the version published in the store. Depending on how long it takes to get updates to either the article or the app published, the CodeProject article can contain newer or older source code than that was used to build the latest published app in the store.

App Certification

There really is not much to say about this. MoneyPit passed the certification process first time. The most important thing is to read through the App Certification Requirements [^] and make sure you adhere to those requirements that apply for your app. The list is long but reading through it makes sure your app does not fail certification because you missed one of the requirements.

For MoneyPit, the certification process took four days. I do not know if this a normal amount of time but I can imagine that it may take longer than that depending on how many apps are in the certfication queue and how complex the app is. The more features you have in your app, the more work has to be done to actually check if it meets all of the requirements.

References

MoneyPit was not created in a vacuum. I have used many, many resources from the web to create it so it would be quite impossible to list them all. However, the following lists the most important ones:

  • Windows Phone Dev Center [^]
    This is the starting point for any Windows Phone Development I guess. Especially, the Jumpstart videos offer a tremendous amount of information to get started.
  • Live Connect Development Center [^]
    The site for Live Connect resources. The SkyDrive functionality of MoneyPit was created using the Live SDK which can be downloaded from this site.
  • DialogBehaviour [^]
    This is where I got the idea from for the behavior driver dialogs.
  • NumericInputBox [^]
    A culture neutral numeric input box which is used in MoneyPit.
  • Navigation and MVVM [^]
    A blog post explaining the idea of a navigation service to control page navigation from a view model. The MoneyPit navigation service is build around this idea.

In Conclusion

As I have mentioned at the start of this article, I did this project as a learning exercise. I did it to get into mobile development but also (and maybe even more so) to get into XAML, MVVM and the latest .NET Framework. I know that MVVM and XAML have been around for quite some time now, but I never before had the opportunity to actually do something with it.

I am sure that for the seasoned Windows Phone/.NET developer, there are a lot of things that could have been or should have been done better/easier/different. To those people, I would like to say "Teach Me." Please let me know what could have been done better or different. Let me know what I did wrong and also why what I did is wrong.

History

Version 1.3

  • Bug fix: Database restore did not always work
  • Some cosmetic changes
  • Removed screenshots from the source download. They should not have been in there in the first place. My apologies.
  • Added a summary overview for the total history of a car

Version 1.2

  • Spell checked the resources
  • Added database backup and restore (on SkyDrive)
  • Added location and note markers to the fill up list
  • Fixed a few design time data issues
  • Enhanced the UI a bit

Version 1.1

  • Fixed a couple of bugs

Version 1.0

  • Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
I have been programming for a hobby since 1985. I have started programming on the C= 64. After that I migrated to the C= Amiga which I traded in for a PC back in 1997 I believe. Back in 2000 I decided to lose a hobby and start developing software for a living.

Currently I am working mainly in developing software for building security and access control systems.

Comments and Discussions

 
QuestionSuper! Pin
AndySolo25-Aug-14 20:31
AndySolo25-Aug-14 20:31 
AnswerRe: Super! Pin
Jan van den Baard26-Aug-14 6:05
professionalJan van den Baard26-Aug-14 6:05 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA14-Feb-14 17:08
professionalȘtefan-Mihai MOGA14-Feb-14 17:08 
GeneralRe: My vote of 5 Pin
Jan van den Baard15-Feb-14 6:51
professionalJan van den Baard15-Feb-14 6:51 
QuestionGreat Article Pin
Wayne Gaylard19-Jan-14 21:57
professionalWayne Gaylard19-Jan-14 21:57 
AnswerRe: Great Article Pin
Jan van den Baard20-Jan-14 0:00
professionalJan van den Baard20-Jan-14 0:00 
QuestionReally nice Article Pin
pardeep sharma914-Jan-14 1:59
pardeep sharma914-Jan-14 1:59 
AnswerRe: Really nice Article Pin
Jan van den Baard20-Jan-14 0:00
professionalJan van den Baard20-Jan-14 0:00 
GeneralMy vote of 5 Pin
gicalle7513-Jan-14 5:01
professionalgicalle7513-Jan-14 5:01 
GeneralRe: My vote of 5 Pin
Jan van den Baard13-Jan-14 8:20
professionalJan van den Baard13-Jan-14 8:20 
GeneralGreat Job Pin
Mitchell J.9-Jan-14 18:47
professionalMitchell J.9-Jan-14 18:47 
GeneralRe: Great Job Pin
Jan van den Baard9-Jan-14 23:55
professionalJan van den Baard9-Jan-14 23:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.