MSDN Magazin > Home > Ausgaben > 2007 > January >  Windows Vista und Office: Anzeigen von Daten na...
Windows Vista und Office
Anzeigen von Daten nach Wunsch mit unserem verwalteten Vorschauhandlerframework
Stephen Toub

Themen in diesem Artikel:
  • Schreiben verwalteter Add-Ins für Windows und Office
  • Implementieren eines verwalteten Vorschauhandlerframeworks
  • Entwickeln benutzerdefinierter Vorschauhandler für eine beliebige Dateierweiterung
  • COM-Interop
In diesem Artikel werden folgende Technologien verwendet:
.NET Framework, Windows Vista, Outlook
Laden Sie den Code für diesen Artikel herunter: PreviewHandlers2007_01.exe (444 KB)
Code online durchsuchen
Jede Version von Microsoft Windows und Office bringt neue Methoden und Ansätze für eine Verbesserung der Art und Weise mit sich, wie Sie Daten anzeigen und verstehen und mit ihnen interagieren können. Jede Version enthält außerdem neue und verbesserte Erweiterungspunkte für das Anschließen benutzerdefinierter Funktionalität. In Windows Vista™ und im Microsoft® Office System 2007 wurden diese zwei fortschrittlichen Bereiche kombiniert. Dies gibt Ihnen die Möglichkeit, benutzerdefinierte Vorschauhandler für Windows Vista und Outlook® 2007 zu schreiben.
Outlook 2003 hat einen Lesebereich für E-Mails bereitgestellt, der das Anzeigen des Inhalts einer Nachricht ermöglichte, ohne diese öffnen zu müssen. Sie brauchten die Nachricht nur in der Listenansicht des E-Mail-Ordners auszuwählen, und sie wurde in einem Seitenfenster oder einem Fenster am unteren Bildschirmrand gerendert. Outlook 2007 erweitert dieses Konzept, indem Sie Nachrichtenanlagen in demselben Vorschaufenster anzeigen können, ohne auf eine Anlage klicken zu müssen, um sie im entsprechenden Viewer zu öffnen.
Standardmäßig umfasst Outlook Vorschauhandler für Word-Dokumente, Powerpoint®-Präsentationen, Excel-Tabellen, Schriftartendateien, Video- und Audiodateien und eine Vielzahl sonstiger Dateitypen, die häufig als Anlagen gesendet werden. Aber das ist nur der Anfang. Windows Vista unterstützt ein ähnliches Vorschaufenster, auf das von einem beliebigen Ordner in der Shell zugegriffen werden kann. Sie können das Vorschaufenster aktivieren, indem Sie im Menü des Ordners „Organisieren | Layout | Vorschaufenster“ wählen (siehe Abbildung 1).
Abbildung 1 Aktivieren von Vorschauen in Windows Vista-Ordnern 
Sowohl Outlook als auch Windows Vista verwenden die gleiche zugrunde liegende Vorschaumethode und ermöglichen Entwicklern die Implementierung benutzerdefinierter Vorschauhandler für einen beliebigen Dateityp, ihre Registrierung und die sofortige Nutzung von Vorschaufunktionen für diese Dateitypen in Outlook 2007 und Windows Vista.
In diesem Artikel wird erläutert, was für die Implementierung eines Vorschauhandlers erforderlich ist und wie diese mithilfe von verwaltetem Code durchgeführt werden kann (Windows® SDK für Vista enthält einen Beispielvorschauhandler, der in systemeigenem C++ geschrieben ist). Der Codedownload für diesen Artikel umfasst ein Framework, das die Implementierung Ihrer eigenen Vorschauhandler zu einem Kinderspiel macht. Er bietet zahlreiche Beispielvorschauen (einschließlich Vorschauen für PDF-, XML-, ZIP-, MSI-, BIN-, CSV-, XPS- und XAML-Dateien).

Hosten eines Vorschauhandlers
Diejenigen von Ihnen, die in der Vergangenheit schon einmal versucht haben, verwaltete Add-Ins für die Windows-Shell zu implementieren, fühlen sich bei diesem Konzept möglicherweise etwas unbehaglich. Microsoft rät schließlich sehr davon ab, Shell-Add-Ins in verwaltetem Code zu implementieren, und solche Add-Ins werden nicht als unterstütztes Szenario betrachtet. Das liegt daran, dass Add-Ins prozessintern in die Shell (explorer.exe) geladen werden, dass nur eine Version der Common Language Runtime (CLR) in einen gegebenen Prozess geladen werden kann und dass verwalteter Code, der für eine Version der Laufzeit erstellt wurde, möglicherweise nicht in einem Prozess ausgeführt werden kann, der eine frühere Version der Laufzeit ausführt. Was passiert also, wenn Sie über zwei Shell-Add-Ins verfügen, die beide in verwaltetem Code geschrieben sind – eines, das auf .NET Framework 1.1 abzielt, und eines, das auf .NET Framework 2.0 abzielt? Wenn das auf 2.0 abzielende Add-In zuerst geladen wird, bemerken Sie wahrscheinlich keine Probleme. Das 1.1-Add-In sollte geladen werden und erfolgreich gegen CLR 2.0 ausgeführt werden. Wenn jedoch das auf 1.1 abzielende Add-In geladen wird, wird die .NET Framework 1.1-CLR in explorer.exe. geladen. Assemblys, die auf .NET Framework 2.0 abzielen, können nicht von .NET Framework 1.1 geladen werden. Daher schlägt das auf 2.0 abzielende Add-In fehl, wenn es nach dem auf 1.1 abzielenden Add-In geladen wird.
Die gleichen Versionskontrollprobleme sind bei Windows Vista vorhanden. Deshalb rät Microsoft nach wie vor von einer Implementierung von Shell-Add-Ins in verwaltetem Code ab. Dies gilt selbst für neue Erweiterungspunkte innerhalb der Shell, wie z. B. Miniaturansichtsanbieter und Eigenschaftshandler (die von der Windows Vista-Suchindexerstellung prozessextern, von der Windows Vista-Shell jedoch prozessintern verwendet werden).
Es gibt jedoch in Bezug auf Vorschauhandler auch gute Neuigkeiten: Sie werden immer prozessextern geladen, jedenfalls was die Shell betrifft. Vorschauhandler werden als COM-Komponenten implementiert, sie werden jedoch nicht von der Windows Vista-Shell gehostet. Ein Vorschauhandler wird stattdessen vom Stellvertreterhost (prevhost.exe) des Vorschauhandlers gehostet oder als ein lokaler COM-Server implementiert. Für verwalteten Code geht die Implementierung des lokalen COM-Servers über den Rahmen dieses Artikels hinaus. (Eine Übersicht dazu finden Sie in der Randleiste „Vorschauhandler und Windows XP“ von Ryan Gregg.) Die Verwendung von prevhost.exe ist außerdem das bevorzugte und von Microsoft empfohlene Dienstmodell.

Das PreviewHandler-Framework
Damit ein Vorschauhandler gültig ist, müssen mehrere Schnittstellen implementiert sein. (Diese sind unter windowssdk.msdn.microsoft.com/aa361576.aspx dokumentiert.) Dazu gehören IPreviewHandler (shobjidl.h); IInitializeWithFile, IInitializeWithStream oder IInitializeWithItem (propsys.h); IObjectWithSite (ocidl.h); IOleWindow (oleidl.h). Es gibt auch optionale Schnittstellen, wie z. B. IPreviewHandlerVisuals (shobjidl.h), die ein Vorschauhandler implementieren kann, um zusätzliche Unterstützung zu bieten.
Wenn Sie einen Vorschauhandler in systemeigenem Code schreiben würden, könnten Sie sofort beginnen, da alle diese Schnittstellen bereits definiert und für ein Hinzufügen aus den Headerdateien, die ich gerade in Klammern erwähnt habe, bereit sind. Um jedoch einen Vorschauhandler in verwaltetem Code zu schreiben, müssen Sie zunächst COM-Interop-Definitionen für jede dieser Schnittstellen schreiben oder beschaffen. Meine Definitionen werden in Abbildung 2 gezeigt.
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("8895b1c6-b41f-4c1c-a562-0d564250836f")]
interface IPreviewHandler
{
    void SetWindow(IntPtr hwnd, ref RECT rect);
    void SetRect(ref RECT rect);
    void DoPreview();
    void Unload();
    void SetFocus();
    void QueryFocus(out IntPtr phwnd);
    [PreserveSig]
    uint TranslateAccelerator(ref MSG pmsg);
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("8327b13c-b63f-4b24-9b8a-d010dcc3f599")]
interface IPreviewHandlerVisuals
{
    void SetBackgroundColor(COLORREF color);
    void SetFont(ref LOGFONT plf);
    void SetTextColor(COLORREF color);
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("b7d14566-0509-4cce-a71f-0a554233bd9b")]
interface IInitializeWithFile
{
    void Initialize([MarshalAs(UnmanagedType.LPWStr)] string pszFilePath, 
    uint grfMode);
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f")]
interface IInitializeWithStream
{
    void Initialize(IStream pstream, uint grfMode);
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")]
public interface IObjectWithSite
{
    void SetSite([In,MarshalAs(UnmanagedType.IUnknown)] object pUnkSite);
    void GetSite(ref Guid riid, 
                 [MarshalAs(UnmanagedType.IUnknown)] out object ppvSite);
}

[ComImport]
[Guid("00000114-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleWindow
{
    void GetWindow(out IntPtr phwnd);
    void ContextSensitiveHelp(
             [MarshalAs(UnmanagedType.Bool)] bool fEnterMode);
}

In meinem verwalteten Framework für die Implementierung von Vorschauhandlern ist ein Großteil der Funktionalität für diese Schnittstellen in eine abstrakte Basisklasse namens „PreviewHandler“ integriert:
public abstract class PreviewHandler : 
    IPreviewHandler, IPreviewHandlerVisuals, IOleWindow, IObjectWithSite
{ ... }
Dadurch werden alle komplizierten Implementierungsdetails (die, wie Sie sehen werden, nicht wirklich kompliziert sind) ausgeblendet. Beachten Sie jedoch, dass weder IInitializeWithFile noch IInitializeWithStream von PreviewHandler implementiert werden. Dies ist zwei abstrakten Klassen vorbehalten, die von PreviewHandler abgeleitet sind:
public abstract class StreamBasedPreviewHandler : 
    PreviewHandler, IInitializeWithStream { ... }

public abstract class FileBasedPreviewHandler : 
    PreviewHandler, IInitializeWithFile { ... }
Um einen benutzerdefinierten Vorschauhandler zu implementieren, leiten Sie eine neue Klasse von StreamBasedPreviewHandler oder FileBasedPreviewHandler ab und setzen eine Methode außer Kraft. Folgendes ist die Implementierung aus meiner XmlPreviewHandler-Klasse:
public sealed class XmlPreviewHandler : FileBasedPreviewHandler
{
    protected override PreviewHandlerControl 
        CreatePreviewHandlerControl()
    {
        return new XmlPreviewHandlerControl();
    }
}
Die CreatePreviewHandlerControl-Methode gibt eine Instanz eines benutzerdefinierten Typs zurück, den Sie entweder aus StreamBasedPreviewHandlerControl oder FileBasedPreviewHandlerControl abgeleitet schreiben. Beide sind aus meiner abstrakten PreviewHandlerControl-Basisklasse abgeleitet.
public abstract class FileBasedPreviewHandlerControl :
   PreviewHandlerControl { ... }

public abstract class StreamBasedPreviewHandlerControl :
   PreviewHandlerControl { ... }

public abstract class PreviewHandlerControl : Control
{
    public abstract void Load(FileInfo file);
    public abstract void Load(Stream stream);
    public virtual  void Unload() { ... }
}
Wie der Name schon sagt, wird die Load-Methode aufgerufen, wenn eine Datei oder ein Datenstrom geladen und in der Vorschau angezeigt werden soll. Ebenso wird die Unload-Methode aufgerufen, wenn die aktuelle Vorschau abgebrochen werden soll. Ein benutzerdefiniertes PreviewHandlerControl ist dann verantwortlich für das Ableiten aus dem entsprechenden Typ (entweder FileBasedPreviewHandlerControl oder StreamBasedPreviewHandlerControl), wodurch die Load-Methode außer Kraft gesetzt und Windows Forms-Steuerelemente erstellt werden, um die Datei oder den Datenstrom zu rendern. Für meinen XML-Vorschauhandler erstelle ich einfach ein WebBrowser-Steuerelement und lade das XML-Dokument hinein, wodurch Windows Vista-Shellbenutzern die gleiche XML zur Verfügung gestellt wird, die von Internet Explorer® geboten wird.
public class XmlPreviewHandlerControl : FileBasedPreviewHandlerControl
{
    public override void Load(FileInfo file)
    {
        WebBrowser browser = new WebBrowser();
        browser.Dock = DockStyle.Fill;
        browser.Navigate(file.FullName);
        Controls.Add(browser);
    }
}
Abbildung 4 ZIP-Vorschau in Outlook 2007 (Klicken Sie zum Vergrößern auf das Bild)
Die grundlegende Unload-Implementierung von PreviewHandlerControl entfernt und löscht alle Steuerelemente aus der Steuerelementesammlung. Wenn diese Funktion für Ihr Steuerelement geeignet ist, müssen Sie sie nicht außer Kraft setzen.
Abgesehen von der Anwendung einiger Attribute auf die abgeleitete PreviewHandler-Klasse ist dies alles, was zum Schreiben eines benutzerdefinierten Vorschauhandlers gehört. Abbildung 3 zeigt die vollständige, in Outlook 2007 ausgeführte Implementierung eines ZIP-Vorschauhandlers, deren Ergebnisse in Abbildung 4 dargestellt werden. (Der Download umfasst einen umfangreicheren ZIP-Vorschauhandler, der eine Strukturansicht der Dateien und Verzeichnisse innerhalb der ZIP-Datei, Dateisymbole sowie Doppelklickunterstützung für die Anzeige der im Archiv enthaltenen Dateien bietet.) Diese Klasse verwendet die Visual J#® 2005-ZIP-Bibliothek (in .NET Framework 2.0 enthalten), um eine Liste aller in der ZIP-Datei enthaltenen Dateien anzuzeigen. Zur Veranschaulichung, dass diese Vorschauhandler sowohl in Windows Vista als auch in Outlook funktionieren, zeigt Abbildung 5 den XmlPreviewHandler, der in der Windows Vista-Shell ausgeführt wird.
[PreviewHandler("ZIP Preview Handler", ".zip", 
                "{c0a64ec6-729b-442d-88ce-d76a9fc69e44}")]
[ProgId("MsdnMag.ZipPreviewHandler")]
[Guid("853f35e3-bd13-417b-b859-1df25be6c834")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public sealed class ZipPreviewHandler : FileBasedPreviewHandler
{
    protected override PreviewHandlerControl CreatePreviewHandlerControl()
    {
        return new ZipPreviewHandlerControl();
    }

    private sealed class ZipPreviewHandlerControl :
        FileBasedPreviewHandlerControl
    {
        public override void Load(FileInfo file)
        {
            ListView listView = new ListView();
            listView.Dock = DockStyle.Fill;
            listView.BorderStyle = BorderStyle.None;
                
            listView.FullRowSelect = true;
            listView.HeaderStyle = ColumnHeaderStyle.Nonclickable;
            listView.MultiSelect = false;
            listView.View = View.Details;
                
            listView.Columns.Add("File Name", -2);
            listView.Columns.Add("Size", -2);
            listView.Columns.Add("Comment", -2);

            ZipFile zip = new ZipFile(file.FullName);
            Enumeration entryEnum = zip.entries();
            while (entryEnum.hasMoreElements())
            {
                ZipEntry entry = (ZipEntry)entryEnum.nextElement();
                if (!entry.isDirectory())
                {
                    listView.Items.Add(new ListViewItem(new string[] { 
                        entry.getName(), entry.getSize().ToString(), 
                        entry.getComment() }));
                }
            }
            zip.close();

            Controls.Add(listView);
        }
    }
}

Abbildung 5 XML-Vorschau in Windows Vista (Klicken Sie zum Vergrößern auf das Bild)

Implementieren von PreviewHandler
Nun, da Sie wissen, wie Sie einen verwalteten Vorschauhandler mithilfe meines Frameworks implementieren können, sehen wir uns die Grundlagen der PreviewHandler-Basisklasse an, um besser zu verstehen, wie Vorschauhandler funktionieren.
Grundsätzlich ist PreviewHandler einfach ein Container für ein Windows Forms-Steuerelement, das in einer privaten Membervariable, _previewControl, gespeichert wird. Er wird zum Steuerelement initialisiert, das von der GetPreviewHandlerControl-Methode, die von der benutzerdefinierten Vorschauhandlerimplementierung implementiert wird, zurückgegeben wurde (z. B. XmlPreviewHandlerControl oder ZipPreviewHandlerControl):
protected PreviewHandler()
{
    _previewControl = CreatePreviewHandlerControl();
    IntPtr forceCreation = _previewControl.Handle;
}
Während es sich hierbei um einen sehr kleinen Konstruktor handelt, muss auf einige wichtige Punkte hingewiesen werden. Zunächst verletze ich eine wichtige .NET Framework-Entwurfsrichtlinie, die auf hoher Ebene besagt, dass Konstruktoren keine virtuellen Methoden aufrufen sollten. (Dies wird von der FxCop-Regel „ConstructorsShouldNotCallBaseClassVirtualMethods“ übernommen.) In .NET ist das Ziel eines virtuellen Aufrufs anders als in ISO C++ der abgeleitete Typ (der außer Kraft gesetzte) statt des Basistyps, der gerade erstellt wird (der virtuelle). Das Problem ist hier, dass zu dem Zeitpunkt, zu dem der Konstruktor des Basistyps ausgeführt wird, der Konstruktor des abgeleiteten Typs noch nicht ausgeführt wurde. Dies bedeutet, dass die Außerkraftsetzung von Methoden auf der abgeleiteten Instanz aufgerufen wird, bevor die Erstellung dieser Instanz abgeschlossen wurde. Daher die Regel, dass Konstruktoren virtuelle Methoden nicht aufrufen sollten, die nicht in derselben Klasse versiegelt wurden. Berücksichtigen Sie dies, da es bedeutet, dass Sie nichts in Ihrer Außerkraftsetzung von CreatePreviewHandlerControl ausführen sollten, das von etwas abhängt, das im Konstruktor eingerichtet wurde (das vom Konstruktionscode noch nicht ausgeführt wurde). Basierend auf dem von mir erstellten Framework, auf dem CreatePreviewHandlerControl einfach den korrekten Steuerelementtyp instanziieren und zurückgeben sollte, sollte diese Regel jedoch nicht schwer zu befolgen sein.
Daraus ergibt sich natürlich die Frage, wenn ich diese Richtlinie bereits von Anfang an kannte, weshalb habe ich gegen sie durch Aufrufen von CreatePreviewHandlerControl aus dem Konstruktor verstoßen? Weil ich das Steuerelement auf dem VI-Thread erstellen muss. Dies führt zum zweiten Punkt, den ich erwähnen möchte. Am Ende des zuvor gezeigten Konstruktors werden Sie sehen, dass ich den Wert von _previewControl.Handle abgerufen, aber nichts mit den Ergebnissen unternommen habe. Dies habe ich getan, um den get-Accessor der Handle-Eigenschaft auf dem Steuerelement aufzurufen. Mit anderen Worten: Ich war nicht am Ergebnis interessiert, ich wollte lediglich den Accessor ausführen. Das Aufrufen des Control.Handle-get-Accessors erzwingt die Erstellung des zugrunde liegenden Fensters für das Steuerelement. Dies ist wichtig, weil der Thread, der die Vorschauhandlerkomponente instanziiert und ihren Konstruktor aufruft, ein STA-Thread (Singlethreadapartment) ist, aber der Thread, der zu einem späteren Zeitpunkt die Schnittstellenmember aufruft, ein MTA-Thread (Multithreadapartment) ist. Wie Sie möglicherweise wissen, sind Windows Forms-Steuerelemente dazu gedacht, auf STA-Threads ausgeführt zu werden, und scheitern manchmal kläglich, wenn versucht wird, sie von MTA-Threads aus zu verwenden. Am besten ist es daher, das Windows Forms-Vorschaufenster im Konstruktor des Vorschauhandlers zu erstellen. Spätere Aufrufe der anderen Schnittstellenmethoden, die eine Interaktion mit dem Vorschausteuerelement erfordern, müssen zurück in diesen STA-Hauptthread gemarshallt werden. Dies ist mithilfe der ISynchronizeInvoke-Schnittstelle des Steuerelements einfach durchführbar. Beachten Sie, dass dieses Problem mit zwei Threads bei der Implementierung eines systemeigenen Vorschauhandlers nicht gegeben und höchstwahrscheinlich ein künstliches Ergebnis davon ist, wie die CLR COM-Interop verarbeitet.
(Es muss dabei angemerkt werden, dass, obwohl ich Implementierungen bespreche, die auf Windows Forms beruhen, es Zeiten geben kann, in denen der einfachste Weg für das Rendern einer Vorschau die Verwendung des ActiveX-Steuerelements ist. Eine Behandlung dieses Themas finden Sie in der Randleiste „Verwenden von ActiveX-Steuerelementen“.)

Verwenden der COM-Schnittstellen
Ob Sie es glauben oder nicht, der Konstruktor ist der schwierigste Teil der gesamten PreviewHandler-Implementierung. Die verbleibende Implementierung dient lediglich der Weiterleitung von Aufrufen, die auf den COM-Schnittstellen vorgenommen wurden, zu den Windows Forms-Steuerelementen sowie einfachen Überwachungszwecken. Ich werde mich zunächst mit den einfachsten Schnittstellen befassen.
IOleWindow IOleWindow ermöglicht einer Hostanwendung, ein Handle zum Fenster zu erhalten, das an Vor-Ort-Aktivierung beteiligt ist, d. h. ein Handle zu unserem Steuerelement (und für Vorschauhandler wird ContextSensitiveHelp nie aufgerufen). Dadurch ist eine Implementierung der Schnittstelle extrem einfach, wie hier gezeigt:
void IOleWindow.GetWindow(out IntPtr phwnd)
{
    phwnd = _previewControl.Handle;
}

void IOleWindow.ContextSensitiveHelp(bool fEnterMode)
{
    throw new NotImplementedException();
}
IObjectWithSite IObjectWithSite ist ebenso einfach. Die Schnittstelle wird verwendet, um ein Objekt mit einem Zeiger zu der Site auszustatten, die seinem Container zugeordnet ist. Die Site, die für SetSite bereitgestellt wird, ist der IPreviewHandlerFrame, der das Fenster des Vorschauhandlers enthält. Als solche speichert die SetSite-Methode den bereitgestellten IUnknown-Schnittstellenzeiger in einem privaten Member (der GetSite auf Abfrage zurückgeben kann) und wandelt ihn in einen IPreviewHandlerFrame (den sie auch speichert) um. Im Hintergrund führt diese Umwandlung zu einem QueryInterface-Aufruf:
private object _unkSite;
private IPreviewHandlerFrame _frame;

void IObjectWithSite.SetSite(object pUnkSite)
{
    _unkSite = pUnkSite;
    _frame = _unkSite as IPreviewHandlerFrame;
}

void IObjectWithSite.GetSite(ref Guid riid, out object ppvSite)
{
    ppvSite = _unkSite;
}
IInitializeWithFile und IInitializeWithStream Des Weiteren werden IInitializeWithFile und IInitializeWithStream jeweils in FileBasedPreviewHandler und StreamBasedPreviewHandler implementiert. Sie werden aufgerufen, um dem Vorschauhandler den vollständigen Pfad oder IStream zur Datei zu liefern, die in der Vorschau angezeigt werden soll. Die Implementierung dieser Methoden erfordert nur einige wenige Codezeilen:
// in FileBasedPreviewHandler
private string _filePath;
void IInitializeWithFile.Initialize(string pszFilePath, uint grfMode) {
    _filePath = pszFilePath;
}

// in StreamBasedPreviewHandler
private IStream _stream;
void IInitializeWithStream.Initialize(IStream pstream, uint grfMode) {
    _stream = pstream;
}
Die einzige Methode auf diesen Schnittstellen, Initialize, wird mit einem Pfad zur Datei versehen, die in der Vorschau angezeigt werden soll, oder mit einem IStream, der die Datei darstellt, gemeinsam mit einem Dateimodus, der angibt, wie die Datei geöffnet werden kann. Der Dateipfad oder Datenstrom wird in einer Membervariablen gespeichert, sodass später auf ihn zugegriffen werden kann. Vorschauhandler sollten schreibgeschützt sein. Daher habe ich den zweiten Parameter ignoriert. (Eigentlich sollten alle Vorschauhandler den zweiten Parameter ignorieren und Dateien schreibgeschützt öffnen. Außerdem sollten sie ein späteres Löschen, Lesen und Schreiben von Dateien ermöglichen.) IInitializeWithFile und IInitializeWithStream sind einfach zu implementieren, verdienen aber eine weitere Erörterung.
Wenn Sie die MSDN®-Dokumentation zur Implementierung von Vorschauhandlern lesen, werden Sie feststellen, dass die Dokumentation stark die Implementierung von IInitializeWithStream statt IInitializeWithFile befürwortet. Der Hauptvorteil, den IInitializeWithStream gegenüber IInitializeWithFile hat, besteht darin, dass IInitializeWithStream einem Vorschauhandler ermöglicht, Daten in der Vorschau anzuzeigen, die nicht in einer unabhängigen Datei gespeichert sind, wie z. B. eine in einer ZIP-Datei gespeicherte Textdatei. (Wenn die Shell versucht, Daten anzuzeigen, die nur als Datenstrom verfügbar sind, und dafür eine Vorschau verwendet, die nur IInitializeWithFile implementiert, speichert die Shell eine Kopie des Datenstroms in einer lokalen Datei und zeigt eine Vorschau dieser Datei an. Dies ist natürlich nicht so effizient wie das direkte Anzeigen des Datenstroms in der Vorschau.) Wann immer möglich, befolgen Sie diesen Rat. Zugegebenermaßen ist dies nicht immer möglich oder angebracht. Das liegt daran, dass viele Vorschauen für Dateitypen bestimmt sind, die am einfachsten mit APIs geladen und gerendert werden, die nur Dateipfade oder URLs unterstützen. Die MsiOpenDatabase-Funktion, die ich beispielsweise in meinem Beispiel-MsiPreviewHandler für das Öffnen einer MSI-Datei verwende, erwartet einen Dateipfad zur Ziel-MSI-Datei. Sie akzeptiert keinen IStream, wobei es sich um die einzigen Daten handelt, die mir zur Verfügung stehen würden, wenn ich IInitializeWithStream statt IInitializeWithFile implementiert hätte.
Ich habe entschieden, dieses Modell in meinem Framework mithilfe der zwei zuvor erwähnten PreviewHandler-Unterklassen zu unterstützen: FileBasedPreviewHandler und StreamBasedPreviewHandler. Jede implementiert nur eine der Schnittstellen, nicht beide. Wenn PreviewHandler selbst beide implementieren würde, würde die Shell immer die IInitializeWithStream-Implementierung verwenden, die es aus Gründen bevorzugt, die mit Sicherheit und Isolation zu tun haben. (Outlook würde aufgrund der Eigenschaften früherer Versionen die IInitializeWithFile-Implementierung bevorzugen.) In Ihrer abgeleiteten Klasse können Sie auswählen, welche Schnittstelle Sie durch Ableiten aus der entsprechenden PreviewHandler-Unterklasse verwenden möchten.
Eine andere wichtige Überlegung ist, dass die Daten aus der Datei oder dem Datenstrom nicht in Initialize geladen werden sollten. Stattdessen sollte der Pfad zur Datei oder der Verweis auf den Datenstrom wie hier gezeigt gespeichert werden, und erst wenn der Vorschauhandler aufgefordert wird, die Vorschau zu rendern, sollte der Inhalt der Datei geladen werden. Zusätzlich sollte die Datei nicht auf irgendeine Art für exklusiven Zugriff gesperrt werden, während die Vorschau angezeigt wird. Ein Benutzer sollte in der Lage sein, die Vorschau anzuzeigen und die Zieldatei gleichzeitig zum Bearbeiten zu öffnen. Am wichtigsten ist, dass der Benutzer nach wie vor in der Lage sein sollte, die Datei zu löschen, während sie in der Vorschau angezeigt wird.
IPreviewHandler Dies bringt mich zur Hauptschnittstelle, die ein Vorschauhandler unterstützen muss und die sehr treffend als IPreviewHandler bezeichnet wird. (Meine Implementierung dieser Schnittstelle wird in Abbildung 6 gezeigt.) IPreviewHandler macht sieben Methoden verfügbar, die implementiert werden müssen: SetWindow, SetRect, DoPreview, Unload, SetFocus, QueryFocus und TranslateAccelerator.
private void InvokeOnPreviewThread(MethodInvoker d)
{
    _previewControl.Invoke(d);
}

private void UpdateWindowBounds()
{
    if (_showPreview)
    {
        InvokeOnPreviewThread(delegate()
        {
            NativeWin32.SetParent(_previewControl.Handle, _parentHwnd);
            _previewControl.Bounds = _windowBounds;
            _previewControl.Visible = true;
        });
    }
}
void IPreviewHandler.SetWindow(IntPtr hwnd, ref RECT rect)
{
    _parentHwnd = hwnd;
    _windowBounds = rect.ToRectangle();
    UpdateWindowBounds();
}

void IPreviewHandler.SetRect(ref RECT rect)
{
    _windowBounds = rect.ToRectangle();
    UpdateWindowBounds();
}

protected abstract void Load(PreviewHandlerControl c);

void IPreviewHandler.DoPreview()
{
    _showPreview = true;
    InvokeOnPreviewThread(delegate()
    {
        Load(_previewControl);
        UpdateWindowBounds();
    });
}

void IPreviewHandler.Unload()
{
    _showPreview = false;
    InvokeOnPreviewThread(delegate()
    {
        _previewControl.Visible = false;
        _previewControl.Unload();
    });
}

void IPreviewHandler.SetFocus()
{
    InvokeOnPreviewThread(delegate() { _previewControl.Focus(); });
}

[DllImport("user32.dll")]
private static extern IntPtr GetFocus();

void IPreviewHandler.QueryFocus(out IntPtr phwnd)
{
    IntPtr result = IntPtr.Zero;
    InvokeOnPreviewThread(delegate() { result = GetFocus(); });
    phwnd = result;
    if (phwnd == IntPtr.Zero) throw new Win32Exception();
}

uint IPreviewHandler.TranslateAccelerator(ref MSG pmsg)
{
    if (_frame != null) return _frame.TranslateAccelerator(ref pmsg);
    return 1; // S_FALSE
}

Wenn eine Datei kurz davor ist, in der Vorschau angezeigt zu werden, übergibt der Host Informationen über die Datei oder über den Datenstrom an den Vorschauhandler mithilfe einer der Initialisierungsschnittstellen, die zuvor beschrieben wurden. Das Handle für das Anzeigefenster, das das Vorschaufenster enthalten wird, wird daraufhin an den Vorschauhandler mithilfe der SetWindow-Methode übergeben. Dies ermöglicht dem Vorschauhandler, dieses Anzeigefenster als übergeordnetes Element des Vorschaufensters einzustellen. Der Host kann anschließend die Größe des Vorschaufensters mithilfe der SetRect-Methode entsprechend festlegen. Zu einem bestimmten späteren Zeitpunkt ruft der Host die DoPreview-Methode auf, um die Vorschau anzuzeigen. Während die Vorschau angezeigt wird, kann der Host SetRect erneut aufrufen, wenn die Größe des Anzeigefensters geändert wird. Wenn die Vorschau schließlich geschlossen wird, weist der Host den Vorschauhandler durch Aufrufen der Unload-Methode an, die Vorschau abzubrechen. Dies bezieht sich auf das Entladen aller geladenen Ressourcen des in der Vorschau angezeigten Elements. Es bedeutet nicht, dass der Vorschauhandler selbst entladen wird. Dieselbe Instanz des Vorschauhandlers kann für mehrere Vorschauen wiederverwendet werden.
Für die Unterstützung meiner Implementierung verwende ich einige Hilfsfunktionen. Zunächst akzeptiert mein InvokeOnPreviewThread-Hilfsprogramm einen MethodInvoker-Delegaten (ein ungültiger und parameterloser Delegat, der im System.Windows.Forms-Namespace definiert ist). Dies wird auf dem Hauptbenutzeroberflächenthread mithilfe der ISynchronizeInvoke-Methode des Vorschausteuerelements ausgeführt. (Denken Sie daran, dass das Steuerelement im Konstruktor des Vorschauhandlers erstellt wurde, sodass es über einen STA-Thread instanziiert werden kann.) Zweitens verwende ich mein UpdateWindowBounds-Hilfsprogramm, um das übergeordnete Fenster für das Vorschausteuerelement festzulegen, an die korrekte Position zu verschieben und anzuzeigen. Diese Funktionalität ist in einer Hilfsmethode nützlich, da mehrere IPreviewHandler-Schnittstellenmethoden diese Informationen ändern und eine Auswirkung auf die Vorschau erforderlich machen.Verwenden von ActiveX-Steuerelementen
Die in diesem Artikel gezeigten Vorschauhandler wurden mit Windows Forms-Steuerelementen wie z. B. ListView implementiert. Für einige Dateiformate besteht die einfachste Möglichkeit, eine Vorschau zu rendern, in der Verwendung eines ActiveX®-Steuerelements, dem das Dateiformat zugeordnet ist. Mittels COM-Interop ermöglicht Windows Forms ActiveX-Steuerelementen, wie andere Steuerelemente gehostet zu werden. Die einfachste Möglichkeit, einen verwalteten Wrapper für ein ActiveX-Steuerelement zu erhalten, ist die Verwendung des Windows Forms ActiveX Control Importer, aximp.exe. (Weitere Informationen finden Sie unter msdn.microsoft.com/library/en-us/cptools/html/cpgrfWindowsFormsActiveXControlImporterAximpexe.asp.) Dadurch werden Interop-Assemblys erstellt, denen Sie in Ihrem Projekt manuell Verweise hinzufügen müssen. Wenn Sie stattdessen das ActiveX-Steuerelement der Visual Studio-Toolbox hinzufügen und eine Instanz des Steuerelements auf eine Ihrer Entwurfsoberflächen per Drag & Drop ziehen, generiert Visual Studio die Assemblys und fügt die Verweise automatisch für Sie hinzu.
Eine alternative Methode besteht darin, einen Wrapper manuell für das Steuerelement zu erstellen. Für einfache Steuerelemente oder für Steuerelemente, bei denen Sie nicht auf viele Funktionen zugreifen müssen, ist dieses Verfahren u. U. vorzuziehen. Hierbei handelt es sich um die Methode, die ich für die Erstellung eines Vorschauhandlers für das Adobe PDF-Format verwendet habe.
Ich empfange viele PDF-Anlagen in E-Mail-Nachrichten, und es wäre sehr nützlich, die PDFs direkt innerhalb des Anlagenvorschaufensters anzeigen zu können. Obgleich Office 2007 Dokumente im PDF-Dateiformat speichern kann (siehe microsoft.com/downloads/details.aspx?FamilyID=4d951911-3e7e-4ae6-b059-a2e79ed87041 für den Download), kann es dieses Format nicht zum Lesen öffnen. Deshalb habe ich meinen PdfPreviewHandler erstellt, mit dem ich PDF-Dateien durch Hosten des ActiveX-Steuerelements, das mit Adobe Reader geliefert wird, in der Vorschau anzeigen kann. Um das Steuerelement zu hosten, umschließe ich es mit einer aus AxHost abgeleiteten Klasse:
public class PdfAxHost : AxHost {
    public PdfAxHost() : 
        base("ca8a9780-280d-11cf-a24d-444553540000") {}

    object _ocx;
    protected override void AttachInterfaces(){_ocx = base.GetOcx(); }

    public void LoadFile(string fileName) {
        _ocx.GetType().InvokeMember(
            "LoadFile", BindingFlags.InvokeMethod, null, 
            _ocx, new object[] { fileName });
    }
}
Aximp.exe erledigt dies und noch viel mehr (indem High-Fidelity-Wrapper für alle Methoden und Ereignisse bereitgestellt werden). Ich habe das notwendige Minimum durchgeführt, um das Steuerelement zu umschließen und die eine Methode (LoadFile), die ich für dessen Aufruf benötige, zur Verfügung zu stellen. LoadFile akzeptiert den Speicherort der anzuzeigenden Datei als Argument und rendert es im Steuerelement. Ich kann dieses Steuerelement dann wie alle anderen verwenden:
public override void Load(FileInfo file)
{
    PdfAxHost viewer = new PdfAxHost();
    Controls.Add(viewer);
    IntPtr forceCreation = viewer.Handle; // creates the OCX
    viewer.Dock = DockStyle.Fill;
    viewer.LoadFile(file.FullName);
}

SetWindow und SetRect sind zwei solche Methoden. Erstere stellt das übergeordnete Fensterhandle und die neuen Grenzen für das Vorschaufenster zur Verfügung. Letztere stellt lediglich die neuen Grenzen zur Verfügung. SetFocus wird einfach in einen Aufruf an die Focus-Methode des Vorschausteuerelements übertragen, und QueryFocus gibt das Ergebnis zurück, das entsteht, wenn die GetFocus-Funktion von user32.dll vom Thread des Vorschaufensters aufgerufen wird.
Es bleiben TranslateAccelerator, Unload und DoPreview. Meine Implementierung von PreviewHandler bietet derzeit nicht viel Unterstützung für Tastenkombinationen. TranslateAccelerator delegiert lediglich zur TranslateAccelerator-Methode auf dem IPreviewFrameHandler, der in SetSite gespeichert ist. Wenn Ihr Vorschauhandler mehrere Steuerelemente anzeigt, die Registerkarten enthalten, erweitern Sie TranslateAccelerator für eine stabilere Registerkartenverarbeitung.
IPreviewHandlerFrame verfügt über eine weitere Methode, GetWindowContext, die der Vorschau eine Tabelle zur Verfügung stellt, die die Filterung von Beschleunigern ermöglicht, die an IPreviewHandlerFrame::TranslateAccelerator weitergeleitet werden sollten. Dadurch sollen Leistungssteigerungen ermöglicht werden. Selbst in systemeigenem Code bietet dies jedoch keine wesentlichen Leistungsverbesserungen, und in Anbetracht der Komplikationen, die die Verwaltung einer Beschleunigertabelle mit sich bringt, ist es wahrscheinlich nicht lohnenswert. Vorschauhandler müssen die Methode nicht verwenden – eine einfache Weiterleitung zu TranslateAccelerator ist ausreichend. Kurz gesagt, machen Sie sich keine Sorgen um GetWindowContext.
Die Unload-Methode blendet das Vorschaufenster aus und delegiert daraufhin zur virtuellen PreviewHandlerControl.Unload-Methode, die bereits in diesem Artikel beschrieben wurde. DoPreview, wohl die wichtigste Methode auf der Schnittstelle, ist nicht schwieriger zu implementieren. DoPreview wird aufgerufen, um das eigentliche Rendern der Vorschau durchzuführen. Meine Implementierung ruft die abstrakte Load-Methode auf und übergibt ihr das PreviewHandlerControl. Die abgeleitete Implementierung ruft daraufhin entweder die Load-Methode von FileBasedPreviewHandler mit dem Pfad auf, der von IInitializeWithFile gespeichert wurde, oder die Load-Methode von StreamBasedPreviewHandlerControl mit dem Datenstrom, der in IInitializeWithStream bereitgestellt wird:
// in FileBasedPreviewHandler
protected override void Load(PreviewHandlerControl c)
{
    c.Load(new FileInfo(_filePath));
}

// in StreamBasedPreviewHandler
protected override void Load(PreviewHandlerControl c)
{
    c.Load(new ReadOnlyIStreamStream(_stream));
}
(Beachten Sie, dass sie den COM-IStream mit einem Wrapper umschließt, der aus System.IO.Stream abgeleitet wurde und der der abgeleiteten Klasse ermöglicht, den IStream schreibgeschützt zu nutzen, wie sie das mit einem beliebigen anderen .NET-Datenstrom tun würde.) Nachdem die Daten geladen wurden, ruft die DoPreview-Methode UpdateWindowBounds auf, um sicherzustellen, dass die Vorschau ordnungsgemäß angezeigt wird.

Registrieren von Handlern
Sowohl die Windows Vista-Shell als auch Outlook 2007 bestimmen mithilfe der Windows-Registrierung, welche Vorschauhandler verfügbar und welche Dateitypen ihnen zugeordnet sind.
Wie allen anderen COM-Klassen muss einem Vorschauhandler eine Klassen-ID zugewiesen werden. In Abbildung 2 ist dies die Aufgabe des GuidAttribute, das auf die Klasse angewendet wird:
    [Guid("853f35e3-bd13-417b-b859-1df25be6c834")]
Sie sehen sicherlich, dass jeder meiner Vorschauhandler über eine andere GUID verfügt. Jedem der von Ihnen erstellten Vorschauhandler müssen außerdem einmalige IDs zugewiesen werden. Diese ID wird als Teil der Registrierung des Vorschauhandlers verwendet. Die in Windows enthaltenen Dienstprogramme „uuidgen.exe“ und „guidgen.exe“ bieten schnelle Möglichkeiten für die Erzeugung von GUIDs.
Abbildung 7 Liste registrierter Vorschauhandler (Klicken Sie zum Vergrößern auf das Bild)
Fügen Sie zunächst dem PreviewHandler-Schlüssel unter HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers einen REG_SZ-Wert hinzu, wobei der Name des Werts die GUID ist, die dem Vorschauhandler zugewiesen ist. Auch wenn er nicht direkt verwendet wird, sollte der Wert dieses Eintrags der Anzeigename des Vorschauhandlers sein. Alle integrierten Vorschauhandler verhalten sich so und machen es damit leicht zu sehen, welche Vorschauhandler registriert sind, indem diese Liste einfach in der Registrierung geprüft wird (siehe Abbildung 7). Später, wenn alles korrekt registriert worden ist, können Sie auch eine Liste aller registrierten Vorschauhandler innerhalb des Outlook-Vertrauensstellungscenters anzeigen (siehe Abbildung 8).
Abbildung 8 Liste von Vorschauhandlern im Outlook-Vertrauensstellungscenter (Klicken Sie zum Vergrößern auf das Bild)
Als Nächstes müssen Sie einen speziellen Registrierungsschlüssel unterhalb des Shellex-Schlüssels für die Zielerweiterung erstellen. Dieser spezielle Schlüssel ist mit einer bestimmten Kennung, „{8895b1c6-b41f-4c1c-a562-0d564250836f}“, benannt, wodurch das System erkennt, dass die Daten, die es enthält, einen Vorschauhandler darstellen. Der Standardwert für diesen Schlüssel ist auf die GUID für den Vorschauhandler, der für diese Erweiterung verwendet wird, eingestellt:
using (RegistryKey extensionKey = 
       Registry.ClassesRoot.CreateSubKey(extension))
using (RegistryKey shellexKey = extensionKey.CreateSubKey("shellex"))
using (RegistryKey previewKey = shellexKey.CreateSubKey(
       "{8895b1c6-b41f-4c1c-a562-0d564250836f}"))
{
    previewKey.SetValue(null, previewerGuid, RegistryValueKind.String);
}
Wenn ein bestimmter Vorschauhandler mit mehreren Dateierweiterungen verwendet werden kann, wiederholen Sie diesen Schritt einfach für so viele Erweiterungen wie nötig. Mein Framework sucht nach einem PreviewHandlerAttribute auf dem Vorschauhandler. Dieses Attribut gibt den Anzeigenamen des Handlers, die Erweiterungen, die vom Handler unterstützt werden, sowie eine AppID (die weiter unten beschrieben wird) an. Die unterstützten Erweiterungen können eine einfache Zeichenfolge sein, wie es beim ZipPreviewHandler der Fall ist:
[PreviewHandler("ZIP Preview Handler", ".zip", 
                "{c0a64ec6-729b-442d-88ce-d76a9fc69e44}")]
Es kann außerdem eine durch Semikolons getrennte Liste von Erweiterungen sein, wie es beim BinPreviewHandler der Fall ist:
[PreviewHandler("Binary Preview Handler", ".bin;.dat", 
                "{e92d3c10-89c8-4543-91b9-7a74305e9df4}")]
In einem solchen Szenario durchläuft meine Registrierungsfunktion (die Sie im Download sehen können) jede der aufgelisteten Erweiterungen und registriert den Handler für jede von ihnen. (Beachten Sie, dass es bestimmte Vorschauhandler in Windows Vista gibt, die mehr Dateitypen verarbeiten können, als sie laut Registrierung unterstützen. Informationen zum Anzeigen von mehr Dateitypen in der Vorschau finden Sie in der Randleiste „Zeigen Sie, was Sie haben“.)
Zeigen Sie, was Sie haben
Es gibt eine Reihe von Vorschauhandlern, die in Windows Vista integriert sind. Einige von ihnen können mehr Dateitypen verarbeiten, als sie laut Registrierung standardmäßig unterstützen können. Nehmen Sie zum Beispiel den Microsoft Windows-TXT-Vorschauhandler. Wie Sie sich denken können, rendert dieser Vorschauhandler .txt-Dateien. Aber nichts hindert diesen Vorschauhandler daran, mit anderen Dateien als .txt-Dateien zu arbeiten. Als Entwickler empfange ich häufig C#-, Visual Basic®- und C++-Codedateien als E-Mail-Anlagen. Ich würde diese Anlagen gern innerhalb von Outlook in der Vorschau anzeigen, statt Visual Studio® als Standardviewer zu öffnen. Das ist möglich. Ich muss lediglich .cs-, .vb-, .cpp- und .h-Dateien als Dateien registrieren, die mit dem Microsoft Windows-TXT-Vorschauhandler in der Vorschau angezeigt werden sollen. Die folgende .reg-Datei ist genau dafür zuständig:
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.cs\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}]
@="{1531d583-8375-4d3f-b5fb-d23bbd169f22}"

[HKEY_CLASSES_ROOT\.vb\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}]
@="{1531d583-8375-4d3f-b5fb-d23bbd169f22}"

[HKEY_CLASSES_ROOT\.cpp\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}]
@="{1531d583-8375-4d3f-b5fb-d23bbd169f22}"

[HKEY_CLASSES_ROOT\.h\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}]
@="{1531d583-8375-4d3f-b5fb-d23bbd169f22}"
Am besten ist es, eine einfache Anwendung oder ein einfaches Skript zu schreiben, die bzw. das alle Dateierweiterungen unter HKEY_CLASSES_ROOT aufzählt. Sie können prüfen, ob für jede Erweiterung ein Content Type-Wert eingestellt ist, und wenn dies der Fall ist, ob der Wert dieses Content Type mit „text/“ beginnt. Wenn dies der Fall ist und wenn für die Erweiterung nicht bereits ein Vorschauhandler konfiguriert ist, können Sie sie für die Vorschau mit dem TXT-Handler einrichten, wie ich es im vorherigen Registrierungsskript getan habe. In nur wenigen Minuten können Sie die Anzahl der Dateitypen, die in der Vorschau angezeigt werden können, erheblich erhöhen.
Selbstverständlich ist der TXT-Vorschauhandler nicht der einzige Handler, der fähig ist, unterschiedliche Dateitypen in der Vorschau anzuzeigen. Einige Handler, die Sie schreiben, können ebenso flexibel sein. Denken Sie an den XmlPreviewHandler, der in diesem Artikel beschrieben wurde. Wenn Sie sich den Codedownload ansehen, werden Sie sehen, dass der Vorschauhandler „InternetExplorer­PreviewHandler“ genannt wird. Ich habe seinen Namen geändert, da er nicht wirklich für das XML-Format spezifisch ist. Er ist eher spezifisch für das Steuerelement, das für das Rendern der XML (das WebBrowser-Steuerelement) verwendet wird. Da der Vorschauhandler das WebBrowser-Steuerelement für das Navigieren zu der Datei, die in der Vorschau angezeigt wird, verwendet, kann dieser Handler mit einer Vielzahl von Dateitypen verwendet werden. Viele dieser Dateitypen werden bereits mit anderen Vorschauhandlern verarbeitet, einige jedoch nicht. Das neue XPS-Dateiformat (XML Paper Specification), das mit Windows Vista geliefert wird, verfügt beispielsweise über keinen integrierten Vorschauhandler. Internet Explorer kann jedoch XPS-Dokumente rendern. Daher habe ich den InternetExplorerPreviewHandler durch Hinzufügen von .xps zur Liste unterstützter Dateierweiterungen erweitert. Durch diese einfache Maßnahme verfüge ich nun über einen Vorschauhandler für XPS.

 
Zusätzlich zur Registrierung von Dateitypen können Sie Prog IDs registrieren. Zum Beispiel ist der Standardwert in HKEY_CLASSES_ROOT\.xml „xmlfile“. Deshalb ist mein XML-Vorschauhandler unter HKEY_CLASSES_ROOT\xmlfile\ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f} registriert:
[PreviewHandler("XML Preview Handler", "xmlfile", 
                 "{88235ab2-bfce-4be8-9ed0-0408cd8da792}")]
Wenn Sie also über mehrere Dateierweiterungen verfügen, die tatsächlich demselben Typ entsprechen, verwenden Sie dieselbe Prog ID für jede Erweiterung, und registrieren Sie die Vorschau nur einmal. (Sie könnten z. B. auf HKEY_CLASSES_ROOT\jpegfile statt sowohl auf HKEY_CLASSES_ROOT\.jpeg als auch auf HKEY_CLASSES_ROOT\.jpg registrieren.) Das Windows-Shellteam empfiehlt, dass neue Dateitypen neue Prog IDs definieren und Vorschauhandler auf diesen statt auf den eigentlichen Erweiterungen definieren, auch wenn derzeit eine 1:1-Zuordnung zwischen der Prog ID und der Erweiterung besteht.
Die nächste Registrierungsänderung (für verwaltete Handler) beinhaltet AppIDs. Der Grund hierfür geht zurück auf meine Erörterung des Schreibens verwalteter Add-Ins für die Windows Vista-Shell. Wie bereits gesagt, werden Vorschauhandler prozessextern aus der Shell geladen, standardmäßig in einen Stellvertreterhost (prevhost.exe). Aus Gründen der Effizienz verlangt das System die Ausführung von so wenig Instanzen von prevhost.exe wie möglich. Daher werden mehrere Typen von Vorschauhandlern in denselben externen Prozess geladen. Dies bedeutet, dass, während die Shell nicht versuchen wird, verwaltete Vorschauhandler zu laden, die auf verschiedene Versionen der CLR abzielen, prevhost.exe dies möglicherweise tut. Dies konfrontiert uns abermals mit dem anfänglichen Problem. Die Lösung liegt darin, das System anzuweisen, Instanzen Ihres Vorschauhandlers in einen Stellvertreterhost zu laden, der für Vorschauhandler desselben Typs reserviert ist. Dadurch ist garantiert, dass Sie nicht mehrere Vorschauhandler haben, die auf unterschiedliche Versionen der CLR, die in denselben Stellvertreterprozess geladen wurden, abzielen. Die COM-Klassenregistrierung für den Vorschauhandler (auf die ich als nächstes zu sprechen komme) gibt eine ID für Konfigurationsdetails darüber an, welcher Stellvertreterhost zu verwenden ist. Diese ID wird als AppID bezeichnet. Wenn ein verwalteter Vorschauhandler registriert ist, erstelle ich eine neue AppID-Registrierung unter HKCR\AppID, die nur von diesem verwalteten Vorschauhandler verwendet wird.
using (RegistryKey appIdsKey = Registry.ClassesRoot.OpenSubKey(
       "AppID", true))
using (RegistryKey appIdKey = appIds.CreateSubKey(appID))
{
    appIdKey.SetValue("DllSurrogate", 
                   @"%SystemRoot%\system32\prevhost.exe", 
                   RegistryValueKind.ExpandString);
}
Beachten Sie, dass diese AppID nach wie vor auf prevhost.exe als Stellvertreterhost zeigt.
Jetzt muss nur noch die COM-Komponentenregistrierung dem Vorschauhandler hinzugefügt werden. Der Großteil davon kann einfach durch Verwendung des regasm.exe-Tools, das mit dem .NET Framework SDK geliefert wird, durchgeführt werden:
regasm /codebase ManagedPreviewHandler.dll
(Dies kann ebenso über ein Installationsprogramm durchgeführt werden, mit einer kleinen Menge Code, der die RegisterAssembly-Methode der RegistrationServices-Klasse im System.Runtime.InteropServices-Namespace von mscorlib verwendet.)
Wie bereits erwähnt, muss ich jedoch die Komponentenregistrierung ändern, um auf die neue, von mir erstellte AppID zu zeigen. Außerdem muss ich ihr einen Anzeigenamen hinzufügen, der von Hosts wie Outlook 2007 verwendet wird, um registrierte Handler aufzulisten:
using (RegistryKey clsidKey = Registry.ClassesRoot.OpenSubKey("CLSID"))
using (RegistryKey idKey = clsidKey.OpenSubKey(previewerGuid, true))
{
    idKey.SetValue("AppID", appID, RegistryValueKind.String); 
    idKey.SetValue("DisplayName", name, RegistryValueKind.String);
}
Bei einem Produktionsvorschauhandler sollte DisplayName tatsächlich auf einen REG_SZ-Wert eingestellt werden, der auf eine lokalisierte binäre Win32-Ressourcenzeichenfolge (z. B. „@myhandler.dll,-101“) zeigt. Leider bietet die IDE für Visual C#® 2005 keine integrierte Möglichkeit, um eine Win32-Ressourcendatei einer verwalteten Assembly hinzuzufügen. Um dies zu tun, müssen Sie die Befehlszeile verwenden, da csc.exe den /win32res-Schalter unterstützt, der das Hinzufügen von Win32-Ressourcendateien ermöglicht. Für den Zweck dieses Artikels und meinen Registrierungscode habe ich mich für einen nicht lokalisierten Wert entschieden.
Die gesamte Registrierungslogik ist in eine einzige Methode auf dem PreviewHandler integriert:
protected static void RegisterPreviewHandler(
    string name, string extensions, string previewerGuid, string appID)
