MSDN Magazine > Inicio > Todos los números > 2007 > January >  Windows Vista y Office: Vea los datos a su mane...
Windows Vista y Office
Vea los datos a su manera con el marco de controladores de vista previa administrados
Stephen Toub

En este artículo se analizan los siguientes temas:
  • Escritura de complementos administrados para Windows y Office
  • Implementación de un marco de controladores de vista previa administrados
  • Personalización de controladores de vista previa para cualquier extensión de archivo
  • Interoperabilidad COM
En este artículo se utilizan las siguientes tecnologías:
.NET Framework, Windows Vista, Outlook
Descargar el código de este artículo: PreviewHandlers2007_01.exe (444 KB)
Examinar el código en línea
Cada versión de Microsoft Windows y Office aporta métodos y enfoques nuevos para mejorar la capacidad de ver los datos, interactuar con ellos y comprenderlos. De igual modo, también presenta puntos de extensibilidad nuevos y mejorados para integrar más funciones personalizadas. En Windows Vista™ y el sistema Microsoft® Office 2007, estas dos áreas de avance se han combinado y ofrecen la posibilidad de escribir controladores de vista previa personalizados para Windows Vista y Outlook® 2007.
Outlook 2003 ofrecía un panel de lectura para el correo electrónico que facilitaba la visualización del mensaje sin tener que abrirlo. Bastaba con seleccionarlo en la vista de lista de la carpeta de correo y este se procesaba en una ventana lateral o inferior. Outlook 2007 amplía este concepto, ya que permite ver los datos adjuntos en el mismo panel de vista previa sin necesidad de hacer doble clic para abrirlos en el visor correspondiente.
Ahora, Outlook se proporciona con controladores de vista previa para documentos de Word, presentaciones de PowerPoint®, hojas de cálculo de Excel, archivos de fuentes, archivos de vídeo y audio, y muchos otros tipos de archivos que suelen enviarse como datos adjuntos. Pero eso es sólo el principio. Windows Vista admite un panel de vista previa similar, accesible desde cualquier carpeta del shell. Para habilitar este panel, seleccione Organizar | Diseño | Panel de vista previa en el menú de la carpeta (consulte la figura 1).
Figura 1 Habilitación de la vista previa en las carpetas de Windows Vista 
Outlook y Windows Vista adoptan el mismo mecanismo de vista previa subyacente y permiten que los desarrolladores puedan implementar controladores personalizados para cualquier tipo de archivo, registrarlos y obtener una vista previa inmediata en Outlook 2007 y Windows Vista.
En este artículo, explicaré los requisitos para implementar un controlador de vista previa y comentaré cómo hacerlo mediante código administrado (el SDK de Windows® para Vista incluye un controlador de ejemplo escrito en C++ nativo). La descarga de código de este artículo incluye un marco de trabajo que agiliza la implementación de los controladores y ofrece varias muestras (por ejemplo, para los formatos PDF, XML, ZIP, MSI, BIN, CSV, XPS y XAML).

Alojamiento de los controladores de vista previa
Aquellos que hayan intentado implementar complementos administrados en el shell de Windows en el pasado se sentirán incómodos con este concepto. Después de todo, Microsoft aconseja encarecidamente que no se implementen los complementos de shell con código administrado. Además, estos complementos no se consideran compatibles. ¿Por qué? Porque los complementos se cargan en el shell en proceso (explorer.exe). Sólo se puede cargar una versión de Common Language Runtime (CLR) en un proceso dado y es posible que el código administrado creado con una versión de CLR no se ejecute en un proceso con una versión anterior. Entonces, ¿qué ocurre si hay dos complementos de shell y ambos están escritos con código administrado, uno destinado a .NET Framework 1.1 y, el otro, a .NET Framework 2.0? Si se carga primero el complemento destinado a 2.0, quizá no experimente ningún problema; el complemento de 1.1 se cargará y se ejecutará correctamente en CLR 2.0. Pero si el complemento destinado a 1.1 se carga primero, CLR de .NET Framework 1.1 se cargará en explorer.exe. Los ensamblados destinados a .NET Framework 2.0 no se pueden cargar en .NET Framework 1.1 y, por lo tanto, el complemento de 2.0 producirá un error al cargarse después que el complemento de 1.1.
Los mismos problemas existen en Windows Vista, así que Microsoft sigue advirtiendo que los complementos de shell no se implementen con código administrado, ni siquiera en los puntos de extensibilidad nuevos del shell como, por ejemplo, proveedores de miniaturas y controladores de propiedades (que el indizador de búsqueda de Windows Vista utiliza fuera de proceso, al contrario que el shell).
Aun así, hay buenas noticias sobre los controladores de vista previa: siempre se cargan fuera de proceso, al menos, en lo que se refiere al shell. Aunque estos controladores se implementan como componentes COM, no se alojan en el shell de Windows Vista. En su lugar, se alojan en el host suplente del controlador de vista previa (prevhost.exe) o se implementan como servidor COM local. En lo que respecta al código administrado, la implementación de servidores COM queda fuera del alcance de este artículo. Si desea leer una introducción al tema, consulte la barra lateral "Controladores de vista previa y Windows XP", de Ryan Gregg (puede estar en inglés). Además, Microsoft prefiere y recomienda el modelo de servicio prevhost.exe.

Marco de trabajo PreviewHandler
Para que el controlador de vista previa sea válido, deben implementarse varias interfaces; todas se encuentran documentadas en windowssdk.msdn.microsoft.com/aa361576.aspx (puede estar en inglés). Entre ellas, se incluyen IPreviewHandler (shobjidl.h), IInitializeWithFile, IInitializeWithStream o IInitializeWithItem (propsys.h), IObjectWithSite (ocidl.h) y IOleWindow (oleidl.h). También hay interfaces opcionales, como IPreviewHandlerVisuals (shobjidl.h), que el controlador puede implementar para aumentar la compatibilidad.
Si tuviera que escribir un controlador de vista previa con código nativo, podría ponerse manos a la obra inmediatamente, ya que todas estas interfaces están definidas y listas para incluirlas desde los archivos de encabezado entre paréntesis. Sin embargo, para escribir un controlador de vista previa con código nativo, primero debe escribir u obtener definiciones de interoperabilidad COM para cada interfaz. Mis definiciones se muestran en la figura 2.
[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);
}

En mi marco administrado para implementar los controladores de vista previa, casi toda la funcionalidad de estas interfaces queda contenida en una clase base abstracta, PreviewHandler:
public abstract class PreviewHandler : 
    IPreviewHandler, IPreviewHandlerVisuals, IOleWindow, IObjectWithSite
{ ... }
Así se ocultan todos los detalles de implementación morbosos (que, como verá, no son tan morbosos). No obstante, tenga en cuenta que PreviewHandler no implementa IInitializeWithFile ni IInitializeWithStream. Tal honor recae en dos clases abstractas derivadas de PreviewHandler:
public abstract class StreamBasedPreviewHandler : 
    PreviewHandler, IInitializeWithStream { ... }

public abstract class FileBasedPreviewHandler : 
    PreviewHandler, IInitializeWithFile { ... }
Para implementar un controlador de vista previa personalizado, hay que derivar una clase nueva a partir de StreamBasedPreviewHandler o FileBasedPreviewHandler, e invalidar un método. Observe la implementación desde mi clase XmlPreviewHandler:
public sealed class XmlPreviewHandler : FileBasedPreviewHandler
{
    protected override PreviewHandlerControl 
        CreatePreviewHandlerControl()
    {
        return new XmlPreviewHandlerControl();
    }
}
El método CreatePreviewHandlerControl devuelve una instancia de un tipo personalizado que se escribe derivado de StreamBasedPreviewHandlerControl o de FileBasedPreviewHandlerControl. Ambos derivan de mi clase base abstracta PreviewHandlerControl:
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() { ... }
}
Como indica su nombre, se llama al método Load cuando es necesario cargar un archivo o una secuencia y obtener la vista previa. Asimismo, se llama al método Unload cuando se debe interrumpir la vista previa actual. Entonces, un PreviewHandlerControl personalizado se encarga de la derivación a partir del tipo adecuado (bien FileBasedPreviewHandlerControl o StreamBasedPreviewHandlerControl), invalida el método Load y crea los controles de Windows Forms necesarios para procesar el archivo o la secuencia. Para mi controlador de vista previa de XML, me basta con crear un control WebBrowser y cargar el documento XML. Así, los usuarios del shell de Windows Vista perciben la misma estructura de XML que ofrece Internet Explorer®:
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);
    }
}
Figura 4 Vista previa de un archivo ZIP en Outlook 2007 (Hacer clic en la imagen para ampliarla)
La implementación básica del método Unload de PreviewHandlerControl elimina y borra todos los controles de la colección. Si dicha funcionalidad es apropiada para el control, no hace falta invalidarla.
Aparte de aplicar unos cuantos atributos a la clase derivada PreviewHandler, escribir un controlador de vista previa personalizado no tiene mayor complicación. La figura 3 muestra la implementación completa de un controlador de vista previa para archivos ZIP que se ejecuta en Outlook 2007 (los resultados se muestran en la figura 4). En la descarga se incluye un controlador para archivos ZIP más avanzado, con una vista de árbol de los archivos y los directorios, iconos de archivos y compatibilidad con doble clic para ver los archivos contenidos en el ZIP. Esta clase utiliza la biblioteca ZIP de Visual J#® 2005 (incluida en .NET Framework 2.0) para ofrecer una lista de todos los archivos contenidos en el ZIP. Para demostrar que estos controladores de vista previa funcionan en Windows Vista y Outlook, en la figura 5 aparece XmlPreviewHandler ejecutándose en el shell de Windows Vista.
[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);
        }
    }
}

Figura 5 Vista previa de XML en Windows Vista (Hacer clic en la imagen para ampliarla)

Implementación de PreviewHandler
Ahora que ya sabe implementar un controlador de vista previa administrado con mi marco de trabajo, examinemos a fondo la clase base PreviewHandler para entender mejor el funcionamiento de estos controladores.
Básicamente, PreviewHandler es el contenedor de un control de Windows Forms almacenado en la variable de miembro privado _previewControl. Se inicializa en el control devuelto del método GetPreviewHandlerControl implementado en la implementación del controlador personalizado (por ejemplo, XmlPreviewHandlerControl o ZipPreviewHandlerControl):
protected PreviewHandler()
{
    _previewControl = CreatePreviewHandlerControl();
    IntPtr forceCreation = _previewControl.Handle;
}
Aunque se trata de un constructor muy pequeño, hay varias cuestiones sutiles que debemos señalar. La primera es que estoy infringiendo una instrucción de diseño importante de .NET Framework, según la cual los constructores no deben llamar a los métodos virtuales (esto lo detectará la regla ConstructorsShouldNotCallBaseClassVirtualMethods de FxCop). En .NET, a diferencia de ISO C++, el destino de una llamada virtual estará en el tipo más derivado (el invalidado), y no en el tipo base que se está construyendo (el virtual). Hay un problema: cuando el constructor del tipo base se está ejecutando, el constructor del tipo derivado aún no se ha ejecutado. Así pues, la invalidación del método se llamará en la instancia derivada antes de que termine de construirse, de ahí la regla de que los constructores no deben llamar a los métodos virtuales que no se hayan sellado en la misma clase. Téngalo en cuenta, pues significa que no debe hacer nada en la invalidación de CreatePreviewHandlerControl que se base en algo definido en el constructor (este código de construcción aún no se habrá ejecutado). No obstante, según el marco de trabajo que he diseñado, donde CreatePreviewHandlerControl estará creando instancias y devolverá el tipo de control correcto, no resultará difícil respetar esta regla.
Claro está, ahora nos planteamos esta cuestión: si conocía esta instrucción desde el principio, ¿por qué la infringí al llamar a CreatePreviewHandlerControl desde el constructor? Porque tengo que crear el control en el subproceso de la interfaz de usuario, lo cual nos lleva al segundo punto que quiero abordar. Al final del constructor mostrado anteriormente, verá que he recuperado el valor de _previewControl.Handle, pero no estoy haciendo nada con los resultados. Lo he hecho para invocar el descriptor de acceso get de la propiedad Handle del control. Es decir, no me interesaba el resultado, simplemente quería que se ejecutara el descriptor. Al invocar el descriptor de acceso get Control.Handle, se obliga a crear la ventana subyacente del control. Esto es importante, ya que el subproceso que crea las instancias del componente del controlador de vista previa y que llama a su constructor es un subprocesamiento controlado de proceso único (STA), pero el subproceso que llama después a los miembros de la interfaz es un subprocesamiento controlado multiproceso (MTA). Como sabrá, los controles de Windows Forms están diseñados para ejecutarse en subprocesos STA y, a veces, acaban de mala manera si se intenta utilizarlos en subprocesos MTA. Así pues, el constructor del controlador de vista previa constituye el mejor momento para crear la ventana de vista previa de Windows Forms. Las llamadas posteriores a los demás métodos de interfaz que requieran interacción con el control de vista previa tendrán que volver al subproceso STA principal. Para ello, basta con utilizar la interfaz ISynchronizeInvoke del control. Tenga en cuenta que este problema de los dos subprocesos no existe si implementamos un controlador de vista previa nativo; lo más probable es que sea un artefacto del modo en que CLR administra la interoperabilidad COM.
Merece la pena señalar que, aunque estoy comentando las implementaciones basadas en Windows Forms, habrá veces en que lo más sencillo sea procesar la vista previa con un control ActiveX. Para más información sobre el tema, consulte la barra lateral "Uso de los controles ActiveX".

Uso de las interfaces COM
Aunque no lo crea, el constructor es la parte más complicada de toda la implementación de PreviewHandler. El resto sólo sirve para dirigir las llamadas realizadas en las interfaces COM a los controles de Windows Forms y para tareas de contabilidad básicas. Empezaré por las interfaces más sencillas.
IOleWindow IOleWindow permite que una aplicación de alojamiento obtenga un identificador para la ventana que participa en la activación en contexto, es decir, un identificador para nuestro control (para los controladores de vista previa, no se llama nunca a ContextSensitiveHelp). Así, resulta facilísimo implementar la interfaz, como se muestra aquí:
void IOleWindow.GetWindow(out IntPtr phwnd)
{
    phwnd = _previewControl.Handle;
}

void IOleWindow.ContextSensitiveHelp(bool fEnterMode)
{
    throw new NotImplementedException();
}
IObjectWithSite IObjectWithSite resulta igual de sencilla. La interfaz se utiliza para proporcionar un objeto con puntero al sitio asociado con su contenedor. El sitio proporcionado a SetSite es, en realidad, el IPreviewHandlerFrame que contiene la ventana del controlador de vista previa. Como tal, el método SetSite almacena el puntero de interfaz IUnknown proporcionado en un miembro privado (que GetSite puede devolver a petición) y lo convierte en un IPreviewHandlerFrame (que también almacena). En un segundo plano, dicha conversión produce una llamada QueryInterface:
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 e IInitializeWithStream Pasamos ahora a IInitializeWithFile e IInitializeWithStream, que se implementan en FileBasedPreviewHandler y StreamBasedPreviewHandler, respectivamente. Proporcionan al controlador de vista previa la ruta de acceso completa o IStream al archivo que requiere la vista previa. Para implementar estos métodos, sólo hacen falta un par de líneas de código:
// 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;
}
El único método de estas interfaces, Initialize, se proporciona con una ruta de acceso al archivo que requiere la vista previa o con una IStream que representa el archivo, junto con un modo de archivo que indica cómo se abre. La ruta de acceso o la secuencia se almacenan en una variable de miembro para que podamos utilizarla más tarde. Como los controladores de vista previa deben ser de sólo lectura, no me fijo en el segundo parámetro. De hecho, todos los controladores deben omitir el segundo parámetro y abrir los archivos en modo de sólo lectura. Además, deberían admitir la eliminación, la lectura y la escritura de archivos con posterioridad. Por fácil que sea implementar IInitializeWithFile e IInitializeWithStream, merecen una explicación más a fondo.
Si consulta la documentación de MSDN® sobre la implementación de controladores de vista previa, verá que se defiende a capa y espada que se utilice IInitializeWithStream en lugar de IInitializeWithFile. La principal ventaja que tiene IInitializeWithStream en comparación con IInitializeWithFile es que permite que el controlador obtenga una vista previa de los datos que no están almacenados en un archivo independiente, por ejemplo, un archivo de texto guardado en un ZIP. Si el shell intenta obtener una vista previa de los datos disponibles sólo como secuencia mediante un controlador que sólo implementa IInitializeWithFile, guardará una copia de la misma en un archivo local y, después, obtendrá la vista previa del archivo. Está claro que este enfoque no resulta tan rápido como obtener directamente la vista previa de la secuencia. En la medida de lo posible, siga este consejo. Sin embargo, lo cierto es que, a veces, no resulta posible ni práctico. Esto se debe a que muchos controladores de vista previa funcionan con tipos de archivo que se cargan y se procesan con mayor facilidad mediante API que sólo admiten rutas de acceso o direcciones URL. Por ejemplo, la función MsiOpenDatabase que utilizo en el MsiPreviewHandler de ejemplo para abrir un archivo MSI espera una ruta de acceso al archivo MSI de destino. No acepta una IStream, los únicos datos que tendría si implementara IInitializeWithStream en lugar de IInitializeWithFile.
He integrado este modelo en mi marco de trabajo mediante las dos subclases de PreviewHandler mencionadas anteriormente: FileBasedPreviewHandler y StreamBasedPreviewHandler. Cada una implementa una de las interfaces, no ambas. Si PreviewHandler implementara ambas, el shell siempre utilizaría la implementación de IInitializeWithStream, que prefiere por motivos de seguridad y aislamiento. De hecho, Outlook favorecería la implementación de IInitializeWithFile por motivos de herencia. En la clase derivada, puede elegir la interfaz que desea usar al llevar a cabo la derivación a partir de la subclase adecuada de PreviewHandler.
Otra consideración importante: los datos del archivo o la secuencia no se deben cargar en Initialize. En su lugar, como he hecho yo, se debe almacenar la ruta de acceso al archivo o la referencia a la secuencia, y sólo se debe cargar el contenido del archivo cuando se haya solicitado al controlador que procese la vista previa. Además, de ningún modo se debe bloquear el archivo para acceso exclusivo mientras se muestra la vista previa. El usuario debe contar con la posibilidad de examinar la vista previa y abrir el archivo de destino para editarlo al mismo tiempo. Y, lo que es más importante, debe tener la posibilidad de eliminarlo mientras la vista previa está en pantalla.
IPreviewHandler Abordo ahora la interfaz principal que debe admitir un controlador de vista previa, IPreviewHandler. Mi implementación de esta interfaz se muestra en la figura 6. IPreviewHandler expone siete métodos que deben implementarse: SetWindow, SetRect, DoPreview, Unload, SetFocus, QueryFocus y 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
}

