
It’s time to take a step back to explain what’s going to happen next: The way that Microsoft implements Find/Replace in Notepad today is non-standard and would be difficult to recreate using WPF. Complicating matters, my original implementation of Find/Replace is somewhat rudimentary. And so I’ve spent a lot of time this past summer trying to crack this nut. As with so much else related to this project, I’ve had some successes and some setbacks. But it’s time to pick a path forward.
The decision I’ve come to is rooted in the core conceit of this project: I have this app, what we might call a legacy app, and I want to modernize it so that it looks and runs well in Windows 11. Microsoft is providing basic support for this work in .NET 9 by supporting what it calls Windows 11 theming in WPF, the app framework I used to create the original version of the app. (Well, not the original version: I created Windows Forms versions of the app before that, but let’s stay on target here.) But it’s only basic support. There are (many) missing features and bugs, and those are piled on top of the missing features and bugs that were already in WPF. And so we have to be pragmatic. The goal is for this app to be as much like its inspiration, Notepad, as possible. But there are things we can do. And things we cannot.
If you’ve followed along with this series since the beginning, you’ve seen the struggle. And in the Step-by-Step posts, you’ve seen “what we can do” unfolding. Despite all the limitations, there is a lot we can do to modernize this app. But with most of that work complete, we’re heading into a gray area between can and cannot. This is the most frustrating part of the project. If something were just impossible, I could at least move on. Some of this is tantalizing close. I want to get it done, and correctly. But because of time, knowledge, and technical issues, I will settle on just getting it done to some degree. I will do what I can. This is the nature of the work.
Find/Replace–a simple name for a complex set of functionality–is one of three big gray areas that has consistently set me back. (The other two are tabs and getting theming/styling to fully work.) This makes sense, in a way, because Find/Replace has always been a problem, in every version of this app I’ve made over the past several years.
It started in 2019 with the Windows Forms/C# version of .NETpad, the second version of the app I created, and the first I documented publicly. The parallels are interesting. Then, as now, Microsoft had implemented its interface for Find/Replace using a type of window that wasn’t available to me. And then, as now, I lacked the technical know-how to create a reasonable workaround.
In that case, I got help from a reader, who provided the basic logic behind for the core Find/Replace features–Find, Find Next, Find Previous, Replace, and Replace All–and a dialog I could use to approximate the original Microsoft UI. But when I moved on to my first WPF version of the app, that dialog type was no longer available to me. So I did what WPF developers have always done–and what I’ve done this past summer with my custom Content dialog work–by creating my own custom dialog.
We are replacing all the custom dialogs as part of this modernization project. But Find/Replace remains. It’s the last dialog standing. And that’s because it’s unique, different from all the other dialogs in Notepad and, unless I’m missing something, from any other app in Windows 11 or elsewhere. I can’t recall seeing anything quite like it.
What I can do is recreate it in XAML. That’s relatively straightforward, at least for me, and I’ve spent so much time on this kind of work over the past few months that I’m getting pretty good at it.
I can also wire up this custom dialog to the underlying C# code I created–well, adapted using a reader submission–for the OG version of the app. But there are all kinds of issues in the interactions between the dialog the user accesses the underlying app window, which needs to display text selections. Without getting too deep in the weeds, the custom dialogs we’ve implemented so far are modal dialogs that take over temporarily, but Find/Replace needs to be modeless (or, non-modal) while retaining a visual lock on the underlying app window. If you search for a term in the dialog, and it’s found, that text has to be selected in the main app window, but the dialog has to remain on-screen too. And it has to not cover up the selected text.
This is difficult and not just because of my own limitations. There are bugs in WPF that prevent me from implementing this feature related to the positioning of dialogs (sub-windows) relative to the on-screen position of the parent window (the owner, or main app window). These bugs are well understood, have existing for many, many years, and have never been fixed by Microsoft. What should be straightforward positioning code just doesn’t work. I tried all kinds of workarounds. But there are so many bugs. I can let the user move the dialog around, something Notepad doesn’t support, but doing so removes the dialog’s shadow for some reason (it’s a bug and/or non-implemented feature in WPF). Etc.
So I finally decided to punt.
Instead of implementing Find/Replace correctly and creating a custom dialog that looks nearly identical to that in Notepad today–which bothers me because I literally nailed the look and feel–I will fall back to my original pane-based approach. I will implement Find/Replace as a single-row grid that sits between the menu bar and the text box in the main app window that only appears when needed. This simplifies a lot of the underlying code because Find/Replace is in the same window as the text box it interacts with and will never cover it up.
I almost wrote “problem solved” there, but that’s not accurate. This is a workaround driven mostly by limitations in WPF, not a solution. But that’s what developers deal with all the time.
I don’t like that this interface is non-standard. I don’t like that it doesn’t look exactly like the corresponding UI in Notepad. This is an area I’d like to revisit in the future. In WPF if possible. Or perhaps in a future Windows App SDK/WinUI 3 rewrite. But for now, for this project and its finite goals and time frame, I have to move forward. The clock is ticking.
I’ll write up a more complete step-by-step guide to this work soon. Here, I will instead provide a quick overview of what I have done, what it used to look like, and what it will now look like in the modernized version of the app.
The OG Find/Replace code is still in today’s app, and you can access it via the relevant Edit menu items or with keyboard shortcuts (Ctrl + F, Ctrl + H, and so on). The resulting windows aren’t styled correctly and never will be. But the bigger issue is that I implemented Replace and Replace All as a series of dialogs. The first one asks for the text to find, the second one asks for the replacement text, and then it makes the change (or displays an error Message box).

