Home > Windows Presentation Foundation > Easy help with WPF

Easy help with WPF

If, like me, you like your applications to provide Context sensitive help, you’ve probably had a play around with the ApplicationCommands.Help command. In order to simplify hooking your help into your application, I’ve written the following code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using form = System.Windows.Forms;
using System.Windows.Media;

namespace HelpProvider
{
  /// <summary>
  /// This class provides the ability to easily attach Help functionality 
  /// to Framework elements. To use it, you need to
  /// add a reference to the HelpProvider in your XAML. The FilenameProperty
  /// is used to specify the name of the helpfile, and the KeywordProperty specifies
  /// the keyword to be used with the search.
  /// </summary>
  /// <remarks>
  /// The FilenameProperty can be at a higher level of the visual tree than
  /// the KeywordProperty, so you don't need to set the filename each time.
  /// </remarks>
  public static class Help
  {
    /// <summary>
    /// Initialize a new instance of <see cref="Help"/>.
    /// </summary>
    static Help()
    {
      // Rather than having to manually associate the Help command, let's take care
      // of this here.
      CommandManager.RegisterClassCommandBinding(typeof(FrameworkElement),
        new CommandBinding(ApplicationCommands.Help,
          new ExecutedRoutedEventHandler(Executed),
          new CanExecuteRoutedEventHandler(CanExecute)));
    }

    #region Filename

    /// <summary>
    /// Filename Attached Dependency Property
    /// </summary>
    public static readonly DependencyProperty FilenameProperty =
      DependencyProperty.RegisterAttached("Filename", typeof(string), typeof(Help));

    /// <summary>
    /// Gets the Filename property.
    /// </summary>
    public static string GetFilename(DependencyObject d)
    {
      return (string)d.GetValue(FilenameProperty);
    }

    /// <summary>
    /// Sets the Filename property.
    /// </summary>
    public static void SetFilename(DependencyObject d, string value)
    {
      d.SetValue(FilenameProperty, value);
    }

    #endregion
    
    #region Keyword

    /// <summary>
    /// Keyword Attached Dependency Property
    /// </summary>
    public static readonly DependencyProperty KeywordProperty =
      DependencyProperty.RegisterAttached("Keyword", typeof(string), typeof(Help));

    /// <summary>
    /// Gets the Keyword property.
    /// </summary>
    public static string GetKeyword(DependencyObject d)
    {
      return (string)d.GetValue(KeywordProperty);
    }

    /// <summary>
    /// Sets the Keyword property.
    /// </summary>
    public static void SetKeyword(DependencyObject d, string value)
    {
      d.SetValue(KeywordProperty, value);
    }
    #endregion

    #region Helpers
    private static void CanExecute(object sender, CanExecuteRoutedEventArgs args)
    {
      FrameworkElement el = sender as FrameworkElement;
      if (el != null)
      {
        string fileName = FindFilename(el);
        if (!string.IsNullOrEmpty(fileName))
          args.CanExecute = true;
      }
    }

    private static void Executed(object sender, ExecutedRoutedEventArgs args)
    {
      // Call ShowHelp.
      DependencyObject parent = args.OriginalSource as DependencyObject;
      string keyword = GetKeyword(parent);
      if (!string.IsNullOrEmpty(keyword))
      {
        form.Help.ShowHelp(null, FindFilename(parent), keyword);
      }
      else
      {
        form.Help.ShowHelp(null, FindFilename(parent));
      }
    }

    private static string FindFilename(DependencyObject sender)
    {
      if (sender != null)
      {
        string fileName = GetFilename(sender);
        if (!string.IsNullOrEmpty(fileName))
          return fileName;
        return FindFilename(VisualTreeHelper.GetParent(sender));
      }
      return null;
    }
    #endregion

  }
}

Using it couldn’t be simpler, set up the Filename in your XAML and add any keywords you need to search on against your FrameworkElement items. The advantage of this approach is that you can bind different parts of your UI to different helpfiles if you want.

<Window 
  x:Class="HelpSample.Window1"
  xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
  xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
  Title="Window1" Height="300" Width="300"
  xmlns:help="clr-namespace:HelpProvider;assembly=HelpProvider"
  help:Help.Filename="MyHelpfile.chm"
  >
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>
    <TextBox help:Help.Keyword="MyKeyword" Grid.Row="0" Text="Keyword based search" />
    <TextBox Grid.Row="1" Text="No keyword" />
  </Grid>
