.NETpad 2025: Reset (Premium)

.NETpad 2025: Reset

In August 2004, Microsoft group vice president Jim Allchin finally bowed to the inevitable and hit “reset” on Longhorn, the company’s ambitious would-be follow-up to Windows XP. Today, I find myself in a similar position and for similar reasons. So I am hitting “reset,” this time on the tabs-enabled version of .NETpad, which requires features that are either unavailable or nearly impossible to use in the Windows Presentation Foundation (WPF) framework that I’ve been using. And like Allchin, I will shoulder the blame for this setback: My inexperience as a developer also played a major role.

If you read Ask Paul each week, you may be familiar with how frequently someone will ask me a question and I’ll start my response with a note that I was already partway through writing something on that topic, but it wasn’t clear when I’d complete it, if ever. I’m not sure what other writers are like, but I sometimes do find it odd how I much I write that never sees the light of day. But there is nothing I’ve written more about–and not published–than the work I’ve done on .NETpad. Since the last update, which I posted on April 21, I started and then never completed over a dozen articles. There must be tens of thousands of words in there. All pointless now.

Well, not entirely. Using those partially finished writings as a sort of forensic tool, I can piece together what went wrong where, at least. And I can summarize that in far fewer words than I would have used had I actually published any of them. Let’s see how concise I can make this.

The goal, as you know, was to create a version of .NETpad that, like Notepad, supports multiple documents and tabs. To get there, I cleaned up the codebase repeatedly through refactoring, modularization via multiple new classes, and more side trips down blind alleyways than I wish to count. I got so close, so very close, but the basic issues I kept running into are all tied to document state management: Each time I rewrote the app from scratch to make sure I could replicate the work correctly, I would start with a basic shell, get multiple tabs and documents working correctly, beginning adding back all the features from the original version, and it would start failing. This happened at least four times now.

Trying again to be concise, I tie this problem to WPF, which is so sophisticated and complicated that it must have seemed like technology gleaned from a crashed alien spacecraft when it was first introduced 20 years ago. But WPF also sat untouched for far too long, and the specific pieces I need to make this work–mostly tied to tabs, of course–are among those things that were never updated for the modern app era. In fact, they missed out on several eras of changes.

⁉️ Now what?

I explain all that in more detail below if you’re curious. I’m sure it’s too much detail for most people. But first, let me focus on what I intend to do about it. Because I’m not done with .NETpad. There is still so much I want to do. And I will figure out a version of this app that supports multiple documents and tabs. But that work is being pushed back, or aside, now. So I can get a few smaller wins first. And, I think, some not so small wins too. I’ve been spinning my wheels for too long. I need to make some progress.

A couple of quick points about this app.

Since publishing last year’s version of .NETpad to GitHub, I’ve mostly worked on the 2025 version, the version with tabs. But I have occasionally gone back to the 2024 version to fix bugs or make some small changes. And each time I do that, I am struck by two contrary feelings. One, I very much prefer the old version of the app, without tabs, as it’s cleaner, better looking, and much less complicated. And two, because of all the work I’ve done on the new, tabs-based version of this app, its code base is superior in that it’s better written, more modularized, and more sophisticated. I kept wondering about whether it would make sense to improve the old app. And if so, what that would look like.

As important, Microsoft has updating Notepad, the inspiration for .NETpad, more rapidly than ever in its history. I know some people can’t stand that, but I love the changes its brought to this oldest of Windows apps. More problematically, this means that the functional gap between Notepad and .NETpad has grown, starting with the tabs-based functionality but accelerating anew this past year. There are other Notepad features I’d like to add to .NETpad. But I’ve been stuck, trying and failing to figure out tabs.

? The good news

I’m going to unstick myself.

I’m starting from scratch and rebuilding .NETpad in WPF yet again. This version is a hybrid of sorts of the single-document/no tabs (2024) version of the app and the multiple documents/tabs version I’ve been working on so far this year. This is a single document/no tabs version of the app, but rewritten using what I learned over the past several months with heavy class-based modularization. This may not sound particularly ambitious, but the goal is to provide this app with a better foundation on which I can more easily add new features too.

One of the things I had hoped to do–literally months ago now–was post an unfinished, work-in-progress version of the updated tabs-based version of .NETpad to GitHub so that those who are interested in this topic could download the code for themselves and follow along as I continued updating it. To get it to that point, however, I had wanted to actually get it completely working. And so that never happened.

But here’s the good news. Despite having only spent less than a week pulling this new interim version of .NETpad together, it’s already in good enough shape that I’ve published it on GitHub so that anyone can clone it in Visual Studio and play around with it themselves. So where the previous public version is officially called .NETpad 3.0 for Windows 11, this version is .NETpad 3.2 for Windows 11. And you can get it now on GitHub.

