Die Sache mit der kleinen Hilfe

Microsoft als Betriebssystem-Monopolist hat sich etwas ausgedacht, damit der ansonsten zumindest bei Webdesignern und etwas bewanderteren Anwendern eher unbeliebte Internet Explorer einen Lebenszweck behält. Und zwar das „res:“ Protokoll. Ich habe das mal mit DelphiXE ausprobiert.

Ich bin ein Dokumentations- und Erklärungsfan. Deshalb gibt es — abgesehen von einigen wenigen, ausschließlich für den Eigenbedarf geschaffenen Progrämmchen — bei meinen Programmen immer in irgend einer Form eine Hilfe. Vorzugsweise direkt im Programm, denn dann geht nichts verloren.

Allerdings bedeutet das typischerweise, dass ich die Hilfe mit einer integrierten Darstellungsfunktion präsentieren muss, was den Entwicklungsaufwand, speziell bei kleinen Helfern teilweise erheblich steigert. Für *Einfachst-Dokumentation* ist das „res:“ Protokoll aber gar nicht so schlecht, vorausgesetzt, der Internet Explorer wurde nicht deinstalliert. Dann läuft es nämlich ins Leere und die Hilfe versagt. Da das jedoch zumindest bei den Hilfe-Bedürftigen eher die Ausnahme ist, lassen wir das mal außen vor.

Im Grunde ist es brutalst einfach (wenn man sich durch die z.T. sinnlos langen, umständlichen und teilweise falschen Ratschläge dazu gekämpft hat):

  1. Ich binde eine HTML-Datei als Ressource mit dem Typ RT_HTML in das Programm ein.
  2. Ich rufe sie mit dem „res:“ Protokoll aus der Datei auf.

Nehmen wir an, ich will die Datei Anleitung.html als Ressource-Datei einlagern und aufrufen. In DelphiXE (und mutmaßlich analog mit den Vorgängern und Nachfolgern) lade ich die Datei als Ressource, gebe ihr den (in der Vorschlagsliste der Typen nicht enthaltene) Typ RT_HTML und den Bezeichner ANLEITUNG. Der Typ ist entscheidend. Nur dann klappt der nächste Schritt.

Der Aufruf ist „stumpf“:

var Die_Anleitung : String;
...
Die_Anleitung := 'res://' + ChangeFileExt(ParamStr(0),'.exe') + '/RT_HTML/ANLEITUNG';
ShellExecute(Handle, nil, PChar(Die_Anleitung), nil, nil, SW_SHOWNORMAL);

Das ChangeFileExt ist womöglich nicht die cleverste Lösung, denn ich tausche den Extender gar nicht. Dafür bekomme ich aber stressfrei den kompletten Pfad inklusive Extender, was der Internet Explorer offenbar braucht. Zumindest unter Windows 8.1 mit dem IE 11.

Ab wann und auf welchen Betriebssystemen das „res“ Protokoll von welchem IE unterstützt wird, weiß ich nicht. Nach vorn blickend ist diese Methode jedoch durchaus interessant.

Der kleine Haken

Bei meinen (zeitlich überschaubaren) Versuchen hat sich der IE störrisch gezeigt, was die Interpretation von eingebetteten Style-Sheets betrifft. Grundsätzlich kann man Stylesheets und Bilder ebenfalls als Ressource einbetten, die dann (wahrscheinlich) verwendet werden. Allerdings haben mich an der Lösung zwei Dinge genervt, weshalb ich es dann doch anders realisiert habe:

  1. Eine HTML-Datei soll in dem Browser aufgehen, den ich als Benutzer gewählt habe und nicht in einer in vielen Fällen bewusst dafür nicht als Standard eingestellten Anwendung.
  2. Die HTML-Datei soll so aussehen, wie sie erstellt wurde.

Meine Lösung

Ich habe mich statt dessen entschieden, die Hilfe zu extrahieren und mit dem Standardbrowser aufzurufen. Wenn das Programm geschlossen wird, wird die Hilfe-Datei gelöscht, so entstehen keine Datei-Leichen. Da meine Programme nicht installiert werden müssen, sondern typischerweise irgendwo im Benutzerverzeichnis liegen, gibt es bei diesem Verfahren keine Rechteprobleme, es sei denn, ein Anwender darf in seinem eigenen Verzeichnis keine Dateien speichern. Das wäre aber eher ungewöhnlich.

procedure TFUplader.BTanleitungClick(Sender: TObject);
var RS: TResourceStream;
begin
  if Die_Anleitung = '' then  
   // Anleitung war noch nicht offen, existiert also noch nicht
   Begin
    Die_Anleitung := ExtractFilePath(ParamStr(0)) + 'Anleitung.html';
    RS := TResourceStream.Create(HInstance, 'ANLEITUNG', RT_RCDATA);
    try
      RS.SaveToFile(Die_Anleitung);
    finally
      RS.Free;
    end;
   End;
  ShellExecute(Handle, nil, PChar(Die_Anleitung), nil, nil, SW_SHOWNORMAL);
end;

  ...  

procedure TForm.FormCreate(Sender: TObject);
begin
   // Globale Variable vorbesetzen
   Die_Anleitung := '';
End;

...

procedure TForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // Wenn die Anleitung geöffnet war, wieder wegräumen
  if Die_Anleitung <> '' then 
    DeleteFile(Die_Anleitung);
end;