February 2012

Volume 27 Number 02

XAML - Creating a Single Codebase for Silverlight and Windows Presentation Foundation

By Hristo Hristov | February 2012

Many Telerik products are designed to shorten the software development effort by providing controls built on the .NET Framework programming model. When Microsoft announced Silverlight, we quickly identified its similarities with Windows Presentation Foundation (WPF) and decided to explore the idea of creating a single codebase to allow shared controls that would support both frameworks.

This article chronicles the challenges and the successes of our development journey, which took us through five versions of Silverlight and three versions of WPF. Along the way, we developed tips and tricks for handling the differences between the platforms; we learned how to optimize theming with the least amount of code; and we explored how virtualization works within the frameworks and within custom controls to produce more reliable and responsive components.

Equalizing Silverlight and WPF

Telerik was one of the pioneers in using Silverlight, creating MediaPlayer, Buttons, Labels and Cube controls that targeted Silverlight 1. Silverlight 1.1 Alpha was the first managed version of Silverlight, and it was extremely minimalistic. It had no proper layout mechanism, no generic.xaml for theming, and provided no style support.

It wasn’t until Silverlight 2 that features supporting serious development appeared. Silverlight 2 added a layout system, control templates, the Style property, several layout panels (Canvas, StackPanel and Grid) and some base controls (Buttons, ListBox and ComboBox) and part of the base class library (BCL from .NET Framework). These features allowed us to create controls for Silverlight and WPF that share the same codebase.

When first released, Silverlight functionality represented a small subset of the capabilities of WPF. We had to decide whether to create controls that were limited to Silverlight and directly reuse them in WPF or to build two types of controls, one for Silverlight and one for WPF. The second approach might sound like the better choice, but it would have limited the application’s portability. Given that we were aiming at true code reuse, we rejected the second option. We decided to implement all the major WPF features (or at least the ones we thought were useful and important) in Silverlight.

We first created an EventManager, which provided support for routed events. Routed events and dependency properties are the core functionality of the WPF platform. For these to work, you need to walk up or down the visual tree. This implementation was relatively easy because Silverlight provides the VisualTreeHelper class. Because the VisualTreeHelper.GetParent method doesn’t return the parent of the Popup control, however, we had to use the logical parent property (e.g., popup.Parent) to gain access to the parent element.

We then worked on ItemsControl. In WPF, ItemsControl is used to display multiple items as a base class of ListBox, ComboBox, DataGrid and so on. ItemsControl also provides many ways to customize the containers generated to show the data items by using ItemContainerStyle, ItemContainerStyleSelector, ItemTemplate, ItemTemplateSelector and DisplayMemberPath. Silverlight supports only ItemTemplate and DisplayMemberPath. The challenge of implementing other necessary properties was far from trivial.

In Silverlight 2, you could set the Style property only once. On the second attempt, it threw an exception. Fortunately, this limitation was removed in Silverlight 3. Even in Silverlight 5, working with the ItemTemplate property presents some challenges. The Silverlight implementation of ItemsControl always sets ContentTemplate if the container is ContentPresenter or ContentControl. If you specify ItemTemplate from ItemContainerStyle, the platform overrides it. These properties are set in the virtual PrepareContainerForItem method.

A work-around for this problem is to apply the container Style, cache the ContentTemplate value and then call the base method (e.g., base.PrepareContainerForItem). You can then check to see whether the ContentTemplate value is DisplayMemberTemplate. If it is, you override it with the ItemTemplate from Style, but only if that ItemTemplate is not null (if it were null, you wouldn’t see anything).

We have implemented most core controls, including HeaderedContentControl and HeaderedItemsControl, from WPF in Silverlight. This effort was straightforward, and the Silverlight controls work exactly the same as their WPF counterparts. Most of the time (but not always), this means you can port the existing application from WPF to Silverlight without having to change anything.

