Click here to Skip to main content
Click here to Skip to main content

RegiRide, a complete Windows Phone application

By , 24 Jun 2012
Rate this:
Please Sign up or sign in to vote.

SplashScreen RegiRide Screenshot RegiRide

Contents

Introduction

This article describes the implementation and process of developing RegiRide. RegiRide is the second application that I developed for the Window Phone. More information about why I am developing Windows phone applications can be found here. I got a lot of positive reactions from my first article that made me decide to write a new article and open up the source of this application. I hope that this article will inspire other people to start developing new windows phone applications. I wrote this article during the implementation of the application and finished it just after RegiRide got certified. RegiRide is currently available in the Marketplace.

RegiRide 

The second application that I developed is called RegiRide which as its name implies can be used to register rides. I am in the luxurious position to drive a company car. I use this car to drive to and from clients. When you drive a company car in the Netherlands and use the car privately you must pay extra taxes. When you only use the company car for business, you don't have to pay those additional taxes. But to prove this to the tax authority, you have to create a complete registration that includes all the rides made with the car.

You can create this registration using pen and paper, the method I used before developing RegiRide or use an automated solution. RegiRide is a Windows Phone application that provides such an automated solution, it enables people that own a company car to create a registration of their business rides.

The tax authority in the Netherlands defined the following requirements for the registration.

For every ride you have to register the following

  • The Date 
  • The start and end mileage of the car
  • The start and end address 
  • The route (only if it is not the normal route) 
  • If the ride was private or business.

Additionally I added the following requirements

  • Easily register a ride via a mobile telephone 
  • Export the registered rides via a file with comma separated value (CSV) to import the data into excel 
  • Be able to register the rides without an active data connection

It is also required that you register the make, the model, the license plate and the period the car was used.

I hear you thinking, are their not a ton of these kinds of apps already available in the app store? Well in the Windows phone marketplace some application exists. But all these applications require you to register with an online service to export your data. For me it was important that the ownership of the data and the data itself stays with customer.

Architecture 

Based on the requirements, the first thing that the application needs was a way to persist the registered rides on the telephone. Luckily for me Windows Phone Mango 7.5 supports storing relational data in a local database. This database is stored on to your application's Isolated Storage. The local database is an implementation of SQL compact specifically for Windows Phone Mango. LINQ to SQL is available and can be used as an ORM engine.

The image below shows the high level architecture of the application.

I will explain each part of the architecture from left to right. On the left is the view of the application which is data bound to the View Model according to the MVVM pattern. The view model is filled using the mapper, which is responsible for mapping the model to the view model. The repositories are responsible for creating the LINQ to SQL queries; I create a single repository per entity. Therefore, for RegiRide I implemented a RideRepository and an AddressRepository.

For example, the AddressRepository has a method which retrieves all the addresses from the database.

public List<address> GetAll()
{
    return 
    ( from address in rideDataContext.Addresses 
        orderby address.Name 
        select address
    ).ToList();
}

This method returns a list of Addresses or according to the architecture a list of models. The application translates the model to view modes using a mapper.

Another part of the functionality is the ability to export the registered rides to Dropbox. A user clicks the export button on a view, this results into a command getting fired in the view model. This command gets all the necessary models from the database via a repository and maps them to an export model. The list of export models gets translated into a csv file. This file is then uploaded to the Dropbox account of the user.

Tools and framework

I made some changes to the list of tools and frameworks that I used for developing my previous application. The list below shows the current list of tools and frameworks used during the development of RegiRide. 

Why did I switch from Prism to MVVM Light? I saw a video of a presentation of Laurent Bugnion called "Deep Dive MVVM" in which he talked about how to set up your xaml based application when using the MVVM pattern. In this video he used the MVVM Light framework which he implemented. The one thing that got my attention was the way how the framework handled the OnPropertyChanged events. 

Instead of calling the propertychanged event using a string, the MVVMLight framework uses an expression to define a reference to the property. This way you get compile time checking instead of the normal run-time. I found this to be an elegant solution. See below an example from RegiRide. It will introduces a slight overhead as the MVVM Light uses reflection to get the actual  name of the property as a string.

public AddressViewModel SelectedAddress
{
  get
  {
    return addressViewModel;
  }
  set
  {
    if (addressViewModel == value)
    {
      return;
    }
    addressViewModel = value;
    RaisePropertyChanged(() => SelectedAddress);
  }
}

MVVM and MVVM Light

As already mentioned I switched from Prism to MVVM Light for this implementation. In this section I describe in more detail how data is retrieved from the database, changed and submitted back to the database. Data that is retrieved from the repository is converted into a view model using a mapper. This all results into for example the manage addresses screen.

The code below shows the view model from the above view in action.

public class AddressListViewModel : ViewModelBase, INavigable 
{
  public AddressListViewModel(IAddressRepository addressRepository)
  {
    this.addressRepository = addressRepository;
    this.addNewAddressCommand = new RelayCommand(this.AddNewAddress);
    this.loadAddressListCommand = new RelayCommand(this.LoadAddressList);
  }
 
  private void LoadAddressList()
  {
    AddressList = AddressListMapper.Map(
          this.addressRepository.GetAllWithNumberOfRides());
  }
    ...
}

The loadAddressListCommand is a RelayCommand which is databound to the load event of the actual view and when executed calls the LoadAddressList method. This method uses the addressRepository, which is injected through the constructor by SimpleIOC, to retrieve all the addresses. The AddressListMapper does the actual work by creating the AddressList which is an ObservableCollection<AddressViewModel>. Lets look into more detail into the Map method in the AddressListMapper.

public static ObservableCollection<AddressViewModel> Map(List<Address> addressList)
{
  var addresses = new ObservableCollection<AddressViewModel>();
  foreach (Address address in addressList)
  {
    addresses.Add(new AddressViewModel(address, WhichAddress.None));
  }
  return addresses;
}

The Map method creates for each instance of an Address, an instance of an AddressViewModel. The actual model is given as an argument to the constructor. By keeping a reference to the model in the view model and writing changes directly from the view model into the model, LINQ to SQL keeps track of the state of the model. When we want to perform an update to an address, it becomes as easy as calling SubmitChanges on the data context. 

public void Save(Address address)
{
  rideDataContext.SubmitChanges();
}

New addresses are created and saved using the same method. When the user adds a new address, a new instance of the Address model is created and filled using data binding. I changed the Save method so that it calls the InsertOnSubmit when the address is a new instance. The GetOriginalEntityState returns   an instance that contains the original state of the entity. If this method returns null, I assume that it is a new entity. 

public void Save(Address address)
{
  if (rideDataContext.Addresses.GetOriginalEntityState(address) == null)
  {
    rideDataContext.Addresses.InsertOnSubmit(address);
  }
  rideDataContext.SubmitChanges();
}

More on LINQ to SQL and how to model and generate the database later in this article.

Communication between view(models)

The managing address functionality consists of two views, both can be seen below. When the user selects an address the detail address screen opens. Also when the user presses the add button, the user switches to the detail screen.

In both scenarios adding or changing, communication is necessary between the view(model) that shows the list and the view(model) that shows the address details.

The application uses the MVVM Light framework for communication. MVVM Light has some features to make communication easy. The MVVM Light framework includes a messaging infrastructure that enables you to send messages internally in your application. Basically there are two possible ways to send a message, first using an overload of the RaisePropertyChanged and secondly by using the Messenger.Default.Send command. For the address detail screen I used the messaging overload of RaisePropertyChanged

public AddressViewModel SelectedAddress
{
  set
  {
    var oldValue = selectedAddress;
    selectedAddress = value;
    this.RaisePropertyChanged(() => SelectedAddress, oldValue, selectedAddress, true);
    NavigationService.Navigate("/Views/AddressDetailView.xaml");
  }
  get
  {
    return selectedAddress;
  }
}

The RaisePropertyChanged method includes an overload that accepts a boolean argument, called broadcast. This last argument of the RaisePropertyChanged event indicates whether or not a message should be broadcasted regarding this change. MVVM Light, under the covers construct and sends a message of type PropertyChanged<AddressViewModel>. So any subscribers listening for this specific message will get it delivered via the messaging infrastructure.

On the other receiving side, the detail view, you have to subscribe to this specific message. This is possible using the Register method of the Messenger. In the constructor of the AddressViewModel, a subscription is created on messages of type PropertyChangedMessage<AddressViewModel>.