Each time I publish a new article about this app, I will update the GitHub repository. So it will always be up-to-date, and if you jump in at a later date, you won’t have any issues.

? What’s new and what’s next

This version of .NETpad looks a lot like the previous version, but there are numerous changes. Some are visual/surface-level, but most are under the hood. The most important, to me, is that this refactored version of .NETpad has a more modularized code base. So in addition to the classes for the main window and the various custom dialog boxes, it utilizes the following new classes.

  • Document. This is a single document replacement for my DocumentTab class, but it also incorporates two important additional changes: A data binding between the TextHasChanged property and a new UI element that’s not below (data binding being one of the more difficult WPF concepts, at least to me), and new file operation methods that were previously in my FileOperations class.
  • AppSettings. I had at one point gone down the path of making the app settings behaviors part of a class and given up on it. But I went back and did it again, and it seems to work fine.
  • AppState. I had added this class to the tabs-based .NETpad as well, and it works the same here by isolating state-related properties–spell checking, word count, and so on–that are specific to the app, as opposed to being user settings. That said, some of these should be user settings, so they will be moved over soon too.
  • FindReplace. This pane was previously implemented in XAML directly in MainWindow.xaml, which contributed to that file begin quite long and difficult to find things in. While experimenting with other app designs to address the document/tab state-related issues I experienced, I started using a WPF called a UserControl that can arbitrarily be inserted in other XAML layouts, and that led to me reimplementing the Find/Replace as a UserControl. I’m looking at whether it makes sense to do the same with the app settings interface.

There are also several other changes. Like the changes I’m thinking about for the future, some of these are based on updates that Microsoft has made to Notepad. But most represent a split with Notepad, functionality that is perhaps a bit different from what we see in the original.

  • Standard title bar. This may be temporary, but for now, I’ve removed the customized title bar and gone back to the standard title bar you just get with WPF.
  • Document needs to be saved indicator. I really like how I was able to customize the tab headers in the tabs-based version of .NETpad to display a bullet character instead of the standard “x” on the Close button. But this version was using the old-style indicator in the title bar, and I want to do something a little differently here. So for now, I’m using a red dot indicator over by the Settings (gear) icon that appears when the current document has been edited but not saved. When it’s saved or unchanged, the indicator doesn’t appear. The big deal is that this is my first successful implementation of WPF data-binding. And I’m hoping that as I get better with this, I can use it for document/tabs later to make that work easier.
  • Context menu. It always bothered me that the right-click context menu in the text box only had three stock options and that the fonts were all wrong. So I finally fixed that and started building out the menu to include more options, some of which are just placeholders for now. But all the editing options at the top work fine, and the menu looks great now. In fact, I may use this text/icon style more throughout the app.
  • New app icon. .NETpad uses a Thurrott “T” logo like you see on Thurrott.com, but I never really liked using that for the app. So with this version, I’ve been experimenting with different app icon designs. The initial version has a blended rainbow-colored “T” logo based on some work I’ve been doing to update the Thurrott.com logo. But I’m also working on similar designs with a “.n”-type icon. This will change over time.

Looking ahead, I would like to make many improvements to .NETpad, both in the short term and then longer-term. Some that come to mind include:

  • Auto save improvements. Currently, Auto save can be toggled on/off, but it’s hard-coded to auto-save every 30 seconds. I will create an interface so the user can change the auto save interval.
  • Autocorrect. .NETpad supports spellcheck but not autocorrect, mostly because the former is automatically available with the TextBox control and Autocorrect is not. So I will try to figure that out.
  • Recent files. Microsoft recently added a Recent submenu to the File menu in Notepad that lists the ten most recently accessed documents. So I will add this feature to .NETpad too. In fact, I already started that work, so there’s a mostly empty Recent submenu now and the start of a new Recents class to handle the code.
  • AI features. Notepad supports AI editing functionality like Write, Rewrite, and Summarize and though these things are not (and probably never will be) available natively in .NETpad, I suspect I will be able to add them via the Windows App SDK and what’s now called the Windows AI Foundry APIs. You can see some early work along those lines in Hands-On: Coding to the Windows Copilot Runtime (Premium).
  • Markdown “lightweight text formatting” support? Microsoft just started testing this feature in Notepad pre-release builds. But I’ve long thought about building my own Markdown editor, and always figured I could base it on .NETpad. So we’ll see where this goes: The TextBox control doesn’t support Markdown, of course, so I’d have to find an acceptable replacement that does. Ideally from Microsoft, though I won’t hold my breath.

And then there are those things that prevent .NETpad from truly being used as a Notepad replacement. These are technical in nature, and part of the reason I’ve never really thought seriously about putting this app in the Microsoft Store. Things like making the app a Share and Open with target, integrating with Default apps, and so on. Getting all that right will require a lot of work. But I’m thinking about it. And if I can swing it, I would consider putting .NETpad in the Store so that people consider it as a get a cleaner, uncluttered Notepad-type replacement.

I’m sure I’m forgetting something. But there will be more, and soon. In the meantime, those who enjoy watching car crash videos can move on to …

? What went wrong?

If you are curious about the problems I kept running up against, here I will expand on what I wrote above. I’ll try to keep this reasonably short.

? WPF events and event bubbling

In my post In Defense of Notepad (Premium), I wrote about this idea I’d had that RAD environments like Visual Basic (and, today, WPF or the Windows App SDK) made it easy to create UI and that all you had to do as a developer was wire up the events they needed to handle with code. I came to understand why that was overly simplistic as I worked through several versions of .NETpad, but my DocumentTab statement needs in .NETpad betrayed a big problem.

WPF’s event handling is sophisticated and complex. Among other things, it supports routed events, which allows events–like a mouse click, a key press, or whatever–to “bubble up” through the “tree” of controls and other elements that constitute the app’s UI. (This is mostly easily seen in XAML, but you can of course build UI in C# as well.) It also triggers certain events–and thus their event handlers–far more frequently than I’d thought, and I spent–wasted a lot of time–debugging that and trying to figure out why and when this was happening and how I might short-circuit it when needed.

Here’s one example. I need to track the TextHasChanged property associated with each DocumentTab object so that I know what to do if the user closes that tab, saves its, or performs other operations. This should be straightforward, but because I use a single TextBox (instead of having a different TextBox in the content part of each TabItem), I also need to display the right document as the user moves between tabs and ensure that whatever other DocumentTab state is correctly reflected on that change. So if the TextHasChanged property is set to True, that tab should display a “bullet” button in the header instead of the normal “x” button.

This seems simple. The user goes to tab 1 and it changes to display the document in DocumentTab 1. If the text has changed, the tab header button changes to a bullet, if not, it’s an “x.” But it’s not simple. Just switching tabs fires the TextChanged() event handler for the TextBox. And if I don’t handle this right, that means that I could very easily changed the TextHasChanged property of the associated document that’s now being displayed to True even though it has not changed.

That’s only one of the several times something like this happened while I was working on the app. And while I could handle the state management of multiple documents and tabs in a stripped down version of the app, once I started adding back features that had been in there before, various events–including the TextBox’s TextChanged() event–fired even more frequently. It got really weird.

First, I tried to figure out event bubbling. In this experiment, I have a button on top of an (invisible) panel on top of the app window grid. If I double-click outside of the panel, only the grid/app window handles the event. If I double-click inside the panel, the panel and the grid/app window both handle the event, in turn. And if I double-click the button, all three handle the event individually.

(Aggregate image)

There are ways to handle an event in such a way that it won’t bubble to other controls, but I found that to be unreliable. And without going into all the debugging work I did, I will say that … I did a lot of debugging. Both formally in Visual Studio and less formally through my own stupid little UI additions so I could see what was happening in real time. For example, here, I output the name of every event handler method whenever each is accessed. Stupid, but it was helpful.

At one point, I started writing about debugging in Visual Studio and I may update that and publish it later. But for now, suffice to say I spent a lot of time on this process and still came up blank. This made me wonder if there was a better way. A way that would make state management more automatic and less error-prone.

Two ideas came to mind. I could finally figure out WPF data binding. Or I could use the innate document state management capabilities in the TabItem control.

Or maybe I could do both.

? Data binding

Data binding is core to any sophisticated WPF application, but this is a concept I’ve never mastered or even used effectively for that matter. It isn’t necessary in .NETpad 3 (or 3.2, actually) since there’s only a single document. There’s no instance in which some event will occur and change something in any way that screws up the document state. It can’t happen. So life is good.

But once you get into multiple documents, state management is crucial. That’s why I built the DocumentTab class, so I could track and save each document state property as needed and ensure that whenever the user switched tabs, opened a new tab, closed a tab, or whatever else, they would never lose data and the correct things would display and happen on-screen. For the reasons glossed over above, however, this was unreliable. And so I began to realize, and not for the first time, that I would need to figure out data binding.

In .NETpad 3.2, you can see the first basic impact from my work doing so: As noted previously, there is a new “document needs to be saved” indicator (a red dot) that appears next to the Settings (gear) icon in the menu bar. In time, I will try to expand what I did there to bind other state properties to UI as possible, with an eye on expanding that dramatically in a version of the app with multiple tabs. So this is still in the baby steps phase. But it’s also the first time I’ve ever gotten anything like this working, too. A step is a step.

? TabItem and document state management

Among the issues bothering me during all this was the understanding that TabItem solved some of these problems for me automatically. But that assumes that I use the TabItem in ways that I wasn’t, to date.

Stepping back for a moment, WPF implements tabs using two controls. It has a TabControl control that’s a container for one or more TabItem controls, each of which is what a user would consider a “tab.” Less obviously, each TabItem has two parts, a Header object and a Content property. The header is what’s unique: Most controls have content of whatever kind, but only some (called HeaderedContentControl controls) have headers too. In a TabItem, the header is the tab part that you see with the document name and the Close tab button. The content can be pretty much anything, but if you think back to when this control was made, it addressed the needs of the day. Something like this:

In time, tabs became more common for web browsers–and more recently, apps like Notepad–basically apps that can contain multiple documents but only display one at a time. Not too different from that old school dialog, you’re thinking, but they’re quite different. There’s more functionality–like adding and removing tabs, where the old school WPF-type tabs were static, and didn’t typically change, which you can see in the fact that there is no way to “remove” or “delete” a TabItem from a TabControl. These newer apps also have much more UI to accommodate the additional features we use today. There are things WPF doesn’t support natively, like an “Add new tab” button in TabControl, or easily, like a “Close” button in each tab. So I had to work around these and other limitations.

Think about a basic web browser window and how its tabs are–or can be–completely separated from the content you’re viewing. Notepad is the same way: There’s a menu bar between the tabs and the content.

I can’t speak for more modern frameworks, and I have no idea how Microsoft made Edge or Notepad, but with a WPF TabItem, you can’t visually separate its header from its content. They are either physically attached, or one or the other (typically the content) isn’t displayed or used. You can work around that, as it turns out, but only if the layout of the app allows it. You could, for example, implement the menu bar in a Notepad-like app like mine as a UserControl and then just reuse it as part of the content part of every TabItem you display. This is literally why I started experimenting with UserControl.

For example, in this experiment, I created two TabItems. The first uses a grid for its Content that contains two UserControls, for the menu bar and the textbox.

And the second one has just the textbox.

This was an interesting avenue of approach because it allowed me to use the Content part of each TabItem, and that handles some of the document state management automatically (the document it contains, but also the caret index for the text cursor location and whatever text may be selected). That led to more experimentation, in which I replaced each TabItem header and footer with UserControls. And this work was so successful, I started thinking that maybe I had solved a problem.

As I had. But I had also caused a problem that reminded me why I didn’t just take this approach from the beginning. When I put the TabControl with its UserControl-filled TabItems inside the real .NETpad layout, what should have been obvious to me before was finally made obvious: A TabItem’s header and footer are visually aligned vertically, and it’s not possible (or I haven’t figured out how) to span the UserControls in the contents area beyond the width of the header in each tab. For example, here you can see that’s there room to the left of the TabControl for the app icon and then room to the right of its TabItems for an Add new tab button. But these do not line up, and the TabItem can’t span the width of the entire app.

But I was positive that I could make this work.

My experience with WPF and the panel controls it provides for layout is, by definition, lackluster. I pretty much just stick the Grid control, which is all about laying out elements in rows and columns, and it’s not particularly dynamic. But I also needed to do this work in C#. While most UI layout in WPF and more modern Microsoft frameworks is done in XAML, I need for .NETpad to add a new tab dynamically at runtime. And to date, I’ve done that via C#.

But my work with UserControls had me wondering if there was a way to mix the two approaches. Could I create specific elements of this app’s layout–each TabItem header, and then the constituent parts of each TabItem content, which would be a menu bar and a text box–in XAML using individual UserControls and then add each dynamically at runtime in C#?

You bet I could. And while this work is only partway there, you can see the beginnings of this working in this next experiment. Most of this is dynamically created UserControls.

There may be a better way. It’s possible that even trying to do this in WPF is folly, and that a more modern framework–perhaps the Windows App SDK–is the way to go. But I have this nagging feeling in the pit of my brain that I will have the same event handling/state management issues with that. I’ll find out eventually.

Oh, right. There is one more thing.

#️⃣ What happened to .NETpad 3.1?

You may be wondering why I went from .NETpad 3.0 to 3.2. In truth, I didn’t: There’s a 3.1 as well. That version was based in part on me giving up temporarily on the tabs, and I figured that if I was doing that, maybe I could just rethink the UI a little bit, make something more streamlined and minimal. And you know? I may still go in this direction at some point.

This is what it looked like when I finally gave up on it.

I may get there again. And I’m thinking about doing a true full-screen mode-type thing, like a distraction-free writing mode. But we’ll see.

 

More soon.

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