In this customer scenario, the customer has a Windows Forms application built on .NET Framework. They are interested in “modernizing” the user interface to support touch and ink in addition to support for high DPI monitors. In addition they are interested in leveraging MSIX for deployment. A few twists: their current implementation dynamically creates and adds controls to their form at runtime. The “definition” of their form is serialized to xml – as their line of business application is very dynamic with many different “forms” generated at runtime. This proof of concept should also do the same. Another complexity – they have not migrated all of their users to Windows 10 yet, many are still on Windows 7. The interim solution should be a stepping stone towards modernization demonstrating a single package that will work on both Windows 7 and Windows 10 and if the modern controls are not available – fall back to the standard .NET Framework implementation.
Let’s break this Proof of concept into 4 parts, as shown in the video:
- Step 1 - Recreate the current Windows Forms application (simplified)
- Step 2 - Migrate it to Core & enable high-DPI.
- Step 3 - Add Xaml Islands & dynamic controls
- Step 4 - Add MSIX packaging (and MSIX Core)
Step 2 - Migrate it to Core & enable high-DPI.
I’m going to skip Step 1 because it is just regular Windows Forms, .NET Framework 4.7.2, standard stuff. Step 2 is where things start to get interesting. In September of 2019 Microsoft released .NET Core support for building Windows desktop applications which includes WPF and Windows Forms. .NET Core is the future of .NET. All the major improvements will be made in .NET Core (not .NET Framework). Migrating to Core brings improved runtime performance, single .exe files, smaller app size, self-contained deployment and side-by-side deployment.
If this had been a more complex project, perhaps with Nuget packages, we would have used the try-convert tool route. Instead, I directly edited my project file by right-clicking the project in Visual Studio 2019 Preview, and selecting "Edit Project File".
.NET Core has a vastly simplified project system and project file.
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Nullable>annotations</Nullable>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Toolkit.Forms.UI.Controls" Version="6.1.0-preview1" />
<PackageReference Include="Microsoft.Toolkit.Forms.UI.Controls.WebView" Version="6.1.0-preview1" />
<PackageReference Include="Microsoft.Toolkit.Forms.UI.XamlHost" Version="6.1.0-preview1" />
</ItemGroup>
<Target Name="CheckIfShouldKillVBCSCompiler" />
</Project>
You might wonder about the target CheckIfShouldKillVBCSCompiler, I ran into a problem compiling with .NET Core compiling with the XamlHost, that target fixed it.
I also added an app.manifest file with DPI settings. The combination of below two tags have the following effect : 1) Per-Monitor for >= Windows 10 Anniversary Update, 2) System < Windows 10 Anniversary Update. This indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher DPIs, thus preventing the rasterization. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need to opt in but Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
In addition, in our Program Main() I added:
Application.SetHighDpiMode(HighDpiMode.SystemAware); //-- setting DPI aware
And finally, our application configuration.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2"/>
</System.Windows.Forms.ApplicationConfigurationSection>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs"
type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, internalKeyToken=31bf3856ad364e35"
warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701"/>
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb"
type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, internalKeyToken=31bf3856ad364e35"
warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+"/>
</compilers>
</system.codedom>
</configuration>
There were a few other minor adjustments shown in the video.
Step 3 - Add Xaml Islands & dynamic controls
For this step we introduce XAML Islands. Most of the cool new touch-enabled features for Windows 10 have been supported for UWP (Universal Windows Platform) apps only. Not anymore. With XAML Islands we can now call UWP UI APIs from the .NET framework.
There are three nuget packages we will leverage that make it even easier to implement XAML Islands in our Windows Forms application.
First is Microsoft.Toolkit.Forms.UI.Controls. This package is just chock full of wonderful awesomesauce. With it we get five pre-wrapped controls - with properties and event handlers expose. We will make use of four out of the five in our demo. It gives us the InkCanvas which receives and displays ink strokes, the InkToolbar which shows a collection of buttons that activate ink-related features. The MapControl which is for rendering digital maps from different providers (like OpenStreetMap and Bing Maps) and various types of map overlays. Also the MediaPlayerElement which embeds a view that streams and renders media content such as video. Finally, although we are not using it in our demo, this library also includes the SwapChainPanel which provides a hosting surface for DirectX swap chains to provide content that can be rendered into a XAML UI - that opens the doors for some super exciting advanced animation capabilities.
We are also leveraging the Microsoft.Toolkit.Forms.UI.Controls.WebView package, which provides a WebView XAML control for for web content that unlike the WebBrowser control in Windows Forms this one renders richly formatted HTML5 content, dynamically generated code, or even content files.
Lastly we leverage the Microsoft.Toolkit.Forms.UI.XamlHost library which provides interop helpers for to easily leverage the built-in UWP controls such as TextBox, ComboBox, Button, Calendar. All of the UWP controls that are touch friendly out-of-the-box.
Then we implemented a simple factory pattern:
ControlFactory factory = (isXaml) ?
(ControlFactory)(new ControlFactoryXaml()) :
(ControlFactory)(new ControlFactoryNet());
Step 4 - Add MSIX packaging (and MSIX Core)
Finally in step four we added MSIX packaging and then MSIX Core support.
First I added what we refer to as a "WAP" (pronounced whap) project, or Windows Application Packaging project. Then set that as your start project, and add an application reference to our Windows Forms project, like this:
And finally, in order to support MSIX Core (so we can install this on Windows 7 SP1), we added that TargetDeviceFamily to our Dependencies node in our Package.appxmanifest like this.
<TargetDeviceFamily Name="MSIXCore.Desktop" MinVersion="6.1.7601.0" MaxVersionTested="10.0.10240.0" />
Find the full sample code here:
http://aka.ms/modernizationstory
Enjoy!