Click here to Skip to main content
15,897,371 members
Articles / Desktop Programming / WPF

A Simple, Integrated WPF Help System

Rate me:
Please Sign up or sign in to vote.
4.97/5 (16 votes)
1 Mar 2010CPOL5 min read 89.4K   1.1K   57  
Give your users quick, visual help without forcing them to leave your app.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Resources;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Automation;
using System.Windows.Media.Effects;
using System.Windows.Controls.Primitives;

namespace WpfTestApp
{
    public partial class Window1 : Window
    {

        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 };

        public Window1()
        {
            InitializeComponent();
        }

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

        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;
            }
            else
            {
                winMain.MouseMove -= _helpHandler;
            }

            // Start recursive toggle at visual root
            ToggleHelp(canvMain);
        }

        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);
                ToggleHelp(child);
            }

            // 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;
                }
            }
        }

        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 it to look 
                    // with any number of options. I chose a simple tooltip look-and-feel.
                    CurrentHelpPopup = new Popup()
                    {
                        AllowsTransparency = true,
                        PopupAnimation = PopupAnimation.Scroll,
                        PlacementTarget = (UIElement)hitTestResult.VisualHit,
                        IsOpen = true,
                        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;
                }
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Architect
United States United States
Expert in C#, .NET, WinUI/WPF, Azure, and SQL Server.
I started working with .NET and C# professionally in 2003 and never looked back. I have been an integral part of a dozen complete software lifecycles, been a driving force behind two successful startups, and have led development teams.

Comments and Discussions