Another useful class from WPF is the static Mouse class. This class can be used to attach handlers for MouseUp, MouseDown event and MouseWheel events to get the position of the mouse relative to some visual element. We first implemented this class in Silverlight 2 using the HTML Bridge to get the mouse position. (Recall that in Silverlight 2 there was no RoutedEvents class even for the core Mouse events.) We also added a MouseWheel support, again using the HTML Bridge. In Silverlight 2, our mouse wheel implementation worked only in browser. Fortunately, this limitation was removed in Silverlight 3.

Routed commands implement the ICommand interface, so they can be used on every control supporting commands such as RadButton and RadMenuItem. (The prefix Rad in control names is a Telerik convention.) Using routed commands gave us a static set of commands we could attach to multiple controls. For example, the Paste command can be used throughout an application, attached to a RadMenuItem placed in RadMenu. The Paste command can also be set in a RadContextMenu or even in an InputBinding. Then all we need to do is handle the command with a single method using CommandBinding and a single method handler.

InputBindings provides the option to raise a command on an input event (mouse or key event), while CommandBinding associates Command CanExecute/Executed events with event handlers. Routed commands and input bindings depend on routed events. Both are useful when you want to create applications using the Model-View-ViewModel (MVVM) pattern.

Implementing InputBindings was far from straightforward. WPF has one InputManager that orchestrates the input events. InputManager is responsible for managing all input bindings. It receives all input events and raises the corresponding InputBinding. The element then handles the input event. Silverlight requires a different implementation because there is no way to handle all input events before they happen. In Silverlight, when attaching an input binding to a UIElement, we added MouseDown, MouseUp, KeyDown, KeyUp and MouseWheel event handlers to respond to any input events. If you attach MouseBinding with Gesture=”LeftClick” on a control that handles MouseLeftButtonDown event (as you would in WPF), the MouseBinding won’t be executed.

Mouse gestures support MouseAction values such as LeftClick, LeftDoubleClick and so on, which we implemented in Silverlight (except for MiddleClick and MiddleDoubleClick). Until Silverlight 5, there was no ClickCount property in MouseButtonEventArgs. To support double-click on earlier versions, we added our own MouseDown routed event with our implementation of MouseButtonEventArgs. When a UIElement MouseLeftButtonDown event arises, it is captured and raises our MouseDown event with the correct ClickCount.

These classes helped us unify our codebase despite the platform differences inherit in Silverlight and WPF. Some platform differences between Silverlight and WPF couldn’t be fixed using common classes, however. For example, the GotFocus and LostFocus events in Silverlight are asynchronous. In the GotFocus event handler, you have to call FocusManager.GetFocusedElement and compare the result with the sender. If they are equal or the sender is an ancestor of the focused element, the sender is really focused (i.e., holds the keyboard focus). If focus is called on two different controls, both of them will receive the GotFocus event, but only the second one will have focus. If you miss this check, your control could be in the wrong visual state.

Other platform differences in Silverlight are the result of other asynchronous events. As an example, KeyDown can’t be used to open a file dialog. There is no workaround for this issue. The recommended approach is to use the KeyUp event.

It’s good to know these platform limitations and differences. When using our RadControls for Silverlight, we work around or standardize the differences so developers can worry less about platform-specific limitations and concentrate more on their application.

Building Efficient XAML Theming

A major benefit of XAML is that it allows you to change the look of the control. Because our ASP.NET and WinForms controls support skinning, it was mandatory to support themes in Silverlight and WPF in our codebase. We added support for application themes as well as local themes -- though figuring out how to do this required some creative thinking.

Silverlight 2 didn’t have a MergedDictionaries property on the ResourceDictionary class or a ResourceDictionary enumerator. The Style property could be set only once, and there was no implicit Style support (until Silverlight 4). Considering these limitations, how could we support application themes and local themes?

First we created our own MergedDictionaries. We saved our resources in XML files using a custom hash table and set keys on all elements. Then we were able to load them using XmlReader, enumerate them and add them to generic.xaml ResourceDictionary.

