.NETpad 2025: Recent Documents (Premium)

.NETpad 2025: Recent Documents

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.

Look and feel

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.

Recents

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:

  • Recently opened documents will display in Files > Recent along with a separator and then a “Clear list” menu item.
  • If there are no recently opened documents (or the user has cleared the list), the Recent submenu will have a single item, “No recent files,” that is grayed out.
  • There’s a new MyRecents user setting in app settings that is a StringCollection to contain the full path and file name for each item you will see in Recent. I will limit this list to 10 items as with Notepad.
  • When you successfully open a document using the system Open file dialog, that document is added to the Recent list. If there are already 10 items, one is removed.

To make all this work, I created the following methods in the Recents class:

  • LoadRecents(). This was originally in the constructor, but I realized that I could reuse it when the menu was cleared, so I separated that out. Basically, it opens MyRecents in app settings and populates the submenu programmatically, adding a separator and the “Clear list” menu item at the bottom. If MyRecents is null/empty, it displays that single “No recent files” menu item instead.
  • AddRecent(). This is called when the user successfully opens a document. I am thinking about whether I should also call this when the user saves a new document for the first time. Probably.
  • RecentMenuItemClick(). All the Recents menu items call this event handler when clicked. I had to figure out a way to map the item number in the menu to item number in the MyRecents string collection so that the right document loads when clicked. And I came up with something I’m pretty proud of, noted below.
  • ClearListMenuItemClick(). This is the event handler that runs when the user clicks the “Clear list” menu item. It removes all the Recents menu items, clears out the MyRecents setting, and then calls LoadRecents() to get the “No recent files” menu item.

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.

MainWindow.xaml

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.

MainWindow.xaml.cs

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.

Recents.cs

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:

  • First, it clears out whatever items might be in the Recent submenu.
  • If MyRecents (in app settings) is null or empty, it creates a single submenu item, the “No recent files” item, which is grayed out.

  • Otherwise, it populates the submenu using the items in MyRecents and then adds a separator and a “Clear list” menu item.

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.)

Documents.cs

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.

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