Cuando la vista previa de un archivo está a punto de mostrarse, el host transmite información sobre el archivo o la secuencia al controlador mediante una de las interfaces de inicialización mencionadas anteriormente. El identificador de la ventana de vista que contendrá la ventana de vista previa pasa entonces al controlador mediante el método SetWindow, lo que le permite definir esta ventana de vista como la principal. A continuación, el host puede definir el tamaño de la ventana de vista previa mediante el método SetRect. Posteriormente, el host llama al método DoPreview para que se muestre la vista previa. Mientras la vista previa está en pantalla, el host puede volver a llamar a SetRect siempre que se cambie el tamaño de la ventana de vista. Por último, cuando se cierra la vista previa, el host indica al controlador que la destruya mediante el método Unload. Esto hace referencia a la eliminación de los recursos cargados del elemento de vista previa, no significa que se vaya a destruir el controlador de vista previa. La misma instancia de controlador se puede utilizar para varias vistas previas.
En mi implementación, utilizo un par de funciones auxiliares. Primero, InvokeOnPreviewThread acepta un delegado de MethodInvoker (un delegado vacío y sin parámetros definido en el espacio de nombres System.Windows.Forms). Se ejecuta en el subproceso principal de la interfaz de usuario con la ayuda del método ISynchronizeInvoke.Invoke del control de vista previa (recuerde que el control se originó en el constructor del controlador para que se crearan instancias a partir de un subproceso STA). En segundo lugar, utilizo UpdateWindowBounds para definir la ventana principal del control de vista previa, moverla a la posición correcta y mostrarla. Esta funcionalidad resulta útil en un método auxiliar, ya que varios métodos de la interfaz IPreviewHandler modifican esta información y la necesitan para influir en la vista previa.Uso de controles ActiveX
Los controladores de vista previa de este artículo se han implementado con controles de Windows Forms, como ListView. Sin embargo, para ciertos formatos de archivo, el modo más sencillo de procesar una vista previa es emplear un control ActiveX® con el que se asocia el formato. Mediante la interoperabilidad COM, Windows Forms permite que los controles ActiveX puedan alojarse como los demás. La manera más fácil de obtener un contenedor administrado para un control ActiveX es emplear el Importador de controles ActiveX de Windows Forms, aximp.exe. Si desea más información, consulte msdn.microsoft.com/library/en-us/cptools/html/cpgrfWindowsFormsActiveXControlImporterAximpexe.asp (puede estar en inglés). Así generará ensamblados de interoperabilidad a los que tendría que agregar manualmente las referencias en su proyecto. Si, en vez de eso, agrega el control ActiveX al cuadro de herramientas de Visual Studio, y arrastra una instancia del control y la suelta en una superficie de diseño, Visual Studio generará los ensamblados y agregará las referencias automáticamente.
Como alternativa, también puede crear manualmente un contenedor para el control. Para controles sencillos o que no requieran acceso a muchas funciones, esta técnica puede resultar más atractiva. Con este método creé un controlador de vista previa para el formato Adobe PDF.
Recibo muchos correos electrónicos con datos adjuntos en formato PDF y resultaría muy útil ver los archivos en el panel de vista previa. Aunque Office 2007 guarda documentos en formato PDF, no abre este formato para leerlo. Consulte microsoft.com/downloads/details.aspx?FamilyID=4d951911-3e7e-4ae6-b059-a2e79ed87041 para la descarga (puede estar en inglés). Así que creé un PdfPreviewHandler. Para ello, alojé el control ActiveX incluido en Adobe Reader, lo que me permite obtener la vista previa de los archivos PDF. Para alojar el control, lo incluyo con una clase derivada de AxHost:
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 se encarga de esto y de muchas otras cosas (ofrece contenedores de gran fidelidad para todos los métodos y eventos). He hecho lo mínimo necesario para contener el control y exponer el único método (LoadFile) que necesito para invocarlo. LoadFile acepta como argumento la ubicación del archivo que debe mostrarse y la procesa en el control. Entonces, puedo utilizar este control como cualquier otro:
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 y SetRect son dos de estos métodos. El primero proporciona el identificador de la ventana principal y los nuevos límites de la ventana de vista previa. El segundo sólo ofrece los nuevos límites. SetFocus tan sólo se convierte en una llamada al método Focus del control de vista previa, mientras que QueryFocus devuelve el resultado de llamar a la función GetFocus de user32.dll desde el subproceso de la ventana de vista previa.
Nos quedan TranslateAccelerator, Unload y DoPreview. Mi implementación actual de PreviewHandler no resulta muy compatible con teclas de aceleración. Como tal, TranslateAccelerator tan sólo delega en el método TranslateAccelerator del IPreviewFrameHandler almacenado en SetSite. Si el controlador muestra varios controles que pueden seleccionarse sucesivamente con el tabulador, es aconsejable aumentar TranslateAccelerator para proporcionar un control más sólido en este sentido.
IPreviewHandlerFrame tiene otro método, GetWindowContext, que permite al controlador obtener una tabla que se puede utilizar para filtrar los aceleradores que se deben enviar a IPreviewHandlerFrame::TranslateAccelerator. Con ello se persigue aumentar el rendimiento. Sin embargo, incluso con código nativo, no ofrece grandes mejoras de rendimiento y, si tenemos en cuenta las complicaciones a la hora de administrar una tabla de aceleradores, quizá no valga la pena hacerlo. Los controladores de vista previa no necesitan utilizar el método, basta con reenviarlo todo a TranslateAccelerator. En resumen, no se preocupe por GetWindowContext.
El método Unload oculta la ventana de vista previa y delega en el método virtual PreviewHandlerControl.Unload, explicado con anterioridad. Tampoco resulta complicado implementar DoPreview, probablemente, el método más importante de la interfaz. Se llama a DoPreview para llevar a cabo el procesamiento real de la vista previa. Mi implementación llama al método abstracto Load, al que transmite PreviewHandlerControl. Seguidamente, la implementación derivada llama al método Load de FileBasedPreviewHandler con la ruta de acceso almacenada desde IInitializeWithFile o bien al método Load de StreamBasedPreviewHandlerControl con la secuencia ofrecida en IInitializeWithStream:
// 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));
}
Observe que engloba la IStream de COM con un contenedor que he derivado de System.IO.Stream, lo que permite a la clase derivada emplear la IStream en modo de sólo lectura, igual que lo haría con cualquier secuencia de .NET. Cuando los datos se han cargado, el método DoPreview llama a UpdateWindowBounds para asegurarse de que la vista previa se muestra correctamente.

Registro de controladores
Tanto el shell de Windows Vista como Outlook 2007 consultan el Registro de Windows para determinar qué controladores de vista previa están disponibles y ver con qué tipos de archivo están asociados.
Igual que ocurre con las demás clases COM, un controlador de vista previa requiere la asignación de un Id. de clase. En la figura 2, esta es la tarea del GuidAttribute aplicado a la clase:
    [Guid("853f35e3-bd13-417b-b859-1df25be6c834")]
Verá que cada controlador de vista previa tiene un GUID distinto. Todos los controladores personalizados que cree requieren también la asignación de Id. únicos, que se emplean como parte del registro del controlador. Las utilidades uuidgen.exe y guidgen.exe incluidas en Windows ofrecen métodos rápidos de generar GUID.
Figura 7 Lista de controladores de vista previa registrados (Hacer clic en la imagen para ampliarla)
Primero, agregue un valor REG_SZ a la clave de PreviewHandlers en HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers, donde el nombre del valor es el GUID asignado al controlador. Cuando no está en uso, el valor de esta entrada es el nombre para mostrar del controlador de vista previa. Todos los controladores integrados funcionan así, lo que simplifica la labor de ver cuáles están registrados, ya que basta con echar un vistazo a la lista del Registro (consulte la figura 7). Más adelante, cuando el registro se haya completado correctamente, podrá ver una lista de todos los controladores registrados en el Centro de confianza de Outlook (consulte la figura 8).
Figura 8 Lista de controladores de vista previa en el Centro de confianza de Outlook (Hacer clic en la imagen para ampliarla)
A continuación, tiene que crear una clave de Registro especial en la clave shellex para la extensión de destino. Dicha clave especial tiene un identificador específico, "{8895b1c6-b41f-4c1c-a562-0d564250836f}", para comunicar al sistema que los datos que contiene representan un controlador de vista previa. El valor predeterminado de la clave es el GUID del controlador de vista previa utilizado para dicha extensión:
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);
}
Si un controlador específico se puede usar con varias extensiones, basta con repetir este paso tantas veces como extensiones haya. Mi marco de trabajo busca un PreviewHandlerAttribute en el controlador de vista previa. Este atributo especifica el nombre para mostrar del controlador, las extensiones que admite y un AppID (enseguida lo explico). Permito que las extensiones admitidas sean una cadena simple, como es el caso de ZipPreviewHandler:
[PreviewHandler("ZIP Preview Handler", ".zip", 
                "{c0a64ec6-729b-442d-88ce-d76a9fc69e44}")]
También permito que sean una lista de extensiones delimitadas por puntos y comas, como es el caso de BinPreviewHandler:
[PreviewHandler("Binary Preview Handler", ".bin;.dat", 
                "{e92d3c10-89c8-4543-91b9-7a74305e9df4}")]
En una situación así, mi función de registro (que puede ver en la descarga) recorre las extensiones de la lista y registra el controlador para cada una de ellas. Tenga en cuenta que hay controladores en Windows Vista que admiten más tipos de archivo de los que tienen registrados. Para más información sobre cómo mostrar la vista previa de más tipos de archivo, consulte la barra lateral "Si lo tiene, presuma de ello".
Si lo tiene, presuma de ello
Hay numerosos controladores de vista previa integrados en Windows Vista. Sin embargo, algunos admiten más tipos de archivo de los que tienen registrados de forma predetermina. Tomemos como ejemplo el controlador de vista previa de TXT de Microsoft Windows. Como supondrá, este controlador procesa archivos .txt, pero no hay nada que impida su funcionamiento con otros formatos. Como desarrollador, suelo recibir archivos de código de C#, Visual Basic® y C++ adjuntos en los mensajes de correo electrónico. Me encantaría disponer de vistas previas de estos datos adjuntos en Outlook, en vez de utilizar Visual Studio® como visor predeterminado. Pues es posible. Sólo tengo que registrar los archivos .cs, .vb, .cpp y .h como extensiones que pueda abrir el controlador de vista previa de TXT de Microsoft Windows. El siguiente archivo .reg se encarga de hacerlo:
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}"
Si desea dárselas de ingenioso, escriba una aplicación o una secuencia de comandos sencilla que enumere todas las extensiones de archivo que haya en HKEY_CLASSES_ROOT. Puede comprobar si cada una tiene definido un valor Content Type y, en caso afirmativo, si ese valor empieza por "text/". Si es así y la extensión no tiene aún configurado un controlador, puede definirlo para que la vista previa se abra con el controlador de TXT, igual que hice yo en la secuencia de comandos del Registro anterior. En tan sólo unos minutos, ampliará en gran medida el número de tipos de archivo que pueden mostrarse en vista previa.
Y, por supuesto, el controlador de vista previa de TXT no es el único capaz de mostrar la vista previa de varios tipos de archivo. Otros controladores también podrían presentar esta flexibilidad. Por ejemplo, veamos el XmlPreviewHandler que se describe en este artículo. Si consulta la descarga de código, verá que el nombre real del controlador es InternetExplorer­PreviewHandler. Cambié el nombre porque no es realmente específico para el formato de archivo XML, sino más bien para el control utilizado en el procesamiento de XML (el control WebBrowser). Como el controlador utiliza el control WebBrowser para navegar al archivo que se muestra en vista previa, es posible usar el controlador con gran variedad de tipos de archivo. Muchos de ellos ya están asociados con otros controladores, pero otros no. Por ejemplo, el nuevo formato XML Paper Specification (XPS) que se incluye en Windows Vista no tiene un controlador integrado, pero Internet Explorer puede procesar los documentos XPS. Por tanto, he agregado .xps a la lista de extensiones admitidas para ampliar InternetExplorerPreviewHandler. Con este simple esfuerzo, ahora tengo un controlador de vista previa para XPS.

 
Además de registrar los tipos de archivo, también puede registrar los Id. de programas. Por ejemplo, el valor predeterminado de HKEY_CLASSES_ROOT\.xml es "xmlfile", así que mi controlador de vista previa de XML está registrado en HKEY_CLASSES_ROOT\xmlfile\ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f}:
[PreviewHandler("XML Preview Handler", "xmlfile", 
                 "{88235ab2-bfce-4be8-9ed0-0408cd8da792}")]
De esta forma, si tiene varias extensiones que, en realidad, son del mismo tipo, puede utilizar el mismo Id. de programa para cada una y registrar el controlador sólo una vez. Por ejemplo, podría realizar el registro en HKEY_CLASSES_ROOT\jpegfile en lugar de hacerlo tanto en HKEY_CLASSES_ROOT\.jpeg como en HKEY_CLASSES_ROOT\.jpg. De hecho, el equipo de shell de Windows recomienda que los tipos de archivo nuevos definan Id. de programas nuevos y que registren los controladores con ellos, en lugar de hacerlo con las extensiones reales, aunque haya una correspondencia de 1:1 entre el Id. de programa y la extensión.
El siguiente cambio del Registro (para los controladores administrados) implica varios AppID. Para explicar el motivo, hay que retomar el tema de la escritura de complementos administrados para el shell de Windows Vista. Como mencioné anteriormente, los controladores de vista previa se cargan del shell fuera de proceso, en un host suplente (prevhost.exe) de forma predeterminada. Para aumentar la eficacia, el sistema espera que se ejecute el menor número posible de instancias de prevhost.exe. Así, varios tipos de controladores de vista previa se cargan en el mismo proceso externo. Esto significa que, aunque el shell no intentará cargar controladores administrados destinados a distintas versiones de CLR, puede que prevhost.exe sí lo haga. Y esto nos lleva al problema que teníamos al principio. Como solución, comunicamos al sistema que las instancias de nuestro controlador de vista previa se deben cargar en el host suplente dedicado a los controladores de este mismo tipo. Así garantizamos que varios controladores no se destinen a distintas versiones de CLR cargadas en el mismo proceso suplente. El registro de la clase COM del controlador (enseguida lo aclaro) especifica un Id. para los detalles de configuración que indican el host suplente que se debe utilizar. Este Id. se conoce como AppID. Cuando se registra un controlador de vista previa administrado, creo un registro de AppID nuevo en HKCR\AppID para el uso exclusivo por parte de dicho controlador administrado:
using (RegistryKey appIdsKey = Registry.ClassesRoot.OpenSubKey(
       "AppID", true))
using (RegistryKey appIdKey = appIds.CreateSubKey(appID))
{
    appIdKey.SetValue("DllSurrogate", 
                   @"%SystemRoot%\system32\prevhost.exe", 
                   RegistryValueKind.ExpandString);
}
Tenga en cuenta que este AppID sigue señalando a prevhost.exe como host suplente.
Sólo queda agregar el registro del componente COM del controlador. Casi todo esto se puede llevar a cabo fácilmente con la herramienta regasm.exe incluida en el .NET Framework SDK:
regasm /codebase ManagedPreviewHandler.dll
También puede usar un instalador, con código que utiliza el método RegisterAssembly de la clase RegistrationServices, en el espacio de nombres System.Runtime.InteropServices de mscorlib.
Sin embargo, como he mencionado antes, tengo que modificar el registro del componente para que señale al AppID nuevo que he creado. Y tengo que agregarle un nombre para mostrar que utilizarán los host como Outlook 2007 para enumerar los controladores registrados:
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);
}
Para un controlador de producción, DisplayName se debe establecer en un valor REG_SZ que señale a una cadena de recurso binario Win32 localizada (por ejemplo, "@micontrolador.dll,-101"). Lamentablemente, el IDE de Visual C#® 2005 no ofrece un modo integrado que incluya un archivo de recurso Win32 en un ensamblado administrado. Para hacerlo, tiene que ir a la línea de comandos, ya que csc.exe admite el modificador /win32res que permite la inclusión de archivos de recursos Win32. Para simplificar este artículo y mi código de registro, he recurrido a un valor sin localizar.
Toda la lógica de registro se incluye en un único método de PreviewHandler:
protected static void RegisterPreviewHandler(
    string name, string extensions, string previewerGuid, string appID)
También tenemos el método correspondiente para anular el registro.
La herramienta regasm.exe admite ComRegisterFunctionAttribute, del espacio de nombres System.Runtime.InteropServices en mscorlib. Puede aplicar este atributo a un método estático de un ensamblado para que se registre con regasm. Cuando se encuentran tipos ComVisible en el ensamblado, su información se pasa al método marcado con el atributo ComRegisterFunction (después de que la clase COM del tipo se haya incluido en el Registro). Esto facilita muchísimo la labor de escribir un método que controle todo ese embrollo de registro mencionado anteriormente cuando el ensamblado se registra para la interoperabilidad COM (consulte la figura 9). He incluido una función para anular el registro, marcada con ComUnregisterFunctionAttribute.
[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);
        }
    }
}

Gracias a ella, para instalar y registrar mis controladores de vista previa, sólo hacen falta dos líneas en el símbolo del sistema:
gacutil -i MsdnMagPreviewHandlers.dll
regasm /codebase MsdnMagPreviewHandlers.dll
De igual modo, para anular el registro de los controladores, basta con dos líneas en el símbolo del sistema:
regasm /unregister MsdnMagPreviewHandlers.dll
gacutil -u MsdnMagPreviewHandlers
Si escribe sus propios controladores personalizados con este marco de trabajo y en el mismo ensamblado que yo, no tendrá que hacer nada más para que funcionen. Si los escribe con mi marco de trabajo pero utiliza un ensamblado distinto, sólo tendrá que agregar la siguiente clase al ensamblado:
internal sealed class PreviewHandlerRegistration
{
    [ComRegisterFunction]
    internal static void Register(Type t) { 
        PreviewHandler.Register(t); 
    }
    [ComUnregisterFunction]
    internal static void Unregister(Type t) { 
        PreviewHandler.Unregister(t); 
    }
}
Primero, instale y registre MsdnMagPreviewHandlers.dll tal y como expliqué anteriormente. A continuación, instale y registre el ensamblado. Cuando regasm vea la función ComRegisterFunction en la clase PreviewHandlerRegistration, la llamará tantas veces como controladores haya en el ensamblado. La función delegará en toda la funcionalidad descrita anteriormente incluida en MsdnMagPreviewHandlers.dll.
Figura 10 XamlPreviewHandler en Outlook 2007 (Hacer clic en la imagen para ampliarla)
Para ejemplificar este proceso, he incluido un controlador de vista previa para archivos XAML en la descarga de este artículo. Se implementa en su propio ensamblado, que contiene las clases XamlPreviewHandler y PreviewHandlerRegistration. La figura 10 muestra una captura de pantalla del controlador en Outlook 2007 (el archivo XAML que se muestra en vista previa procede del blog del equipo del SDK de Windows Presentation Foundation y está disponible en blogs.msdn.com/wpfsdk/archive/2006/05/23/Animating_XAML_Clip_Art.aspx). Observe que XamlPreviewHandler también deriva de StreamBasedPreviewHandler, y no de FileBasedPreviewHandler. La clase XamlReader proporcionada con Windows Presentation Foundation se puede cargar desde una secuencia, así que tiene sentido implementar IInitializeWithStream en vez de IInitializeWithFile:
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);
}

Depuración
Como los controladores en proceso se ejecutan en un host suplente (de forma predeterminada, prevhost.exe), para depurarlos, debe asociar el depurador al proceso de host. Existen varias maneras de hacerlo. La primera consiste en esperar a que se inicie el proceso host y utilizar entonces la capacidad del depurador para asociarse a un proceso existente. Sin embargo, puede haber varias instancias de prevhost.exe, sobre todo si sigue mi sugerencia de crear un AppID nuevo por cada controlador de vista previa administrado. Cuando se ejecutan varias instancias de prevhost.exe, tiene que saber a cuál debe conectarse. Puede utilizar una herramienta como Process Explorer (www.sysinternals.com/Utilities/ProcessExplorer.html) de Sysinternals (adquirida recientemente por Microsoft) para examinar los argumentos de la línea de comandos utilizados al iniciarse la instancia adecuada de prevhost.exe. Como archivo ejecutable del host suplente, prevhost.exe tiene que saber desde el inicio qué componente COM debe alojar, y recibirá el GUID del controlador de vista previa en la línea de comandos:
prevhost.exe {8FD75842-96AE-4AC9-A029-B57F7EF961A8} -Embedding
Puede consultar los argumentos de la línea de comandos de cada instancia de prevhost.exe y buscar el que tenga el GUID que coincida con el controlador que desea depurar. A continuación, puede utilizar el Id. de proceso para asociar el depurador al proceso correcto.
Si el proceso se está ejecutando, el controlador ya se habrá construido y parte del código se habrá ejecutado. Para depurar su inicio, se precisa otra solución. Por ejemplo, se puede realizar una llamada a System.Diagnostics.Debug.Break al principio del constructor. Así, el depurador se inicia y se asocia al proceso en ese momento, lo que permite depurar todo el componente.
No obstante, la solución de asociar un depurador no siempre es la mejor. A veces lo que busca es depuración clásica "al estilo printf" que utiliza para producir una serie de información que se puede examinar durante la ejecución del controlador. En tal caso, es aconsejable utilizar la clase System.Diagnostics.Trace. De forma predeterminada, la TraceListenerCollection en que opera Trace contiene un TraceListener que escribe los datos de seguimiento en OutputDebugString, lo que permite que los depuradores a la escucha muestren el texto de salida. El SDK de Windows incluye un monitor de depuración (DbMon.exe) que facilita la labor de ver el seguimiento.Controladores de vista previa y Windows XP
De Ryan Gregg

