Whilst there are a number of common pitfalls that are easily resolved, there are few differences between the two frameworks that are surprising and make little or no sense at all! I have called these the ‘unexpected’ because, as the name suggest, you will not expect them, and you typically only find them when things start to misbehave in peculiar and illogical ways!
Next is the control life-cycle:
Within WPF, OnApplyTemplate
is called before the Loaded event fires, whereas, in Silverlight this is not guaranteed and often OnApplyTemplate
occurs last. This can be problematic, if you use OnApplyTemplate to wire up your control’s UI, locating element within your XAML markup. In Silverlight, this logic may not be called before user-logic within the Loaded event. There are ways to resolve this issue; Page Brooks describes a method of forcing the life-cycle to follow the WPF order of events.
Finally, XAML resources, if you use the file linking approach within your WPF project to link a XAML resource into your WPF solution, build the project, then inspect the assembly with ILSpy, you will find that XAML resources are always located in the root of the assembly, regardless of which project folder they were dropped into. For custom controls, which require a Themes/generic.xaml file, this is a disastrous!
A nice resolution for this issue, courtesy of Alan Mendelevich, is to have all of your XAML resources in the project root, then use MergedDictionaries
to include the required resources in your generic.xaml file:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/WpfControl;component/Themes/MyTemplatedControl.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Adapting to each Platform
The previous section described techniques that can be employed to provide the same functionality on each platform. However, if you simply try to deliver exactly the same application across the desktop, web and mobile, your application will probably never really feel “at home” in any of the three.
The various UI controls that are common across all three frameworks do a good job of adapting to the platform within which they are hosted. If we compare a simple interface, generated using identical XAML, across all three, we see some fairly big differences:
The desktop WPF and web-based Silverlight interfaces are pretty similar, however, the WP7 interface looks very different. The buttons, checkboxes and radio-buttons are much bigger to account for the relative inaccuracy of the main input device (your finger!), there are also some dynamic differences, for example, scrolling is achieved via a swipe gesture, with the scrollbar hidden so as to save valuable screen estate.
However, the different platforms offer much more scope for adapting your application. Desktop and web-based applications are becoming more and more similar; however, the mobile environment offers many capabilities that are typically absent from web and desktop:
Orientation – mobile devices can detect orientation, this allows you to develop a portrait or landscape interface, or perhaps one that adapts as the orientation changes.
Camera, GPS, Accelerometer – mobile devices contain a number of peripheral inputs that you can use within your application to deliver novel functionality. The most commonly used one is GPS, providing location aware search results.
Multi-touch – there are multi-touch desktop interfaces available, but these are not terribly common (perhaps due to ergonomic issues), in contrast, multi-touch is a standard feature of smartphones. Making use of multi-touch interfaces and gestures allows the user to explore your application and their data in new and interesting ways.
With XAML Finance I wanted to see how much code it was possible to share between a desktop, web and mobile application, whilst at the same time making the application feel “at home” within each environment, i.e. making use of the functionality available to each.
The XAML Finance application allows you to explore the shares that comprise the UK’s FTSE100 index. You can browse the FTSE 100 by share price, drill-down to view performance and static data for each share and view historic performance via charts. There is also a heatmap visualisation of the FTSE100 index.
I am not going to describe the code in detail; there is quite a lot of it! The full source-code is available to download with this article, so feel free to delve into it at your leisure. Instead, I will describe the overall architecture and highlight the major differences between the three versions of XAML Finance.
To make it easier to share and specialise the code, all of the shared C# and XAML files are located outside of the project folders, in a ‘Common’ folder. This makes the three platform specific projects less tangled when sharing code between just two of the three platforms.
Architecture
Each of the three XAML Finance solutions comprises two projects, the datasource class library and the XAML Finance application. I will describe each in turn.
DataSource Library
The datasource library exposes a single public interface, IDataSource
which has a few simple methods that asynchronously return data, in the form a structured model (of POCO instances). Internally this is implemented by the class DataSource
which depends on an xml source of data as defined by the IXmlDataSource
interface. There are a couple of implementations of this interface, XmlDataSource
which obtains XML data from the internet, which XmlFileDataSource
returns XML data from embedded file resources – useful for offline testing of the application. DataSource
makes use of Linq-to-XML to parse the string response and create the required POCO object graph.
The datasource class library is identical for all three XAML Finance applications.
XAML Finance Application
The XAML Finance application depends on the datasource project, using it to supply the required data. It is in the three versions of the XAML Finance applications that we see framework differences being resolved and the application being adapted for its target platform.
The MVVM pattern
The XAML Finance Application makes use of the Model-View-ViewModel UI design pattern (which is described in detail by Josh Smith in this MSDN article), to me, the one key thing to remember in the MVVM pattern is that the ViewModel is the model-of-a-view, in other words, the ViewModel should have a structure that is closely related to the way that the data is presented on-screen.
The Silverlight version of the XAML Finance Application presents the user with a tabbed interface. Clicking on one of the buttons on the left adds a new FTSE100 or Heatmap view, whilst clicking on one of the shares adds a view which gives you more information about the specific company, including a chart. The top-level view model, XamlFinanceViewModel
exposes an ObservableCollection
of ‘child’ view models, each one being bound to a tab-page. As new ‘child’ view models are added, they initialise themselves by fetching data via the IDataSource interface. The view models implement the INotifyPropertyChanged
interface so that the view can be updated as the data arrives. This is all very standard MVVM fare!
All of the child view models extend NamedViewModelBase
. This base class provides a Name property, which is displayed as the tab heading, and a CloseCommand
, which is bound to the small ‘x’ button that removes a tab. The implementation of this command simply removes the view model from the TabItems
collection in XamlFinanceViewModel
, the collection binding ensures that the UI is updated accordingly.
Adapting for the web
Web based Silverlight applications often live alongside web-based HTML content. The default style for Silverlight applications, which is based on the Aero theme doesn’t really sit well alongside most web content. The flexible styling / templating available within Silverlight makes it quite an easy task to re-style the UI to fit in with existing web content. In the screenshot below it can be seen that the fonts, colour scheme, hyperlinks and other imagery have been copied from the website which hosts the application, making it hard to determine the boundaries of the Silverlight application:
Adapting for the desktop
Desktop applications typically have a similar appearance and layout to web-based applications. I used one of the ‘stock’ themes available to WPF, to give the application an aero theme.
With web-based applications it is often the expectation that the browser will be maximized, giving each a similar amount of screen real-estate to utilise. With XAML Finance I wanted to explore the use of multiple windows. In the web version you can open pages for multiple shares, with each appearing in a separate tab, however you cannot compare two side-by-side.
The WPF version of XAML Finance specialises the top-level model XAMLFinanceViewModel
, via partial classes, to add a second collection of ‘child’ view models.
The TabItems
collection is bound directly to the ItemsSource
of a TabControl
within the view, with the binding framework managing the creation / destruction of TabItems
as this collection changes However, the management of the floating windows must be done manually. The WPF specific additions to the view model require WPF specific views. However, the view is composed of UserControls
, allowing for re-use of the various components, for example, the company summary and chart page is shared between WPF and Silverlight, even though the views that host them differ,
NamedViewModelBase
is extended in the WPF project, via partial classes, to add PopOutCommand
and PopInCommand
. These commands are bound to buttons in the tab heading:
The PopOutCommand
moves the view model from the TabItems
collection to the FloatingItems
collection. The creating of a Window
to host a popped-out view model is performed by the application, which handles collection changed events raised by the FloatingItems
collection:
private XAMLFinanceViewModel _model = new XAMLFinanceViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = _model;
model.FloatingViewModels.CollectionChanged += FloatingViewModels_CollectionChanged);
}
private void FloatingViewModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var host = new ViewModelHost();
host.DataContext = e.NewItems[0];
host.Show();
}
}<span class="Apple-style-span" style="white-space: normal; ">
</span>
When a view model is added, a ViewModelHost
window is created with the view model supplied as the DataContext
. The ViewModelHost
simply contains a ContentControl
:
<Window x:Class="XAMLFinance.ViewModelHost"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
Closed="Window_Closed"
Title="{Binding Path=Title}" Height="600" Width="500">
<Grid>
<ContentControl Content="{Binding}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"/>
</Grid>
</Window>
When the Closed
event is raised, the PopInCommand
is executed (by simply invoking this command via code-behind), which results in the view model being moved back into the TabItems
collection.
The selection of a suitable view for the bound view model is performed via implicit DataTemplates
, with the following XAML merged into the application resources:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="http://XAMLFinance/View"
xmlns:vm="http://XAMLFinance/ViewModel">
<DataTemplate DataType="{x:Type vm:InstrumentDetailsViewModel}">
<view:InstrumentDetailsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PriceListViewModel}">
<view:PriceListGridView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HeatMapViewModel}">
<view:HeatMapView/>
</DataTemplate>
</ResourceDictionary>
Other than the addition of floating windows, the WPF and Silvelight applications are very similar, sharing view models, with the extensions described above, and much of the view code (XAML) as well.
Adapting for Windows Phone 7
The ‘core’ of both the WPF and Silverlight versions of XAML Finance is the tabbed interface that allows the user to open multiple documents and switch between them. Windows Phone 7 doesn’t have a tab control, so there is no easy way to copy the WPF / Silverlight interfaces to WP7. There is nothing stopping you from creating a WP7 tab control, however, with the limited screen size of a mobile device, multiple document interfaces do not work well. As a result, the majority of WP7 applications provide a navigation based approach. In fact the WP7 architecture, with page state, back-stack and tombstoning really push you towards a page-by-page UI model.
This different architecture is reflected in the re-structured view model, where for example, clicking on a company in the PriceListViewModel
doesn’t result in a new view model being added to a collection within the top-level view model (as is the case for the WPF and Silverlight applications). Instead, the view model is ‘expanded’ in place, with the additional data requested via IDataSource
in-situ.
The WP7 landing page makes use of the popular Panorama
control, and the ‘tiles’ UI paradigm is employed to create a Metro style UI.
Rather than display the Panorama immediately, the UI is hidden while the data is loaded. The XAMLFinanceViewModel
(the WP7 specific extension), initialises its various child view models immediately. As the state of each changes, various conditions are checked. When all the children are fully populated, the boolean IsLoaded
property is set to true:
public override void Init()
{
Pricelist.PropertyChanged += (s,e) => CheckChildViewModelsLoaded();
HeatMap.PropertyChanged += (s, e) => CheckChildViewModelsLoaded();
Pricelist.Init();
HeatMap.Init();
App.Current.DataSource.GetFTSE100PriceHistory(
priceHistory =>
{
FTSE100Price = new InstrumentPriceHistoryViewModel(priceHistory);
CheckChildViewModelsLoaded();
});
}
private void CheckChildViewModelsLoaded()
{
IsLoaded = Pricelist.Prices.Count > 0 &&
HeatMap.Sectors.Count > 0 &&
FTSE100Price.Count > 0;
}<span class="Apple-style-span" style="white-space: normal; ">
</span>
The code-behind for the page which hosts the panorama control checks the state of the IsLoaded
property, hiding the loading indicator when all the view models are fully populated:
public PanoramaIndex()
{
InitializeComponent();
this.DataContext = App.Current.ViewModel;
LoadingIndicator.Opacity = 1;
if (App.Current.ViewModel.IsLoaded)
{
DataLoadComplete();
}
else
{
App.Current.ViewModel.PropertyChanged += (s, e) =>
{
if (App.Current.ViewModel.IsLoaded)
{
DataLoadComplete();
}
};
}
}
private void DataLoadComplete()
{
BackgroundImage.Opacity = 0.4;
LoadingIndicator.Visibility = Visibility.Collapsed;
}<span class="Apple-style-span" style="white-space: normal; ">
</span>
Another place where the WP7 application differs is the FTSE100 Heatmap, this provides a view of share performance, indicated by colour, versus market capitalization, indicated by size. The heatmap is further structured by industry sector. The WPF and Silverlight toolkits provide a TreeMap control which is ideal for visualising this kind of data, by using nested TreeMaps it is possible to create this heatmap purely within XAML. Clicking on any of the shares executes a bound command which adds the required view model to the collection of tabbed items.
The WP7 Toolkit does not include the TreeMap
control which is present in the Silverlight and WPF toolkits. However, by taking the sourcecode from the Silverlight version, I found that it was very easy to port this to WP7. The bigger issue on the phone is usability, for companies with a very small market capitalization, the area they occupy within the heatmap is very small. Whilst they are still clickable with a mouse, which can be manipulated with a high degree of precision, they are impossible to click on a phone interface (with even the most slender of finger!).
For the WP7 version of XAML Finance, the user drills-down through the heatmap, the first click selecting sector, the second click selecting the specific company:
The company information, which is displayed as a single page in the Silverlight and WPF applications, is displayed on multiple screens using a WP7 pivot control:
The only third-party control that XAML Finance uses is a Visiblox chart control. This chart has WPF, Silverlight and WP7 editions, each exposing the same API. Furthermore, the WP7 version makes use of the phone’s multitouch interface, allowing the user to scroll and ‘pinch’ the chart using their fingers.
When interacting with the chart, it makes sense to maximise it so that it fills the entire phone screen. Also a landscape orientation makes most sense for a time-series chart. Rather than having the user explicitly navigate to the full-screen chart page, XAML Finance makes use of the fact that the phone can detect orientation. When the user rotates the phone into portrait mode, the chart is rendered full screen.
The WP7 version of XAML Finance also makes use of the WP7Contrib project on codeplex, which provides a number of useful tools and utilities for WP7 developers.
With this article I have demonstrated how you can re-use a considerable amount of code between the desktop, web and mobile applications by use of the WPF, Silverlight and Silverlight for WP7 frameworks respectively. Unfortunately writing cross-platform applications is not an easy ride; there is no real tool support for this, beyond linked files, and the numerous framework differences are obstacles that must be overcome. Fortunately, with the use of standard solutions to these differences, and suitable UI patterns to specialise each version of the application it is possible to write cross-platform applications that share large volumes of code, whilst looking like they really ‘fit’ in each environment.
Just how much code can be shared does depend on the application you are creating. The XAML Finance application attached to this article has approximately 75% of its code within a common codebase:
There is also quite a bit of code shared between the web-based Silverlight and WPF versions. The WP7 edition of XAML Finance has more platform-specific code than the others because of the more dramatic differences in the platform itself.
I hope that this article has been informative and has encouraged others to think about cross-platform development with XAML technologies. The obstacles, once overcome, allow the sharing of a considerable amount of code.
Three applications for the price of one (or one-and-a-half), a real bargain!
Change History
- 22 Sep 2011 - Fixed broken links
- 21 Sep 2011 - Added a video for the WP7 version
- 19 Sep 2011 - Initial article upload