Click here to Skip to main content
12,294,475 members (65,234 online)
Click here to Skip to main content

Stats

47.2K views
837 downloads
56 bookmarked
Posted

A Simple, Integrated WPF Help System

, 1 Mar 2010 CPOL
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)

Share

About the Author

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


You may also be interested in...

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