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.
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!
Pingback: WPFGlue Patterns « The WPFGlue Blog
Hi,
great work, awarded you 5 sticky stars…
Thanks for that – it feels gooey, in a good way;->
Hi,
came back to deliver the stars properly:
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.
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?
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.
Hi How did you achieve for the entire window?
“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);
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.
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
Build->Rebuild Solution will help
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
OK sir now i cleared that help but if i press F1 nothing is working sir how to make this work
Thanks
Raghu
If any one is online please take look at this asap
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
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
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?
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
Hello
Very nice post. We have used this in our module where UI is in WPF.
Regards.
Thanks. I’m glad it’s been of benefit to you.
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!
Thanks Jake. I’m glad it’s still helping after all this time.
HI
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…
@Boopathi
set Keyword to the URL path of HTM/page file of CHM app.
Nice code~~~!! Thank you
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
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.
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?
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!
Awesome, still very useful. Thank you Pete!
Pingback: My Pocket reads (readitlater) « Opposable Bits
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
Hi Pete – 6 years on and your code is still being used 🙂 I too am having problems with the Keyword part of things. I’ve added keywords to the htm files and successfully run ‘Test/Keyword lookup…’ in help workshop, and tested that the keyword is correctly being passed to the Executed method, but on hitting F1, the help viewer opens with ‘This page can’t be displayed’ and asks me to ensure the address: //ieframe.dll/dnserrordiagoff.htm# is correct.
Is there a need to prepend the htm page as Abhishek S suggests, or am i doing something else wrong?
OIC, your solution doesn’t use the “Keywords” that HTML Help Workshop refers to; with Easy Help, you set the Keyword attached property to the name of the htm file (including extension). So you don’t have (for eg) 1 htm file per application window with multiple Keywords, you have one htm file per help topic.
Many thanks for the code Pete. Got it now. 🙂
Abhishek, mcalex,
Thanks for your comments. Was having problems with “Keywords” also. Just want to add that in my .chm file I’m storing .htm pages in a separate folder “html.” To expand on mcalex’s answer, I needed to add the following to my XAML to get Easy Help to jump to the right page:
help:Help.Keywork=”html\IntroPage.htm”
Backslash was needed. Forward slash did not work. “.htm” extension needed as well.
And, Pete, let me add my “thank-you” to the others.