If you’re familiar with how control templates are loaded, you probably know that the Silverlight framework loads the generic.xaml file from the themes folder in your control assembly, caches it and then searches for styles within the cached file. To support application themes, we had to plug in to this mechanism by using an attached dependency property at the end of the generic.xaml file. When generic.xaml was parsed, we got OnPropertyChanged for our attached dependency property. We were then able to check whether the application theme was set; if it was, we loaded application resources and merged them in the main resource dictionary (the one defined in the generic.xaml file). The trick to this is that when the framework searches for Style, it gets the last occurrence defined in ResourceDictionary.

The second trick is not to load all the theme XAML files, which would require all our assemblies to be loaded in the application. If you try to load a XAML file that contains controls whose assemblies aren’t loaded, you’ll get an exception. To work around this, we added functionality for both built-in and external themes. Built-in themes rely on a common path. All styles for controls are defined in a XAML file named like the control assembly. For example, RadComboBox is in the Telerik.Windows.Controls.Input assembly, so we loaded this XAML file (Telerik.Windows.Controls.Input.xaml) only from the application theme resources for built-in themes. If the theme was external (not following the same guideline), we loaded the theme generic.xaml file. It’s up to the user to ensure that all needed assemblies are loaded. Local themes use the same trick as application themes (e.g., loading only the needed resources and caching them), applying a Style on the control. The only drawback to this approach is that it requires the application theme to be set before generic.xaml is loaded.

You can change theme colors even without editing control templates. This option lets you change most brushes while preserving the current control templates. It also saves space because you don’t actually add any XAML files. Before Silverlight 5, bindings in Style setters were not allowed. With RadControls for Silverlight 4, we added several new themes, including the Metro theme, allowing changes to brushes without modifying the theme itself.

You might be wondering how we did this. Here’s the tip. Although binding in Style setters was not allowed, Silverlight 4 did allow bindings on dependency objects. (Silverlight 3 allowed bindings only on FrameworkElements.) We created one singleton class named MetroColorPallete to hold all the Metro theme brushes as dependency properties so we could bind to them. Then we created the new class MetroColors, which has an attached Color property. This property is attached to a brush in XAML (e.g., SolidColorBrush), and its value can be set to a brush from the MetroColorPallete, as shown here:

<SolidColorBrush x:Key="AccentBrush" telerik:MetroColors.Color="Accent" />
<SolidColorBrush x:Key="BasicBrush" telerik:MetroColors.Color="Basic" />
<SolidColorBrush x:Key="StrongBrush" telerik:MetroColors.Color="Strong" />
<SolidColorBrush x:Key="MainBrush" telerik:MetroColors.Color="Main" />
<SolidColorBrush x:Key="MarkerBrush" telerik:MetroColors.Color="Marker" />
<SolidColorBrush x:Key="ValidationBrush" telerik:MetroColors.Color="Validation" />

When the attached property is set, we bound the SolidColorBrush.Color property to the AccentColor property on our MetroColorPalette class. We did this by using our singleton instance as the source of the binding, like so:

private static void OnColorPropertyChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs args) 
{ 
         MetroColorType oldValue = (MetroColorType)args.OldValue; 
         MetroColorType newValue = (MetroColorType)args.NewValue; 
         if (oldValue != MetroColorType.NonMetro) 
         { 
                 throw new InvalidOperationException(); 
         } 
         if (color != MetroColorType.BoundColor) 
         { 
                 DependencyProperty dp = GetColorProperty(dependencyObject); 
                  BindingOperations.SetBinding(dependencyObject, 
                  SolidColorBrush.ColorProperty, new Binding(color.ToString() + "Color") 
                  { Source = MetroColors.PaletteInstance }); 
         } 
 }