</Window>

I hope this helps you as much, as it helps me.

About these ads
  1. May 2, 2009 at 3:38 pm

    I love that one! Thanks for the sample :-)

    • peteohanlon
      May 2, 2009 at 7:52 pm

      That’s the fuel that keeps me going Philipp. Glad you like it.

  2. May 2, 2009 at 4:09 pm

    Pete,

    Nice work sir! Like the way the help file can be scoped.

    I assume for a one off topic, the Filename and Keyword could be passed and used.

    See you soon Mate,

    Cheers,

    Karl

  3. May 2, 2009 at 4:09 pm

    Pete,

    Nice work sir! Like the way the help file can be scoped.

    I assume for a one off topic, the Filename and Keyword could be passed and used.

    See you soon Mate,

    Cheers,

    Karl

    • peteohanlon
      May 2, 2009 at 7:51 pm

      Cheers Karl. You could pass the keyword and filename for a one off topic.

  4. Walt Ritscher
    May 2, 2009 at 10:48 pm

    This is really nice. Thanks for sharing.

    • peteohanlon
      May 3, 2009 at 7:39 pm

      Thanks Walt. I like this one.

  5. May 25, 2009 at 6:03 am

    Great, thanks!

  6. May 28, 2009 at 7:18 pm

    This is a nice implementation – thanks for sharing it. It’s too bad WPF doesn’t have this built in frankly.. but isn’t it cool how you can bolt on additional features?

    Cheers,
    mark

    • peteohanlon
      May 28, 2009 at 7:29 pm

      Mark, I love the extensibility of WPF. It’s just so cool.

  7. thomas
    May 29, 2009 at 10:49 am

    looking for the filename in canexecute is a cpu and time consuming action.
    better to do that once per filename and then store it in a static variable.

    anyway…good solution :)

  8. micaleel
    June 16, 2009 at 10:01 pm

    Thanks, this has been very useful.

    • peteohanlon
      June 17, 2009 at 9:14 am

      I’m glad it’s been of help to you. That’s the music that keeps me blogging.

  9. June 17, 2009 at 9:56 am

    Thanks, this has been very helpful.

    • micaleel
      June 17, 2009 at 10:03 am

      Ooops, please disregard this comment. I totally forgot that I already made the coment. It shows you how truly helpful it was, hehe. Thanks anyway.

  10. July 26, 2009 at 5:31 am

    Nice! Very very nice!!

  11. Peter M
    September 6, 2009 at 8:06 pm

    Great code for help. It was exactly what I was looking for today.

    One question I have is how do you tie this system into a menu item? I tried setting up a menuitem with a command=”help” and this correctly invoked the Executed() method, but it threw an exception on:

    form.Help.ShowHelp(null, FindFilename(parent));

    It seems that FindFilename() continued to walk up the tree looking for a parent object with the correct Filename dependency property until it ran out of things to check.

    • peteohanlon
      September 6, 2009 at 8:15 pm

      @Peter – thanks for the comment. I assume that you’ve hooked your menu item up to ApplicationCommands.Help – if so, this should work out of the box, as long as you’ve put the filename somewhere up the visual tree. In the sample, I hook the filename into the window definition.

  12. September 8, 2009 at 12:57 pm

    Pete,
    I had ignored that part of the program since I wrote the comment and now that I tested it again I find that it works – yet I have done nothing except (most likely) done a clean compile in between. I’ll chalk that one up to the PF gods not liking me this weekend.

  13. September 8, 2009 at 1:21 pm

    Pete .. actually I take that back .. I just encountered my error. I have:

    When I hit F1 the help comes up as desired. But if I navigate to the Help item in the menu and select it I get the exception I was referring too above:

    Type: System.ArgumentException
    Location: System.Windows.Forms.Help.ShowHTMLFile()
    Parameter: URL
    Message: Help URL ” is not valid

    From my understanding it is as if my menu item is not in the Visual Tree for some reason (and I don’t have any experience to know if it should or shouldn’t be there)

  14. September 8, 2009 at 1:23 pm

    Damn comment software swallowed my XAML. But yes I have a reasonably configured system with the Help file defined in the Window tag

  15. October 13, 2009 at 11:19 am

    Thank you for this piece of code, it was exactly what I needed! Keep up the good work, I’ll be reading your stuff from now on!

    • peteohanlon
      October 14, 2009 at 11:46 am

      Thanks Tomas. I’m glad you like it.

  16. Matt R
    November 3, 2009 at 2:14 pm

    This is great, thanks! Is there a way to have the sender react to a mouse over rather than a click or focused property? For example, I want to be able to hover over a button, press F1, and have that button be the sender. Thanks in advance for any advice that anyone has on this!

  17. November 7, 2009 at 8:53 am

    Hi,

    great work, awarded you 5 sticky stars…

    • peteohanlon
      November 7, 2009 at 12:53 pm

      Thanks for that – it feels gooey, in a good way;->

      • November 14, 2009 at 7:45 am

        Hi,

        came back to deliver the stars properly:

  18. Robert
    November 12, 2009 at 7:43 am

    Nice idea, thanks.

    Instead of your recursive FindFilename function, I’d go with inheritable DependencyProperties, for both FileName and Keyword. Simpler code, and probably faster.

    I’d also be cautious doing that much in CanExecute.

  19. Chad
    March 9, 2010 at 4:32 pm

    This is a very nice solution — thank you!

    I’ve got it working for individual elements within a window (which was the aim I believe), but I would also like to set it for the entire window as well. In some of my windows I won’t have help for each element but just for the whole window. Am I missing how to do this?

    • Chad
      March 9, 2010 at 6:58 pm

      I solved my question by creating another helper method called FindKeyword structured just like FindFilename which looks up the tree for a keyword. Pretty simple once I thought about it. Thanks again for the solution.

  20. Richard
    March 11, 2010 at 8:19 pm

    “using form = System.Windows.Forms;”

    Eeew! You’ve just loaded a 5Mb assembly to use one feature! That’s so *nasty*!

    Seriously though, if you’re only using CHM files, it’s not too hard to P/Invoke the HtmlHelp function from hhctrl.ocx to avoid the WinForms dependency.

    public enum NativeHtmlHelpCommand
    {
    Topic = 0,
    TableOfContents = 1,
    Index = 2,
    }

    [DllImport("hhctrl.ocx", CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)]
    public static extern int HtmlHelp(
    HandleRef hwndCaller,
    [MarshalAs(UnmanagedType.LPTStr)] string pszFile,
    NativeHtmlHelpCommand uCommand,
    [MarshalAs(UnmanagedType.LPTStr)] string dwData);

  21. March 12, 2010 at 12:25 pm

    Thanks for the post, I like the direction, but some problems:

    Already very similar to the code posted here
    http://blogs.msdn.com/mikehillberg/archive/2007/07/26/a-context-sensitive-help-provider-in-wpf.aspx

    However, in that post the author does not have to make the recursive function call – rather you can just use the nature of routed events..
    “We’ll also register for the command’s CanExecute handler. This allows us to decide if we should handle a command at an element, or let it bubble up to a higher handler. That is, this will allow the Help command to bubble up from AddressField, which has no HelpString, to Window which does.”

    Second, I totally agree with the last comment – however I blame WPF — is this really the case? That to show help files/chm in WPF, the best practice is to depend on System.Windows.Forms ??? That’s ridiculous.

  22. Christian LOUIS
    May 20, 2010 at 8:05 am

    Nice article. But I cannot compile my project because I have an error for the line “help:Help.Filename=”MyHelpfile.chm” error : property Filename cannot be find in the type Help. Have I to add any thing or configure the project? What is wrong?

    Best regards

    • Oleh
      February 20, 2012 at 5:53 pm

      Build->Rebuild Solution will help

  23. Raghu
    September 23, 2010 at 12:16 pm

    Hi sir i am getting error in xaml file that Attachable Property Filename was not found in Help please help me i am new to WPF i got work to implement help system for our internal project

    Thanks
    Raghu

  24. Raghu
    September 23, 2010 at 12:42 pm

    OK sir now i cleared that help but if i press F1 nothing is working sir how to make this work

    Thanks
    Raghu

  25. Raghu
    September 23, 2010 at 12:45 pm

    If any one is online please take look at this asap

  26. Raghu
    September 23, 2010 at 12:48 pm

    Please teach me Step by step how to make this help feature work

    i dont even know how to create chm files where to store to invoke all these things

    please help me

    Regards
    Raghu

  27. Raghu
    September 23, 2010 at 1:13 pm

    ok i sort out this problem too but the page corresponds to keyword of text box is not displaying.Its showing error This program cannot display the webpage.

    Thanks
    Raghu

  28. A123
    November 8, 2010 at 7:09 am

    hi, i’m having the same problem i can’t find the attached properties filename and keyword when trying to add this to my xaml, can u tell me how you solved it?

  29. November 10, 2010 at 11:36 pm

    Great work, nice to see people doing Help in WPF apps.
    We’re a new company that specializing in “XAML-based Help for XAML platforms”, e.g help for WPF, SL and WP7 apps. We’re working on a few solutions that might be of interest. See http://www.BackSpinSoftware.com for more details.
    Best,
    —bjorn

  30. Vishal
    January 12, 2011 at 9:34 am

    Hello

    Very nice post. We have used this in our module where UI is in WPF.

    Regards.

    • peteohanlon
      January 12, 2011 at 9:37 am

      Thanks. I’m glad it’s been of benefit to you.

  31. Jake
    June 1, 2011 at 9:34 am

    2 years after, your code is still helping people (like me!).

    My only suggested improvement would probably be, able to go directly to a specific page.. or maybe I haven’t found out how to do that yet using your sample.

    Thanks a lot for this!

    • peteohanlon
      June 3, 2011 at 8:00 pm

      Thanks Jake. I’m glad it’s still helping after all this time.

  32. Boo
    July 7, 2011 at 3:19 am

    HI

  33. Boopathi
    July 7, 2011 at 3:22 am

    Your code is very nice. But it is not working while passing ‘Keyword” to help menthod.for this case chm file was opened but not selecting given keyword topic.

    Ex :
    form.Help.ShowHelp(null, FindFilename(parent), form.HelpNavigator.Topic, (Object)keyword);

    Please correct me.

    Advance Thanks…

  34. Abhishek S
    November 24, 2011 at 11:47 am

    @Boopathi
    set Keyword to the URL path of HTM/page file of CHM app.

  35. December 8, 2011 at 9:15 am

    Nice code~~~!! Thank you

  36. Bing
    February 9, 2012 at 1:52 pm

    This example works great for us.

    The question I have is how can I give a path to “MyHelpfile.chm”. It works if I hard code the path in help:Help.Filename, but I want to use a “global” variable to store the path of MyHelpfile.chm”

    Thanks

    • peteohanlon
      February 9, 2012 at 2:08 pm

      Bing – the easiest way to achieve this would be to just modify the codebase to fall back to that variable if the filename wasn’t set. So, if you got null out of FindFilename, just go to your variable.

  37. canatan
    February 16, 2012 at 12:14 pm

    is there anyway we can use the stuff in this class and bind it to a button in mvvm ? so that when you click the button the help file is displayed?

  38. LWDaddio
    March 11, 2012 at 11:18 pm

    I’m trying to use this but running into problems. I’m using WPF with .NET 4.0. I have the declarations in the Window and with that using F1 brings up the .chm file just fine. But if I try to add help to a menuitem, I have to also specify the filename or it doesn’t find it and throws an exception. And if I try to use a keyword, the .chm opens at the Index tab, but the keyword field is blank and so it doesn’t show the indexed page. Any idea what I might be doing wrong here? Thanks!

  39. Ntx
    April 10, 2012 at 11:55 am

    Awesome, still very useful. Thank you Pete!

  40. November 27, 2013 at 12:21 pm

    Abhishek S :
    @Boopathi
    set Keyword to the URL path of HTM/page file of CHM app.

    Hi
    I have an chm file made with Help&Manual.
    What path should I use for KeywordProperty ?
    I can view the help file but I can’t use Keyword (it’s showing a blank topic with an not found error).
    Thank’s
    Sorin

  1. November 7, 2009 at 7:37 am
  2. December 25, 2012 at 6:30 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Canny Coder

Java 8 Functional Programming with Lambda Expressions

pihole.org

Adventures in theoretical computer science, with your host, chaiguy1337

Confessions of a coder

Confessions of a WPF lover

WordPress.com

WordPress.com is the best place for your personal blog or business site.

Follow

Get every new post delivered to your Inbox.

Join 39 other followers

%d bloggers like this: