In the Silverlight 2 ListBox/ScrollViewer FAQ, I mentioned that I'd done a lot of Silverlight control development on WPF. I spoke with a number of people at MIX08 last week and many of you wanted to know more. So I've created a Visual Studio solution with one project to compile these Silverlight controls on WPF and another project to run the corresponding unit tests. I've also implemented a simple demonstration of the WPF ListBox/ScrollViewer running side-by-side with the Silverlight ListBox/ScrollViewer (all on WPF):
While there are some obvious style differences between the default look and feel of the two platforms, the control functionality is very similar. To prove it, I've attached a ZIP of the solution to this blog post so you can run the demo application on your own machine. [Aside: I did not say the behavior was identical, just very similar. :) ]
And though I didn't enable code coverage by default, those of you with the Visual Studio Code Coverage feature installed can easily turn it on by editing the LocalTestRun.testrunconfig settings. When compiled for WPF, ListBox and ScrollViewer live in the
WPF namespace to avoid colliding with WPF's classes in
System.Windows.Controls. So we can see below that the overall coverage for these controls is a smidge under 80% (the uncovered code is mostly involved with user interface manipulation (ex: mouse and key input) which I wasn't able to flush out because I ran out of time):
So if you were wondering whether it was possible to easily share code, XAML, and unit tests across both Silverlight and WPF, the answer is a definite YES! :)
- In this case, the code for ListBox, ListBoxItem, ScrollViewer, and ScrollContentPresenter is taken from Silverlight, but the underlying implementations of ItemsControl, ContentPresenter, and ScrollBar are provided by WPF. (So some behavior differences in the dependent controls are reflected by ListBox/ScrollViewer.)
- I wanted to make as few changes as possible when creating this sample - here's a summary of what I did:
- Created a new WPF project.
- Copied all code files directly from the public Silverlight 2 Beta 1 controls source code download.
- Copied default styles from the associated generic.xaml file to the new project's Window1.xaml as implicit styles for the Silverlight control implementations.
- Removed TemplateBindings from ContentPresenter/ScrollContentPresenter because they're not necessary on WPF (the ContentControl-ContentPresenter hook-up is done automatically) and removed Setters for related properties.
- Added Animations to ListBoxItem's "Normal State" because the "restore default settings" behavior that's core to Silverlight's parts model specification is not present on WPF.
- Added a few calls to Math.Round to ListBox's IsOnCurrentPage method. (I deliberately avoided duplicating WPF's MS.Internal.Double.Util.* helper methods for the Silverlight ListBox and have seen no problems because of that. However, when running the sample on WPF, I found a few situations where the double rounding issues were causing problems, so I patched the relevant location with a simple workaround.)
- Added SnapsToDevicePixels=True to applicable elements in the copied style definitions. (Silverlight doesn't support SnapsToDevicePixels in Beta 1, so this property isn't used by the default Beta 1 control templates.)
- Updated the assembly name for some XAML used by the unit tests.
- More details: Search the solution for comments with the text "LB-SV-WPF:"
- Because they're relevant, here are a few details from the previous post:
What's the meaning of the WPF/WPFIMPLEMENTATION defines in the controls source code? The development of ListBox (+ListBoxItem) and ScrollViewer (+ScrollContentPresenter) was done in parallel with the development of their base types (e.g., ItemsControl, ContentControl) and also in parallel with the development of the Silverlight 2 platform itself. To minimize the risk/impact of developing on a changing foundation, I did much of my development and unit testing on WPF by deriving from the corresponding base classes, using only the subset of WPF that Silverlight exposes, and avoiding features specific to either platform as much as possible.
When compiling and running on WPF, I would add "WPF" to the list of conditional compilation symbols for the project. Therefore, code inside an
#if WPFblock applies only when running on WPF. In many cases, the pattern is
#if WPF ... #else ... #endifand this corresponds to instances where some bit of code needed to be different between the two platforms (typically because it used features that weren't identical across both). In some cases, the relevant code only applies to one platform and the
#elseis missing or
#if !WPFis used instead.
The meaning of "WPFIMPLEMENTATION" is similar and is used by the unit tests for code that applies only when testing the WPF implementations of ListBox or ScrollViewer. I used unit tests in three different scenarios to help ensure the code was as correct and compatible as possible: testing the Silverlight implementation on Silverlight, testing the Silverlight implementation on WPF (
#if WPF), and testing the WPF implementation on WPF (
#if WPF, #if WPFIMPLEMENTATION). Though it may seem silly at first, the point of testing the WPF implementation on WPF was to make sure the unit tests were validating the correct behavior.