In this way, we were able to work around not only the Silverlight binding problem but also the WPF limitation that binding to static properties doesn’t update. With our changes, the brush is now bound to a property; and when this property changes, the brush will also update and the colors will change. The production code is slightly different because of several other factors, such as needing to run a new WPF Window from a different thread. Just know that with creative thinking you can develop workarounds for nearly every problem you encounter.

Here’s a demo that shows how colors can be changed at run time:

https://demos.telerik.com/silverlight/salesmanagerdashboard/

To change the colors, just click the top right circle (e.g., the options button) and in the Styles section choose a different color palette or click Customize Dashboard to adjust individual dashboard elements. The application also supports live updates.

Optimizing with UI Virtualization

With UI Virtualization, not all visual elements for your data are displayed on the screen. Instead, only a few visuals are created to fit within the viewable area within your application. From our experience, WPF supports around 10,000 visual elements before it slows down. Imagine a DataGrid with1,000 rows and 10 columns. Without UI Virtualization, you would end up with 10,000 cells. If you chose three visuals to represent a simple cell (Border, Grid and ContentPresenter), you would end up with 30,000 visual elements.

With UI Virtualization, you see an average of 20 to 30 rows and 10 to 20 cells. This results in 600 cells, or a minimum of 1,800 visual elements on the visual tree -- a much better option. Fewer visuals mean faster loading time and smaller memory consumption for your application.

Container recycling is an optimization that works with UI Virtualization. When scrolling, instead of creating new visuals to show more data, container recycling reuses existing visuals that are not in the viewable area. The visuals count stays relatively the same.

Silverlight and WPF provide only one panel that supports both concepts -- the VirtualizingStackPanel -- with one exception. The Silverlight DataGrid supports row and cell virtualization and row recycling.

Supporting horizontal and vertical UI Virtualization and container recycling was a complex endeavor. With complex controls such as RadScheduler, it was nearly impossible to support both concepts with our current architecture. RadScheduler internally uses several ItemsControls to show its data: one for the day, with all the time slots; one for the all-day area; and one that creates several days. Even with a complex virtualizing panel, we would not have been able to virtualize every part of the UI.

At one point, we added custom panels to support virtualization of time slots and appointments for the Timeline and Day views. But supporting virtualization of groups/resources, timeslots and appointments was impossible with the current visual structure of the control.

In addition, RadScheduler was using different control templates to represent different views (e.g., Day, Week, Month and Timeline views). When a view was changed, it was impossible to reuse the visuals from the previous view (because the whole control template changed).

We decided the best approach to improving RadScheduler was to create a new control to support UI virtualization and container recycling from the beginning: RadScheduleView. RadScheduleView is a single template with only a few elements inside and a single virtualizing panel. It allows us to concentrate the virtualization logic in one place and reuse the elements even when they are changing between different views. Our RadScheduleView control also provides many other features, such as multiple grouping, dragging multiple appointments, exact rendering and custom views.

When creating custom controls, you must carefully consider the use cases. If your custom control needs to display a large data set, you should implement UI Virtualization and container recycling for the best performance and smallest memory footprint. Here’s a tip: Consider using one virtualizing panel; synchronizing the horizontal and vertical offset of multiple virtualizing panels is not trivial, and you could end up with a LayoutCycleException, which is notoriously hard to debug.

Conclusion

Now you know some of the ways we overcame the challenges we faced in building a single codebase with Silverlight and WPF. We started by learning the differences between Silverlight and WPF. Then we intelligently worked around any limitations within both platforms. As you saw, we created custom routed events and duplicated WPF input functionality in Silverlight to ensure mirror functionality with both frameworks.

By understanding the theming system as well as how dependency properties work, we were able to overcome theming limitations and allow for runtime customization of themes without having to modify any code in the themes themselves. With UI Virtualization and container recycling, we constructed more robust controls that resulted in more streamlined and efficient applications being delivered to our customers.


Hristo Hristov is a Senior Software Engineer at Telerik. You can reach him at Hristo.Hristov@telerik.com.