Kontextsensitive Hilfe, gar nicht so schwer

In diesem Artikel will ich noch mal ein Thema aus den Win32-API Tutorials für Delphi [1] aufgreifen, weil ich das Gefühl habe, dass es dort etwas untergegangen ist. Und zwar geht es um die kontextsensitive Hilfe. Man hat wohl das Vorurteil, dass das Erstellen einer Hilfe immer mit viel Arbeit und Mühe verbunden ist, insbesondere wenn es darum geht eine kontextsensitive Hilfe zu implementieren. Dem muss aber nicht so sein. Dabei kann eine gute Hilfe wichtig für eine Anwendung sein. Denn sie sorgt dafür, dass sich der Anwender, der das Programm letztendlich nutzt, nicht so alleine gelassen fühlt und somit ein gutes Gefühl hat, wenn er die Anwendung benutzt. Letztendlich denke ich, dass ab einem bestimmten Programmumfang eine Hilfe einfach dazu gehört. Nicht umsonst erstellen wir in der Firma für jede Anwendung, die wir ausliefern, auch eine Hilfe auch wenn die Anwendung noch so klein ist.

Beim Stichwort "kontextsensitive Hilfe" denkt man wohl zu erst an Hilfedateien und das Erstellen selbiger mit mehr oder weniger gut zu handhabenden Werkzeugen, festlegen von TopicIDs, zuweisen der TopicIDs und dann beim Aufrufen, an das Anzeigen der Hilfetexte, was mehr oder weniger schwer erscheinen mag. Aber gerade die Implementation einer kontextsensitiven Hilfe muss nicht unbedingt mit dem Aufwand zum Erstellen einer ganzen Hilfe verbunden sein. Es geht auch wesentlich unkomplizierter. Man sollte sich aber bewußt sein, dass eine kontextsensitive Hilfe wohl nie eine vollständige Hilfe in Form einer CHM-Datei ersetzen kann. Was sie aber ersetzen kann, sind Tooltipps. Tooltips sind zwar eine schicke Sache, aber nicht immer sinnvoll. Tooltipps haben die Eigenschaft, dass sie immer angezeigt werden, wenn man mit der Maus über ein Steuerelement und der Text, der angezeigt werden kann, ist im Umfang doch etwas begrenzt, da ein Tootipp nach einer gewissen Zeit von alleine wieder verschwindet. Ist der Text länger, kann ihn der Benutzer nicht lesen. Setzt man die Zeit hoch, die der Tooltipp angezeigt werden soll, stört es eventuell den Benutzer. Bei einer kontextsensitive Hilfe hingegen, kann der Benutzer selber entscheiden, wann und wie lange er sie sehen will:

The Setup-up

Als aller erstes brauchen wir natürlich die Hilfetexte. Dazu müssen wir keine eigene Hilfe anlegen, sondern können sie in einem Array direkt im Quelltext ablegen. Es handelt sich dabei um ein einfaches, statisches String-Array:

const
  ContextInfo       : array[0..4] of string = (
    'Diesem Element ist keine Hilfe zugewiesen.',
    'Dies ist ein Texteingabefeld. Und dise ist ein absolut sinnfreier Text, der nur dazu dient etwas überflüssigen ' +
    'Text anzuzeigen, um zu demonstrieren wie das mit der Kontextsensitiven Hilfe funktioniert.',
    'Dies ist eine nutzlose Schaltfläche. Und ein weiterer überflüssiger Text, der nur zur Belustigung des geneigten ' +
    'Leser dient.',
    'Zeigt die Hilfe an der Mausposition an, auch wenn sie mit F1 aufgerufen wurde.',
    'Zeigt die Hilfe am Steuerelemet ausgerichtet an, auch wenn sie mit der Maus aufgerufen wurde.'
    );

Als nächstes müssen wir unseren Steuerelementen eine HelpID zuweisen, die festlege, welcher Text aus dem Array zu welchen Steuerelement gehört. Dies machen wir, in dem wir die Windows-API Funktion SetWindowContextHelpId aufrufen. Der erste Parameter ist das Handle von dem betreffenden Steuerelement und der zweite Parameter entspricht dem zugehörigen Element aus unserem String-Array. Interessant ist dabei, dass, wenn ein Kindfenster keine HelpID hat, es die HelpID des Elternfensters erbt.

Und zu guter letzt ist es noch erforderlich, dass unser Fenster den Stil DS_CONTEXTHELP hat, damit in der Titelzeile das Fragezeichen angezeigt wird und wir beim Drücken der Taste F1 die Nachricht WM_HELP gesendet bekommen. Allerdings verträgt sich dieser Stil nicht mit den Fensterstilen WS_MAXIMIZEBOX bzw. WS_MINIMIZEBOX, so dass sie sich gegenseitig ausschließen. In der VCL entspricht der Fensterstil DS_CONTEXTHELP dem Wert biHelp der Formular-Eigenschft BorderIcons.

The Hook

Haben wir dies erledigt, brauchen wir eine Routine, die den Hilfetext anzeigt. Die dafür verantwortliche Funktion steckt in dem ActiveX Modul hhctrl.ocx. Dieses Modul ist auch erforderlich, um die CHM-Hilfedateien anzeigen zu können. Es kann vorkommen, dass es auf älteren System nicht vorhanden ist und separat installiert werden muss. Auf einem aktuellen System, mit einem aktuellen Internet Explorer, sollte diese ActiveX Bibliothek aber vorhanden sein. Die Funktion heißt HtmlHelp. Die Parameter dieser Funktion will ich hier nicht im Einzelnen besprechen. Wichtig ist nur der letzte Parameter, dem ein Zeiger auf eine Struktur vom Typ THHPopup übergeben werden muss:

  PHHPopup = ^THHPopup;
  tagHH_POPUP = packed record
    cbStruct: Integer;                     // sizeof this structure
    hinst: HINST;                          // instance handle for string resource
    idString: UINT;                        // string resource id, or text id if pszFile is specified in HtmlHelp call
    pszText: LPCTSTR;                      // used if idString is zero
    pt: TPoint;                            // top center of popup window
    clrForeGround: TColorRef;              // use -1 for default
    clrBackground: TColorRef;               // use -1 for default
    rcMargins: TRect;                      // amount of space between edges of window and text, -1 for each member to ignore
    pszFont: LPCTSTR;                      // facename, point size, char set, BOLD ITALIC UNDERLINE
  end;
  {$EXTERNALSYM tagHH_POPUP}
  HH_POPUP = tagHH_POPUP;
  {$EXTERNALSYM HH_POPUP}
  THHPopup = tagHH_POPUP;

Wirklich wichtig sind für uns erstmal nur die Parameter zwei bis fünf:

ParameterBedeutung
hinstHandle der Instanz, welche die Stringressource für den Hilfetext enthält. Da wir den Hilfetext im Quellcode direkt vorliegen haben, kann er in unserem Fall null sein.
idStringID der Stringressource, die angezeigt werden soll.
pszTextpszText beinhaltet den anzuzeigenden Text, wenn keine StringID angegeben wurde. Wir übergeben in unserem Fall an dieser Stelle die ContextID, welche wiederrum der ID für das Element aus unseren Hilfetext-Array entspricht. Wo wir die ContextID herbekommen, dazu später.
ptpt legt die Koordinaten fest, wo das Fenster mit dem Hilfetext angezeigt werden soll.

The Sting

Lassen wir die Falle zuschnappen. Zieht der Benutzer nun das Fragezeichen aus der Titelleiste auf ein Steuerelement oder drückt er die Taste F1, bekommt unser Fenster die Windows-Nachricht WM_HELP, auf die wir in unserer Fensterprozedur reagieren können. Benutzen wir die VCL, könnte dies so aussehen:

procedure TForm1.WndProc(var msg: TMessage);
begin
  case msg.Msg of
    WM_HELP:
      begin

      end;
  end;
  inherited;
end;

Der lParam der Nachricht enthält eine Struktur, die uns alle nötigen Informationen liefert: Handle des Fenster, Position der Maus und die HelpID:

typedef struct tagHELPINFO {
    UINT cbSize;
    int iContextType;
    int iCtrlId;
    HANDLE hItemHandle;
    DWORD dwContextId;
    POINT MousePos;
} HELPINFO, *LPHELPINFO;

Die ContextID ist unsere HelpID und entspricht dem Index des Elementes aus unserem Hilfetext-Array, welches zu dem Steuerelement gehört, welches durch das Handle identifiziert wird. Und jetzt wissen wir auch, wo wir die ContextID herbekommen, von der weiter oben die Rede war. Alles was wir jetzt noch tun müssen, ist die Hilfe anzuzeigen. Um dies etwas zu vereinfachen habe ich dafür zwei Wrapper geschrieben: ShowHelpAtMousePos und ShowHelpAtControl. Erstere zeigt die Hilfe immer an der Mausposition an, was aber nicht unbedingt sinnvoll ist, wenn die Hilfe mit F1 aufgerufen wurde. Deswegen habe ich noch zusätzlich den zweiten Wrapper geschrieben, welcher das Hilfefenster immer am Steuerelement ausgerichtet anzeigt. Beide Wrapper sind in der beiliegenden Demo [200 KB] [2] enthalten. Desweiteren enthält die Demo noch die Delphi Unit HtmlHlp, welche die nötigen Deklarationen aus dem ActiveX Modul hhctrl.ocx enthält.

Übrigens, in diesem Artikel ist ein Film versteckt. Und wie ich finde, eine der besten Gaunerkomödien die es gibt. Aber da habe ich schon zu viel verraten. Viel Spass beim Suchen. ;)

2010-12-29T23:44:39 +0100, mail+homepage[at]michael-puff.de