
With .NET Conf 2024 and the release of .NET 9 behind us, I’ve finally had a chance to watch the relevant session videos from the show. And there is unexpectedly at least one other change to WPF’s support for Windows 11 theming, one that addresses a shortcoming I noticed over the summer.
That’s good, of course. But I wish this information were communicated more clearly, and in a more timely manner. I’ve been waiting for this kind of thing since May, literally, and for any indication at all that Microsoft would improve its dodgy but better nothing support for Windows 11 theming in WPF. And there’s been nothing but radio silence. And now that .NET 9 is available, the information is coming out in little drips, and it’s poorly communicated, and not in the right places. It’s a little disheartening. More than a little.
I don’t want to keep recapping what happened and what I’ve done so far this year to modernize the WPF version of my .NETpad app. So here I will mostly focus on the issues I’ve faced in this effort and then explain where Microsoft has–and has not–addressed them. I’m not a professional developer, and .NETpad is not a complex app, but my experiences are a good indication of what more proficient developers will face with their own modernization efforts, I think. Hopefully, the pain I’ve endured will be beneficial to others.
Simply stated, I made this app. It predates Windows 11 and was built using WPF at a time when it supported what I’ll call a classic Windows user interface. It was updated a few times since its initial release, and it still runs fine, such as it is. But it looks like a legacy app, because it is. And thanks to long-running issues with WPF, a developer framework Microsoft basically ignored for 15 years-ish, there were other limitations and workarounds that I was never happy with. And they are exacerbated in some cases by the UI changes Microsoft introduced in Windows 10 and then Windows 11.
Most obviously, the app didn’t support the new look and feel introduced by each OS version, or features like light and dark mode. Thanks to it being based on WPF, it didn’t even support common controls, and window and dialog types, that Microsoft had introduced in Windows, and in some cases subsequently abandoned, since WPF was a going concern. But developers soldiered on, and continued to use WPF, both for new apps as needed (typically in business, I’d imagine) and by maintaining existing apps. And though Microsoft pushed Metro/WinRT, the Universal Windows Platform (UWP), the Windows App SDK, and MAUI, plus related technologies like React Native, on developers in the intervening years, WPF chugged along, beloved by developers though ignored by Microsoft.
But it’s ignored no more. At Build 2024 this past May, Microsoft announced that WPF was once again a first-class citizen for Windows developers and that it would update this framework to support the native Windows 11 look and feel. And so I decided to update my app. I spent much of the subsequent several months on that, and I wrote many, many articles describing this work. And I ran into all kinds of problems. And all kinds of limitations, places where WPF’s newfound support for Windows 11 theming didn’t just fall short, but didn’t work at all. There were and still are missing controls and features. So many missing features. And through each subsequent pre-release version of .NET 9, which is how this support would be delivered, Microsoft did nothing, and said nothing, about addressing those limitations and problems. It was silent.
And then .NET Conf arrived. Microsoft shipped .NET 9 alongside an updated version of Visual Studio. And there was the smallest of updates to the official documentation that includes a few subtle changes to how one could implement this theming, plus a single new (experimental) feature that addresses one of the many issues I discovered over the summer: There’s now a ThemeMode property for apps and windows that lets WPF developers specify whether to use the system theme or manually switch to light or dark mode. It wasn’t much, but it was something.
It also doesn’t work with .NETpad. For reasons that are still unclear to me, my app crashes hard when I try to manually set the theme to light or dark mode. I have tried this feature with an empty new app project, and it works fine there. But it brings .NETpad down immediately. I have no idea why.
I figured that was surmountable: I am rebuilding .NETpad from scratch yet again so that I can put the cleanest possible version of the app in GitHub. But I wanted to know when and if Microsoft would address the other issues with WPF support for Windows 11 theming that I identified over the summer. And to discover that, I needed to watch the relevant .NET Conf session videos that are all now available on GitHub. I am surprised to report that Microsoft quietly fixed one of those issues. But not surprised to report that it did little else. And that it vaguely intends to fix more issues in .NET 10 throughout 2025 and into that system’s final release in one year.
So here, I am summarizing what’s possible with WPF theming and what isn’t. And providing a status update about which issues are, and are not, fixed in the stable release of .NET 9. And if possible, which may or will be fixed in .NET 10 next year. It’s a mixed bag.
In May, Microsoft released .NET Preview 4 with initial support for Windows 11 theming. This was incomplete, but it never issued a single functional update to this capability during the entire pre-release process, despite there being five more monthly releases. With the release of .NET 9 to stable, however, there are a few improvements and other changes. But they’re not automatic. If you have an existing app, or even if you create a new WPF project, it will use the classic theme, not the modern Windows 11 theme.

There was only a single way to add theming support to your app during the preview: You had to add a reference to what’s now called the Fluent theme resource dictionary into the project’s App.xaml file. In a new app, this file is basically empty, like so.

But the following code adds the necessary reference.

And now, when you run the app, you get basic Windows 11 theme support.

That’s nice. But with the initial stable release of .NET 9, Microsoft has added a second way to get this theming support, by using an experimental new ThemeMode property. This can be used against the Application object, which will impact the entire application, as above. Or, it can be used against individual Window objects, if you for some reason only want to use this style on some windows in an app.
To achieve something identical to the resource dictionary reference above (which will impact the entire app), you could use code like the following, by creating an OnStartup() event handler in App.xaml.cs.

