Unit Converter and a Custom Control in WP7

I recently finished the development on my first Windows Phone 7 app, titled Unit Converter.  While the idea is hardly unique, I decided to write one for two reasons 1) it was a good chance to get some hands on experience developing WP7 apps and 2) it was something I needed while traveling through Japan.

Most of the code in the application is straightforward and easy to understand. There was one obstacle I ran into that I felt was worth blogging about: custom controls.  The app supports a number of different units.  I needed a way to select these units.  I choose to use LoopingSelector control, which is available via the WP7 Toolkit, in the Primitives namespace.

My first thought was to make these selections available on the screen itself.  However, given the way the LoopingSelector displays and the effect it tries to achieve, it just didn’t look right.  This is where I drew inspiration from the DatePicker control and how it uses a second page to support the selection.

My first attempt at this worked, but was pretty flakey.  I used a TextBox and whenever it got focus Navigated to the page supporting the selection.  After selection the user would “Go Back” to the main page and the unit selected would be shown.  The flakiness came in with the management of the Focus.  I found it very hard to get ride of the focus once the user returned to the page.  This meant the user would have to select somewhere else on the screen.  Like I said, flaky.

It was at this point, drawing upon more inspiration from the DatePicker that I finally achieved a good solution, a custom control.  And thankfully, I could use the DatePicker source code from the Toolkit.

What I ended up doing was creating a custom button, similar to the DatePicker, and internalizing everything.  It ended up working quite well.

First thing to do is to inherit your class from Control.  This will give you access to several methods and allow you to use the class in XAML.

   1: [TemplatePart(Name = ButtonPartName, Type = typeof(ButtonBase))]

   2: public abstract class ConversionUnitPickerBase : Control

   3: {

   4:    // code goes here

   5: }

The TemplatePart attribute denotes what sort of base type this control “is”.  In this case, we are viewing the control as basic button. The actually look of this control is applied through a style, shown here:

  1:         <Style TargetType="pickers:ConversionUnitPickerBase" x:Key="ConversionUnitPicker">
  2:             <Setter Property="Background" Value="{StaticResource PhoneTextBoxBrush}" />
  3:             <Setter Property="BorderThickness" Value="0"/>
  4:             <Setter Property="Foreground" Value="{StaticResource PhoneTextBoxForegroundBrush}" />
  5:             <Setter Property="HorizontalContentAlignment" Value="Left" />
  6:             <Setter Property="PickerPageUri" Value="/Pickers/ConversionPicker.xaml" />
  7:             <Setter Property="Template">
  8:                 <Setter.Value>
  9:                     <ControlTemplate TargetType="pickers:ConversionUnitPickerBase">
 10:                         <StackPanel>
 11:                             <Button x:Name="PickerButton" Content="This is a Test"
 12:                                 Background="{TemplateBinding Background}"
 13:                                 BorderThickness="{TemplateBinding BorderThickness}"
 14:                                 FontFamily="{TemplateBinding FontFamily}"
 15:                                 Foreground="{TemplateBinding Foreground}"
 16:                                 Height="72" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
 17:                         </StackPanel>
 18:                     </ControlTemplate>
 19:                 </Setter.Value>
 20:             </Setter>
 21:         </Style>

The key thing to look at with this code sample is the Setter.Value which effectively creates the template.  Unfortunately, to this point I have not figured how to apply this style to all controls automatically, as is done in the Toolkit source.  For the sake of time and sanity, I simply specified it manually in my XAML:

   1: <pickers:TemperatureUnitConversionPickerBase x:Name="sourceUnit"

   2:                               Style="{StaticResource ConversionUnitPicker}"

   3:                               TemperatureType="Celcius" />

It would appear, based on the samples, that the way to do this is through themes, but I simply have not figured it out yet.

Returning to our class which inherits from Control and is decorated with TemplatePart attribute, the only method that I have found the need to override is OnApplyTemplate which effectively gets the ball rolling, see below:

   1: public override void OnApplyTemplate()

   2: {

   3:     // this is a cleanup step

   4:     if (_mainButton != null)

   5:         _mainButton.Click -= new RoutedEventHandler(HandleUnitPickerLaunch);

   6:  

   7:     base.OnApplyTemplate();

   8:  

   9:     // get a reference to the button we are representing

  10:     _mainButton = GetTemplateChild(ButtonPartName) as ButtonBase;

  11:     if (_mainButton != null)

  12:     {

  13:         _mainButton.Click += new RoutedEventHandler(HandleUnitPickerLaunch);

  14:         _mainButton.Content = Value;

  15:     }

  16: }

This approach reminds me a lot of Android, where we are looking up controls based on names and then populating them through a find method.  Since this control is really a “super” button, we are only wiring up the click event.

From this point the implementation is your own.  In my case, I choose to make this class abstract so I could inherit it for my specific conversion types (Distance, Weight, Volume, and Temperature).  As part of the click handler for the button itself, I had it open my conversion picker, as shown below:

   1: private void OpenUnitPicker()

   2: {

   3:     _applicationContent = PhoneApplicationFrame.Content;

   4:  

   5:     PhoneApplicationFrame.Navigated += new NavigatedEventHandler(

   6:        PhoneApplicationFrame_Navigated);

   7:  

   8:     PhoneApplicationFrame.NavigationStopped += new NavigationStoppedEventHandler(

   9:        PhoneApplicationFrame_NavigationStoppedOrFailed);

  10:  

  11:     PhoneApplicationFrame.NavigationFailed += new NavigationFailedEventHandler(

  12:        PhoneApplicationFrame_NavigationStoppedOrFailed);

  13:  

  14:     _applicationFrame.Navigate(PickerPageUri);

  15: }

This code is effectively helping to do the switch, by saving the content of the control before the Navigate.  PickerPageUri is simply the Uri path within the Silverlight application to the XAML page which will allow the user to pick the unit they are using in the conversion.

We can pass values to this Uri using an interface and the Navigated event, an example is below:

   1: void PhoneApplicationFrame_Navigated(object se, NavigationEventArgs e)

   2: {

   3:     if (e.Content == _applicationContent)

   4:     {

   5:         // close the picker

   6:         CloseUnitPicker();

   7:     }

   8:  

   9:     if (_pickerPage == null)

  10:     {

  11:         _pickerPage = e.Content as IUnitPickerPage;

  12:         if (_pickerPage != null)

  13:         {

  14:             // assign default values to picker page

  15:             _pickerPage.ConversionType = ConversionType;

  16:             _pickerPage.SelectedUnit = GetConversionUnit();

  17:         }

  18:     }

  19: }

What is happening here is as we leave the “page”, we track a reference to the next page.  Through the casting to an interface we can access properties.  We use the same approach for reading the data coming back from the Picker page:

   1: private void CloseUnitPicker()

   2: {

   3:     if (_applicationFrame != null)

   4:     {

   5:         _applicationFrame.Navigated -= new NavigatedEventHandler(

   6:           PhoneApplicationFrame_Navigated);

   7:         _applicationFrame.NavigationFailed -= new NavigationFailedEventHandler(

   8:           PhoneApplicationFrame_NavigationStoppedOrFailed);

   9:         _applicationFrame.NavigationStopped -= new NavigationStoppedEventHandler(

  10:           PhoneApplicationFrame_NavigationStoppedOrFailed);

  11:  

  12:         _applicationFrame = null;

  13:         _applicationContent = null;

  14:     }

  15:  

  16:     // selected unit will be null if they hit cancel

  17:     if (_pickerPage != null && _pickerPage.SelectedUnit != null)

  18:     {

  19:         // get the value through the interface

  20:         _mainButton.Content = _pickerPage.SelectedUnit.LongDisplay;

  21:         SetUnit(_mainButton.Content.ToString());

  22:     }

  23:  

  24:     _pickerPage = null;

  25: }

The key lines here are 17 – 22, which take care of reading the data coming back, so long as the user didn’t cancel, which is handled by the null check.

Writing custom controls is a very powerful tool for your toolbox, I learned this writing Android as well.  The ability to customize is essential in mobile development.  Always consider the user, the usage, and the native paradigms of the platform when designing an interface.  This is a case where being unique may not be a good thing, mobile interfaces are highly specialized.

I plan to deploy this app after additional testing next week, the source code will be made available here at that time.

Advertisements

One thought on “Unit Converter and a Custom Control in WP7

  1. Absolutely brilliant, after spending many hours (maybe even days) i finally found this article ! this is amazing stuff.

    There a lots of examples of creating a loopselector around but none to actually implement into a usable scenario , a textbox gotfocus or button click event is what I was after.

    I dont have any experience in creating custom controls so I will give it a go and it will be very interesting to compare me code when you release your source.

    Thank you so much, I know of a few other forums with the same same question that i can point to your atricle.

    Great Job! I am Ctrl-d'ing your site imediatly.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s