
I’d like to reach a certain level of quality before I post the pre-release code for the tabs-based version of .NETpad to GitHub. This is proving elusive, but we’ve all been told that the final 20 percent of any project is often the most difficult. Or as I think of it, this is when my procrastination comes back to haunt me.
Maybe that’s not fair: I did spend a lot of time on what I still think is the most difficult technical challenge–literally managing multiple documents and tabs–and I feel it’s understandable that that work would take a bit of time. But once that was largely one, I had to start addressing a lot of related issues, fit and finish issues. Some of that was backend modularization work, while others were tied to the UI, like maintaining the text insertion cursor location and selected text for each document/tab.
I could easily get bogged down by this work, since it seems like it never ends. And each time I make a step forward, I always seem to come up with something new, some new issue to solve. I know I need to let go at some point and just make the code public, but I also want the app to meet some minimum requirements for functionality and quality.
Here’s one example. My tabs-based version of .NETpad needs to support keyboard-based navigation, where typing Ctrl + Tab will display the next available/visible tab and its associated document. (Typing Ctrl + Shift + Tab should likewise navigate backward through the available/visible tabs.)
I had this idea in the back of my head that tab navigation was somehow tied to the Z-index order of controls in the app, but when it came time to finally address this issue, I realized how ill-formed this notion was. Instead, what I’d need to do was specifically address the relevant keyboard shortcuts and then change the focus to the appropriate tab and its associated document.
This is easier said than done.
Part of the problem here is that I had to (once again) work around some limitations in WPF’s implementation of tabs, which involves the TabControl and TabItem controls. Without getting into the explanation for this again, what I found is that it’s best to hide TabItems that are closed by the user rather than literally killing them. And because of that, a lot of the tab management code I’ve written involves checks to see whether whatever TabItem(s) are visible, meaning “open” (and thus available), and which are hidden (meaning “closed” and thus unavailable). So, starting with the simpler Ctrl + Tab case, I would need to select/display the “next” tab each time the user typed that keyboard shortcut.
This felt doable. Yes, I remain that naive.
Without yet worrying about what happens when you’re viewing the last visible/open tab–in which case, it should go back to the first visible open/tab–what I came up at first with seems simple enough. The idea here is to step through all the TabItems starting with the current TabItem, determine which is the next visible/open TabItem, and then switch to that.

I also had to figure out when to execute this code. A little Google “research” taught me that I needed to handle a KeyDown() event handler … somewhere. Because I wanted this to work no matter which control was selected at the time, I added that to the main app window itself. There are two checks in the AppWindow_KeyDown() event handler, one for Ctrl + Tab and one I would later write for Ctrl + Shift + Tab.

This seemed obvious enough and I felt that it should work because I pretty much always think that at first. But it wasn’t working. Instead, I would select a tab, type Ctrl + Tab, and it would always select the first tab. For some reason.
There are two types of debugging, from what I can tell. There’s what everyone does, and then there’s the official and formal way to debug code. I started with the former. And by that I mean, I displayed a MessageBox so I could see what various variables and values were at whatever point. And I discovered something curious. With the MessageBox displaying inline in the code, it worked. That is, when I typed Ctrl + Tab, it would select/display the next tab (after displaying the MessageBox). But when I removed the MessageBox, it would always select/display the first tab.
This was a wake-up call that caused me to use the debugging tools in Visual Studio to set a breakpoint and step through the code to see why this was really happening. And what I found, put simply, is that my AppWindow_KeyDown() event handler was firing repeatedly when I feel it should have only been firing once. I tried various things to ‘fix’ that, but in the end, I tried something I was unsure of that ended up working nicely. Instead of just running my SelectNextTab() custom method each time it fired, this event handler also explicitly informs the system that the event is handled, which causes the code execution to move outside the handler (using e.Handled = true), as shown here.

And … that worked. It wasn’t the first thing I tried. It may not even be the 10th thing I tried. But after researching such exciting topics as KeyEventArgs, the WPF FocusManager class, and how keyboard focus and logical focus differs in WPF, I finally had a mini-epiphany. And … whatever. Now that part of it worked.
Well. It worked until you had the final tab displayed and typed Ctrl +Tab again. At this point, nothing happened, as expected. So I had to account for that. What I came up with was the following addition. Here, the original for loop concludes without locating a next visible tab, so I just step through each visible tab using a foreach loop, starting at the beginning.

This is simple enough–and it worked, with is always appreciated–but I didn’t like that I had two concurrent loops, one a for loop, one a foreach loop, that each used basically the same code. But I couldn’t figure out a way to make that more efficient, so I fell back on AI: I highlighted the SelectNextTab() code I had I written and asked GitHub Copilot, “Can this be rewritten to be more efficient?” It literally replied with, “Certainly!” And spat out the following:

I have to be honest here. When I first looked at this code and stepped through it internally, I wasn’t 100 percent sure why it works. But it uses a single for loop and there’s no redundant, repeated code. It works exactly the same as I what I came up with. Lovely.
Next up, I had to create the SelectPreviousTab() method. And this time, I went straight to AI, asking Copilot whether it could use the code in SelectNextTab() to “write a SelectPreviousTab() method?” Once again, the answer was “Certainly!” And once again, it spit out code that I dutifully pasted into the empty SelectPreviousTab() method I’d already created.
This time, it didn’t work. When I typed Ctrl + Shift + Tab, it would navigate to the next visible/open tab, just as it did when I typed Ctrl + Tab. Obviously, I would need to do some work.
Nah, I embraced the laziness and just tried a different AI.
“I wrote this method in C# for the Windows Presentation Foundation (WPF),” I prompted Claude Anthropic. “It navigates to the next visible tab (TabItem control) when the user types Ctrl + Tab.” Then I pasted in the code for SelectNextTab() and added, “Can you write a corresponding SelectPreviousTab() method that will navigate to the previous tab (TabItem control) when the user types Ctrl + Shift + Tab?”
It quickly spit out its own similar code block. Which I dutifully pasted into SelectPreviousTab() and then tested. But it did the same thing: When I typed Ctrl + Shift + Tab, it would select the next tab, not the previous tab.
Sigh.
OK, maybe I would really need to do some work.
Starting with the obvious. Maybe the problem wasn’t the code that AI wrote. Maybe the problem was me.
Looking at AppWindow_KeyDown(), I had created this monster if/else-if block that tested whether the user typed Ctrl + Tab or Ctrl + Shift + Tab, as shown above. But perhaps that’s not what this was testing.
Without getting into the nitty-gritty here, I had reversed the two cases in the if-else-if. That is, I had put the test for Ctrl + Tab ahead of that for Ctrl + Shift + Tab. And that could not work.
Think about it. If the user types Ctrl + Shift + Tab and the code tests for Ctrl + Tab first, the result is true. The user did type Ctrl and Tab (and also Shift). So the else-if clause will never run. I have to test for Ctrl + Shift + Tab first. If that’s true, then go in reverse. If it’s not, test for (just) Ctrl + Tab, and if that’s true, go forward. Here’s the corrected code.

And now it works fine. (And the original GitHub Copilot code was likewise fine, too. Of course it was.) Here’s the code I kept for SelectPreviousTab(), which was AI-generated.

AI is great. But in this case, a human caused a problem AI didn’t know about. Fortunately, that human was able to fix it, too.
More to the point, keyboard-based tab navigation pretty much works now. It’s just one of several bits of fit and finish I wanted to figure out before posting this code. But it’s an important one.
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.