Replacing the default Dialogs
A development team at a legal-tech firm finishes integrating the WinForms HTML Editor into their case-management app. Everything fits the brand -- their custom Fluent ribbon, their navy-and-gold accent colors, their typography. Then a tester clicks Insert Image. A standard gray Windows Forms dialog pops up. Three columns of inputs. System font. The "Browse..." button sits in the wrong place. It is, suddenly, 2008 inside an otherwise 2026 application.
The tester files a bug: "the image picker looks like a different app." The lead developer opens the source of the editor's image dialog to copy-paste it and restyle. Then she finds a better option in the API.
The hand-off point
The editor exposes every built-in dialog through a single service hung off the control: editor.Dialog, typed as IDialogService. That service has nine properties, one per dialog, and each property has a setter. Assigning a new instance replaces the default for the lifetime of the editor; leaving a property alone keeps the factory dialog.

The nine interfaces all live in SpiceLogic.HtmlEditor.WinForms.Models.Dialogs, and every one of them extends a single base contract from SpiceLogic.HtmlEditor.Abstractions.Dialogs:
public interface IDialog : IDisposable
{
DialogResult ShowDialog();
}That is the entire surface area. System.Windows.Forms.Form already returns DialogResult from its own ShowDialog() and already implements IDisposable, so a Form-derived class satisfies IDialog without writing a single extra line.
| Interface | What the dialog edits | Property on editor.Dialog |
|---|---|---|
IImageDialog | Image insert / properties | ImageDialog |
IHyperlinkDialog | Hyperlink insert / properties | HyperlinkDialog |
ISearchDialog | Find & Replace | SearchDialog |
IStyleBuilderDialog | CSS style builder for the body / selection | StyleBuilderDialog |
ISymbolDialog | Special-character picker | SymbolDialog |
ITableDialog | Table properties | TableDialog |
ITableCellDialog | Table-cell properties | TableCellDialog |
IYouTubeVideoInsertDialog | YouTube embed | YouTubeVideoInsertDialog |
ISpellCheckerDialog | Modal spell-check window | SpellCheckerDialog |
Replacing the Image dialog
Back at the legal-tech firm, the lead opens a new Form named BrandedImageDialog and makes it implement IImageDialog. That interface adds three members on top of ShowDialog(): an Element property of type ImageElement (the inbound and outbound image model), a bool IsLocalResourceSelectionDisabled hint, and a bool UseInlineStyleForDimensions hint that says "write width and height as inline CSS rather than as legacy attributes."
using System.Windows.Forms;
using SpiceLogic.HtmlEditor.Abstractions.Entities;
using SpiceLogic.HtmlEditor.WinForms.Models.Dialogs;
public class BrandedImageDialog : Form, IImageDialog
{
// --- IImageDialog members ---
public ImageElement Element { get; set; }
public bool IsLocalResourceSelectionDisabled { get; set; }
public bool UseInlineStyleForDimensions { get; set; }
public BrandedImageDialog()
{
InitializeComponent(); // designer-generated branded layout
this.Text = "Insert Image";
this.BackColor = System.Drawing.ColorTranslator.FromHtml("#0A2540"); // brand navy
this.ForeColor = System.Drawing.Color.White;
this.Font = new System.Drawing.Font("Segoe UI Variable", 10F);
}
protected override void OnLoad(System.EventArgs e)
{
base.OnLoad(e);
// The editor populates Element before calling ShowDialog().
// For a brand-new image, fields on Element are blank; for an existing
// selection, they are pre-filled. Map them onto your form controls.
txtUrl.Text = this.Element?.Src ?? string.Empty;
txtAlt.Text = this.Element?.Alt ?? string.Empty;
nudWidth.Value = this.Element?.Width ?? 0;
nudHeight.Value = this.Element?.Height ?? 0;
// Honor the editor's "no local files" mode (e.g., when authoring
// for the web and pasted file URIs are not wanted).
btnBrowse.Enabled = !this.IsLocalResourceSelectionDisabled;
}
private void btnOK_Click(object sender, System.EventArgs e)
{
// Push form values back into Element before closing.
this.Element.Src = txtUrl.Text;
this.Element.Alt = txtAlt.Text;
this.Element.Width = (int)nudWidth.Value;
this.Element.Height = (int)nudHeight.Value;
this.DialogResult = DialogResult.OK;
this.Close();
}
}One line in Form1_Load wires the dialog in:
htmlEditor1.Dialog.ImageDialog = new BrandedImageDialog();From the next click of the Image button onward, the editor calls into BrandedImageDialog. It populates Element with whatever the user has selected (or a fresh ImageElement for an insert), sets the runtime hints from the matching UserOption values, calls ShowDialog(), and, if the result is OK, reads Element back and applies it to the document.

The same pattern repeats for the other eight
Each interface adds two or three domain-specific members. IHyperlinkDialog carries a HyperlinkElement Element, an IsLocalResourceSelectionDisabled flag, a RemoveLink flag (set on close when the user chooses "remove link"), and UseCtrlClickTooltipDefault. ITableDialog exposes a single TableElement Element. ISymbolDialog returns the picked character. The contract per interface is small and reads directly from the source -- one file each under Models/Dialogs/.
The wiring at startup looks the same regardless of which dialogs the team chose to brand:
private void Form1_Load(object sender, EventArgs e)
{
htmlEditor1.Dialog.ImageDialog = new BrandedImageDialog();
htmlEditor1.Dialog.HyperlinkDialog = new BrandedHyperlinkDialog();
htmlEditor1.Dialog.TableDialog = new BrandedTableDialog(htmlEditor1.Dialog.TableCellDialog);
htmlEditor1.Dialog.SearchDialog = new BrandedSearchWindow();
// SymbolDialog, StyleBuilderDialog, etc. left untouched -- the
// factory versions are fine for those.
}Edge cases worth knowing
The TableDialog default implementation takes a constructor argument of type ITableCellDialog -- the table dialog can launch a cell-properties sub-dialog. If a team replaces both the table dialog and the cell dialog, the cell-dialog instance must be passed into the table-dialog constructor (or however the custom table form invokes its cell-properties button). Replacing only the table dialog and reusing the factory cell dialog is fine; reach back through htmlEditor1.Dialog.TableCellDialog to get it.
The runtime hints arrive on every ShowDialog() call, not just the first one. Read them in OnLoad (or in a setter that updates UI state), not in the constructor -- the editor updates them between calls to reflect changes the host application made to editor.Options.
Returning DialogResult.Cancel aborts the operation cleanly. The editor does not read back Element on Cancel; the document is left untouched.

Shipping
The legal-tech firm ships its next release. The Image button, the Hyperlink button, the Table button -- they all open windows that look like the rest of the app. The factory dialogs are still in the binary, still wired up for the four interfaces nobody bothered to override. No fork of the editor's source, no copy-pasted controls, no maintenance debt from "we customized the editor." One Form per dialog, one property assignment per dialog, and the editor stays on the official upgrade path.