Click here to Skip to main content
13,352,631 members (38,995 online)
Click here to Skip to main content
Add your own
alternative version


57 bookmarked
Posted 17 Feb 2010

A Simple, Integrated WPF Help System

, 1 Mar 2010
Rate this:
Please Sign up or sign in to vote.
Give your users quick, visual help without forcing them to leave your app.


I was recently confronted with the common problem of creating a help system for a WPF application I created. The program was a "standard" business application (buttons, lists, etc.), so I figured there was probably some built-in support system or new standard to easily provide user help. To my surprise, there was no clear standard, so I decided to create my own.


Initially, I assumed WPF would have built-in support for a help system standard such as CHM files. After some investigation, I discovered the AutomationProperties.HelpText attached property, but found that the consensus seems to be that it is an incomplete feature or was added for future use. I could not find anything in the framework that actually used the HelpText property directly and, for now, it is obviously meant to be consumed by a client application rather than drive an automated system (silly me, I took that whole "Automation" thing literally).

Potential Solutions

Before creating my solution, I thought of some of the existing ways to provide a help system to users (for business reasons, third party and/or Open Source solutions were pretty much not an option). This partial list addresses many of the issues I encountered:

  1. External documentation
    • Pros:
      • Ummm...give me a minute...
    • Cons:
      • As soon as you put something in a document, it is outdated.
      • User must find and open an external file, then switch between the app and the document.
      • I don't want to spend my day pasting screenshots into Word.
  2. Tooltips
    • Pros:
      • Familiar to users.
      • Built into WPF.
    • Cons:
      • No way to tell if a control has a tooltip or not; user must hover and wait on every control.
      • Tooltips are generally meant to contain only a few words, and I needed to potentially have two or three sentences.
      • "Always on" - once you know what a control does, you most likely will not need the tooltip again. However, if you pause over the control, it will pop up no matter what. This can be annoying if there is a lot of text in the tooltip.
  3. Sidebar help (like MS Office)
    • Pros:
      • Familiar to users.
      • Can be turned off/hidden.
      • Made to handle larger amounts of text.
    • Cons:
      • Best suited for a robust, fully indexed, and linked help system.
      • Changes existing UI layouts.

After looking at these options (and others), I decided that I liked the way tooltips worked the best. They tell the user exactly what will happen when they act on the control they are pointing at. The problem, however, is that I wanted to be able to turn them off like you can with the sidebar help.

My Solution

I decided that any sort of external solution was out of the question. WPF has many advanced features, and there is no reason I should force users to navigate away from my application or even open up a new window. I also did not want the help to ever "be in the way", so I knew it had to be able to be turned on and off easily. What I came up with was this simple, yet powerful, solution.

A sample screenshot

Hitting the F1 key puts a yellow highlight around the controls that have help available. When the mouse is placed over the control, the help text is displayed in a tooltip like fashion (there is no delay, however) using the handy Popup class. When the user is done, hitting F1 again removes the highlight and the help is no longer displayed. In the sample above, if the mouse is over button "Two", the help for the group box is displayed because "Two" has no help available. When you move the mouse to button "Three", the help for that button is displayed. Of course, when the mouse is moved over the canvas or a control with no help, the help bubble disappears.

How it Works

The code to make this work is rather simple. Let me first say, however, that this solution is not optimized, and I'm sure the WPF disciples among us will probably come up with better ways to walk the visual tree or use a visual effect that is more efficient (which BitmapEffects are not). I will gladly listen to suggestions.

Key classes/properties

  • System.Windows.Automation.AutomationProperties.HelpText
  • System.Windows.Controls.Primitives.Popup
  • System.Windows.Media.VisualTreeHelper
  • System.Windows.Media.Effects.OuterGlowBitmapEffect

Step 1 - XAML

<Window ...usual stuff... Name="winMain" KeyDown="winMain_KeyDown">
    <Canvas Name="canvMain">
        <Button Content="No Help" ... />
        <Button Content="Has Help" AutomationProperties.HelpText="I have help" ... />
    ...just the basic controls...

