Modernizing .NETpad: .NET 9, Arm64, and More (Premium)

Modernizing .NETpad: .NET 9, Arm64, and More

As part of my .NETpad modernization work this summer, I’m investigating various ways one might update an existing app for Windows 11. There are many paths forward, each with some benefits and some challenges.

The app I’m updating is based on the Windows Presentation Foundation (WPF), a .NET-based app framework that first arrived in 2005 with Windows Vista. Microsoft tried to replace WPF, in turn, with the Windows Runtime (WinRT), the Universal Windows Platform (UWP), and then the Windows App SDK/WinUI 3. But WPF has remained too popular with the Windows customer base, especially in the enterprise. And so at Build 2024 this past May, Microsoft announced that is once again a first-class choice for developers looking to build modern Windows apps. It updated the Windows App Development section of its Windows Dev Center website to address this change, putting WPF on equal footing with WinUI 3 (Windows App SDK). But more detailed information is sparse: The WPF subsection of the Windows Dev Center is full of years-old content that, despite the dates, hasn’t been updated in a long time.

Here’s what I know. WPF’s elevation is tied to .NET 9, which is now in beta and will be released publicly in November 2024. At the time of this writing, the current release of .NET 9 is preview 5, from June 11, but there is almost no documentation that addresses exactly how WPF will be updated to support modern Windows 11-style apps and their unique controls and capabilities. The .NET 9 Preview 4 release notes, from late May, mention only that you can add Windows 11 theming support to existing apps, plus the release of a new WPF Gallery app in preview that shows off how this framework’s controls and styles will look in Windows 11. (There’s no WPF information in the .NET 9 Preview 5 release notes at all.)

I am vaguely proud that I figured out to add Windows 11 theming support to .NETpad on my own, as I discussed previously in Modernizing .NETpad: First Steps (Premium). But there is more work to do—other parts of the app need to be updated once this change is made—and I’m hoping that there will be more/better documentation as well. In the meantime, I’m experimenting. Not just with WPF and Windows 11 theming, but with other aspects of app modernization. The good news is, I have time: With a November release date for .NET 9, I still have about 5 months to research, test, and consider my options.

Before I first published the WPF version of .NETpad to GitHub, I updated the app in various ways, including changing the target .NET version to .NET 6, which was current at the time.  Today, .NET 8 is current, of course, and .NET 9 is coming in November. But the first versions of .NETpad WPF date back to early 2020 and .NET Core 3.1. (At that time, Microsoft was transitioning .NET from the old proprietary Windows-only version to the cross-platform version we have today. So .NET Core 3.1 was followed by .NET 5, .NET 6., .NET 7, and then .NET 8.) And one of the single best things you can do as a developer of apps based on .NET, WPF or otherwise, is staying up to date with new .NET versions: Each version, to date, has come with impressive performance improvements, and unless you experience a compatibility issue, which is increasingly rare, this is no/low-effort upgrade with no downsides.

.NETpad is a simple app, of course, and so the performance upsides are likely small. But so, too, is the risk: Aside from calling a handful of Windows Forms routines, it’s a straightforward, uncomplicated app, and so upgrading it to new .NET versions as they release is a no-brainer.

It’s also very simple with Visual Studio: Just display the properties for the app project and change the “Target framework” to the latest version and rebuild it. I’m currently working with version 9.0, of course, with my eye on November. And so far, each of my .NET version upgrades, including the recent change to .NET 9.0, has been uneventful. But your mileage will vary depending on the app.

The performance improvements are probably nice for most. But changing the .NET version doesn’t change the look and feel of the app, of course. Windows 11 applies rounded window corners, but all the controls in the app—like its traditional menu bar—are still the old-school style from yesteryear.

This is what the Windows 11 theming support adds (or, will add, when .NET 9.0 is generally available). As I wrote earlier, once you target .NET 9, you can add some new code to App.xaml to enable that theming support. This is a big deal: You get automatically native support for Windows 11’s light and dark themes so the app matches the system look and feel. And you get Windows 11-style control upgrades, so the app looks modern and native in Windows 11. Here, for example, you can see that .NETpad has adopted the (dark) system theme and that the old-school menu looks modern, with curved corners.

Granted, you’ll have some other work to do, too: Because Windows 11 controls are bigger and/or have more spacing than the old controls, there will be some busy work that, again, will vary by app. In my case, I had to fix the status bar and text box so that the app looked right.

