We are developing the “Home Budget”. Part 1

This article is the first part of a potential series that will describe the process of creating an application for conveniently managing your home budget on WP7 from the very beginning. At the same time, various APIs and platform capabilities will be used in the development in order to maximize their coverage. At the same time, the program will be absolutely usable (it will have at least one permanent user), and there will be no garbage used exclusively for academic purposes.

The choice of the type of application is due to the fact that the first program I bought in the marketplace was exactly a budget, but there are no plans for it, and some useful functions (for example, automatic backup to SkyDrive, etc.).

Development principles

The functionality will be gradually increased, in small iterations lasting 10-14 days, of which 3-5 days are allocated for running the previous version, 1 day for brainstorming and the rest for the implementation of the plan, testing and design of the article.

Since I plan to use the created application every day instead of a similar utility, the functionality will have to be exactly what is needed, and not one that is easier to write.

First iteration: plan

First of all, we should be very able to add transactions and have at least basic categories, because otherwise a week of use will result in a small garbage dump that you don’t even want to open.
In this article, we will take a detailed (perhaps too much) look at the process of creating a database and the process of adding categories. We will not consider the process of adding transactions since it is almost 100% similar to working with categories and its consideration would inflate the article to too large a size. Also, we will not consider the process of editing categories, but given the model of work used, it will take + - 10 minutes.
If necessary, these topics can be briefly covered in the next article or skipped.

Accordingly, we will consider such questions:
  • WP7 Development Fundamentals
  • Some basics of Metro [1]
  • Working with SQLCE database and creating a model on the principle of code-first

Prerequisites

Development: creating a database

In case someone wants to use this article as a lesson, after each logically completed and more or less important stage, a link to the archive with the results will be given. All sources on SkyDrive . Individual files in case of abbreviations will be presented on pastebin .

Choosing the structure of the application, I focus primarily on the logical separation of the functions performed by projects. Based on these considerations, to create a description of the database and all types of data, we create a separate project called Entities, do not forget to add System.Data.Linq and Microsoft.Practices.Prism to References. Solution here .

I’ll make a reservation right away — by the concept of transaction I mean a financial transaction, not a transaction in the database.
When creating the database, we will use the code-first approach [3] . For today's task, we are satisfied with two whole tables - Transactions and Categories. Create two empty tables and add them to the database.

Transaction.cs
[Table(Name = "Transactions")]
public class Transaction : NotificationObject

Category.cs
[Table(Name = "Categories")]
public class Category : NotificationObject

Database.cs
public class Database : DataContext
{
    private static string DBConnectionString = "Data Source=isostore:/Database.sdf";
    public Database()
        : base(Database.DBConnectionString)
    {
        this.DeferredLoadingEnabled = true;
    }
    public Table Categories;
    public Table Transactions;
}
Do not even think of making tables in the database properties and not fields. Because of the stylistic habit of using properties for public, I killed about an hour trying to understand why the database does not work at all.
Here, transactions and categories inherit the NotificationObject defined in Prism for normal interaction with the UI in the future. By the way, we use the MVVM pattern when developing.
In the database constructor, set the DefferedLoadingEnabled flag to disable automatic loading of related objects from the database. It will be necessary - we indicate separately.
We proceed to the formation of the contents of the tables. As a result, we should get something similar:

The most interesting moments in Transaction.cs :
[Column(IsPrimaryKey = true)]
public Guid ID
{ ... }
...
private EntityRef category;
[Association(Name = "FK_Transactions_Category", Storage = "category", ThisKey = "CategoryID", IsForeignKey = true)]
public Categories.Category Category
{
    get
    {
        return this.category.Entity;
    }
    set
    {
        Categories.Category previousValue = this.category.Entity;
        if (((previousValue != value) || (this.category.HasLoadedOrAssignedValue == false)))
        {
            if ((previousValue != null))
            {
                this.category.Entity = null;
                previousValue.Transactions.Remove(this);
            }
            this.category.Entity = value;
            if ((value != null))
            {
                if ((value.AddedTransactions == null) || (!value.AddedTransactions.Contains(this)))
                {
                    value.Transactions.Add(this);
                }
                this.CategoryID = value.ID;
            }
            else
            {
                this.category = new EntityRef();
            }
            this.RaisePropertyChanged(() => this.Category);
        }
    }
}
The ID parameter is the table column and primary key. The remaining columns are also specified by the Column attribute. You can read more about Attribute-based mapping on msdn .
Category along with CategoryID are responsible for binding transactions to categories, and in this example we created the foreign key FK_Transactions_Category. The reason for the bloated setter is that when you assign a transaction to a parent category, we must delete the transaction from the previous parent category and add it to the new one. Roughly speaking - Navigation Property from EF . Category, in turn, uses a minimum of code to implement this.

Category.cs :
public EntitySet Transactions
{
    get
    {
        if (this.transactions == null)
        {
            this.transactions = new EntitySet();
            this.transactions.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(this.OnTransactionsChanged);
        }
        return this.transactions;
    }
    ...
}
...
private void OnTransactionsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    this.AddedTransactions = e.NewItems;
    foreach (Transactions.Transaction t in e.NewItems)
    {
        t.Category = this;
    }
    this.AddedTransactions = null;
    this.RaisePropertyChanged(() => this.Transactions);
}
In fact, in Category we catch a situation when a category is not assigned to transactions, but a new element is added to the category’s transaction list.
The base is ready. Solution on SkyDrive .

Development: create a UI

Our Shell project was created as a Windows Phone Application, that is, at this stage we will not use such controls as Pivot / Panorama. User interaction with the application will take place in approximately the following way:

For creation, Expression Blend with SketchFlow (not included in the free SDK) and sketchflow template for WP7 ( CodePlex ) were used.

We will break these screens into such View: New / Edit transaction, New / Edit category , Categories list, Transactions list, and the part responsible for working with transactions is moved to a separate project. Solution on SkyDrive .

First of all, we need to implement the functionality of viewing the list of categories and adding categories. There is nothing special about this, BUT as we try to focus on performance, we will need to refine our Database a bit. The fact is that when viewing a list of categories we are not going to edit anything - we just need to get a list of categories as quickly as possible. To do this, we will make such an amendment in Database.cs :
public Database(bool isReadOnly = false)
    : base(Database.DBConnectionString)
{
    if (!this.DatabaseExists())
    {
        this.CreateDatabase();
    }
    this.DeferredLoadingEnabled = true;
    this.ObjectTrackingEnabled = !isReadOnly;
}
Thus, with isReadOnly == true, we turn off tracking of context objects for changes, which on average increases the speed of simple reading by more than 10 times.

When creating a UI, one of the problems we are facing is the inability to attach any Behavior to the ApplicationBarButton (we need this for binding to the command). In Prism.Interactions there is a DependencyProperty ApplicationBarButtonCommand but for some reason it did not work for me. Therefore, I had to use quite a convenient library AppBarUtils .
Interesting points from CategoriesView.xaml :
Most often, the actions of the buttons will be transitions to other pages of the application and we need to make a convenient mechanism for working with navigation from ViewModel. A convenient and relatively familiar (I once worked with desktop MVVM on a similar basis), the method is described here . We implement a similar principle in our Common project by creating the ApplicationController class. Also, all of our View's will be defined in the KnownPages static class:
public static class KnownPages
{
    public const string AddCategory = "/Views/CategoryAddEditView.xaml?mode=Add";
    public const string EditCategory = "/Views/CategoryAddEditView.xaml?mode=Edit&id={0}";
    public const string ListCategories = "/Views/CategoriesView.xaml";
    public const string CategoryDetails = "/Views/CategoryDetailsView.xaml?id={0}";
}
, a NavigateTo () from the ApplicationController (there’s not much left of the original) will look like this
public void NavigateTo(string url, params object[] data)
{
    Uri address = new Uri(String.Format(url, data), UriKind.Relative);
    PhoneApplicationFrame root = Application.Current.RootVisual as PhoneApplicationFrame;
    root.Navigate(address);
}
Now, since we pass the parameter mode = Add to the AddEdit page, we need to catch the navigation event in the ViewModel and get the data from the string. Unfortunately, at the moment I settled on the option of overriding the OnNavigatedTo method in CodeBehind'e and calling the corresponding method in the ViewModel.
CategoryAddEditView.xaml.cs :
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    ((CategoryAddEditViewModel)this.DataContext).OnNavigatedTo(this.NavigationContext, this.NavigationService.BackStack.First());
}
As you can see from the code, we transmit not only the navigation context (where it is convenient to pull out the parameters from the page address), but also the page with which we switched to the current one.
Now it's time to implement the process of adding categories. Plain View and Plain ViewModel. But there are two but. First, we will use the same MV-VM for editing categories (it will be homework), respectively, from the NavigationContext we get and process the mode parameter. The second - in WP7 for a TextBox, a change in the property value occurs only when the element loses focus. In native ways, this is not implemented, so for this we use Prism (CategoryAddEditView.xaml file):
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
...
The process of saving a category looks like this:
CategoryAddEditViewMode.cs
public void SaveCategory()
{
    if (!this.isEditMode)
    {
        this.model.AddCategory(this.Category);
        ApplicationController.Default.GoBack();
    }
}
What you should pay attention to - we do not go to the CategoriesView page, but return to the previous page. It is worth paying attention to such transitions inside the application so that the user does not find himself at a loss where he expected after clicking on the Back button.
In CategoryAddEditModel.cs, the save looks like this:
public void AddCategory(Category cat)
{
    using (Database db = new Database())
    {
        db.Categories.InsertOnSubmit(cat);
        db.SubmitChanges();
    }
}
It can be seen that there are no checks and validations - and this is bad. But for the first article there is already quite a lot of material, and now it is more important for us to finish the main functionality and start using the program - we will do the rest between the articles or in the next ones.
When returning to the list of categories, the View and ViewModel are not reassigned, so when switching from the list page to the add page, we will set the IsReloadPending flag and process it and reset it upon return.
CategoriesViewModel.cs :
private void AddCategory()
{
    this.isReloadPending = true;
    ApplicationController.Default.NavigateTo(KnownPages.AddCategory);
}
public void OnNavigatedTo(NavigationContext context, JournalEntry lastPage)
{
    if (this.isReloadPending)
    {
        this.isReloadPending = false;
        this.Categories = this.model.GetCategoriesList();
    }
}

Summary
Today we got all the necessary tools for working with WP7, tested working with the database, set the stage for further development of the program and learning programming technologies for Windows Phone. We also encountered a couple of jambs (ApplicationBar, TextBox) and overcame them.
Yes - we didn’t get a program ready for use (just as a data collector), but it takes about 1-2 hours to separate us from this stage. Who cares - they will try it themselves. Solution on SkyDrive .

At the same time, those familiar with C # should have understood that Microsoft’s mobile platform is quite simple to learn and can easily be mastered independently.

At the same time, I realized that writing such an article takes a lot of time. The article was written in a diary format in parallel with the writing of the application.

Future plans
In the following article, I would like to consider:
  • secondary tile creation process
  • application launch optimization
  • prototyping an application in SketchFlow

Literature
  1. WP7 UI / UX Notes
  2. Developer's Guide to Microsoft Prism
  3. Programming Guide (LINQ to SQL)

Also popular now: