|
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Windows.Forms;
using System.Text;
using System.Xml;
namespace ContextHelpMadeEasy
{
/// <summary>
/// Utility class for maintaining relationship between context Id and path.
/// </summary>
public class ContextIDHTMLPathMap
{
public string ContextID;
public string HTMLPath;
public ContextIDHTMLPathMap(string ID, string Path)
{
ContextID = ID;
HTMLPath = Path;
}
}
/// <summary>
/// This class implements support for context sensitive help, including both runtime
/// behavior of looking up the appropriate help file and launching help, and the
/// development time behavior of permitting help writers to create mappings between
/// a user context and an HTML help page.
/// </summary>
public static class HelpUtility
{
/// <summary>
/// The local cache of ContextID to HTMLHelp Paths mapping.
/// </summary>
static private StringDictionary ms_sdContextPaths = null;
private const string mc_sMAPPING_FILE_NAME = "HelpContextMapping.Config";
private const string mc_sIDMAP_ELEMENT_NAME = "IDMap";
private const string mc_sCONTEXTID_ELEMENT_NAME = "ContextID";
private const string mc_sID_ATTRIBUTE_NAME = "ID";
private const string mc_sHTMLPATH_ATTRIBUTE_NAME = "HTMLPath";
private const string mc_sHELPFILE = "ContextHelpMadeEasy.chm";
#region Public Methods
/// <summary>
/// Process an F1 or Ctrl-F1 request.
/// </summary>
/// <param name="ctrContext"></param>
public static void ProcessHelpRequest(Control ctrContext)
{
// You will probably want to add an additional test for some registry key
// here. Set this key if you want to enable the Help Mapping feature.
if (Control.ModifierKeys == Keys.Control)
{
ShowHelpMappingDialog(ctrContext);
return;
}
ShowContextHelp(ctrContext);
}
/// <summary>
/// Process a request to display help for the context specified by ctrContext.
/// Go up the parent chain until we find a control where:
/// 1. The control implements IContextHelp,
/// 2. The control has a non-null IContextHelp.ContextHelpID, and
/// 3. The ContextHelpID has a corresponding entry in the mapping XML file.
///
/// If this is found, launch the help viewer to display it.
/// If it is not found, launch the help viewer to display the default topic for
/// the Application.
/// </summary>
/// <param name="ctrContext"></param>
public static void ShowContextHelp(Control ctrContext)
{
Control ctr = ctrContext;
string sHTMLFileName = null;
while (ctr != null)
{
// Get the first control in the parent chain that supports the IContextHelp interface.
IContextHelp help = GetIContextHelpControl(ctr);
// If there isn't one, display the default help for the application.
if (help == null)
break;
// Check to see if it has a ContextHelpID value.
if (help.ContextHelpID != null)
{
// Check to see if the ID has a mapped HTML file name.
sHTMLFileName = LookupHTMLHelpPathFromID(help.ContextHelpID);
if (sHTMLFileName != null && ShowHelp(ctrContext, sHTMLFileName))
return;
}
// Get the parent control and repeat.
ctr = ((Control)help).Parent;
}
// Show the default topic.
ShowHelp(ctrContext, "");
}
/// <summary>
/// Process a request to pop up a mapping dialog that permits the help writer to
/// associate an HTML path with the current context.
///
/// Traverse the parent control chain looking for controls that implement the
/// IContextHelp interface. For each one found, add it to the list of available
/// contexts. Include the associated HTML path if it's defined.
///
/// Finally, show the dialog for the help writer to edit the mappings.
/// </summary>
/// <param name="ctrContext"></param>
public static void ShowHelpMappingDialog(Control ctrContext)
{
IContextHelp help = GetIContextHelpControl(ctrContext);
List<ContextIDHTMLPathMap> alContextPaths = new List<ContextIDHTMLPathMap>();
// Create a list of contexts starting with the current help context
// and moving up the parent chain.
while (help != null)
{
string sContextID = help.ContextHelpID;
if (sContextID != null)
{
string sHTMLHelpPath = LookupHTMLHelpPathFromID(sContextID);
alContextPaths.Add(new ContextIDHTMLPathMap(sContextID, sHTMLHelpPath));
}
help = GetIContextHelpControl(((Control)help).Parent);
}
// Pop up the mapping dialog. If it returns true, this means a change was made
// so we rewrite the XML mapping file with the new information.
if (FHelpMappingDialog.ShowHelpWriterHelper(alContextPaths) == true)
{
foreach (ContextIDHTMLPathMap pathMap in alContextPaths)
{
if (!string.IsNullOrEmpty(pathMap.ContextID))
{
if (!string.IsNullOrEmpty(pathMap.HTMLPath))
{
ContextPaths[pathMap.ContextID] = pathMap.HTMLPath;
}
else
{
if (ContextPaths.ContainsKey(pathMap.ContextID))
ContextPaths.Remove(pathMap.ContextID);
}
}
}
SaveMappingFile(ContextPaths);
}
}
#endregion // Public Methods
#region Private Properties
/// <summary>
/// Return the cached ContextPaths string dictionary. Creates it
/// from the Mapping XML file if it doesn't exist.
/// </summary>
private static StringDictionary ContextPaths
{
get
{
if (ms_sdContextPaths == null)
{
ms_sdContextPaths = ReadMappingFile();
}
return ms_sdContextPaths;
}
}
/// <summary>
/// Return the path to the mapping file.
/// </summary>
private static string MappingFilePath { get { return Path.Combine(System.Windows.Forms.Application.StartupPath, mc_sMAPPING_FILE_NAME); } }
/// <summary>
/// Return the path to the CHM file.
/// </summary>
private static string HelpFilePath { get { return Path.Combine(System.Windows.Forms.Application.StartupPath, mc_sHELPFILE); } }
#endregion // Private Properties
#region Private Methods
/// <summary>
/// Given an ID, return the associated HTML Help path
/// </summary>
/// <param name="sContextID"></param>
/// <returns></returns>
private static string LookupHTMLHelpPathFromID(string sContextID)
{
if (ContextPaths.ContainsKey(sContextID))
return ContextPaths[sContextID];
return null;
}
/// <summary>
/// Display the specified help page.
/// </summary>
/// <param name="sHTMLHelp"></param>
private static bool ShowHelp(Control ctlContext, string sHTMLHelp)
{
try
{
if (string.IsNullOrEmpty(sHTMLHelp))
Help.ShowHelp(ctlContext, HelpUtility.HelpFilePath);
else
Help.ShowHelp(ctlContext, HelpUtility.HelpFilePath, HelpNavigator.Topic, sHTMLHelp);
}
catch (ArgumentException)
{
// Ideally, we would return false when the HTML file isn't found in the CHM file.
// Unfortunately, there doesn't seem to be a way to do this.
return false;
}
return true;
}
/// <summary>
/// Read the mapping file to create a list of ID to HTML file mappings.
/// This method returns a StringDictionary containing the list.
/// </summary>
/// <returns></returns>
private static StringDictionary ReadMappingFile()
{
StringDictionary sdMapping = new StringDictionary();
XmlDocument docMapping = new XmlDocument();
if (File.Exists(MappingFilePath) == true)
{
try
{
docMapping.Load(MappingFilePath);
}
catch
{
MessageBox.Show(string.Format("Could not read help mapping file '{0}'", MappingFilePath), "Context Help Made Easy", MessageBoxButtons.OK, MessageBoxIcon.Error);
throw;
}
XmlNodeList nlMappings = docMapping.SelectNodes("//" + mc_sCONTEXTID_ELEMENT_NAME);
foreach (XmlElement el in nlMappings)
{
string sID = el.GetAttribute(mc_sID_ATTRIBUTE_NAME);
string sPath = el.GetAttribute(mc_sHTMLPATH_ATTRIBUTE_NAME);
if (sID != "" && sPath != "")
sdMapping.Add(sID, sPath);
}
}
return sdMapping;
}
/// <summary>
/// Saves the specified StringDictionary that contains ID to Path mappings to the
/// XML mapping file.
/// </summary>
/// <param name="sdMappings"></param>
private static void SaveMappingFile(StringDictionary sdMappings)
{
// Create a new XML document and initialize it with the XML declaration and the
// outer IDMap element.
XmlDocument docMapping = new XmlDocument();
XmlDeclaration xmlDecl = docMapping.CreateXmlDeclaration("1.0", null, null);
docMapping.InsertBefore(xmlDecl, docMapping.DocumentElement);
XmlElement elIDMap = AddChildElementToNode(docMapping, docMapping, mc_sIDMAP_ELEMENT_NAME);
// Add the defined mappings between contextID and filename.
foreach (DictionaryEntry de in sdMappings)
{
XmlElement elMapping = AddChildElementToNode(elIDMap, docMapping, mc_sCONTEXTID_ELEMENT_NAME);
elMapping.SetAttribute(mc_sID_ATTRIBUTE_NAME, de.Key as string);
elMapping.SetAttribute(mc_sHTMLPATH_ATTRIBUTE_NAME, de.Value as string);
}
try
{
docMapping.Save(MappingFilePath);
}
catch
{
MessageBox.Show(string.Format("Could not write help mapping file '{0}'", MappingFilePath), "Context Help Made Easy", MessageBoxButtons.OK, MessageBoxIcon.Error);
throw;
}
}
/// <summary>
/// Small utility method to add XML elements to a parent node.
/// </summary>
/// <param name="node"></param>
/// <param name="elementName"></param>
/// <returns></returns>
private static XmlElement AddChildElementToNode(XmlNode node, XmlDocument doc, string elementName)
{
XmlElement el = doc.CreateElement(elementName);
node.AppendChild(el);
return el;
}
/// <summary>
/// Get the first control in the parent chain (including the control passed in)
/// that implements IContextHelp.
/// </summary>
/// <param name="ctl"></param>
/// <param name="ctlIContextHelp"></param>
private static IContextHelp GetIContextHelpControl(Control ctl)
{
while (ctl != null)
{
IContextHelp help = ctl as IContextHelp;
if (help != null)
{
return help;
}
ctl = ctl.Parent;
}
return null;
}
}
#endregion // Private methods
}
|
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.
I've been programming in C, C++, Visual Basic and C# for over 35 years. I've worked at Sierra Systems, ViewStar, Mosaix, Lucent, Avaya, Avinon, Apptero, Serena and now Guidewire Software in various roles over my career.