public AddressDetailViewModel(IAddressRepository addressRepository, 
  IRideRepository rideRepository)
{
  this.addressRepository = addressRepository;
  this.rideRepository = rideRepository;

  SaveAddressCommand = new RelayCommand(SaveAddress);
  CancelCommand = new RelayCommand(Cancel);
  DeleteCommand = new RelayCommand(Delete);

  Messenger.Default.Register<PropertyChangedMessage<AddressViewModel>>(
    this,
    message =>
      {
        SelectedAddress = null;
        SelectedAddress = message.NewValue;
        if (message.NewValue != null)
        {
          whichAddress = message.NewValue.WhichAddress;
        }
      });
}

The entire AddressViewModel is sent as payload with the message and is directly set to the SelectedAddress property of the AddressDetailViewModel. This property is bound to the different fields on the view which in its turn gets refreshed so that the data is directly shown on the screen.

By communicating using messages instead of direct references, you decouple both view models. In the RegiRide example the list and the detail view don't know about each others existence. This will increase the flexibility and testability of your view models.

Working with Design Time Data

Windows Phone applications should be designed according to the Metro design guidelines. Jeff Wilcox has translated this into a set of concrete actions and recommendations. To be able to make sure your application follow these guidelines it is important to be able to work with data populated views during design time. This is also known as blendability, the ability to work with design time data in Microsoft Blend. It is possible to show your (dummy) data during design time using some guidance. There are different possibilities to be able to work with design-time data. It is possible to use the Silverlight design-time attributes which allow you to work with attributes that are only available during design time.

Another approach is to check if the application is working in design-time mode and based on that information use different parts of your application. This is the approach I took when implementing RegiRide. I use a class called ContainerLocator which is responsible for registering the types in the SimpleIoc container. In the configure method of this class I switch repositories depending on whether the application is running in design time or runtime. 

if (!ViewModelBase.IsInDesignModeStatic)
{
  SimpleIoc.Default.Register<IAddressRepository, AddressRepository>();
  SimpleIoc.Default.Register<IRideRepository, RideRepository>();
  SimpleIoc.Default.Register<ISettingsHelper, SettingsHelper>();
}
else
{
  SimpleIoc.Default.Register<IAddressRepository, DesignAddressRepository>();
  SimpleIoc.Default.Register<IRideRepository, DesignRideRepository>();
  SimpleIoc.Default.Register<ISettingsHelper, DesignSettingsHelper>();
}

The ViewModelBase is a class from the MVVM Light framework which as the name suggest is a base class from which ViewModel can inherit. It also has a static property IsInDesignModeStatic that indicates if the application is working in design-time.

Another helpful tool is the MetroGridHelper which displays a grid on your view according to the Metro design guidelines. Using this grid it is possible to align your controls. It is also possible to show the grid in design-time using a slightly modified version.

Code first / Database model

When you need to presist data in your Windows Phone application you can use LINQ to SQL to create and query a database. The suggested approach is to start code first when using the LINQ to SQL framework for Windows Phone. This means creating first the model classes that you want so store in the database and annotating these classes using the LINQ to SQL attributes. For RegiRide I needed the following database model.

 

A Ride has a set of properties and a relation to two addresses, the start address and the end address. First, I tried to create the classes code first and generate the database from that. This was somewhat a challenge, as getting the right annotations was difficult.

I found an article on www.windowsphonegeek.com  that suggested using SQLMetal. SQLMetal is a command-line code generation tool that is able to generate the code including the mapping attributes from an already existing database. The SQLMetal tool is specifically meant for the full .Net Framework and LINQ to SQL. Therefore, the generated code will not be 100% compatible with the LINQ to SQL version on the Windows Phone. What I did was create both the tables in a local SQL Server express edition and generated the classes using the SQLMetal command line tool. After that I removed the parts that I did not needed or did not compile.

Below a part of the generated and changed Address class can be seen.

[Table]
public class Address : PropertyChangeAndChangingEventHandlerBase
{
    private readonly EntitySet<Ride> startRide;
    private readonly EntitySet<Ride> endRide;
    private Guid id;
    private string name;
    private string street;
    private string postalCode;
    private string city;

