.NETpad 2025: Tabs First Steps (Premium)

Basic tabs with WPF

I’ve been pushing ahead on some of the more complex coding that .NETpad will require for tabs and state management. But in working through that mess, it occurred to me to take a step back, follow my own advice, and focus on one of the more fundamental aspects of this app update.

I am referring, of course, to tabs.

That sounds fairly obvious, I know. But tabs are complex, and it’s still not clear to me that the WPF implementation of this feature–via its TabControl and TabItem controls–is up to the task. So let me step through what I’d like to do, and what I’ve already completed, and a few unknowns (to me).

In WPF, the TabControl is a container for individual TabItems. That’s pretty straightforward, and similar in many ways to how a Menu control and the MenuItems it contains work. Tabs are also well-understood from a usage perspective, as we’ve been using tabs in web browsers for what feels like forever. And Microsoft, of course, has been adding tabs to individual in-box Windows apps–so far, File Explorer, Notepad, Terminal–since the release of Windows 11.

But tabs are complex to code with WPF. And for so many reasons.

I’ve mentioned the style issues a few times. But regardless of the new and buggy Windows 11 theming issues in WPF, this framework’s implementation of tabs hasn’t visually mapped to how tabs display in Windows for several years or more. And fixing that–or working around it–is difficult. I came up with an OK solution for now, but I would like to improve this, so that .NETpad looks more natural, and more like Notepad.

Basic functionality is another issue. When WPF appeared, Microsoft mostly used tabs in control panel windows like System Properties as a way to display multiple pages of settings in a single window that could fit in the low resolution displays of the day. These interfaces were static, laid out at design time, and not dynamic.

But the tabs in Notepad and other apps are dynamic, The user can open an arbitrary number of tabs and then close them, randomly, as they see fit. Tabs-based apps need to accommodate this behavior and scale the UI accordingly. The tabs resize as needed, such as when the user adds or closes them, or when the app window is resized. To my knowledge, WPF tabs do not support this functionality natively, so I need to figure that out. (If you know differently, I’m listening.)

As I wrote previously, supporting multiple documents is complex. Previously, .NETpad supported a single document (per app instance), so tracking document state–via variables like DocumentIsSaved, TextHasChanged, and so on–was relatively easy. But now there will be an arbitrary, ever-changing number of documents, each in its own tab and with its own document state. That’s a lot of data to track.

But there’s more. Notepad also supports a feature I call session state, which is enabled by default, by which the app remembers which documents–some of which could be saved to disk and some not–were open when the app closes. And then it restores those documents, along with some but not all of their respective document states, to their respective tabs when the user relaunches the app. And that all by itself is an incredible amount of complexity.

So it’s no wonder I made little headway with tabs last year: Add all that up, and it’s a lot of work. Most of which is new to me, and some of which may be impossible to implement in an ideal way.

But I’m plowing forward. And I have vague ideas, a few I’ve briefly tested, that might help me overcome WPF’s tabs limitations. Unfortunately, I’m also all over the place. And though I’ve made a tidy to-do list so I can cross items off the list I go, it’s not particularly organized. And sometimes I tackle a bigger issue with no end in sight before going for the quick win with a smaller, easier issue.

This isn’t therapy. But this is me trying to get on track before this gets out of hand. And that’s what I meant by following my own advice. The issues I face implementing tabs are each so complex that it makes sense to break them down into smaller sub-tasks. This way, I can at least make some progress.

So. Tabs. Let’s talk about the structure a bit.

There’s going to be a TabControl. It will contain a single TabItem at first, and that TabItem will map to a single DocumentTab object that stores data about the document displayed in the tab. Creating this basic tabs UI with XAML is straightforward if you stop worrying about the style limitations. At a high level, it looks like the following.

Obviously, there’s a bit more to it. The TabControl will need to be placed within the main app window’s layout grid, in the top row, where it will replace the custom title bar text I implemented earlier, and to the left of the custom window buttons. The TabControl and TabItem both have various properties to assign. But it’s not too difficult to make the switch from an app with no tabs to one with a single tab.

That said, one of the many limitations in the WPF implementation of tabs immediately obvious. TabItem lets you display some text, which I’ll use for the document name. But it doesn’t have a (direct) way to implement a Close tab button. If you look at the tabs in Notepad, you’ll see that each tab has a document name and a Close tab button. When the document needs to be saved, that Close tab button changes into a bullet to visually indicate that. And when you mouse-over the bullet, it temporarily changes back to the Close tab button (“x”).

