Modernizing .NETpad: The Trouble with Tabs (Premium)

When I started this year’s .NETpad modernization project, it wasn’t entirely clear where the new version of the app would end up. But thanks to the coming November release of .NET 9, which this requires, I at least had a rough schedule to stick to. It seemed like plenty of time. I’d figure it out.

I think I’ve figured it out.

That said, there’s so much more to do. I’m still working on–and struggling a bit, it’s the process–with the Find/Replace functionality. I haven’t thought all that much about Go to line, though that feature seems comparatively simple. And then there are two remaining tasks, both major. The tabbed interface, most obviously, as it’s the most important functional difference between .NETpad and Notepad. And then a less obvious but equally important need to customize the title bar, a modern app design pattern that is difficult but not impossible to do with WPF.

I’ve known about all these tasks from the beginning. What I wasn’t sure about was whether I would be able to complete them all successfully. To find out, I’ve experimented. A lot. This involves standalone Visual Studio projects, each aimed at solving a particular problem. When this work is successful–my implementation of the app’s new Notepad-like settings page is a great example–I’ve rolled it into the main app. When it’s not, I keep trying. Two months into this project, and dozens of mini side projects later, I am semi-confident of two things. There’s still so much more work to do. And I think I can get it all working. The tabs. And the title bar customization.

That said, November suddenly looms over me like a weight. It doesn’t seem like plenty of time anymore.

I will discuss title bar customization in a future post. Here, I’m focusing instead on the tabs. The white whale. The most important modernization task of them all, really. And to do that, I have to step back a bit and discuss what this means conceptually. Getting this to work was–still is–a hurdle. A problem that needed to be worked. And I think I figured it out.

You may recall that Microsoft rearchitected its Office desktop applications at one point to implement what’s called a Multi-Document Interface (MDI), as opposed to the previous interface, which was called Single Document Interface (SDI). Without debating the relative merits of these interface types, MDI and SDI are quite different, and the adoption of MDI was tied to Microsoft’s object-oriented programming (OOP) push in the 1990s. (Which I documented in the Programming Windows series that became Windows Everywhere.)

Without going too far into the weed, an app with a SDI interface contains a single document. If you open another document, it replaces the document you were previously using. Otherwise, you can open a second instance of the app, and have two copies of it running side-by-side, each with its own document.

An app with an MDI interface can contain multiple documents, each in its own child window (or sub-window) contained within–visually and structurally–the main app window. We didn’t really have tabs in the 1990s, at least not in Office, which was Microsoft’s most famous MDI implementation. Instead, the document child windows could float within the main app window. Or they could each display “full-screen” (maximized) within the main app window, and the user would switch between them using a drop-down menu. (They could even be minimized within the main app window.)

MDI example. Source: Microsoft
MDI example. Source: Microsoft

For third-party developers creating their own Windows apps in the 1990s, Microsoft created the Microsoft Foundation Classes (MFC), an object-oriented C++ class library that abstracted the old-school C-based application programming interfaces (APIs) in the Win32 SDK. MFC was delivered with Visual C++, and it was infamously bloated in many ways. And it came and went as Microsoft’s OOP obsession crested and then crashed. But among its strengths, and there were a few, it provided the scaffolding for full-featured MDI-based apps.

Word, Excel, and the other major Office apps pushed forward with MDI for some number of versions, but Microsoft transitioned away from OOP–it became obsessed with componentization next, which led to .NET–so, Office did as well, eventually, switching back to the cleaner, simpler SDI interface. The Office apps we use today are SDI, just as they were originally.

When I discuss the modernization of .NETpad, I often use the term modern design pattern. This is purposeful. A design pattern is not an interface, an implementation of an interface, or an architecture. It’s not that sophisticated. It’s just a look and feel, a way something should look that doesn’t in any way describe how one might implement that look. MDI, in particular, was a sophisticated app architecture. Implementing it required significant effort, a lot of new code, a complete rethinking of how apps were built. But that’s what Microsoft did back in those days.

It’s not those days anymore.

In Windows 11, Notepad, like File Explorer, is not an MDI app. Neither are web browsers, though they may seem like MDI apps in some ways. Each provides a tabbed-based interface, of course. And each can contain multiple documents (which I’ll use as an umbrella term for simplicity’s sake) and provide what we now accept as common ways to navigate between them. But they are not structurally built like MDI apps. You can’t decide to display each document as a floating sub-window within the main app frame. Instead, you can only view one document at a time. (Some browsers, like Edge, confuse matters by offering a “split screen” view, but let’s stay focused here.)

