This is the finale of a 5-part series in which I explore and demonstrate cross-platform app creation for mobile and desktop platforms using iFactr, an enterprise-grade framework, and Monocross, the open-source core of iFactr. The other articles are easily accessible at the bottom of each post.
Drum roll please!
Let’s get to coding for a real platform. Windows Phone 7.
When we added our Console and Webkit Monocross projects previously, we had templates available to us in VS. Unfortunately it doesn’t look like the current version of Monocross has those yet, so let’s see how we can work around this.
First, add a regular Windows Phone project to our solution:
But naturally none of this is wired up to Monocross. To figure out how we go about doing that, let’s have a look at the Console project’s references list:
A couple of these stand out, namely the ones that the template put in the “MonoCross” directory under the Console project itself: MonoCross.Navigation and MonoCross.Console.
Since we don’t have a Monocross.Navigation.WP, have a look through the source for Monocross. There’s a Monocross.Navigation folder, with a WP project in it. Add that to the solution, then add it to our Windows Phone project.
The Monocross.Console is what provides us with
// initialize container MXConsoleContainer.Initialize (new MonkeySpaceMX.App ()); // initialize views MXConsoleContainer.AddView (new Views.SpeakerListView (), ViewPerspective.Default); // navigate to first view MXConsoleContainer.Navigate (MXContainer.Instance.App.NavigateOnLoad);
That special MXConsoleContainer. No doubt an integral part of getting MonoCross to function. So we need something similar for Windows Phone. Upon examining the contents of the Monocross package, I found the following:
So hell, let’s just include this in our solution and reference it from our Windows Phone project:
Build solution… and… win! The whole thing builds.
Ok so now we have to wire up the Windows Phone project with the MonoCross platform library. To figure out how best to do this, I examined our previous implementation in iFactr along with what the Monocross Console project has in it.
Remember again that since Windows Phone targets a specific subset of the .Net Framework, we’ve got to use that handy “Add Link” option to create a new library that the WP implementation will use, but that will share all the same code from the current version we’ve got going.
After plugging in all the necessary pieces here’s what my building Solution looks like:
I had to make a couple of tweaks to the existing code to make it compatible w/ the WP platform libraries:
public class SpeakerController : MXController { public override string Load (Dictionary parameters) { this.Model = Data.Instance.Speakers.First (s => s.Id == int.Parse (parameters["id"])); return ViewPerspective.Default; } }
The highlighted line used to be a .Find() method but that doesn’t exist on List in the WP library.
I also excluded Logging\RestfulQueue.cs from the Monocross.Utilities.WP project because it negates to implement the web calls asynchronously which, of course, WP bitches about.
After doing that, I was able to add the following line to App.xaml in my Windows Phone Container:
MXPhoneContainer.Initialize (new MonkeySpaceMX.App (), this.RootFrame);
Sweet! Now it’s time to create the views so we can wires those up to the models. Let’s get crackin’ to whip up a couple of quick screens.
The Monocross.WindowsPhone project provides us with a couple of other classes that we’ll be employing to get our job complete:
So now I see that there’s a special MXPhonePage, which, I assume, is what my Pages will need to inherit from (instead of PhoneApplicationPage). The problem I found while trying to do this was that MXPhonePage takes a generic type for the underlying Model and for the life of me I couldn’t figure out how to specify the generic type, especially one like List, for the page. So I ended up with the following:
namespace WindowsPhone.MXViews { public class SpeakerListPage : MXPhonePage<observablecollection> { } }
First thing to note is that I’ve changed the collection type to an ObservableCollection to be more conducive to data binding.
<mx:SpeakerListPage x:Class="WindowsPhone.SpeakerList" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mx="clr-namespace:WindowsPhone.MXViews"
Notice how the definition for my page changes from
<phone:PhoneApplicationPage
to
<mx:SpeakerListPage
This affords Xaml wire-up to Monocross. Naturally this means our backing .cs file has to have the same:
[MXPhoneView("/SpeakerList.xaml")] public partial class SpeakerList : SpeakerListPage {
but notice I’m also taking advantage of an attribute that Monocross.WindowsPhone gives us. MXPhoneViewAttribute. You use this attribute to specify the Navigation URI that will, in turn, be used in indexing the controllers and driving the Monocross Navigation. I will admit it took me some debugging through the initial load of the application to figure this out. But hey, now YOU don’t have to!
Ok so now we’re all wired up with a view for the Speaker List. Let’s add it to the application’s navigation. Previously we added the .Initialize() to the App() constructor of the Windows Phone container. Now it’s time to add the other parts:
MXPhoneContainer.Initialize (new MonkeySpaceMX.App (), this.RootFrame); MXPhoneContainer.AddView (new SpeakerList ()); MXPhoneContainer.Navigate ( MonoCross.Navigation.MXContainer.Instance.App.NavigateOnLoad);
We add a handler for a View, then we tell the application to go ahead and navigate to the OnLoad of the underlying MX application.
And now the fun part. Let’s create our view. The bonus of Monocross over iFactr is that we get to make this view look however we want! You get to use all the Native controls, animations, whatever you want and create a view for the underlying Model that you already wrote and used with Console and Webkit. Sweet.
To show it off, let’s create a simple view in the SpeakerList.xaml page:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <ListBox ItemsSource="{Binding Mode=OneWay}"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Image Source="{Binding HeadshotUrl, StringFormat='http://monkeyspace.org{0}'}" Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" /> <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Name}" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>
And voila. A databound listbox that will show the picture and name of all the speakers. Let’s wire up the underlying model:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { base.OnNavigatedTo(e); this.LayoutRoot.DataContext = this.Model; }
Let’s fire it off!
But what about making use of some of the special UI constructs available to Windows Phone? Like that pretty Panorama control… Just throw in a new Panorama Page, inherit from the SpeakerListPage to get the MXView applied, re-write the Application wireup to look something like this:
MXPhoneContainer.AddView (new SpeakerListPanorama ());
And voila:
Your same data shared across multiple platforms now has a view that only Windows Phone can provide. Awesome.
I’ll leave you with this, my friends. If you have any questions, comments, or challenges feel free to comment or e-mail and I’ll either give you the answer, or learn right along with you!
B