Windows Presentation Foundation or WPF is one of the few things of the Windows Vista era that has survived, or that people actually use and enjoy. It was originally called Avalon and was the first step away from GDI32 WinForms based developing which was becoming more and more difficult to create rich engaging applications.
WPF used a XML based UI markup called XAML, with a C# code behind.
Roll on 2015 and the Microsoft Build Conference, with Windows 10 and Microsoft hopes to evolve WPF. Microsoft’s long term plan is to unify Silverlight, WPF to Big Windows, Windows Phone development and all XAML based development under the banner of Windows Universal Apps (WUA).
This means one app could potentially run on Windows 10 Phone, Windows 10 IoT and full Windows 10. The notes below were taken while I was porting a WPF application running on an Intel Core I7 to a Raspberry Pi 2 running the Windows 10 IoT stack. Yep that’s right, having an app previously needing an i7 running on a £30 Raspberry Pi.
This is all great news, apart from the glaring problem. Not all devices are created equal, different devices have different performance characteristics and different accessories e.g. Your PC may not have a GSM module, and your phone doesn’t have a mouse.
So Microsoft had to comprise and refactor most of the System .NET library and the core Xaml libraries.
This post goes through some of the issues and challenges I have encountered while porting a Windows desktop based WPF application to a Universal App. Not all the changes here are solely related to WUA but in general changes to the Windows Universal libraries.
Reference, Library and Using Changes
The refactoring of the core libraries has pushed most of the XAML and UI libraries to the Windows.UI namespace.
Note: What would have been System.Windows.Forms is now Windows.UI.Xaml.Controls
XAML and Graphics differences
The Visual Class is stored under Windows.UI.Composition
The Visibility Enum Windows.UI.Xaml.Visibility no longer has a Hidden property, replace with Collapsed.
Note: that this removes it from the render tree and so can cause lag when re-rendering.
Can’t set a FrameRate via DesiredFrameRateProperty on a TimeLine.
Note: Manual control of timeline and animation properties may be required, apps may use more CPU as they could run at a default FPS of the platform (usually 60 FPS.)
Pre-cached SolidColorBrushes (Brushes) doesn’t exist for each colour, they have to be created manually through the SolidColorBrush constructor with a Colour.
StreamGeometry is not supported, the closest variant is PathGeometry or just Path. <StreamGeometry>…</StreamGeometry> becomes <PathGeometry Figures=”…” /> Another way is to convert it to a Path object with the figures being the Path.Data. This could be stored as a Static resource and then referenced via a ContentControl binding {Binding Source={StaticResource=…}}
OpacityMask does not exist. Thus semi transparent controls could be achieved via use of transparent imagebrush, layering shapes or background image (PNG) with transparencies
EnableClearType is not supported as an attribute to BitmapCache User Control CacheModes. Text may not appear correctly if cache resolution is too small.
Assets can not be frozen in graphics memory.
Note: This is due to memory constraints on mobile devices. This is now all done dynamically. Developers will need to make sure that assets are still responsive.
VisualBrush is not supported, you can clone an image via ImageBrush however it is undetermined yet whether this can render video.
The Media.Effects namespace has been removed, DropShadows could be achieved via shadow image, interoping to Direct2D or clever use of brushes.
Note: Pixel Shader effects can not be used in their current form unless using DirectX.
Accessing control elements across controls requires x:FieldModifier=”public”.
Note: The compiler generated class for the x:Name is internal now not public
XAML Style TargetTypes do not need x:Type information, they can be done directly. TargetType=”{x:Type Path}” becomes TargetType=”Path”
UIElement.Clip in the Windows Runtime API must be a RectangleGeometry. WPF and Silverlight allowed complex non-rectangular shapes via PathGeometry.
FindResource(…) Resource Dictionary Helper function has been removed, either use This.Resources[…] or App.Current.Resources[…]
Animation Differences
DiscreteBooleanKeyFrame has been removed, it is now replaced with a more generic DiscreteObjectKeyFrame to allow users to set object values at discrete points during an animation.
SetTargetPropertyPath now uses a direct string path for the property rather than a PropertyPath Class
Threading Differences
Windows.Thread does not exist, you have to use the ThreadPool or Tasks. This can cause issues with the Dispatcher or accessing the current thread.
As Threading and as such WaitCallBack is not supported, you can use WorkItemHandler to queue items for invocation.
.Net has shifted over to the async and await concept with a lot of the new refactored libraries taking advantage of this new technique, this can cause issues with old code (e.g. file access). Below is an example of how to (crudely) wrap a dispatcher call into a new task to allow UI updates.
// Old Code window.Dispatcher.BeginInvoke( new PerformSomeUIOperationDelegate(PerformSomeUIOperation), true);
// New Code Task.Run(async () =>; { await window.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>; { PerformSomeUIOperation(true); }); });
Eventing Differences
StoryBoard EventHandlers are slightly different.
Note: Now they normally are EventHandler<object> rather than having an EventArgs
Routed Event Handlers can also now handle already handled events. The default behaviour for porting would be AddHandler(, , false)
Note: Custom Routed Event Handlers aren’t in general supported in Windows.UI.Xaml and should be carefully thought about before implementation
Compilation Issues ( MakePRI warning 0xdef00520 )
During the development of the original application we found that the built in multiple language support didn’t work how we wanted it so we stored resource dictionaries for each supported language with the language prefix on the end. e.g. StringResources.ar-AR.xaml
This causes a compiler warning:
5>MakePRI : warning 0xdef00520: Invalid qualifier: AR-AR
5>MakePRI : warning 0xdef00520: Invalid qualifier: DA-DK
5>MakePRI : warning 0xdef00520: Invalid qualifier: DE-DE
etc…
To fix it you need to rename the files not to include the language code, as the .NET compiler upon seeing the codes expects the app to support it.
General Changes
Instead of DesignerProperties.GetIsInDesignMode(this) there is Windows.ApplicationModel.DesignMode.DesignModeEnabled
UIPropertyMetadata has been renamed to just PropertyMetadata
BinaryFormatter and the [Serializable] attribute are not available, you have to serialize via a contract (DataContractSerializer)
The [Browsable(…)] XML attribute for serialisation is not supported, you can use [EditorBrowsableAttribute(EditorBrowsableState.x)] instead to hide values from the properties window.
Timers are limited (e.g. No System.Timers) thus most have shifted over to DispatcherTimer instead.
Typography is limited, you can’t use things like the TypeFace or FormattedText class. It may be possible to use the RichTextBox class to calculate sizes of text blocks.
Settings Files are not available. Settings can either be done via LocalStorage, or IsolatedStorage. The System.Configuration namespace is not available.
The Invariant Culture has been removed for string comparisons, it may be best to compare via OrdinalIgnoreCase if Culture is not important.
RenderOptions does not exist, therefore you can not adjust cached image quality or scaling mode.
Note: All scaling and image caching will now be done at the default scale, this may therefore increase the overall in-memory size of the app.
Platform Specific APIs
Platform specific functionality is provided via the Windows Universal SDK. Tests have to be performed for Platform Specific functionality as it will crash the app otherwise on different platforms.
e.g.
if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons")) Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;