Modernization Story: Windows Forms to .NET Core, XAML Islands and MSIX Core
Published Jun 09 2020 02:12 PM 6,692 Views
Microsoft

 

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".

 

editp.gif 

 

.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=\&quot;Web\&quot; /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:

WAP.gif

 

 

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!

 

 

5 Comments
Copper Contributor

A very nice post! and very timely. We started porting our current .NET Framework application to .NET Core. It will take quite a long time as we used remoting, App Domains, dynamic compilation and many more things.

 

One of our biggest problems at the moment is that we used ClickOnce to deploy our client software. We generated the manifest files for the deployments dynamically so that we could add configuration files that administrators can modify centrally at the server (for example with the url to the server itself ). Could this be done with MSIX? As I understand it, the MSIX packages are signed and cannot be modified later (post deployment at our customer's locations).

 

Or is there a way of using a MSIX package with the software itself and define a "satellite package" with files generated dynamically with configuration information (or other things).

Microsoft

Hello,

 

I'm glad to hear you have begun your own modernization journey! 

 

In Windows 10, version 1809, we introduced a new type of MSIX package called a modification package. Modification packages are MSIX packages that store customizations - such as registry changes or configurations. That is probably the way to go for what you are describing. You can create a modification package by using the latest version of the MSIX packaging tool (for Windows 10 version 1809 or later). However, those those are distributed - not dynamically generated as you are describing also.

 

With MSIX there is no restriction preventing you from separately dynamically generating files - so long as you are not saving them to your install directory nor a system protected folder. So if you want to download and/or dynamically generate some configuration file on first run that is an option also, if you wanted to perform that operation outside of your application you might want to check out PSF with the new scripting capabilities - https://docs.microsoft.com/en-us/windows/msix/psf/run-scripts-with-package-support-framework

Modification packages : https://docs.microsoft.com/en-us/windows/msix/modification-packages

 

 

Copper Contributor

Thank you very much @Robert_Evans 

I will take a look at the modification packages!

Copper Contributor

Raul,

I understand ReportViewer won’t be ported to core. You seem to have a pretty complex app. Are you not using ReportViewer?

ReportViewer is a show stopper to us. Do you guys know of a replacement for ReportViewer? PowerBI doesn’t seem to fit our needs of showing typical documents, sales orders, etc.

Copper Contributor

Hello Sterenas,

we don't use the ReportViewer. Our software is built on DevExpress components and we use their reports. They already migrated all the components to .NET Core. 

Version history
Last update:
‎Jun 09 2020 02:12 PM
Updated by: