
Temporarily putting tabs aside has been delightfully freeing, and I spent much of the weekend making progress in other areas of the app. All the changes I describe here are in GitHub, so if you are following along, you can pull those down or just clone the DotNetPad3.2 repository again. Remember, this is work in progress, so the code is not as clean as it should be yet.
As per the title of this post, most of the work I will discuss here is related to the new File > Recent submenu. Microsoft started testing this feature in Notepad in the Dev and Canary channels back in March, and I knew I’d be adding it to .NETpad as soon as I saw it. But I also spent time on a few look and feel items, so let me blow through that quickly first.
Say what you will about Microsoft Edge, but one thing I’ve always like about the browser is how good it looks. Part of that appeal is its heavy use of WinUI iconography and fonts, both are which seem slightly upsized to match the Windows 11 design aesthetic. So as I move .NETpad away from just being a Notepad clone, I would like it to adopt a unique identity that also looks natural in Windows 11.
That first bit could be tied to color themes or whatever, similar to how the Office apps can have these really rich colored header areas. But for now, I’m starting small because I would like to eventually handle styles more elegantly. So this is basically just a series of tests for now. It’s getting there, and if I finally settle on the look/feel I want, my goal is to use styles extensively instead of hard-coding things in XAML. For now, it’s a mess of both.
Anyway. The first step was adding WinUI 3 icons to the items in the status bar. and then bumping up the font sizes to roughly match that Edge look and feel.

Then I did similar font updates–minus the icons, though I did test it–in the menus and, via styles, on the Settings page. I feel like adding icons to every menu item would get busy, but … we’ll see.

And then I noticed text on the custom dialogs looked too small by comparison. So I had to update those as well. There are several things I need to fix in there, so more soon. I also noticed an Auto save bug that was present before but I had forgotten about in all the tabs work. So I made a to-do for that to make sure I get to it.
My original GitHub release of this version of the app included the start of a Recents class to handle a new Recent documents feature that’s accessed via a new submenu in the File menu, as per Notepad. This seems like a straightforward feature, but there’s a lot to it. So I’ve already eliminated a lot of my original work or changed it a lot. But the basics remain the same:
To make all this work, I created the following methods in the Recents class:
As I write this, what I’ve done is arguably quick and dirty and should probably be reviewed carefully. But I’m pretty happy by how this came together, given the various challenges. It’s nice to actually get something done for a change.
As I built this out, I hard-coded some filename paths so I had data to work with. And as that started working, I could write new entries to MyRecents in app settings and then work with live data. Rather than document that process, here I’ll just give a quick rundown of where it’s at now.
The first step was simple enough: I added a Recent MenuItem between Open and Save in the File menu like Notepad does. I built out its submenu a bit with a separator and “Clear list” menu item, thinking this might save me some hand-coding later, but it turns out that this is unnecessary. So I’ll likely remove those items later, not that it matters: I clear them out in C# regardless.

When the main window class is instantiated, it creates instances of my core classes: Document (the replacement for DocumentTabs), AppState, and AppSettings. The Recents functionality is inarguably a setting–I mean, it is literally an app setting–but it’s also unique, and so I wanted to isolate this work from AppSettings. So now also create an instance of Recents as well.

In the main window constructor, I load the app settings through a LoadSettings() method that’s in the new AppSettings class, so I added a similar call to LoadRecents() in Recents to get that going.

I went through a few permutations of the Recents class, but it starts with by instantiating a StringCollection object called MyRecentsList to contain the contents of the MyRecents list in app settings. I make that assignment in the class’s only constructor.

As noted above, I broke the code that reads MyRecents out into a LoadRecents() method. This method accepts the File > Recent menu as a parameter so that it can work on that. And it does a lot of work:


That else clause that populates the submenu deserves some attention. Cycling through the available items in MyRecents is straightforward. But the items it contains are full document paths, like this:
C:\Users\paul\SynologyDrive\Docs\Current\Work\2025-05\Test document.txt
What displays in the submenu, however, is just the filename, minus the path. Like so:
Test document
That’s easy enough. But I needed some way to locate the original item–the full document path–when the user clicked it. This seemed solvable. If the user clicked, say, the third item in the Recents list, it should load the file that’s stored as the third item in the MyRecents app setting. There are probably a few ways to do this, but I was scanning the available properties for the MenuItem class, I came across the answer: Tag (which it inherits from an upstream class). This is defined as “gets or sets an arbitrary object value that can be used to store custom information about this element.” Bam.
Tag can be set to any type that has a built-in converter, including string, the type I needed. So this was simple enough: As I dynamically create each MenuItem in the loop, I use the filename minus the path for its display (Header), and I use the full path for its Tag. I also use the same Click() event handler for each of these items. And below all that, I build out the rest of the menu and dynamically assign the Click() event handler for that “Clear list” item.
That meant I needed to create those two event handlers.
RecentMenuItem_Click() gets the MenuItem that called it using its sender parameter, and then I use that Tag property to call OpenDocument()–which is part of my Document class now and had to be modified for this purpose, as noted below–to open the correct document.

ClearListMenuItem_Click() clears out the Recent sub-menu, clears out the MyRecents app setting, runs LoadRecents() so that the single “No recent files” item displays there, and then saves the app settings.

I also need to create a method for adding a new item to Recents. This happens when the user opens a document, as noted. (It will likely happen on Save as, too, but I haven’t written that yet.)

As noted above, I had to edit the OpenDocument() method in my Documents class to accommodate the items in Recents. Before this change, this method was only called once, by the OpenCommandBinding_Executed() event handler, and it had a single parameter mapped to TextBox1 in MainWindow. But Recents is a different class and to open a document displayed in that submenu, I had to change OpenDocument(), which previously only opened a document using the system Open file dialog.
To do that, I added a second parameter to OpenDocument(), a string named FilePath. I then changed the call in OpenCommandBinding_Executed() to include an empty string, since it’s not needed there. And changed the body of OpenDocument() to be an if-else loop based on whether FilePath was empty or not. If it is, it runs the code from before. If it isn’t, it takes the FilePath parameter–which contains the value of the Tag property of the Recents MenuItem that the user clicked–and uses that to open the document and populate the TextBox instead.

This works fine, but it is quick and dirty code and should be cleaned up to address redundancies in the if and else blocks. I was just happy to get it working for now.
Indeed, more testing is warranted. I should make sure my code to restrict the Recents sub-menu to 10 items actually works. There’s more to do. But this came together pretty quick, and it’s clear it can work and mostly does as-is. So that’s good.
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.