Step 2 - Members / Extension Method

public static class StringUtils
    public static bool IsNothing(this string value)
        return value == null || value.Trim().Length == 0;

// Members in Window1
private DependencyObject CurrentHelpDO { get; set; }
private Popup CurrentHelpPopup { get; set; }
private bool HelpActive { get; set; }
private MouseEventHandler _helpHandler = null;
private readonly OuterGlowBitmapEffect YellowGlow =
        new OuterGlowBitmapEffect() 
        { GlowColor = Colors.Yellow, GlowSize = 10, Noise = 1 };

Step 3 - F1 Key Causes Help Toggle

private void winMain_KeyDown(object sender, KeyEventArgs e)
    if (e.Key == Key.F1)
        e.Handled = true;

Step 4 - Recursively Toggle Help through the Visual Tree

private void ToggleHelp()
    // Turn the current help off
    CurrentHelpDO = null;
    if (CurrentHelpPopup != null)
        CurrentHelpPopup.IsOpen = false;

    // Toggle current state; add/remove mouse handler
    HelpActive = !HelpActive;

    if (_helpHandler == null)
        _helpHandler = new MouseEventHandler(winMain_MouseMove);

    if (HelpActive)
        winMain.MouseMove += _helpHandler;
        winMain.MouseMove -= _helpHandler;

    // Start recursive toggle at visual root

private void ToggleHelp(DependencyObject dependObj)
    // Continue recursive toggle. Using the VisualTreeHelper works nicely.
    for (int x = 0; x < VisualTreeHelper.GetChildrenCount(dependObj); x++)
        DependencyObject child = VisualTreeHelper.GetChild(dependObj, x);

    // BitmapEffect is defined on UIElement so our DependencyObject 
    // must be a UIElement also
    if (dependObj is UIElement)
        UIElement element = (UIElement)dependObj;
        if (HelpActive)
            string helpText = AutomationProperties.GetHelpText(element);
            if (!helpText.IsNothing())
                // Any effect can be used, I chose a simple yellow highlight
                ((UIElement)element).BitmapEffect = YellowGlow;
        else if (element.BitmapEffect == YellowGlow)
            element.BitmapEffect = null;

Step 5 - Track the Mouse

private void winMain_MouseMove(object sender, MouseEventArgs e)
    // You can check the HelpActive property if desired, however 
    // the listener should not be hooked up so this should not be firing
    HitTestResult hitTestResult = 
      VisualTreeHelper.HitTest(((Visual)sender), e.GetPosition(this));
    if (hitTestResult.VisualHit != null && 
            CurrentHelpDO != hitTestResult.VisualHit)
        // Walk up the tree in case a parent element has help defined
        DependencyObject checkHelpDO = hitTestResult.VisualHit;
        string helpText = AutomationProperties.GetHelpText(checkHelpDO);
        while (helpText.IsNothing() && checkHelpDO != null 
               && checkHelpDO != canvMain && checkHelpDO != winMain)
            checkHelpDO = VisualTreeHelper.GetParent(checkHelpDO);
            helpText = AutomationProperties.GetHelpText(checkHelpDO);

        if (helpText.IsNothing() && CurrentHelpPopup != null)
            CurrentHelpPopup.IsOpen = false;
            CurrentHelpDO = null;
        else if (!helpText.IsNothing() && CurrentHelpDO != checkHelpDO)
            CurrentHelpDO = checkHelpDO;
            // New visual "stack" hit, close old popup, if any
            if (CurrentHelpPopup != null)
                CurrentHelpPopup.IsOpen = false;

            // Obviously you can make the popup look anyway you want with
            // any number of options. I chose a simple tooltip look-and-feel.
            // (caching/reuse omitted for example)
            CurrentHelpPopup = new Popup()
                AllowsTransparency = true,
                PopupAnimation = PopupAnimation.Scroll,
                PlacementTarget = (UIElement)hitTestResult.VisualHit,
                Child = new Border()
                    CornerRadius = new CornerRadius(10),
                    BorderBrush = new SolidColorBrush(Colors.Goldenrod),
                      BorderThickness = new Thickness(2),
                      Background = new SolidColorBrush(Colors.LightYellow),
                      Child = new TextBlock()
                        Margin = new Thickness(10),
                        Text = helpText.Replace("\\r\\n", "\r\n"),
                        FontSize = 14,
                        FontWeight = FontWeights.Normal
            CurrentHelpPopup.IsOpen = true;