    ...
}

The EntitySet<Ride> fields represent the references to the Ride and allow you to automatically load the referenced Ride when using that field. For me it was not important to have those fields on this side. I only need to be able to navigate from the Ride to the Address instead of the other way around. But I kept it as it may prove to be useful in the future.

Database creation

The database is created by the CreateDatabase method from the DataContext class. This DataContext class comes from LINQ to SQL. The source code below show the RideDataContext, this class derives from the DataContext base class and contains an Initialize method that creates the database if it does not exist. When the user deinstalls the application the database is automatically removed from the phone.

public class RideDataContext : DataContext
{
  public Table<Ride> Rides;
  public Table<Address> Addresses; 
        
  public RideDataContext(string fileOrConnection) 
           : base(fileOrConnection) { }

  public void Initialize()
  {
    if (!this.DatabaseExists())
    {
      this.CreateDatabase();
    }
  }
}

The Initialize method of the data context is called when the application starts. This data context is registered using the factory method overload from the SimpleIoc container.

SimpleIoc.Default.Register(() =>
{
  var context = new RideDataContext(Constants.Settings.DatabaseConnectionString);
  context.Initialize();
  return context;
});

To check and browse the created database on the Windows Phone or the Emulator I used the tool IsoStoreSpy. This tool enables you to look into the isolated storage of the emulator or phone and directly query or download the database.

Exporting the registered rides

RegiRide offers  functionality to export the registrated rides. My first intention was to implement this functionality by creating an email and attach the registered rides to the email. But to my surprise I found out that there is currently no way to send an email using an attachment in Windows Phone. At least no way to program this.

 

This was a real problem because I did not want to create a server back-end with a web service just to be able to send an email. Putting the content into the body of the email also was not an option because the size of the body is limited to 33k characters. After trying some external email send services I finally settled with Dropbox integration. It felt like a compromise, because users of the application are now forced to open an Dropbox account just to be able export their registered rides. But on the other end Dropbox is a free service and a lot of people already make use of it. Maybe in the future I will add functionality to export the rides to other external services e.g. Microsoft SkyDrive or Google GDrive.

Dropbox integration with DropNet

There are frameworks that provide Dropbox integration for the .Net Framework that include Windows Phone support. There is the Spring.Net Social extension for Dropbox which includes Windows Phone support and there is Sharpbox which also suports Windows Phone. I finally settled with DropNet from Damian Karzon which just worked for me right out of the box.

DropNet includes a sample Windows Phone application that just worked. Integration of Dropbox was easy this way. The RideList view has an export button which calls the Export method to export the registered ride to the Dropbox account.

public void Export()
{
  if (networkConnection.IsAvailable())
  {
    if (regiRideSettingsManager.Authenticated && dropNetClient.UserLogin != null && dropNetClient.UserLogin.Token != null)
    {
      string export = exportManager.GenerateExport(rideRepository.GetAll());
      byte[] exportBytes = Encoding.Unicode.GetBytes(export);
      string fileName = exportManager.GenerateExportFileName();
      dropNetClient.UploadFileAsync(regiRideSettingsManager.DropboxFolder, fileName, exportBytes, UploadSuccess, UploadFailure);     }
    else
    {
      MessageBox.Show(AppResources.ExportDropboxNotification);
      this.NavigationService.Navigate("/Views/DropboxView.xaml");
    }
  }
  else
  {
    MessageBox.Show(AppResources.NoNetworkAvailable);
  }
}

The dropNetClient from the DropNet library is responsible for the connection to the Dropbox account. The Export method checks if the current user is authenticated and if so the csv file is generated and uploaded to the Dropbox of the user using an unique file name. The authentication for the Dropbox account is handled by DropNet and uses the webbrowser control.

SplashScreen RegiRide Screenshot RegiRide

Once the user allows the application to access the Dropbox the access token is save into the isolated storage. This way the user only has to allows once and there is no need to store the users credentials.

LongListSelector 

For displaying rides and grouping I used the LongListSelector control from the Silverlight toolkit. This control is specifically designed for showing large list of data by including a grouping mechanism. The registered rides are grouped by week. So the blue bar in the screenshot below indicates that the rides below it are from week 15 in 2012. These rides all fall into the same group.

