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.
Categories: Windows Presentation Foundation
Help, HelpProvider, WPF



I love that one! Thanks for the sample
That’s the fuel that keeps me going Philipp. Glad you like it.
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
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
Cheers Karl. You could pass the keyword and filename for a one off topic.
This is really nice. Thanks for sharing.
Thanks Walt. I like this one.
Great, thanks!
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
Mark, I love the extensibility of WPF. It’s just so cool.
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
Thanks, this has been very useful.
I’m glad it’s been of help to you. That’s the music that keeps me blogging.
Thanks, this has been very helpful.
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.
Nice! Very very nice!!
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.
@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.
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.
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)
Damn comment software swallowed my XAML. But yes I have a reasonably configured system with the Help file defined in the Window tag
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!
Thanks Tomas. I’m glad you like it.
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!
Hi,
great work, awarded you 5 sticky stars…
Thanks for that – it feels gooey, in a good way;->