And that’s where I left things a month ago. There’s so much more to do, including fixing the themes support I built into the app back when there was no way to do so natively, updating the custom dialogs I created, and a lot more: In the years since I first created .NETpad, Microsoft unexpectedly started modernizing Notepad, my app’s inspiration, and the version in Windows 11 today supports tabs, a more modern app design paradigm without dialogs or secondary windows or any kind, and no classic title bar. So part of my experimenting is to figure out what it would take to modernize .NETpad similarly.

It may require something more complicated than modernization, frankly. It may require me to start over from scratch, using the existing codebase as a guide, of course, but with a more modern base app shell. And that work might require me to create a new Windows App SDK (WinUI 3) version of the app. It’s something I’ve spent time on, not just over the past month or so, but in the past, too, from before WPF made its comeback.

If I do go down that route—and I’m not sure I want to—I would still modernize the WPF version of .NETpad and adapt it as much as possible to be like the Windows 11 version of Notepad. But some key design changes, like that title bar-less top area and tabs, in particular, might require a rewrite. I don’t currently see a way to do this in WPF. I do see how to do it with the Windows App SDK/WinUI 3.

And there are other forms of modernization, of course.

With the release of the first Snapdragon X-based Copilot+ PCs last month, there’s been a flurry of activity around Windows 11 on Arm, which has suddenly elevated out of also-ran status to become a true contender. In my experience, many apps that I use regularly are now native Arm64 apps, which is terrific. And of course the Prism emulator in Windows 11 version 24H2 offers new levels of performance for x64 and x86 apps, albeit with some battery life penalty.

What does it take to create a native Arm64 version of .NETpad?

For a simple WPF like mine, almost nothing: Visual Studio is configured to build apps to “Any CPU” during development, and this setting will build the optimal version based on your PC. So with a modern x64-based PC, it will create a (64-bit) x64 app. With a Snapdragon X-based Copilot+ PC, it creates an Arm64 app. Which you can see for yourself in Task Manager.

But when an app is “done” and it’s time to distribute it publicly, you need to create versions for each supported platform, which could include x64 (64-bit Intel-style), x86 (32-bit Intel style), and Arm64. And so you have to use the Visual Studio Configuration Manager to specify which app types you want. I’ve created Arm64, x64, and x86 versions of .NETpad this way (on Windows 11 on Arm) and have run all three, ensuring that they are each written to the correct architecture. Here’s the x64 version, for example.

So that’s straightforward … for me. (Though each version of the app required me to install the related .NET 9 runtime, as it’s a different version for each architecture.) For more complex apps, including those with dependencies that don’t work on Arm, architecture-specific code, or whatever, there will be more work to do. Maybe a lot more, for lower-level code like drivers and the like. But we’ll keep it light here: I’m not a professional, after all.

That said, I could see adding some code that detected whether the app was being emulated and, if so, it could alert the user that a native version was available. This code would be triggered if you ran the x86 or x64 version of the app on Arm, but also if you ran the x86 version on x64.

In most cases, an app has no idea what architecture it’s running on: That’s sort of the point. But in those cases where you must know, Microsoft offers various APIs that let you find out. In the past, these were all low-level Win32 APIs, which require some complex interoperability gyrations. But now we have .NET APIs that do the work for us. And they’re easy to use.

Everything we need is in the RuntimeInformation class. It only has five properties, of which two are of interest to us: OSArchitecture, for discovering the OS architecture, and ProcessArchitecture, for discovering the architecture of the app itself.

Since it’s not yet clear how or whether I might want to display a warning to the user, my first step was to just make sure I could detect the OS and app architectures properly. And that’s easy enough: I added the System.Runtime.InteropServices namespace to the using section at the top of the app’s main window code (MainWindow.xaml.cs). And then I added the following code to the bottom of the AppWindow_Initialized() function, which is what runs when the app window first appears.

// Test code for architecture tests
String archOS = RuntimeInformation.OSArchitecture.ToString();
String archApp = RuntimeInformation.ProcessArchitecture.ToString();
TextBox1.Text = "This is an " + archApp + " app running on an " + archOS + " PC.";

To test it, I then built and ran three different versions of the app on Windows 11 on Arm in turn: The native Arm64 version, the x64 version, and the x86 version. Each reported its versions correctly.

Fun. I’m not sure if it’s useful per se. But fun.

I will keep experimenting. I assume we’ll see a preview 6 version of .NET 9 soon, and perhaps more/better modern WPF documentation. Either way, I’ll figure out a way forward eventually.

Gain unlimited access to Premium articles.

With technology shaping our everyday lives, how could we not dig deeper?

Thurrott Premium delivers an honest and thorough perspective about the technologies we use and rely on everyday. Discover deeper content as a Premium member.

Tagged with

Share post

Thurrott