My work modernizing the WPF version of .NETpad this year has taken me down multiple paths, often simultaneously. There’s the straight-up updating that I’ve documented so far. And then some work I’ve mentioned in passing or not at all, much of it related to the quality of the code.
For example, I’ve restarted my work several times using the now-dated GitHub-based version of the app, sometimes just to make sure I can duplicate the improvements, and sometimes to experiment with different, perhaps more efficient ways to achieve the same end. Recently, I literally started over from scratch with a blank WPF project in Visual Studio to see what it would look like to build the current app in a cleaner, more logical way. That effort is particularly daunting because I really want to get it right, and I’ve had to revisit some app functionality I stopped thinking about a long time ago. It’s like riding a bike, I guess.
This process is called refactoring: The goal is to improve the code while not changing its runtime behavior. “Improving” code can mean all kinds of things, and I went into this process with only a vague idea of what it would mean for .NETpad specifically. Perhaps not surprisingly, this job grew bigger and became more complex as I went.
The basic app structure is set in stone. There’s a XAML file (MainWindow.xaml) representing the structure of the main app window, which is now the only app window, though I still use old-school Message Boxes and Input Boxes for various pieces. And then there is an associated C# file (MainWindow.xaml.cs) containing all the event handlers and other code that constitutes the app. Beyond that, there are also secondary files like a resource dictionary (for custom control themes), a per-user app settings file, and various other files associated with the project.
To the user, .NETpad is a simple app with a title bar, a menu bar, a text box, and a status bar. For this new, more modern version of the app, there’s also a Settings page that’s implemented in the main app window’s XAML file and is shown and hidden to the user as needed. And so all the code associated with that Settings page is likewise found in the main app window’s C# file. There’s a lot of code in both those (XAML and C#) files, and adding the Settings interface to the main window dramatically expanded the lines of code in each. For example, the XAML for the main app window was under 150 lines of code before this modernization effort, but now it’s over 400.

There’s not much I can do about that: XAML is verbose. But the C# file contained event handlers and other code, and I was curious if it made sense to perhaps separate those into separate files. That is, keep the event handlers in MainWindow.xaml.cs but move all the other methods and code into a new C# file. This isn’t difficult to do in C#, generally, or in WPF specifically: Because MainWindow.xaml and MainWindow.xaml.cs are both partial classes representing the same code structure, you can create any number of other C# files that are also in the same namespace because they, too, can be partial classes. To the programmer, these things are separate, but to Visual Studio and the compiler, they’re all part of one big happy family.
I called this third file, the third partial class, Backend.cs. It has the same basic structure as MainWindow.xaml.cs:
namespace NotepadWPF
{
public partial class MainWindow : Window
{
}
}
Then, as I worked my way through each part of the app code, I moved non-event handler methods into that file. For example, I have methods called AutoSaveEnable() and AutoSaveDisable() that aren’t event handlers, so they went into Backend.cs.

I also organized each C# file so that the order of the methods in each is logical. In MainWindow.cs, for example, I have all of the app window event handlers (Window_Initialized(), Window_Closing()) at the top in its own section, then separate sections for the main user interface event handlers and then the Settings page event handlers. Each is ordered by how they appear in the user interface as well, so it’s easier to find specific methods. Backend.cs is similarly organized, this time by function (Auto save, Scaling, Text, and so on).
After framing out the basic UI in XAML, I started adding event handlers and other methods to the code in what I thought would be a reasonable order, looking each time for ways to clean up the code and comments where needed. I also allowed Visual Studio to optimize my code (which it did via suggestions), something I had resisted in the name of readability when I first created this and the other versions of .NETpad. As I went, I realized that I was almost gamifying this process, as my goal was to eliminate issues identified by Visual Studio entirely. So I stopped ignoring warnings and notes, and addressed each issue in turn.

