Overriding WPF Editor Toolbar Button Icons, Tooltips, and Click Behavior

Vikram is the lead developer on a sysadmin console at a Bangalore managed-services company. His app is an MVVM-style WPF tool that operators use to write incident-resolution notes attached to each ticket. The notes are HTML, the editor is WpfHtmlEditor, and Vikram has spent two years carefully wiring every action in the app through view-model commands - because his QA lead writes automated UI tests against those commands and his analytics team logs them.

The editor's factory Save button breaks his discipline. When the operator clicks it, the editor pops up a standard SaveFileDialog and writes the HTML to disk. That is exactly the wrong behavior here: incident notes have to go to the ticket database, never to a local file, and the save action has to flow through his SaveNoteCommand so his audit log captures it.

WPF HtmlEditor Save button routing through an MVVM ViewModel command instead of opening a file dialog

Vikram could hide the Save button and force operators to a separate button on his shell, but he wants the button to stay where it is - operators have muscle memory for it after two years. He needs to keep the button, repurpose its click, and ideally swap in his app's diskette icon as well so visual continuity with the rest of his toolbar is intact.

He digs into ToolbarItemOverrider and finds two complementary surfaces. The SaveButtonClicked event lets him replace the click behavior - if he subscribes, the default file-dialog flow is skipped and only his handler runs. The ToolbarItems.Save property gives him the live factory Button, which he can mutate at will: change the tooltip, walk the visual tree to swap the icon, hide it, anything a normal Button allows.

private void IncidentNoteEditor_OnLoaded(object sender, RoutedEventArgs e)

{

    var overrider = IncidentNoteEditor.ToolbarItemOverrider;



    // Reword the tooltip so operators see "Save to ticket" not just "Save".

    overrider.ToolbarItems.Save.ToolTip = "Save to ticket database (Ctrl+S)";



    // Hide the New button - operators must never start a fresh blank note.

    overrider.ToolbarItems.New.Visibility = Visibility.Collapsed;



    // Replace the icon with our app diskette so the toolbar stays visually consistent.

    if (VisualTreeHelper.GetChild(overrider.ToolbarItems.Save, 0) is Border border &&

        VisualTreeHelper.GetChild(border, 0) is ContentPresenter presenter &&

        VisualTreeHelper.GetChild(presenter, 0) is Image saveImage)

    {

        saveImage.Source = new BitmapImage(

            new Uri("pack://application:,,,/MyConsole;component/Icons/save_db.png",

                    UriKind.Absolute));

    }



    // Route the click straight through to the MVVM command.

    overrider.SaveButtonClicked += RouteToSaveCommand;

    overrider.OpenButtonClicked += RouteToLoadCommand;

}



private void RouteToSaveCommand(object sender, RoutedEventArgs e)

{

    var vm = (IncidentNoteViewModel)DataContext;

    string html = IncidentNoteEditor.BodyHtml;

    if (vm.SaveNoteCommand.CanExecute(html))

        vm.SaveNoteCommand.Execute(html);

}



private void RouteToLoadCommand(object sender, RoutedEventArgs e)

{

    var vm = (IncidentNoteViewModel)DataContext;

    if (vm.LoadNoteCommand.CanExecute(null))

        vm.LoadNoteCommand.Execute(null);

}

Now when the operator clicks Save, the file dialog never appears. The HTML body lands inside the view model, hits the SaveNoteCommand, gets audited, and lands in the ticket store. Vikram's QA suite picks up the new behavior on its next run because it has always tested SaveNoteCommand directly.

Save button showing the custom database icon and Save to ticket database tooltip

A month later the security team asks Vikram to disable copy-paste for confidential incidents. Same pattern: he subscribes to CopyButtonClicked and PasteButtonClicked, checks his classification flag on the ticket, and either lets the action through or shows a warning. The list of overridable events covers every factory command - NewButtonClicked, OpenButtonClicked, SaveButtonClicked, CutButtonClicked, CopyButtonClicked, PasteButtonClicked, PasteFromMSWordButtonClicked, PrintButtonClicked, UndoButtonClicked, RedoButtonClicked, BoldButtonClicked, ItalicButtonClicked, UnderlineButtonClicked, StrikeThroughButtonClicked, SubscriptButtonClicked, SuperScriptButtonClicked, TextHighlightColorButtonClicked, FontForeColorButtonClicked, HyperLinkButtonClicked, ImageButtonClicked, YouTubeVideoInsertButtonClicked, OrderedListButtonClicked, UnOrderedListButtonClicked, AlignLeftButtonClicked, AlignCenterButtonClicked, AlignRightButtonClicked, OutdentButtonClicked, IndentButtonClicked, TableInsertButtonClicked, SymbolInsertButtonClicked, HorizontalRuleButtonClicked, FormatResetButtonClicked, BodyStyleButtonClicked, SearchButtonClicked, and SpellCheckButtonClicked.

  • ToolbarItemOverrider.ToolbarItems.X exposes the live factory Button / ToggleButton / ComboBox for icon, tooltip, and visibility tweaks.
  • Subscribing to ToolbarItemOverrider.XButtonClicked suppresses the default behavior and routes the click to your handler instead.
  • Walk the factory button's visual tree to swap the inner Image.Source for a custom icon.
Last updated on May 15, 2026