Screenshot RegiRide

This long list selector works with a set of templates that lets you customize its complete layout. The following customizable templates are available

  • HeaderTemplate, FooterTemplate 
  • GroupHeaderTemplate 
  • GroupItemTemplate 
  • GroupItemsPanel 
  • ItemsTemplate

This makes the long list selecter a very customizable control.

The control won't perform the grouping automatically, you must provide a grouped list yourself. For example, bind to an ObservableCollection<T> where T : ObservableCollection<T>. the type T must also expose a Key property which indicates the grouping property.    

In RegiRide I created a property called GroupedRideViewModels which performs the grouping and binds to the ItemsSource property of the LongListSelector.

public IEnumerable<grouping<string,>> GroupedRideViewModels
{
  get
  {
    if (RideList != null)
    {
      var result = from ride in RideList
         group ride by ride.WeekNumberFormatted into grouped
          select new Grouping<string, RideViewModel>(grouped);
      return result;
    }
    return null;
  }
}
</grouping<string,>

YLAD (Your Last About Dialog)

In my previous application I created the about dialog myself. This time I used the YLAD component to create an about dialog. This component from Peter Kuhn enables you to easily create an about dialog. It is easily configurable via a XML file. The image below shows the about dialog. It includes a pivot control which includes the version history of your application. I can recommend this component as it save you time.

Screenshot RegiRide

Showing the dialog is just one line of source code.

private void About()
{
  NavigationService.Navigate(
    "/YourLastAboutDialog;component/AboutPage.xaml");
}

The assembly that contains the about dialog wil be loaded only if the user selects to view the about dialog. This will  improve the start-up time of your application.

ListPicker

The listpicker control is used in the create new ride screen to let the user select the start and end address of the ride. The ListPicker is also a control from the Silverlight Toolkit which is comparable to a ComboBox or DropDownList. It shows the selected item and is also able to show a list of items that can be selected.The ListPicker control has two possible ways of selecting an item, first there is the inplace selection and secondly there is a full-screen popup that shows the selectable items. The control automatically switches between them, depending on the number set in the ItemCountThreshold property. The ItemCountThreshold is default set to 5. Be sure to wrap the ListPicker in a stackpanel, this allows the ListPicker to grow when expanding.

ListPicker to select an address

Both modes have their own templates that you can change. There is the ItemTemplate for the inplace selection and the FullModeItemTemplate for the full-screen variant. Both ListPickers on the Add Ride detail screen use the same collection of addresses. 

<phone:PhoneApplicationPage.Resources>
  <DataTemplate x:Key="ListPickerDataItemTemplate">
    <toolkit:WrapPanel>
      <TextBlock TextWrapping="Wrap" Text="{Binding Model.Name}" />
    </toolkit:WrapPanel>
  </DataTemplate>
  <DataTemplate x:Name="PickerFullModeItemTemplate">
    <StackPanel Orientation="Horizontal" Margin="16 21 0 20">
      <TextBlock Text="{Binding Model.Name}" Margin="12 0 0 0" FontSize="32" FontFamily="{StaticResource PhoneFontFamilySemiBold}"/>
    </StackPanel>
  </DataTemplate>
</phone:PhoneApplicationPage.Resources>

Conclusion

The application is available in the marketplace and the full source code can be downloaded from the top of the article. If you like the article, a vote or comment is appreciated. Thanks.

What's next?

I will continue with implementing my next application which will include food, a live tile and integration with a server back-end. If you are interested stay tuned for my next article for yet another complete Windows Phone application. The new article about Weekly Thai Recipe is available.  

History  

  • v1.0 26/04/2012 First version 
  • v1.1 27/04/2012 Small changes, e.g. links open in new window
  • v1.2 25/06/2012 Added link to new article.  

License

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

About the Author

Patrick Kalkman
Architect http://www.hinttech.nl
Netherlands Netherlands
Patrick Kalkman is a senior Software Architect with more than 20 years professional development experience. He works for Hinttech where he develops state of the art web applications.

Patrick enjoys writing his blog. It discusses software architectures using semantic web technologies. Patrick can be reached at pkalkie@gmail.com.
 
Published Windows 8 apps:
 