I don’t know how Microsoft implements tabs in Notepad. What I do know is that it doesn’t matter: Implementation details are unrelated to modern design patterns in today’s apps. As a developer, you have a choice of tools that include programming environments (like Visual Studio), user interface frameworks (like WPF), languages (C#), and more. And if these tools are modern (or modernized, if they’ve been around a while), they will provide a way to achieve at least some modern design patterns. And you can use them or not.

WPF has offered support for tabs for decades, since the beginning of the framework, most likely. Metro, the Universal Windows Platform (UWP), and now the Windows App SDK have all offered tabs in one way or another. You can implement tabs using Microsoft’s .NET-based and cross-platform framework, MAUI, and the resulting apps will run across Windows, Mac/iPad, iPhone, and Android. Under the covers, each is implemented differently. But looking just at Windows 11, it’s probably possible to create different tabbed-based apps in each environment that look reasonably similar. You can utilize that modern design pattern in each to some degree.

I was stuck on this for a long time. That is, I looked at Notepad and I assumed that the tab control it uses must be a container of some kind, something conceptually like MDI, even if there were no sub-windowing capabilities. I figured that each tab contained some combination of controls–a text box, at least, but maybe also individual menus and even status bars–and that each would thus need to be implemented and managed dynamically in some way.

Implementing that interface, a sort of roll your own modern MDI architecture, in .NETpad wouldn’t be impossible. But it would be insane, would require me to completely rewrite the app, and the resulting app would be very complex.

.NETpad is not complex. Today, this app has just three top-level UIs: A menu bar, a text box, and a status bar. You open, save, and work in a single document. There is an app state–really, an app window state–tied, in part to whatever document in contains. And if you want two or more documents, you open a second instance of the app. This other instance is unrelated and disconnected from the first. This is easy for developers to implement in any environment. But if you look at the source code for .NETpad specifically, you’ll see that the New Windows event handler contains just a single line of code. That’s how easy it is.

Put simply, .NETpad is an SDI app. One app window, one document.

And so is Notepad, architecturally. Microsoft didn’t rewrite Notepad from scratch when it added tabs. It’s the same app as ever. What’s changed is the design. Microsoft customized the title bar–which, again, I’ll be writing about separately–and it added a row of tabs at the top. At first run, Notepad provides a single document in a single tab. But in addition to opening new windows, which you can still do, of course, you can also open new tabs, too. And when you open a second document, by default, Notepad will open that in a new tab in the current window. It won’t replace the existing document, nor will it open in a new window (by default, this is configurable by the user).

So how does Notepad implement multiple documents in an SDI app structure? As noted, I don’t know, and it doesn’t matter. And the reason it doesn’t matter is that I’m using WPF, which is not how Microsoft built Notepad. And so I will simply use WPF to replicate Notepad’s modern design pattern, as I did with the settings interface. I will replicate Notepad’s tabs using the tab controls that WPF provides. The conceptual hurdle is that tabs in WPF, at least, are not containers. They don’t contain other controls. They’re basically just a set of radio buttons with a different UI. Only one in a set can be toggled “on” at a time. The others are off.

(Quick side-track: In WPF, tabs are implemented as TabControl controls that contain individual tabs, which are TabItem controls. They can technically contain certain properties or attributes tied to the tabs. And I will likely use that capabilities for some sort of state management. But let’s not get distracted here.)

On/off. Enabled/disabled. Visible/hidden. Whatever. You can only see one document at a time. That means I only need one text box. I don’t need one text box “in” each tab. In fact, that’s impossible: Tabs (in WPF) are not containers of unrelated controls.

At least that’s what my dream told me.

I was half awake, half asleep at 5 am this morning when it suddenly hit me. This idea about how I could successfully implement tabs with WPF. I guess that’s how the brain works. Lying in bed, half asleep, half awake, and some neuron fires, makes a connection, and … I was awake. I had solved the problem. I woke up, started writing this, and then I started writing the side project noted below as a proof point.

This was exciting. The basic structure of .NETpad doesn’t have to change to accommodate tabs. Instead, the app will evolve from having three rows of controls in the main app window–menu bar, text box, and status bar, from top to bottom–to having four. I will simply add a new fourth row–for the tabs–to this layout, above the menu bar. I will style it so that the tabs and menu bar visually flow together, which is what makes them seem connected somehow in Notepad. But they are not connected, not architecturally. It’s just a design pattern. A design.

But there are multiple documents. So how does that work?

The simplest way I can imagine, that’s how.

In today’s app, .NETpad has a single “document” that’s just a string of text (a C# string) contained in and displayed by its text box. In the modernized version, .NETpad will support multiple “documents” via a collection of strings (like an array). When the user selects (“opens”) a tab, .NETpad will display the corresponding “document” (string) in the text box. If they open a new tab, a new “document” (empty string) will be added to the collection. When they close a tab, it will be removed from the collection.

This isn’t rocket science, as we say, or a hard computer science problem. But it’s also not that difficult, conceptually. Right now, there’s code that works directly with the text in the text box control, so I will need to change that. And there is a state associated with that text related to whether there is unsaved text and a few other things. And so I will need to update that a bit, too. But these changes don’t seem difficult, and I think they’ll make the code more elegant too. I just need to move from a storing a single “document” (which, again, is just a string of text) to using a collection of strings, with a corresponding collections of tabs (and states).

I can do this.

Granted, I worry know that I was perhaps overthinking things. Yes, Microsoft would have absolutely created an entire app infrastructure for this in the past, so I feel like me going there immediately makes some sense, is somewhat defensible. Knowing how little architectural work occurs with Windows these days, I retroactively wonder why I didn’t realize they must have done this in a far simpler way. As I will do now.

First, though, a side-project. A proof point using a simplified version of .NETpad that displays an arbitrary number of tabs, each with its own “document.” One where I can switch between them and the app works properly.

The styling is wrong–an issue with the WPF modernization Microsoft has implemented, as the color of the visible/selected tab is now wrong by default–but I can fix that. More importantly, it works.

Sample tabbed-based UI

The next step is to fine-tune this as necessary and then roll the changes into the main app. First with the current single document/no tabs version. And then with tabs the user can add and remove. It may take a while, and I still have the other tasks to work on too. But this was a big hurdle. And I feel like I’ve cleared it, at least conceptually.

So it’s time to make it real.

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