The problem is that Application.ThemeMode (and the related Window.ThemeMode) is experimental, and this property triggers an error, and the app won’t build or run. There are two ways to bypass that error. You can do so inline, like so:

Or, if you will be using the property a lot and find the pragma-based code ugly or distracting, you can add a single line of XML code to what Microsoft calls your “project file.” Which you find by right-clicking the project name in the Solution Explorer pane and choosing “Edit Project File.” By default, this file looks like so in an empty new WPF app:

And here’s what it looks like with that new line of XML code.

Either way, the app runs now and theming works. But I like this method better as the #pragma stuff is ugly and I’d need to add it everywhere I used ThemeMode.
ThemeMode is more useful than the resource dictionary reference because it can be used inline to change the theme at any time. This is a key feature of modern Windows 11 apps: They will use the system theme by default, of course, but they will also offer a UI by which the user can manually change the theme of just that app. For example, you might have the OS set to Dark mode but want to use one app in Light mode.
When I updated .NETpad to match the user interface of the modern Notepad as much as possible, I built the settings UI for this feature, but I left it disabled because WPF only supported following the system theme. Now, it supports manually changing the theme too, as I previously wrote in Modernizing .NETpad: .NET 9 Arrives with a Few (More) Small Improvements for WPF (Premium). This is nice, theoretically, but I can’t get it to work in .NETpad for some reason.
I’ll figure that out in time. But it works fine in new apps. For example, here’s the code to toggle between Light and Dark mode when a button is pressed.

And sure enough, it works fine. When you click the button, it moves the app between Light and Dark modes.

There were many challenges in updating .NETpad for Windows 11. One of them, which I hashed out over a few months, was figuring out an elegant way to recreate the native Content dialog that’s supported by the Windows App SDK but not the WPF. I went in a few different directions before I finally decided to create my own custom dialogs for sub-windows like Save confirmation, Go to line, and Auto save (on/off). And I was (and am) really happy with the results, but for one limitation I ran into. If you look at this type of dialog in an app like Notepad, you can see that it uses the system accent color (purple in this case) to highlight the default button (“Save,” in this case).

And while it’s subtle, the color of the button softens a bit when you mouse over it.

Making the default button match the accent color was easy enough. But I could not figure out how to handle the mouse-over color change at all. I struggled with this for months and I finally asked Rafael. But in researching this, he concluded that my frustration was deserved: This feature was not supported in WPF and no amount of custom code would ever make it work. When you moused-over the button, it used an non-themed light blue color instead.
As I wrote at the time, “the first button is a bit special. This is the default button, meaning that its code behind is what runs when the user just taps the Enter key (in addition to an explicit click). As with the similar button in Notepad, it’s also colored with the user’s system accent color (though the mouse-over color change isn’t correct because WPF doesn’t correctly support this yet).”
Well, it does support this feature now. As I discovered in the video replay of the .NET Conf session Empowering WPF Developers – WPF in .NET 9, Microsoft quietly added the AccentButtonStyle style to the WPF Button control. (You can see this by jumping ahead to 13:41 in the video.) So instead of styling the button manually, as I had done in an attempt to mimic this behavior, you can now style the default button in a set of buttons explicitly, and it will use the correct accent color and mouse-over color, like God intended.
To get this effect, we just need to add the following code to the default button in whatever set of buttons.

Then, when we run the app, the button is style correctly.

And it’s style correctly when we mouse over it, too. Nice.

And this one does work in .NETpad, so I made the change in each of the custom dialogs in the app.

I sometimes forget about all the one-way dead-end streets I’ve gone down during this project. But if you look at Notepad (the modern version in Windows 11) and compare it to what I’ve done so far with .NETpad, you can see what’s missing pretty clearly.
The biggest issues, in order, are:
Notepad adopted a tabbed user interface, which is obvious enough. But less obvious, it also uses a customized title bar area that’s possible (but difficult) to implement with the Windows App SDK and impossible to implement with WPF. Well, not impossible. Just impossible to do correctly. Among the issues, the TabControl in WPF can’t be styled correctly in a manner similar to the issue described above with the default button. But where Microsoft fixed the default button style, it has not fixed TabControl so that it looks and works correctly in Windows 11. So this functionality is impossible to implement correctly in WPF.
I will keep working it, of course. This is kind of a big deal. But the initial modernization release will not include this key functionality.
Notepad (like the Settings app) in Windows 11 makes extensive use of the WinUI ToggleSwitch control. But this control is not available in WPF. So I used the older but similar WPF ToggleButton control. It doesn’t look the same, but it gets the job done.
Menu items in Windows have supported a Checked property for a long time. This acts like a toggle for commands for that can on or off. You can see this with the “Status bar” and “Word wrap” items in the View menu in Notepad, for example.

The WPF MenuItem control supports a Checked property, as expected. But it’s not styled to match Windows 11 and the checkbox visually offsets that menu item from others, even when the item isn’t checked. Yuck.

I was able to work around this in an acceptable way with my custom dialogs, but WPF should just support the native ContentDialog like the Windows App SDK does. It should also support the modern Message box, which I likewise implemented using a custom dialog.
I think that’s most of it. I’m mostly OK with where .NETpad is now, but I’d obviously love to get that tabbed UI going, and I will keep trying. In the meantime, I’ll rebuild the app from scratch to see whether I can figure out why ThemeMode isn’t working, and so I can post the cleanest possible version of the app to GitHub.
More soon.
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.