It worked, but what you really want is an interface that stays on-screen. This is how Notepad has long worked, and in Windows 11, there’s a new UI too. It isn’t possible to implement Find/Replace using the custom Content dialog I created for Save confirmation, Go to line, Save as, the other custom dialogs in .NETpad. It’s just too different and has too many controls. And it resizes with the main app window in a unique percentage-based way that, oddly, isn’t consistent with the width of that window. Here’s how it looks by default in Notepad today.

The caret to the left is a toggle button. When you click it, the dialog expands to reveal three controls for Replace and Replace All that are hidden by default.

Creating that UI in XAML is simple. I did it with a standalone WPF window modeled on the custom Content dialog and stuck to the default centered positioning and modal nature of that while I worked through wiring up the underlying C# code for this new user interface.

I got all the basics working. Closing with mouse or keyboard. Toggling the Replace controls.

Putting selected text, if any, into the Find text box by default. And so on.

One key to this interface is highlighting found text in the underlying document when the user searches by clicking the “Find down” button (as Notepad calls it, I think of this as “Find next”). This isn’t possible with a modal dialog where the entire point is to ignore the underlying window. But switching to a modeless window introduces its own issues (in WPF; I suspect this is easier/better in other frameworks).

But it all comes crumbling down when you try to position this dialog relative to its owner (the main app window), as with Notepad. Positioning vertical, relative to the top of the window works, but positioning it horizontally does not. And God help you if the user maximizes, resizes, or moves the app window. Long story short, it just doesn’t work.

The Find/Replace dialog can also cover up found text, of course. I wrote code that let the user move the dialog, but ideally this would be automatic.

And … I gave up. There were other issues, but I couldn’t even get the basics to work. So I hit reset.
When I started working on this project, I created pane-based UIs using the WPF Grid control, and then I used z-order to make them appear over other controls, like the text box in the main app window. And then I moved onto actual standalone custom dialogs for most of it. But for Find/Replace. I’m going to simplify things dramatically. I am going back to a pane-based approach, but that pane will not cover up the text box. Instead, it will slot in between the menu bar and text box as needed.

I tried to make this look as natural as possible even though this is non-standard. And I did build in a little sophistication. For example, the buttons are all fixed sizes, but the Find and Replace text boxes resize identically, and according to the width of the window.

I may hide the Replace/Replace all controls if the user selects “Find.” But that’s a simple change.
To wire this up correctly, I had to go back and look at the code that drives this functionality, code that was written by someone else several years ago that I now just treat as a black box of sorts. And there will be changes. Not just to that code, but to the ways I interact with it.
One small example is noted above: When the user selects some text and then engages with Find/Replace, the selected text is prepopulated in the Find text box automatically.

The “Find next” (what Notepad calls “Find down”) button is the default action: Type Enter, and it will find that text and highlight the next instance of it. (And typing Esc should close the Find/Replace pane; I’m working on that.)

In short, making this trade-off is necessary to move forward. And so that’s what I’m doing.
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.