This was mostly straightforward. But then I ran into the inevitable wall. As I started getting into the file operations bits—Save, Save as, Open, and so on—I had a vague memory of that being messy and I resolved that I would get it right this time. And then as I worked my way through this part of the program, I was reminded of the issues I had faced originally. And of how difficult it would be to clean this up.
Here’s a simple example to help explain the problem.
There’s a “New” item in the “File” menu. It has a Click() event handler, of course, that fires each time someone selects it. (Actually, it has a command binding that fires a NewCommand_Executed() event handler, but let’s not get bogged down in the peripheral details here.) This may seem simple if you don’t think it through: All you gotta do is remove whatever is in the text box and start over from scratch.
It’s not that simple, of course.
Leaving aside the possibility that the user has text that might need to be saved first, you also have to reset three variables associated with document state (TextHasChanged, DocumentIsSaved, and DocumentName). And you have to change the app name that appears in the title bar, as it includes the document name if present, or some version of “Untitled – ” plus the app name, depending on whether there are any pending changes.
That’s the simplest scenario. But what if there’s unsaved text in the text box? In that case, you have to prompt the user to see whether they want to save it first before continuing. And what you do from there will vary depending on whether the unsaved text is an existing document or not. If it is, you can just save it. If it isn’t, you can display a Save as dialog.
But it’s not that simple, either. At any point in this process, the user could make choices that need to be handled. And those choices include such things as agreeing that they’d like to save the new document but then backing out by clicking “Cancel” in the Save as dialog box. So you have to fail back to the current app state when required. There’s a lot to consider.
I worked on this one for quite a while. Basically, by flowcharting it.
The initial test is easy enough: Does anything need to be saved? (That is, is there any changed text in the text box?) If the answer is no, you’re all set: Just call the new NewDocument() method (which is in Backend.cs because it’s not an event handler). If the answer is yes, I call DisplaySavePrompt() (also in Backend.cs), which displays a Message Box about saving the changes. There are three choices here: Yes, No, and Cancel. And DisplaySavePrompt() returns the choice they made to the calling method (that New event event handler). If the user choose Yes, I call SaveOrSaveAs(), which consolidates previously separate methods into a single decision tree for saving and then, if required, NewDocument(). If they choose No, I just call NewDocument(). And if they choose Cancel, I back out. The user is returned to the app, as-is.

That’s just the top level of this flowchart, but you get the idea. A lot of work. And then a lot of testing to make sure it always does the right thing in use. I think it came out pretty nicely, but this part of the app update took days to complete. As I write this, the Settings page functionality has all been updated (I only recently wrote this, so that was minimal), as is most of the code for the items in the File menu (minus Print) and everything under View. (As part of this update, I now save the Zoom and Scaling settings that the user chooses and then restore them when the app runs again, which is a nice addition.) Most of the items under Edit are still to come. But it’s moving along.

I also want to update the app icon for this version of .NETpad, which I guess will be version 3.0. (I had different icons for .NETpad 1.x and 2.x.) For now, I’m experimenting with a gray-green icon I hand-made in Affinity Photo that’s obviously (maybe too obviously) based on the Microsoft 365 desktop app icons—it’s like the Word icon but in a different color and with a “.N” notation instead of “W”—but this will almost certainly change. It’s a little too derivative, but it also feels a bit “heavy” to me.
![]()
Parallel to this, I’ve also experimented with other ways to modernize .NETpad. I’ve looked at third-party enhancements to, or replacements for, WPF to see whether they might help me go further and faster.
For example, there’s a community project called WPF UI that implements modern controls and base design elements, and it offers some advances over what Microsoft is doing now with .NET 9 and its Windows 11 theming support. (Microsoft is using WPF UI as the basis for its own work, so it’s possible/likely that some of the missing pieces, like the ToggleSwitch control now missing from WPF, will appear by the public release of .NET 9 or at some other point.)
I’ve also spent some time with Uno Platform, an open source, .NET-based app framework that’s very similar to (perhaps is literally based on) WPF but offers a much wider range of support for modern app needs, including all the WinUI 3 controls, but also things like Content Dialogs that would help put the app over the top. Uno is fundamentally about cross-platform apps, so it’s basically a more sophisticated take on what Microsoft is doing with MAUI. But it would also require me to start over with a new app and a new type of app.
On that note, I have of course looked at the Windows App SDK too, and not for the first time. This is the new generation version of what used to be called UWP, and it has its challenges. But if I ever do want to create a new, modern, and native version of .NETpad, this is the way.
There’s also the uncharted waters of perhaps combining some of these things. For example, the current version of .NETpad references Windows Forms, an even older framework, for a handful of things. And it is possible to use the Windows App SDK in a WPF app. Perhaps I could simply keep the core app as-is, but reference the Windows App SDK for a handful of modern features that aren’t available in WPF. I’m still trying to figure out what that even means, honestly. Could I just display and interact with Content Dialogs this way?
As I’ve noted a few times, I’ve also spent more time than I care to admit trying to implement a tabs-based version of the app that would more fully emulate the modern Notepad in Windows 11. This work has been difficult for a variety of reasons, but my assumption that the Windows App SDK would make this easier is incorrect: It’s just as difficult as doing so in WPF. But I will continue this work, which involves a few challenges:
These three issues all sort of follow each other, though not getting the look right is in some ways the least important: I need to figure out the structure before I can proceed. That said, that bit is so complex (to me), that I’ve been more focused on just making it look right (in WPF and the Windows App SDK, mostly). To do that work, I’ve started multiple new app projects, so I can just focus on the tabs (and some basic UI).
Put simply, I’m all over the place right now. Which is OK, maybe even normal. But at some point, I’m looking for an ah-ha moment, so I can decide on a single path forward. To date, this has eluded me.
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.