Published Windows Phone apps:
 
Awards:

Best Mobile article of March 2012
Best Mobile article of June 2012
Follow on   Twitter

Comments and Discussions

 
QuestionSend email without using ComposeTask PinmemberMember 151651110-Jan-13 6:59 
AnswerRe: Send email without using ComposeTask PinmemberPatrick Kalkman15-Jan-13 20:57 
GeneralMy vote of 5 Pinmemberridoy17-Dec-12 19:20 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman15-Jan-13 20:57 
GeneralMy vote of 5 PinmvpKanasz Robert21-Sep-12 0:49 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman15-Jan-13 20:58 
QuestionEmail attachment feature request PinmemberDanielMaxwell1-Jul-12 2:44 
AnswerRe: Email attachment feature request PinmemberPatrick Kalkman24-Jul-12 7:20 
GeneralMy vote of 5 PinmemberDanielMaxwell1-Jul-12 2:40 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman1-Jul-12 4:46 
GeneralMy vote of 5 Pinmemberindyfromoz25-Jun-12 13:20 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman1-Jul-12 4:45 
GeneralMy vote of 5 PinmemberSergio Andrés Gutiérrez Rojas25-Jun-12 6:13 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman1-Jul-12 4:45 
GeneralMy vote of 5 PinmemberVitaly Tomilov24-Jun-12 22:07 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman1-Jul-12 4:44 
GeneralMy vote of 5 Pinmembermanoj kumar choubey17-May-12 22:55 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman2-Jun-12 0:02 
QuestionFAS and Tombstoning Pinmembermiramares13-May-12 22:34 
AnswerRe: FAS and Tombstoning PinmemberPatrick Kalkman2-Jun-12 0:09 
GeneralMy vote of 5 PinmemberJeremy Hutchinson9-May-12 5:00 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman10-May-12 21:34 
GeneralMy vote of 5 PinmemberDennis Nuss8-May-12 9:36 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman8-May-12 10:13 
Questioncertification Pinmembermbowles2018-May-12 9:27 
AnswerRe: certification PinmemberPatrick Kalkman8-May-12 10:12 
QuestionGreat article there man PinmvpSacha Barber8-May-12 0:31 
AnswerRe: Great article there man PinmemberPatrick Kalkman8-May-12 10:04 
GeneralMy vote of 5 PinmvpMika Wendelius6-May-12 6:18 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman7-May-12 2:37 
GeneralExcellent article PinmvpEspen Harlinn29-Apr-12 5:34 
GeneralRe: Excellent article PinmemberPatrick Kalkman30-Apr-12 0:48 
GeneralMy vote of 5 PinmvpMarcelo Ricardo de Oliveira28-Apr-12 18:02 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman29-Apr-12 2:20 
QuestionMy vote of 5 PinmemberDon Kackman27-Apr-12 13:58 
AnswerRe: My vote of 5 PinmemberPatrick Kalkman28-Apr-12 1:33 
GeneralMy vote of 5 Pinmemberpietvredeveld26-Apr-12 20:30 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman27-Apr-12 0:16 
GeneralMy vote of 5 Pinmembernick_journals26-Apr-12 20:05 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman27-Apr-12 0:00 
GeneralRe: My vote of 5 Pinmembernick_journals27-Apr-12 1:43 
GeneralMy vote of 5 ! PinmemberMazen el Senih26-Apr-12 4:43 
GeneralRe: My vote of 5 ! PinmemberPatrick Kalkman26-Apr-12 9:20 
GeneralMy vote of 5 Pinmembert.a berglund25-Apr-12 22:17 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman25-Apr-12 23:34 
GeneralRe: My vote of 5 Pinmembert.a berglund26-Apr-12 3:02 
QuestionMy vote of 5 Pingroupsmarterdb25-Apr-12 22:17 
AnswerRe: My vote of 5 PinmemberPatrick Kalkman25-Apr-12 23:34 
GeneralMy vote of 5 PinmemberMario Majcica25-Apr-12 21:58 
GeneralRe: My vote of 5 PinmemberPatrick Kalkman25-Apr-12 23:34 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140421.2 | Last Updated 25 Jun 2012
Article Copyright 2012 by Patrick Kalkman
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid