HTML-Hilfe
Inhalt:
Schritt 1: Anwendungsgerüst
Schritt 2: Programmlogik
Schritt 3: Neue Hilfeeinträge
Schritt 4: Von der View zur Hilfe
Bilder
Hilfe für Dialoge
Ziel dieses Beispiels:
Ein HTML-basierendes Hilfesystem soll verwendet werden.
Hier gibt es das Beispiel zum Download: HTMLHelp.zip
Referenz-Links:
Knowledge Base Artikel 191118 How To Create Context-Sensitive HTML Help in an MFC Application
Technical Note TN028 Context-Sensitive Help Support
Schritt 1: Anwendungsgerüst
Erstellen des Anwendungsgerüsts (SDI-Anwendung) wie in den bisherige Beispielen.
In den "Advanced Features" wird die Checkbox "Context-sensitive Help" mit der Option
"HTML Help Format" gesetzt.
Beim Compilieren des Projekts wird der Help Compiler aufgerufen.
Wird das Projekt gestartet gelangt man durch Drücken von "F1" oder über das Menü "Hilfe"
in ein Hilfe-Template, das schon Einträge für die generierten Menüpunkte enthält.
Neu ist ein Unterverzeichnis "hlp" im Projekt, dass die HTML-Dateien der Hilfe enthält.
Aus diesen erzeugt der HTML Help Compiler (hhc.exe, im Verzeichnis "C:\Programme\HTML Help Workshop"),
der beim compilieren kurz als Dos-Fenster auftaucht, eine chm-Datei, die ins
Debug/Release-Verzeichnis kopiert wird.
Will man sich die Ausgabe dieses Kommandozeilenfensters anschauen (z.B. weil ein Fehler zu vermuten ist), dann kann
man das so erreichen: Rechtsklick auf die Datei "HTMLHelp.hhp" im Projektmappen-Explorer, in die Eigenschaften gehen,
und unter "Konfigurationseigenschaften" -> "Benutzerdef. Buildschritt" -> "Allgemein" die "Befehlszeile" öffnen.
Hier ersetzt man die erste Zeile start /wait hhc "hlp\HTMLHelp.hhp"
(der Name der hpp-Datei kann variieren)
durch hhc "hhc hlp\HTMLHelp.hhp"
. Dadurch landet die Ausgabe von "hhc.exe" direkt auf der Konsole. Im
anhängenden Beispielprojekt ist dies geschehen.
Im Code findet sich nur eine einzige Änderung:
In MainFrm.cpp gibt es vier neue Einträge in der Message Map, die die Hilfe-Befehle auf
Standard-Methoden von CFrameWnd (bzw. CWnd, dort sind sie deklariert) umleiten.
// Globale Hilfebefehle
ON_COMMAND(ID_HELP_FINDER, CFrameWnd::OnHelpFinder)
ON_COMMAND(ID_HELP, CFrameWnd::OnHelp)
ON_COMMAND(ID_CONTEXT_HELP, CFrameWnd::OnContextHelp)
ON_COMMAND(ID_DEFAULT_HELP, CFrameWnd::OnHelpFinder)
Das Drücken von "F1" wird durch die Methode "OnHelp" abgehandelt.
Schritt 2: Programmlogik
Das Fenster der CView soll zwei Rechtecke enthalten. Wenn der User in eines davon klickt
(sozusagen ein Auswählen) und dann F1 drückt soll er eine Contextabhängige Hilfe
bekommen. Dazu im OnDraw zwei Rechteckt der Größe 200x200 zeichnen, das aktuell gewählte
bekommt ein Fokus-Rechteck.
Das Event "OnLButtonDown" speichert die Mausklick-Position in einer Instanzvariablen (im Konstruktor
mit "-1/-1" initialisiert) und löst ein Repaint aus.
Schritt 3: Neue Hilfeeinträge
Jeder Hilfseintrag steht in einer eigenen HTML-Datei. Normalerweise enthalten diese den Namen
des Controls/Menüpunkts, für den sie gelten. Da wir hier aber eigene Hilfe-Themen gebaut haben,
verwenden wir einen leicht anderen Ansatz.
Zuerst einmal bauen wir drei HTML-Dateien "htmlhelp_links.htm", "htmlhelp_rechts.htm" und
"htmlhelp_none.htm" im Verzeichnis "hlp". Ich habe hierzu die Datei "scrollbars.htm" kopiert
und umbenannt. Der Inhalt ist reines HTML, zu beachten ist nur dass der Anchor mit dem Namen
der Hilfe-Datei vorhanden ist (im Beispiel: htmlhelp_links.htm):
<A NAME="htmlhelp_links"></A>
Wichtig: Diese Dateien dem Projekt zufügen: im "Projektmappen-Explorer" die "HTML-Hilfethemen"
auswählen, Rechtsklick und "Hinzufügen" -> "Vorhandenes Element..." wählen. Die drei Dateien auswählen.
In der Datei "./help/HTMLHelp.hhp" erfolgt die Definition der Hilfe.
In der Sektion "[FILES]" fügen wir drei neue Einträge ein:
htmlhelp_links.htm
htmlhelp_rechts.htm
htmlhelp_none.htm
In der Sektion "[ALIAS]" (Mapping von internen Alias-Namen auf Dateien) erfolgen ebenfalls drei Einträge:
HTMLHELP_RECHTECK_LINKS = HTMLHELP_LINKS.HTM
HTMLHELP_RECHTECK_RECHTS = HTMLHELP_RECHTS.HTM
HTMLHELP_RECHTECK_NONE = HTMLHELP_NONE.HTM
Schließlich werden in der Sektion "[MAP]" die drei Alias-Namen aus der vorherigen Sektion definiert:
#define HTMLHELP_RECHTECK_LINKS 0x1
#define HTMLHELP_RECHTECK_RECHTS 0x2
#define HTMLHELP_RECHTECK_NONE 0x3
Gemäß Technical Note 28 ist der Bereich von 0x00000000 bis 0x0000FFFF "user defined", darf also
für sonstige Hilfe-IDs verwendet werden.
Achtung: NICHT mit "0x0" beginnen, da die Help-ID bei der Message "WM_HELPHITTEST" als Rückgabe
dient und "0x0" dann "nicht gefunden" bedeuten würde !
Hinweis: In der "[MAP]"-Sektion wird zuerst "HTMLDefines.h" includiert. Dort können
wir unsere Defines jedoch nicht unterbringen da die Datei bei jedem compilieren neu
(aus der Resourcen-Datei ?) erzeugt wird.
Im Beispielprogramm ist dies ein wenig cleverer gelöst, indem die Defines in eine eigene Headerdatei
"HTMLHelpDefines.d" verschoben sind.
In der "[MAP]"-Sektion von "HTMLHelp.hhp" wird nur diese Datei eingebunden (die in diesem Fall nicht
im gleichen Verzeichnis wie die hhp-Datei liegt !):
#include ..\HTMLHelpDefines.h
Letzter Schritt: die Dateien in den Hilfe-Inhalt-Baum eintragen. Dazu die Datei "HTMLHelp.hcc"
öffnen und folgendes einfügen:
<LI> <OBJECT type="text/sitemap">
<param name="Name" value="Rechteck: links">
<param name="Local" value="htmlhelp_links.htm">
</OBJECT>
<LI> <OBJECT type="text/sitemap">
<param name="Name" value="Rechteck: rechts">
<param name="Local" value="htmlhelp_rechts.htm">
</OBJECT>
<LI> <OBJECT type="text/sitemap">
<param name="Name" value="Rechteck: none">
<param name="Local" value="htmlhelp_none.htm">
</OBJECT>
Das Ergebnis sieht so aus:
ACHTUNG, WICHTIG !
Die .hhc-Datei dürfen wir NIE, NIE, NIE mit Visual Studio editieren ! Tun wir dass (was sowieso schwierig ist weil
das HTML beschissen formatiert ist), dann funktioniert das Generieren des Hilfe-Inhaltsverzeichnisses NICHT mehr
(es gibt keine Fehlermeldungen, es passiert einfach nichts mehr) !
Auf jeden Fall nur mit dem absoluten Profi-Editor "Notepad" bearbeiten !
Jetzt können wir das Programm starten und sehen unsere Hilfedateien in der Hilfe selbst,
kommen aber mit F1 noch nicht dorthin.
Einträge in Karteikarte "Index" einfügen:
Hierzu einfach folgenden Eintrag in den Body einer HTML-Datei einfügen:
<OBJECT TYPE="application/x-oleobject" CLASSID="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e">
<PARAM NAME="Keyword" VALUE="Rechteckfenster">
</OBJECT>
Wichtig ist das "Value"-Attribut des Elements "param".
Im Beispiel habe ich für alle drei Rechteckfenster-Hilfedateien diesen Eintrag eingefügt.
Das führt zu diesem Ergebnis:
Schritt 4: Von der View zur Hilfe
Hier können wir keine Standard-MFC-Verknüpfung der Hilfeeinträge verwenden, da
diese nur pro Menüpunkt oder Control angebbar ist, aber nicht dynamisch. Also tricksen wir
in der View ein wenig:
- Zuerst einmal definieren wir uns eine Methode die aus einem Punkt der View die ID des
Hilfe-Topics ermittelt.
Da wir unsere eigenen Hilfe-IDs in die Datei "HTMLHelpDefines.h" verlegt haben können wir diese
includieren und deren Konstanten verwenden !
#include "HTMLHelpDefines.h"
/**Ermitteln der aktuell gültigen Hilfe (je nach Context !)
* @param pointClicked Ein Punkt auf der view für den die Hilfe gesucht wird.
* @return Hilfe-ID für "Rechteck links", "Rechteck rechts" oder "Kein Rechtecke",
* je nachdem wohin geklickt wurde.
*/
int CHTMLHelpView::GetHelpID (CPoint pointClicked)
{
//Die beiden Rechtecke:
CRect rectLinks (0, 0, 200, 200);
CRect rectRechts (200, 0, 400, 200);
//In welchem Rechteck steckt der Punkt ?
if (rectLinks.PtInRect (pointClicked) == TRUE)
return HTMLHELP_RECHTECK_LINKS;
else if (rectRechts.PtInRect (pointClicked) == TRUE)
return HTMLHELP_RECHTECK_RECHTS;
else
return HTMLHELP_RECHTECK_NONE;
}
- Beim F1-Drücken soll je nach gewählten Rechteck eine kontextbezogene Hilfe angezeigt werden.
Es klingt logisch in der View-Klasse die Methode "OnHelp" zu überladen. Leider klappt das nicht so ganz,
da "Wnd::OnHelp" eine ganze Reihe mehr macht. Z.B. wird zuerst geprüft ob gerade ein Menüpunkt
gewählt ist. Wenn ja dann wird dessen Hilfe angezeigt (falls definiert). Am Ende zeigt "OnHelp" auf jeden
Fall die Startseite der Hilfe an. Ein Aufruf der Basisklassenmethode
hilft ebenfalls nicht weiter da nicht erkennbar ist ob in der Basisklasse ein Hilfethema
gefunden wurde oder ob wir unsere Logik greifen lassen können.
Mit ein wenig Debuggen durch MFC-Klassen findet man aber folgendes heraus:
- CWnd::OnHelp sendet eine Message "WM_COMMANDHELP" an das aktive Fenster und anschließend an
dessen Besitzer. Dies geschieht solange bis ein Fenster angibt dass es die Nachricht behandelt hat.
Also in die MessageMap unserer View-Klasse diesen Eintrag einfügen (die Konstante WM_COMMANDHELP
ist in "afxpriv.h" deklariert):
ON_MESSAGE (WM_COMMANDHELP, OnCommandHelp)
Die Implementierung der Methode sieht so aus:
LRESULT CHTMLHelpView::OnCommandHelp(WPARAM, LPARAM lParam)
{
DWORD dwContextID = this->GetHelpID (this->pointClick);
AfxGetApp ()->HtmlHelp (dwContextID, HH_HELP_CONTEXT);
return TRUE;
}
Die HelpID des aktuell gewählten Rechtecks im Fenster wird geholt und die Hilfe wird angezeigt.
- Das alleine hilft uns allerdings nicht, da irgendwo in der MFC trotzdem die Startseite
der Hilfe angezeigt wird, nur das richtige Kapitel wird im Inhaltsverzeichnis aufgerufen.
Liegt scheinbar an Wnd::OnHelpInfo, die am Ende die Default-Window-Prozedur
aufruft und dabei wird intern wiederum OnHelp aufgerufen (WM_HELPINFO scheint im Kern eine WM_HELP-Message
mit anderem Parameter zu sein).
Also die "WM_HELPINFO"-Message verarbeiten.
Der Code sieht so aus:
BOOL CHTMLHelpView::OnHelpInfo(HELPINFO* pHelpInfo)
{
//Geklicktes Rechteck ermitteln:
pHelpInfo->dwContextId = this->GetHelpID ( CPoint (pHelpInfo->MousePos.x, pHelpInfo->MousePos.y) );
return TRUE;
}
Das sorgt dafür dass wir im Clientbereich der View herumklicken können und durch F1
das Hilfefenster zum gewählten Rechteck gerufen wird. Haben wir einen Hauptmenüpunkt aufgeklappt
führt uns F1 zu dessen Hilfe.
- Jetzt gibt es noch den Button "Kontextbezogene Hilfe" in der Toolbar, der es erlaubt,
Hilfe zu einem bestimmten Control abzurufen. Die MFC sendet bei Auswahl eines Controls die Message
WM_HELPHITTEST an das aktive Fenster. Wenn die Nachricht eine Help-ID zurückliefert wird die Hilfe
hierzu angezeigt.
In die MessageMap der view wird folgender Eintrag zugefügt:
ON_MESSAGE (WM_HELPHITTEST, OnHelpHitTest)
Wichtig: WM_HELPHITTEST ist in der Header-Datei "afxpriv.h" deklariert.
Die Implementierung von OnHelpHitTest sieht so aus:
/**Message WM_HELPHITTEST verarbeiten: Help-ID zum Punkt holen.
* @param wParam uninteressant
* @param lParam Enthält im LoWord die x-Koordinate, im HighWord die y-Koordinate
* des geklickten Punkts.
* @return Eine der drei Help-Ids des Fensters.
*/
LRESULT CHTMLHelpView::OnHelpHitTest(WPARAM wParam, LPARAM lParam)
{
return this->GetHelpID( CPoint ( LOWORD(lParam), HIWORD (lParam) ) );
}
Bilder
Den Hilfe-Dateien können wir Bilder zufügen. Diese werden im Unterverzeichnis ".\hlp\Images"
abgelegt und beim Erstellen der Hilfe in die chm-Datei eingebunden, sofern aus einer HTML-Datei
darauf verwiesen wird. Im Beispiel: die Hilfethemen für "Linkes/rechtes Rechteck gewählt"
haben jeweils einen Screenshot eingebunden. Das HTML dazu sieht so aus (aus htmlhelp_links.htm):
<img src="./Images/htmlhelplinks.png" title="Linkes Rechteck gewählt" alt="Linkes Rechteck gewählt" width="542" height="320" />
Ins Projekt (Projektmappen-Explorer) hängen wir diese Dateien unter "Ressourcendateien"
(indem wir sie per Rechtsklick -> "Hinzufügen" -> "Vorhandenes Element..." einhängen).
Hilfe für Menüpunkte und Dialoge
Der Standard-Weg: Hilfe für einen Menüpunkt ohne Tricks und Co: Das Beispiel hat
bisher einen Riesen-Aufwand gezeigt um Mausposition-bezogene Hilfe einzubauen. Für Menüpunkte,
Controls und Fenster gibt es aber auch ganz normale Standard-Hilfe, die man ohne Aufwand erreicht.
Das Beispiel enthält im Menü "Bearbeiten" einen Menüpunkt "ID_EDIT_MENUEPUNKT" (der einen Dialog aufruft).
Beim Compilieren wird in ".\hlp\HTMLDefines.h" ein neuer Hilfe-Alias angelegt:
// Commands (ID_* and IDM_*)
#define HID_EDIT_MENUEPUNKT 0x18004
Die ID des Menüpunkts ergibt durch Voranstellen von "H" den Hilfe-Alias.
Jetzt legen wir die HTML-Datei dazu an und benennen sie genauso wie den Alias (hid_edit_menuepunkt.htm).
Ich habe im Beispiel die Datei aus "hid_edit_cut.htm" dupliziert.
In "HTMLHelp.hhp" müssen wir die Datei wie gehabt unter "[FILES]" eintragen, außerdem einen
Eintrag in die Alias-Verknüpfungen vornehmen.
[FILES]
...
hid_edit_menuepunkt.htm
...
[ALIAS]
...
HID_EDIT_MENUEPUNKT = HID_EDIT_MENUEPUNKT.HTM
Wir könnten den Eintrag als neuen Unterknoten im Inhalt beim Menü "Bearbeiten" einfügen. Dazu in
"HTMLHelp.hhc" folgendes nach dem Knoten "Befehle im Menü 'Bearbeiten'" einfügen:
<UL>
<LI> <OBJECT type="text/sitemap">
<param name="Name" value="Menüpunkt 'Menüpunkt'">
<param name="Local" value="hid_edit_menuepunkt.htm">
</OBJECT>
</UL>
Und nochmal der Standard-Weg: Hilfe für einen Dialog.
Dem Beispiel einen Dialog "DialogHirnlos" zufügen und diesen anzeigen wenn der Menüpunkt aus dem
letzten Schritt gewählt wird. In ".\hlp\HTMLDefines.h" wird ein neuer Eintrag mit einer Hilfe-ID angelegt:
// Dialogs (IDD_*)
#define HIDD_ABOUTBOX 0x20064
#define HIDD_DIALOGHIRNLOS 0x20067
Auch hier eine HTML-Datei für die Dialog-Hilfe anlegen ("hidd_dialoghirnlos.htm") und in
"HTMLHelp.hhp" unter "[FILES]" und "[ALIAS]" Einträge vornehmen.
[FILES]
...
hidd_dialoghirnlos.htm
...
[ALIAS]
...
HIDD_DIALOGHIRNLOS = HIDD_DIALOGHIRNLOS.HTM
Stand 21.08.2006
Historie:
26.06.2006 Beispiel zugefügt (erstellt aus der VS2003-Doku des letzten Semesters)
21.08.2006 Nachricht WM_HELPINFO wird jetzt verarbeitet (wie im 2003er-Beispiel)