Es gibt auch eine entsprechende Methode zum Aufheben der Registrierung.
Das regasm.exe-Tool unterstützt ComRegisterFunctionAttribute aus dem System.Laufzeit.Runtime.InteropServices-Namespace in mscorlib. Sie können dieses Attribut auf eine statische Methode in einer Assembly anwenden, die mit regasm registriert werden soll, und wenn Typen in dieser Assembly ComVisible sind, werden ihre Typinformationen an die Methode weitergegeben, die mit dem ComRegisterFunction-Attribut markiert ist (nachdem die COM-Klasse des Typs in der Registrierung registriert wurde). Dies erleichtert erheblich das Schreiben einer Methode, die die zuvor beschriebenen Registrierungsinformationen verarbeitet, wenn die Assembly für COM-Interop registriert ist (siehe Abbildung 9). Ich habe das Gegenstück einer Funktion zur Aufhebung der Registrierung hinzugefügt, die mit ComUnregisterFunctionAttribute markiert ist.
[ComRegisterFunction]
public static void Register(Type t)
{
    if (t != null && t.IsSubclassOf(typeof(PreviewHandler)))
    {
        object[] attrs = (object[])t.GetCustomAttributes(
            typeof(PreviewHandlerAttribute), true);
        if (attrs != null && attrs.Length == 1)
        {
            PreviewHandlerAttribute attr = 
                attrs[0] as PreviewHandlerAttribute;
            RegisterPreviewHandler(attr.Name, attr.Extension, 
                t.GUID.ToString("B"), attr.AppID);
        }
    }
}

Wenn dies geschehen ist, erfordert die Installation und Registrierung meiner Vorschauhandler lediglich zwei Zeilen an der Eingabeaufforderung:
gacutil -i MsdnMagPreviewHandlers.dll
regasm /codebase MsdnMagPreviewHandlers.dll
Das Aufheben der installierten Vorschauhandler erfordert ebenfalls lediglich zwei Zeilen an der Eingabeaufforderung:
regasm /unregister MsdnMagPreviewHandlers.dll
gacutil -u MsdnMagPreviewHandlers
Wenn Sie Ihre eigenen benutzerdefinierten Vorschauhandler schreiben, die dieses Framework verwenden, und Sie sie in dieselbe Assembly schreiben, wie ich es mit meinem getan habe, müssen Sie nichts weiter tun, damit sie funktionieren. Wenn Sie sie mithilfe meines Frameworks, aber in eine andere Assembly schreiben, müssen Sie Ihrer Assembly lediglich die folgende Klasse hinzufügen:
internal sealed class PreviewHandlerRegistration
{
    [ComRegisterFunction]
    internal static void Register(Type t) { 
        PreviewHandler.Register(t); 
    }
    [ComUnregisterFunction]
    internal static void Unregister(Type t) { 
        PreviewHandler.Unregister(t); 
    }
}
Installieren und registrieren Sie zunächst MsdnMagPreviewHandlers.dll wie zuvor von mir gezeigt. Installieren und registrieren Sie dann Ihre Assembly. Wenn regasm die ComRegisterFunction in der PreviewHandlerRegistration-Klasse in Ihrer Assembly sieht, ruft es die ComRegisterFunction für jeden der Vorschauhandler in Ihrer Assembly auf. Die Funktion delegiert zur gesamten zuvor beschriebenen Funktionalität, die in MsdnMagPreviewHandlers.dll enthalten ist.
Abbildung 10 XamlPreviewHandler in Outlook 2007 (Klicken Sie zum Vergrößern auf das Bild)
Um zu veranschaulichen, wie dies funktioniert, habe ich einen Vorschauhandler für XAML-Dateien dem Download für diesen Artikel hinzugefügt. Er wird in seiner eigenen Assembly implementiert, die meine XamlPreviewHandler-Klasse und die PreviewHandlerRegistration-Klasse enthält. Abbildung 10 ist ein Screenshot des beschriebenen Handlers in Outlook 2007. (Die in der Vorschau angezeigte XAML-Datei, die aus dem Windows Presentation Foundation SDK-Teamblog stammt, ist unter blogs.msdn.com/wpfsdk/archive/2006/05/23/Animating_XAML_Clip_Art.aspx verfügbar.) Beachten Sie, dass XamlPreviewHandler aus dem StreamBasedPreviewHandler und nicht dem FileBasedPreviewHandler abgeleitet wird. Die XamlReader-Klasse, die mit der Windows Presentation Foundation geliefert wird, unterstützt das Laden aus einem Stream. Daher macht es Sinn, IInitializeWithStream statt IInitializeWithFile zu implementieren:
public override void Load(Stream stream)
{
    Frame f = new Frame();

    XamlReader reader = new XamlReader();
    f.Content = reader.LoadAsync(stream);

    ElementHost xamlHost = new ElementHost();
    xamlHost.Child = f;
    xamlHost.Dock = DockStyle.Fill;
    Controls.Add(xamlHost);
}

Debuggen
Da prozessinterne Vorschauhandler unter einem Stellvertreterhostprozess ausgeführt werden (standardmäßig prevhost.exe), müssen Sie für das Debuggen eines Vorschauhandlers den Debugger an den Hostprozess anhängen. Dafür gibt es verschiedene Möglichkeiten. Die erste besteht darin, auf das Starten des Hostprozesses zu warten und dann die Fähigkeit des Debuggers zu nutzen, sich an einen vorhandenen Prozess anzuhängen. Es können jedoch mehrere Instanzen von prevhost.exe vorhanden sein, insbesondere wenn Sie meinen Rat befolgen, eine neue AppID für jeden ihrer verwalteten Vorschauhandler zu erstellen. Wenn mehrere Instanzen von prevhost.exe ausgeführt werden, müssen Sie wissen, zu welcher Instanz eine Verbindung hergestellt werden soll. Verwenden Sie ein Tool wie Process Explorer (www.sysinternals.com/Utilities/ProcessExplorer.html) von Sysinternals (kürzlich von Microsoft erworben), um die Befehlszeilenargumente zu untersuchen, die verwendet werden, wenn die entsprechende Instanz von prevhost.exe gestartet wurde. Als ausführbare Stellvertreterhostdatei muss prevhost.exe beim Start mitgeteilt werden, welche COM-Komponente zu hosten ist, und zu diesem Zweck wird ihr die GUID für Ihren Vorschauhandler in der Befehlszeile übergeben:
prevhost.exe {8FD75842-96AE-4AC9-A029-B57F7EF961A8} -Embedding
Sie können die Befehlszeilenargumente für jede Instanz von prevhost.exe anzeigen, um jene mit der GUID zu finden, die mit dem Vorschauhandler übereinstimmt, den Sie debuggen möchten. Sie können anschließend die Prozess-ID verwenden, um den Debugger an den korrekten Prozess anzuhängen.
Wenn der Prozess bereits ausgeführt wird, wurde der Vorschauhandler bereits erstellt und ein Teil seines Codes ausgeführt. Um seinen Start zu debuggen, benötigen Sie eine andere Lösung. Eine Methode besteht darin, einen Aufruf von System.Diagnostics.Debug.Break an den Anfang des Konstruktors zu stellen. Dadurch wird der Debugger gestartet und in diesem Moment an den Prozess angehängt, was Ihnen ein Debuggen Ihrer gesamten Komponente ermöglicht.
Das Anhängen eines Debuggers ist jedoch nicht immer die beste Lösung. In bestimmten Fällen ist ein klassisches „printf-style“-Debuggen vorzuziehen, mit dem Sie eine Menge an Informationen ausgeben können, die Sie sich ansehen können, während der Vorschauhandler ausgeführt wird. In diesem Fall ist es am besten, die System.Diagnostics.Trace-Klasse zu verwenden. Standardmäßig enthält die TraceListenerCollection, an die Trace seine Ausgaben sendet, einen TraceListener, der die verfolgten Daten in OutputDebugString schreibt und allen aktivierten Debuggern ermöglicht, den Ausgabetext anzuzeigen. Zum Windows SDK gehört Debug Monitor (DbMon.exe), mit dem diese Ablaufverfolgungen einfach angezeigt werden können.Vorschauhandler und Windows XP
Von Ryan Gregg

Mit dem in diesem Artikel zur Verfügung gestellten Framework ist das Schreiben eines prozessinternen Vorschauhandlers ein einfacher Vorgang in verwaltetem Code. Die Erstellung eines Vorschauhandlers, der mit Office Outlook 2007 sowohl unter Windows XP als unter Windows Vista kompatibel ist, ist jedoch etwas komplizierter. Die Proxyanwendung (namens prevhost.exe), die prozessinterne Vorschauhandler unter Windows Vista hostet, ist unter Windows XP nicht verfügbar. Vorschauhandler für Windows XP müssen als lokale COM-Server geschrieben werden, ohne sich auf eine andere Anwendung für das Hosten zu verlassen.
Leider stellt .NET Framework 2.0 den Großteil der Verbindung nicht bereit, die für das Registrieren einer .NET-Anwendung als lokaler COM-Server erforderlich ist. Als Entwickler eines verwalteten Vorschauhandlers müssen Sie Code schreiben, der wie eine systemeigene Codeimplementierung eines Vorschauhandlers aussieht und sich entsprechend verhält. Im Folgenden finden Sie eine High-Level-Ansicht dessen, was Sie schreiben müssen.
Ein typischer lokaler COM-Server ist als Windows-Anwendung statt als Klassenbibliothek kompiliert. Wenn COM eine neue Instanz des lokalen Servers anfordert, startet es die lokale Serveranwendung mit einem besonderen Satz von Flags, um anzugeben, dass die Anwendung im Einbettungsmodus starten soll. Dadurch wird die Anwendung ohne Benutzeroberfläche gestartet, und Klassenfactorys werden mit COM registriert. Derselbe Server verfügt wahrscheinlich über zwei zusätzliche logische Komponenten in Bezug auf die prozessinterne Vorschauhandlerimplementierung: eine COM-Serverimplementierung und eine Klassenfactory.
Die COM-Serverimplementierung stellt das laufende Anwendungsframework für die ausführbare Datei zur Verfügung, die von COM gestartet wird, wenn der Vorschauhandler aufgerufen wird. Innerhalb der Main-Methode für die Anwendung müssen Sie die Befehlszeilenparameter analysieren, jede Klassenfactoryregistrierung mit COM initiieren, entscheiden, wann es sicher ist, den Prozess zu beenden, und die Meldungsschleife aktiv halten, sodass die Anwendung, die den Vorschauhandler hostet, nicht steckenbleibt. Die meisten lokalen COM-Server unterstützen außerdem Befehlszeilenschalter für die Registrierung und das Aufheben der Registrierung des Servers in der Windows-Registrierung. Da regasm.exe keine Methode für die Registrierung als lokaler Server bereitstellt, müssen Sie auch Methoden für die Erstellung der richtigen Registrierungsschlüssel für die lokale COM-Serverregistrierung schreiben. Diese Schlüssel sind in der COM-Dokumentation ausreichend dokumentiert.
Die Klassenfactorykomponente implementiert die IClassFactory-Schnittstelle, die von COM bereitgestellt wird. Außerdem ist diese Komponente verantwortlich für das Registrieren von Klassenobjekten mit COM und das Erstellen von Klassenobjektinstanzen für COM-Aufrufer. Vor allem soll die Klassenfactory Aufrufe an CoRegisterClassObject und CoRevokeClassObject senden, um dem System anzuzeigen, dass eine Klassenfactory für die GUID, die für die Identifizierung des Vorschauhandlers verwendet wird, verfügbar ist (oder widerrufen wurde). Auf Anforderung erstellt die Klassenfactory eine neue Instanz der Vorschauhandlerklasse und marshallt sie zurück zum COM-Aufrufer. Diese Instanzen müssen nachverfolgt und Freispeichersammlung (Garbage Collection) muss möglicherweise erzwungen werden, sodass die Serveranwendung bestimmen kann, wann alle externen Verweise freigegeben wurden, um ein ordnungsgemäßes Herunterfahren zu ermöglichen.
Das Schreiben eines verwalteten Vorschauhandlers, der sowohl unter Windows XP als auch unter Windows Vista mit Outlook 2007 kompatibel ist, erfordert einen beträchtlichen Aufwand. Obgleich diese Randleiste die erforderlichen Punkte abdeckt, habe ich die wichtigen Details nicht näher ausgeführt. Alle, die einen lokalen COM-Server entwickeln, müssen die Funktionsweise von COM, den Unterschied zwischen Threadapartmentstilen sowie die Arbeit mit Win32®-APIs mithilfe von verwaltetem Code verstehen. Ein Fehlen dieses Wissens führt wahrscheinlich zu Enttäuschung.
Obgleich das Schreiben eines lokalen COM-Servervorschauhandlers in verwaltetem Code möglicherweise nicht so einfach ist wie das Schreiben eines prozessinternen Handlers, ist dies ein Detail, das Sie nicht ignorieren sollten, wenn Sie sicherstellen möchten, dass Ihr Vorschauhandler unabhängig von den verwendeten Betriebssystemen für jeden Office 2007-Benutzer funktioniert. Weitere Details und Beispiele dazu, wie diese Punkte zusammenpassen, finden Sie in den folgenden hilfreichen Ressourcen:
  • COM Fundamentals (windowssdk.msdn.microsoft.com/ms694505.aspx) ist eine MSDN-Dokumentation, die das Microsoft Component Object Model beschreibt.
  • PInvoke.net (www.pinvoke.net) ist eine ausgezeichnete Website, die Benutzern beim Definieren von Win32-API-Aufrufen in verwaltetem Code hilft.
  • Building COM Servers in .NET (www.codeproject.com/useritems/BuildCOMServersInDotNet.asp) stellt ein nützliches Beispiel für eine lokale COM-Serverimplementierung bereit.
Ryan Gregg arbeitet für Microsoft als Program Manager für Office Outlook, und zwar hauptsächlich an der Erweiterbarkeitsplattform und an benutzerdefinierten Formularen. Er schreibt mehr oder weniger regelmäßig über Outlook-Erweiterbarkeit unter blogs.msdn.com/rgregg.

Für Testzwecke empfehle ich Ihnen das Hinzufügen einer Ablaufverfolgungsanweisung am Beginn jeder der zuvor beschrieben Schnittstellenmethoden. Alternativ dazu können Sie Debugger.Break gemeinsam mit dem neuen Ablaufverfolgungspunkt-Feature in Visual Studio 2005 verwenden. Dadurch bekommen Sie ein sehr viel besseres Verständnis davon, wie die Shell und Outlook 2007 mit Ihrem Vorschauhandler interagieren.

Schlussbemerkung
Vorschauhandler sind ein großartiger Zusatz sowohl zur Windows Vista-Shell als auch zu Outlook 2007. Wenn Sie die Produktivitätsvorteile von verwaltetem Code hinzufügen (mithilfe meines vorhandenen Frameworks habe ich nur sieben Minuten gebraucht, um den XAML-Vorschauhandler zu erstellen und bereitzustellen), verfügen Sie über eine leistungsstarke Plattform für das Anzeigen vorhandener Dateitypen sowie Ihrer eigenen benutzerdefinierten Dateitypen. Ich bin davon überzeugt, dass Vorschauhandler das Potential haben, unseren Alltag produktiver zu gestalten, und je mehr unterstützte Dateitypen es gibt, desto produktiver werden wir alle sein. Zögern Sie also nicht, einen Vorschauhandler für Ihre bevorzugte Erweiterung zu codieren.

Stephen Toubist technischer Editor für das MSDN Magazin.

Page view tracker