Con el marco de trabajo ofrecido en este artículo, resulta muy fácil escribir un controlador en proceso con código administrado. Sin embargo, resulta más complicado conseguir que funcione con Office Outlook 2007 tanto en Windows XP como en Windows Vista. La aplicación proxy (denominada prevhost.exe) que aloja los controladores en proceso en Windows Vista no está disponible en Windows XP. En este sistema operativo, los controladores se deben escribir como servidores COM locales, sin confiar en que otra aplicación se encargue de alojarlos.
Desafortunadamente, .NET Framework 2.0 no ofrece la mayoría de código de adherencia necesario para registrar una aplicación .NET como servidor COM local. Como desarrollador de controladores de vista previa administrados, tiene que escribir código que se asemeje en gran medida a la implementación de código nativo de un controlador de vista previa. Aquí tiene una vista detallada de lo que debe escribir.
El servidor COM local típico se compila como aplicación de Windows, en lugar de como biblioteca de clases. Cuando COM solicita una instancia nueva del servidor local, inicia la aplicación de servidor con un conjunto especial de indicadores que señalan que la aplicación se debe iniciar en modo de incrustación. Así, se inicia sin interfaz de usuario y se registran los generadores de clases con COM. Es probable que este mismo servidor tenga dos componentes lógicos más sobre la implementación del controlador en proceso: una implementación de servidor COM y un generador de clases.
La implementación ofrece el marco de aplicación en ejecución para el ejecutable iniciado por COM al llamar al controlador. Dentro del método Main de la aplicación, tiene que analizar los parámetros de la línea de comandos, iniciar el registro de cada generador de clases con COM, supervisar cuándo resulta seguro cerrar el proceso y mantener activo el bucle de mensajes para que no se bloquee la aplicación que aloja el controlador. Casi todos los servidores COM locales también admiten modificadores de línea de comandos para registrar y anular el registro del servidor en el Registro de Windows. Como regasm.exe no ofrece un método para registrarse como servidor local, también tendrá que escribir métodos para crear las claves adecuadas con el fin de registrar el servidor COM local. Estas claves se explican a fondo en la documentación de COM.
El componente del generador de clases implementa la interfaz IClassFactory que ofrece COM. Asimismo, se encarga de registrar los objetos de clase con COM y de crear instancias de estos objetos para los autores de las llamadas COM. En concreto, el generador de clases debería llamar a CoRegisterClassObject y CoRevokeClassObject para indicar al sistema que hay un generador disponible (o que se ha revocado) para el GUID utilizado en la identificación del controlador. Cuando se solicita, el generador de clases crea una instancia nueva de la clase de controlador y la devuelve al autor de la llamada COM. Tiene que realizarse un seguimiento de estas instancias y quizá deba forzarse la recopilación de elementos no utilizados para que la aplicación de servidor pueda determinar el momento de liberación de todas las referencias externas, lo que permite que se cierre correctamente.
Requiere mucho esfuerzo que el controlador de vista previa administrado funcione con Outlook 2007 tanto en Windows XP como en Windows Vista. Aunque en esta barra lateral he explicado lo básico, no he profundizado en los detalles importantes. Cualquier usuario que se disponga a desarrollar un servidor COM local debe comprender el funcionamiento de COM, la diferencia entre estilos de subprocesamiento controlado y el trabajo con las API Win32® mediante código administrado. Sin estos conocimientos, reinará la frustración.
Aunque escribir un controlador de servidor COM local con código administrado puede que no resulte tan fácil como escribir un controlador en proceso, no pase por alto este detalle si desea que el controlador funcione para todos los usuarios de Office 2007, independientemente del sistema operativo que empleen. Si desea más detalles y ejemplos prácticos de cómo funciona todo, consulte estos útiles recursos:
  • En la sección "Fundamentos de COM" de MSDN (windowssdk.msdn.microsoft.com/ms694505.aspx), se describe el Modelo de objetos componentes de Microsoft (puede estar en inglés).
  • PInvoke.net (www.pinvoke.net) es un sitio web fantástico que ayuda a los usuarios a definir las llamadas de API Win32 con código administrado (puede estar en inglés).
  • El artículo "Creación de servidores COM en .NET" (www.codeproject.com/useritems/BuildCOMServersInDotNet.asp) ofrece el ejemplo práctico de una implementación de servidor COM local (puede estar en inglés).
Ryan Gregg trabaja en Microsoft como administrador de programas de Office Outlook, sobre todo se ocupa de la plataforma de extensibilidad y de formularios personalizados. Publica casi asiduamente comentarios sobre la extensibilidad de Outlook en blogs.msdn.com/rgregg (puede estar en inglés).

En las pruebas, recomiendo que agregue una instrucción de seguimiento al principio de cada método de interfaz tratado anteriormente. Otra posibilidad es utilizar Debugger.Break junto con la nueva característica de puntos de seguimiento de Visual Studio 2005. Al hacerlo, entenderá mucho mejor la interacción del shell y Outlook 2007 con el controlador de vista previa.

Conclusión
Los controladores de vista previa son un complemento extraordinario para el shell de Windows Vista y Outlook 2007. Si añadimos las ventajas de productividad que ofrece el código administrado (con mi marco de trabajo actual, sólo tardé siete minutos en crear el controlador de XAML y ponerlo en marcha), obtenemos una plataforma muy eficaz para mostrar tanto los tipos de archivo existentes como los personalizados. Creo que los controladores de vista previa tienen el potencial de mejorar nuestra productividad en las tareas cotidianas (y cuantos más tipos de archivo admitidos haya, más productivos seremos todos). Así que no sea tímido y empiece a escribir el código de un controlador de vista previa para su extensión preferida.

Stephen Toub es editor técnico de MSDN Magazine.

Page view tracker