Invoking Help Programmatically
The examples so far require the user to press the F1 key. This automated approach doesn't work as well if you want to provide your own buttons that allow the user to trigger help when needed. Sometimes, this sort of prominent reminder can reassure the user that help is nearby.To trigger help programmatically, you need to use the static ShowHelp() method of the Help class (found in the System.Windows.Forms namespace). The Help class works analogously to the HelpProvider-in fact, the HelpProvider uses the Help class behind the scenes when the user presses F1.There are several overloaded versions of the ShowHelp() method. The simplest requires a help filename (or URL) and the control that is the parent for the help dialog (this second parameter is required for low-level Windows operating system reasons). Here's an example that shows the test.hlp file:
Help.ShowHelp(this, "test.hlp");
Additionally, you can use a version of the ShowHelp() method that requires a HelpNavigator, one that requires a keyword, or one that requires both a keyword and a HelpNavigator. Here's an example that could be used for context-sensitive help:
Help.ShowHelp(this, "test.hlp", HelpNavigator.Topic, "about");
To save yourself some work when using this technique with the HelpProvider, you would probably retrieve these values from another control. For example, you might provide a button on your form that invokes the default form help:
private void cmdHelp_Click(object sender, System.EventArgs e)
{
Help.ShowHelp(this, hlp.HelpNamespace, hlp.GetHelpNavigator(this),
hlp.GetHelpKeyword(this));
}
Similarly, you might use a right-click context menu for a control that provides the control's default help:
private void mnuHelp_Click(object sender, System.EventArgs e)
{
Control ctrl = mnuLabel.SourceControl;
Help.ShowHelp(ctrl, hlp.HelpNamespace, hlp.GetHelpNavigator(ctrl),
hlp.GetHelpKeyword(ctrl));
}
This menu event handler is written using the SourceControl property, which means it's generic enough to be used with any control. When the menu is clicked, it retrieves the control attached to the menu, and gets its assigned Help keyword.
Help Without the HelpProvider
Now that you are this far, it's possible to unshackle yourself completely from the HelpProvider class. It works like this-handle the KeyDown event of every form that should display help, and check for the F1 key. If it is pressed, launch the appropriate help programmatically with the Help class.There are two tricks to making this work. The first one is setting the form's KeyPreview property to true, which makes sure it will receive all key press events, regardless of what control has focus. The second sticky point is to make sure you create an event handler for the KeyDown event, not the KeyPress event, which doesn't react to the special F1 key.The code itself is simple:
private void form1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == Keys.F1)
{
Help.ShowHelp(this, "test.hlp");
}
}
So now that you've seen how it can be done, why would you want to do it? There are actually a number of reasons that you might take this approach when using context-sensitive Help. You examine two of the most common in the next two sections.
Using Database-Based Help
Help files, like any other external resource, change. You don't want to embed information like topic URLs all over your user interface, because they are difficult and time-consuming to update. Instead, you can use a basic form event handler that calls a method in a custom AppHelp class. It would look something like this:
private void form1_KeyDown(object sender System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == Keys.F1)
{
Global.Help.ShowHelp(this);
}
}
The Global class simply provides the current AppHelp instance through a static Help member:
public class Global
{
public static AppHelp Help;
}
The AppHelp.ShowHelp() method examines the submitted form, compares it with a list of forms in a database, and thus determines the appropriate context topic, which it launches. Note that for performance reasons, this list of form-topic mappings would be read once when the application starts, and stored in a member variable.The AppHelp class is shown in the following example. The database code needed to retrieve the FormHelpMappings table has been omitted.
public class AppHelp
{
public DataTable FormHelpMappings = null;
public string HelpFile = ";
public void ShowHelp(Form helpForm)
{
foreach (DataRow row in FormHelpMappings.Rows)
{
if (helpForm.Name == Row["FormName"])
{
// A match was found. Launch the appropriate help topic.
Help.ShowHelp(helpForm, HelpFile, HelpNavigator.Topic,
Row["Topic"]);
return;
}
}
}
}
Using Task-Based Help
Another reason you might take control of the help process is to get around the limitations of form-based help. Form-specific help works well in a dialog-based application, but falters when you create a document-based or workspace-based program where users perform a number of different tasks from the same window. Rather than try to write the code needed to dynamically modify help keywords, you can use the AppHelp class to track the current user's task. When Help is invoked, you can use this information to determine what topic should be shown.Here's the remodeled AppHelp class. Note that in this case, it doesn't decide what topic to show based on form name, but based on one of the preset task types. The logic that links tasks to topics is coded centrally in the AppHelp class (not in the user interface), and it could be moved into a database for even more control. An enumeration is used to ensure that the client code always sets a valid value.
public class AppHelp
{
// These are the types of tasks that have associated help topics.
public enum Task
{
CreatingReport,
CreatingReportWithWizard,
ManagingReportFiles,
ImportingReport
}
public string HelpFile = ";
public Task CurrentTask;
// Show help based on the current task.
public void ShowHelp(Form helpForm)
{
string topic = "
switch (CurrentTask)
{
case Task.CreatingReport:
topic = "Reports";
break;
case Task.CreatingReportWithWizard:
topic = "Wizard";
break;
case Task.ManagingReportFiles:
topic = "Reports";
break;
case Task.ImportingReport:
topic = "Importing";
break;
}
Help.ShowHelp(helpForm, HelpFile, HelpNavigator.Topic, topic);
}
}
Now, the code simply needs to "remember" to set the task at the appropriate times.
Global.Help.CurrentTask = AppHelp.Task.CreatingReport;
When help is invoked, the form doesn't need to determine what task is underway-the AppHelp class simply uses the most recent task setting.
private void form 1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == Keys.F1)
{
Global.Help.ShowHelp(this);
}
}
This system could be made much more complex by using a task list or tracking multiple types of context information in the AppHelp class, which is conceptually similar to how many advanced consumer applications (like office productivity software) work.
Creating Your Own Help
Another advanced option you might want to pursue is creating your own Help from scratch, rather than relying on one of the formats I've described. This technique has significant drawbacks: namely, you surrender advanced features like text searching, hierarchical table of contents, and an index. However, it also has significant advantages, the most important being that you can easily integrate Help content into your application. With the current HTML Help system, it is almost impossible to embed and control a help window in your application. MS Help 2 promises some improvements, but the required tools have not yet appeared.Creating your own Help generally follows two approaches:
You store help as long strings in a database record. This generally works best when you are using your custom Help for error messages, a tip of the day feature, or some other simple content.
You store links to an HTML file that is contained in the program directory (or a Help subdirectory). This allows you to easily create files using any HTML design tool, take advantage of linking, and even provide the Help externally (possibly through an Internet browser). Hosting an HTML window in your application is much easier than trying to integrate a help window.
These designs allow you to provide a design like the one shown in Figure 14-8. It provides a slide-out window that can be used to give a list of steps with information for the current task. The information itself is retrieved from a database and displayed in the application.
Figure 14-8: Integrated custom Help
You'll notice that this .NET example uses a RichTextBox control to display a formatted list of instructions. RichTextBox controls do not support linking, which makes them less useful for complex Help than a full HTML window. Unfortunately, the .NET Framework does not provide a dedicated HTML control. Instead, you need to import the Internet Explorer ActiveX control, which will efficiently provide the same functionality (see Figure 14-9).
Figure 14-9: Integrated custom HTML Help
This design begins to enter a new topic: application-embedded support, which examines how help can be integrated into applications.