As it turns out, this UI isn’t difficult to implement in XAML either. And that’s because the TabItem is what’s called a HeaderedContentControl, and the header–the content it displays–can be any type. By default, the header is just text, and you assign it a value directly as above.

But you can also make a more complex header that’s compromised of whatever controls. What we need, of course, is two elements in the header, the text and the Close tab button. And for that sort of thing, I use a <Grid> as often as possible because it’s familiar to me. And so I can use code like the following instead.

None of this is final, and I’m leaving a lot out, but it’s not a lot of work to re-do the top of .NETpad so that it displays a single hard-coded tab instead of the traditional title bar. Well, it’s a bit of work: XAML is a mystery unto itself, and sometimes layouts can be puzzling. But without getting into the nitty-gritty of this now, I eventually arrived at something like this.

Among several other small updates, I also styled the menu bar so that it visually flows into the selected (and, for now, only) tab. And when I run the app, it looks like so.

This works–literally, it works just like before–because I just set the name of the tab header text to TitleText, which was the old name of the title bar TextBlock control, which is referenced throughout the code. So. Cool.

There’s just one problem.

OK, there are many, many problems. But I’m trying to keep this to more digestible chunks of information. For my brain and yours. So here’s what I’d like to focus on today.

The code for this is all XAML, of course. But there will be a New tab button next to the tab(s) at the top of the app, per Notepad. And that button (or the Ctrl + T and, come to think of it, Ctrl + N keyboard shortcuts) will open a new document in a new tab, along with its associated DocumentTab object instance. And that means that we need to create a tab, identical to the one above, using C# code. In fact, when that code works, I’ll create the first tab that way, too, and not use the TabItem XAML at all.

There are probably several different ways to handle this. But to my mind, there are two semi-obvious choices. I could create a C# method, let’s call it CreateNewTabItem(), that constructs the entire new tab item–the header text, the Close tab button, the grid that contains them, and all the associated properties and event handlers–using C#. Or, I could also create a style for TabItem that applies most of that stuff each time a tab item is created, minimizing the amount of C# code I need to write.

That latter approach is vaguely interesting to me, and I will almost certainly go in that direction as this progresses. But if I’ve ever done a WPF user interface in C#, I can’t recall that, so I started with the all C# version first.

And that is what we call a hard computer science problem. So I had to do a bit of reading and research.

What I came up with is a method called CreateNewTabItem() that steps through all the C# code I need to assemble that TabItem. This includes a TextBlock for the header text (the document name), a Button for closing the tab, the grid that will contain the two, and then the TabItem itself. The TabItem has several isolated properties, but the big one is so small, code-wise, that it’s easy to miss. I assign its Header property the Grid I had previously created, and that Grid is populated with the TextBlock and Button. All three of which are comprised of a lot of code.

This will make more sense if you can see it.

First, I created an Add new tab button next to the one and only tab. It has a Click() event handler that looks like so.

This creates a new DocumentTab object, adds that DocumentTab to the list of DocumentTabs (dts) that the app maintains, creates a new TabItem, and the instantiates it using the CreateNewTabItem() method I created. When that returns, the new TabItem is added to MyTabs, which is the TabControl at the top of the app.

CreateNewTabItem() accepts a single argument–that DocumentTab object, because we need the value of its DocumentName property–and returns a TabItem.

There are four code blocks in there. The first creates the header TextBlock.

The second creates the Close tab button.

The third creates and the Grid that will contain those two elements, configures its layout, and then adds the TextBlock and Close tab button as child elements.

And the four creates the TabItem. That last line of code is what assigns all of the above as the Header property of the TabItem.

Then it returns the TabItem. Pretty cool!

It works, too. Two points, though. The version of the app I created this in is different from the one above–and it includes a lot more stuff, in progress. And while it lets me add tabs all willy-nilly, it doesn’t have a way to constrain the tabs to the size of the window; if I had too many, the window buttons disappear off the right, and it’s all messed up. But again, this is just about the basic tabs code for now.

There is so much more to do. But I just wanted to provide an in-place progress report of sorts so you can understand, to some degree, how much work has to occur here. It’s going to get ugly.

So, baby steps. One thing at a time.

The next step for this part of the project is creating the initial tab using C# instead of using XAML. But there are other things I’m working on, including the session state stuff. And I did already implement a custom MessageBox in addition to the other custom dialogs I added last year. So I’ll probably write that one up next.

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