The Built-in Find Dialog: Show, Hide, Invoke, or Replace
The WinForms HTML Editor ships with a built-in Find / Replace dialog so end users can search the document without you wiring anything up. This page covers the four things customers ask about most: (1) how to open it from your own code, (2) how to listen to its Find Next / Replace events, (3) how to hide its toolbar button when your host application already has its own Find UI, and (4) how to replace the dialog entirely with your own search bar by calling Editor.Search and Selection.SelectText directly. The last section explains the "search up only finds the most recent match" gotcha that several customers have reported.
What the built-in Find dialog does
The Find dialog is a small modal window with a "Find what" text box, "Match whole word only" and "Match case" check boxes, an "Up / Down" direction radio group, and four buttons: Find Next, Replace, Replace All, and Cancel. It is wired to the toolbar magnifying-glass icon (the "Search" button) and to the global shortcut Ctrl+F. When the user clicks Find Next, the editor searches forward (or backward) from the current caret position and selects the first match; when the dialog is dismissed the editor keeps the caret on the last match so the user can continue editing in place.
Invoking the Find dialog from code
If you want a custom button or menu item in your application that opens the same dialog the toolbar uses, call the OnSearchButtonClicked method on the editor's ToolbarItemOverrider. That is the same entry point the toolbar uses internally, so the dialog will appear and behave identically:
private void mnuFind_Click(object sender, EventArgs e)
{
htmlEditor1.ToolbarItemOverrider.OnSearchButtonClicked(this, EventArgs.Empty);
}To pre-populate the "Find what" text box (for example with the currently selected text), open the dialog through the Dialog.SearchDialog service and set PreloadedSearchText before calling ShowDialog:
var dialog = htmlEditor1.Dialog.SearchDialog;
dialog.PreloadedSearchText = htmlEditor1.Selection.GetSelectedText();
dialog.ShowDialog();Listening for Find Next / Replace events
The dialog is an instance of ISearchDialog (namespace SpiceLogic.HtmlEditor.WinForms.Models.Dialogs) and raises four events: FindNextClicked, ReplaceClicked, ReplaceAllClicked, and DialogClosed. The editor itself subscribes to these events to perform the search; you can subscribe alongside to log searches, update a status bar, or veto a replace. The payload (SearchEventArg) carries SearchText, MatchCase, MatchWholeWordOnly, and a Direction property of type SearchEventArg.SearchDirection (Up or Down):
htmlEditor1.Dialog.SearchDialog.FindNextClicked += (s, e) =>
{
Debug.WriteLine($"User searched for: {e.SearchText} " +
$"(direction={e.Direction}, " +
$"case={e.MatchCase}, " +
$"whole-word={e.MatchWholeWordOnly})");
};If you want to completely override the default Find-button behavior (route the toolbar click to your own UI instead of the built-in dialog), subscribe to SearchButtonClicked on the override helper. Setting a handler on that event suppresses the built-in dialog altogether:
htmlEditor1.ToolbarItemOverrider.SearchButtonClicked += (s, e) =>
{
using var myFindBar = new MyApplicationFindBar(htmlEditor1);
myFindBar.ShowDialog(this);
};Selecting matched text programmatically
You do not have to use the built-in dialog at all. The editor exposes the same search engine through two paths:
htmlEditor1.Editor.Search(text, forward, matchWholeWord, matchCase, useCaretPosition)-- the high-level call used by the dialog itself. Works in WYSIWYG and HTML source modes.htmlEditor1.Selection.SelectText(text, forward, matchWholeWord, matchCase)-- the lower-level WYSIWYG-only path.
Either returns true when a match is found and selected, or false when the document has been searched to its end. A self-contained "find and highlight" helper that bypasses the dialog entirely:
private void FindAndHighlight(string query)
{
htmlEditor1.Focus();
bool found = htmlEditor1.Editor.Search(
text: query,
forward: true,
matchWholeWord: false,
matchCase: false,
useCaretPosition: true);
if (!found)
{
// Wrap back to the top and try once more.
htmlEditor1.Selection.SelectText(0, 0);
found = htmlEditor1.Editor.Search(query, true, false, false, true);
}
statusLabel.Text = found ? $"Found: {query}" : $"Not found: {query}";
}Hiding the Find button on the toolbar
If your application already has its own application-wide Find experience, hide the editor's toolbar button so users do not see two Find affordances. The button is exposed on the public API as BtnSearch:
// Remove the Find button from the toolbar at startup.
htmlEditor1.BtnSearch.Visible = false;Hiding the button does not disable the Ctrl+F shortcut by itself -- the shortcut is mapped through the editor's KeyBindings service. To take the shortcut off the keyboard as well, unbind EditorActionId.Search:
using SpiceLogic.HtmlEditor.Abstractions.KeyBindings;
// Unbind Ctrl+F so the built-in dialog no longer opens on shortcut.
htmlEditor1.KeyBindings.Unbind(EditorActionId.Search);Replacing the built-in Find with your own
Customers who ship the editor inside a larger document-editing application typically have a single Find toolbar at the top of the host window and want Ctrl+F inside the editor to route to that toolbar instead of the editor's dialog. The pattern has three pieces: (1) handle Ctrl+F at the form level so your TextBox receives focus, (2) unbind the editor's own Search action so it does not also open the built-in dialog, and (3) call Editor.Search from your toolbar's Find Next button.
using SpiceLogic.HtmlEditor.Abstractions.KeyBindings;
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
this.KeyPreview = true;
// (2) Take Ctrl+F off the editor.
htmlEditor1.KeyBindings.Unbind(EditorActionId.Search);
htmlEditor1.BtnSearch.Visible = false;
}
// (1) Form-level Ctrl+F -> focus the host's own Find TextBox.
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == (Keys.Control | Keys.F))
{
txtMyFindBar.Focus();
txtMyFindBar.SelectAll();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
// (3) Find Next button on the host's own toolbar.
private void btnFindNext_Click(object sender, EventArgs e)
{
htmlEditor1.Focus();
bool found = htmlEditor1.Editor.Search(
txtMyFindBar.Text, forward: true,
matchWholeWord: chkWholeWord.Checked,
matchCase: chkMatchCase.Checked,
useCaretPosition: true);
if (!found) MessageBox.Show("No more matches.");
}
}"Find Up only finds the most recent match" gotcha
A frequently reported behaviour: the user types a word that appears five times in the document, switches the direction to Up, clicks Find Next, and the dialog jumps straight to the last occurrence and then reports "Finished searching the document" on the next click. This is not a bug -- it is the search-from-caret semantics working as designed. The Find engine always searches from the current caret position; Up means "look backward from where I am now". If the caret is at the end of the document (which it is when the dialog first opens on a freshly-loaded document), the only direction that has matches in it is Up, and the first backward hit is the last occurrence -- followed immediately by "no more matches" because the engine has already walked past every earlier occurrence.
If you want a true "search from the top in document order, reverse" -- the way some legacy applications behave -- collect every match yourself by repeatedly calling Editor.Search with forward: true and useCaretPosition: true until it returns false, then walk the list in reverse:
private void FindAllInReverse(string query)
{
var matchStarts = new List<int>();
htmlEditor1.Selection.SelectText(0, 0); // caret to the top
while (htmlEditor1.Editor.Search(query, true, false, false, true))
{
var info = htmlEditor1.Selection.GetSelectionInfo();
matchStarts.Add(info.StartIndex);
}
// Now walk backwards through the gathered starts.
foreach (int start in matchStarts.AsEnumerable().Reverse())
{
htmlEditor1.Selection.SelectText(start, query.Length);
// ... do something with each match ...
}
}The built-in dialog deliberately uses the search-from-caret model because it matches the de-facto Windows convention (the same Find dialog you see in Notepad, Word, and Visual Studio). If your domain expects "always start from the top", build a thin custom Find bar on top of Editor.Search and move the caret yourself before each call.