Update - 2/28/10

One of the things I didn't like about this solution was that traversing the visual tree did not seem very WPF'ish. After Pete O'Hanlon reminded me about the built-in ApplicationCommand.Help command, I took another look and came up with a different approach. It's rather simple, you just create a value converter that converts the boolean HelpActive property (which is now a DependencyProperty) to BitmapEffect. If help is active, you return the yellow glow object; otherwise just null. The only issue then is that you have to bind the BitmapEffect property of each control that has help set. This can be done individually or on a per-Style basis. If anyone knows a way to link the BitmapEffect and HelpText properties so you only have to set the help, please let me know.

[ValueConversion(typeof(bool), typeof(BitmapEffect))]
public class GlowConverter : IValueConverter
    private OuterGlowBitmapEffect _glow = null;

    public GlowConverter()
        _glow = new OuterGlowBitmapEffect() 
              { GlowSize = 10, Noise=1, GlowColor = Colors.Yellow };

    public object Convert(object value, Type targetType, 
                  object parameter, CultureInfo culture)
        return ((bool)value) ? _glow : null;

    public object ConvertBack(object value, Type targetType, 
                  object parameter, CultureInfo culture)
        return value != null;


public partial class Window1 : Window
    public static readonly DependencyProperty HelpActiveProperty =
    DependencyProperty.Register("HelpActive", typeof(bool), typeof(Window1), 
       new FrameworkPropertyMetadata(false,

    public bool HelpActive
            return (bool)GetValue(HelpActiveProperty);
            SetValue(HelpActiveProperty, value);

    public Window1()

        CommandBindings.Add(new CommandBinding(ApplicationCommands.Help,
            (x, y) => HelpActive = !HelpActive,
            (x, y) => y.CanExecute = true));

<gui:GlowConverter x:Key="GlowChange" />

<Style TargetType="Button">
    <Setter Property="Background" Value="Red" />
    <Setter Property="BitmapEffect"

            Value="{Binding Source={x:Static win:Application.Current},
        Path=MainWindow.HelpActive, Converter={StaticResource GlowChange}}" />


Despite the fact that there is no built-in help system in WPF, I was pleasantly surprised to discover how easy it was to create one. I will be the first to admit this solution is not perfect, but I feel it is a good fit for a lot of different applications.

Update History


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Josh Fischer
United States United States
CodeProject MVP 2010
CodeProject prize winner - Best C# article of December 2009

You may also be interested in...


Comments and Discussions

QuestionSimpler Solution.... Pin
jogibear998816-Sep-13 13:09
memberjogibear998816-Sep-13 13:09 
AnswerRe: Simpler Solution.... Pin
rcbapb225-Jan-15 2:14
memberrcbapb225-Jan-15 2:14 
GeneralRe: Simpler Solution.... Pin
rcbapb229-Jan-15 3:31
memberrcbapb229-Jan-15 3:31 
GeneralVery interesting feature Pin
Bjorn Backlund10-Nov-10 6:01
memberBjorn Backlund10-Nov-10 6:01 
GeneralIf anyone is using .Net 4 the Glow effect will no longer work Pin
gardnerp11-Oct-10 9:36
membergardnerp11-Oct-10 9:36 
GeneralRe: If anyone is using .Net 4 the Glow effect will no longer work Pin
Josh Fischer11-Oct-10 12:42
mvpJosh Fischer11-Oct-10 12:42 
GeneralRe: If anyone is using .Net 4 the Glow effect will no longer work Pin
Sheridan1uk21-Oct-10 12:40
memberSheridan1uk21-Oct-10 12:40 
QuestionCan it be used for all controls? Pin
Subrahmanya Narayana10-Aug-10 4:59
memberSubrahmanya Narayana10-Aug-10 4:59 
AnswerRe: Can it be used for all controls? Pin
Josh Fischer12-Aug-10 6:09
mvpJosh Fischer12-Aug-10 6:09 
GeneralBrilliant! 5/5 Pin
John Adams4-Aug-10 7:02
memberJohn Adams4-Aug-10 7:02 
This is exactly what I was looking for. Thanks!!

public Window1()
        new FrameworkPropertyMetadata(
            (dependencyObject, e) =>
                Debug.Assert(dependencyObject is FrameworkElement); // valid assumption? if not, use 'as' cast
                Debug.Assert(e.NewValue is string || e == null);

                FrameworkElement frameworkElement = (FrameworkElement)dependencyObject;
                if (!string.IsNullOrEmpty((string)e.NewValue))
                        new Binding("HelpActive")
                            Source = this,
                            Converter = new GlowConverter()
                    frameworkElement.SetValue(FrameworkElement.BitmapEffectProperty, null);


The above should work with the second (updated) implementation. However, there is a caveat. While the metadata is merged safely with the AutomationProperties.HelpTextProperty metadata, you won't be able to override it again on FrameworkElement. You can't define metadata twice for a type. So this solution would blow up if you need to override AutomationProperties.HelpTextProperty's metadata on FrameworkElement again for some other feature you have, or if Wpf 5.0+ decides to do it internally for some cool feature they add. Neither are likely, but it's good to know.

You might want to create your own attached property for help text. Then you would be the proper owner of the property and free to use it however you like. To be honest, I'm not too familiar with AutomationProperties.HelpTextProperty, but I think it's mostly for helping people with disabilities. If that's the case one might expect to see a different sort of help string assigned to it. Hard to say.

Also, while looking through this I saw Microsoft deprecated BitmapEffect in favor of Effect! Oh well...
GeneralRe: Brilliant! 5/5 Pin
Josh Fischer4-Aug-10 13:16
mvpJosh Fischer4-Aug-10 13:16 
GeneralVery nice Pin
Pete O'Hanlon19-Feb-10 7:12
mvpPete O'Hanlon19-Feb-10 7:12 
GeneralRe: Very nice Pin
Josh Fischer22-Feb-10 3:38
mvpJosh Fischer22-Feb-10 3:38 
GeneralRe: Very nice Pin
Pete O'Hanlon22-Feb-10 13:23
mvpPete O'Hanlon22-Feb-10 13:23 
GeneralGood job, but have you also seen this Pin
Sacha Barber17-Feb-10 21:08
mvpSacha Barber17-Feb-10 21:08 
GeneralRe: Good job, but have you also seen this Pin
Josh Fischer18-Feb-10 4:31
mvpJosh Fischer18-Feb-10 4:31 
GeneralRe: Good job, but have you also seen this Pin
Sacha Barber18-Feb-10 5:19
mvpSacha Barber18-Feb-10 5:19 
GeneralNice Pin
Marcelo Ricardo de Oliveira17-Feb-10 12:19
memberMarcelo Ricardo de Oliveira17-Feb-10 12:19 
GeneralRe: Nice Pin
Josh Fischer17-Feb-10 15:33
mvpJosh Fischer17-Feb-10 15:33 
QuestionWinforms Help System Pin
Ant210017-Feb-10 12:18
memberAnt210017-Feb-10 12:18 
AnswerRe: Winforms Help System Pin
Josh Fischer17-Feb-10 15:31
mvpJosh Fischer17-Feb-10 15:31 
AnswerRe: Winforms Help System Pin
Sacha Barber17-Feb-10 21:05
mvpSacha Barber17-Feb-10 21:05 
GeneralRe: Winforms Help System Pin
Ant210018-Feb-10 1:22
memberAnt210018-Feb-10 1:22 
GeneralRe: Winforms Help System Pin
Sacha Barber18-Feb-10 1:25
mvpSacha Barber18-Feb-10 1:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.180111.1 | Last Updated 1 Mar 2010
Article Copyright 2010 by Josh Fischer
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid