I – Creando una DLL de previsualización de ficheros (Preview Handler)
La idea de esta serie de entradas es ver todos los pasos necesarios para generar desde cero una DLL de Previsualización para Windows, de un determinado tipo de ficheros.
Antes de continuar os adjunto los links de las entradas de esta serie, que iré ampliando a medida que las vaya publicando:
- Crear una Previsualización de ficheros en Windows
- «Registros» de una DLL de previsualización
- Generando previsualización funcional (PreviewHandler)
- Instalador para PreviewHandler de imágenes/texto (Inno Setup)
¿A qué me refiero con eso de «PreviewHandler»?
Si abrimos el Explorador de Windows, para algunos tipos de ficheros que existen en nuestro equipo, el sistema operativo dispone de vista una previa desde el propio explorador. Nos permite ver el contenido del fichero de forma rápida, sin necesidad de abrir el programa asociado.
Al seleccionar una imagen o un fichero .INI en el explorador de Windows, si tenemos la previsualización activada, veremos directamente una «vista previa» de ese archivo sin necesidad de abrir otro programa.
Si por el contrario, el fichero no tiene vista previa definida, veremos un mensaje de Windows indicándolo. Algo similar a esto (varía dependiendo de la versión):
.
INTRODUCCION
Lo que vamos a ver aquí es cómo crear desde cero, un «Controlador de Vista previa» para un determinado tipo de fichero, en Delphi; Entender lo básico que necesitamos implementar para tener un vista previa funcional. Para ello tenemos que crear y registrar una DLL, que el propio sistema pueda utilizar para previsualizar ese determinado tipo de fichero.
Aquí podéis consultar la documentación de Microsoft al respecto:
https://learn.microsoft.com/en-us/windows/win32/shell/preview-handlers
https://learn.microsoft.com/es-es/windows/win32/shell/preview-handlers
La documentación nos describe el proceso que el sistema sigue para mostrar el fichero, de esta manera:
«Windows llama a los controladores de vista previa cuando se selecciona un elemento para mostrar una vista previa ligera, enriquecida y de solo lectura (1) del contenido del archivo en el panel de lectura de la vista. Esto se hace sin iniciar la aplicación asociada del archivo. (2)»
- Nos dice los pasos a seguir para que se muestre el archivo.
- Es importante porque nos recuerda que la aplicación asociada no se inicia (la vista previa puede -y debería- ser más ligera y rápida que el programa asociado a ese tipo de fichero).
Si seguimos con la documentación, llegamos también a la interfaz que debemos implementar en nuestra DLL: IPreviewHandler
La documentación nos lleva ahora aquí.
Los métodos que debemos implementar de esa interfaz son los siguientes:
- DoPreview: Permite cargar datos desde el origen especificado en una llamada de método Initialize anterior y para comenzar la representación en la ventana del controlador de vista previa. Este método será el que utilicemos realmente para mostrar el archivo que queremos.
- QueryFocus: Permite devolver el HWND desde una llamada a la función GetFocus.
- SetFocus: Permite establecer el foco en sí mismo.
- SetRect: Permite cambiar el área dentro del hwnd primario en el que se dibuja.
- SetWindow: Establece la ventana primaria de la ventana de previsualización, así como el área dentro del elemento primario que se va a usar para la ventana del preview.
- TranslateAccelerator: Permite controlar una pulsación de tecla pasada desde la cola de mensajes del proceso en el que se ejecuta el controlador de vista previa.
- Unload: Permite dejar de representar una vista previa y liberar todos los recursos asignados en función del elemento pasado durante la inicialización.
A la hora de inicializar el controlador tenemos que implementar una de estas 2 interfaces que se muestran a continuación, una que trabaja a partir del path del fichero y la otra a partir del stream con los datos:
- IInitializeWithStream (documentación)
- IInitializeWithFile (documentación)
type TBaseShellExt = class (TComObject, IPreviewHandler, IInitializeWithStream, IInitializeWithFile) |
Las dos con el método Initialize y la primera (Stream) como preferida según la documentación. Si creamos una definición de clase, como la anterior, habrá que usar sólo una de ellas.
.
UNIT DE PREVISUALIZACIÓN
Con todo lo visto anteriormente vamos a crear un primer esqueleto para un proyecto (DLL) con lo imprescindible para crear una previsualización.
Este proyecto va a constar de 3 units/formularios:
- Implementación del Preview: La unit que implementa la interfaz comentada anteriormente. Es la que realmente realiza todo el trabajo de comunicarse con el sistema.
- Formulario a incrustar: Formulario con los componentes necesarios para mostrar la previsualización que queremos crear. Por ejemplo, si quisiéramos mostrar imágenes, pues debería ser un formulario con un componente TImage o un componente con Canvas donde pudiéramos dibujar. Si es una previsualización de texto, pues debería incluir algún componente para mostrar texto (TMemo, TRichEdit o algún otro similar). En este ejemplo, por ahora, nuestro formulario sólo tiene un botón y un memo, pero ya nos sirve para ver si la previsualización está cargándose correctamente y no tiene errores.
- Unit de registro: Unit que incluye lo que debemos realizar en el registro de Windows (siguiente entrada de la serie).
Os dejo el código de la unit que implementa el controlador, con lo visto hasta ahora. Es una primera versión que irá sufriendo cambios y evoluciones, pero es el esqueleto básico para que funcione es este. Creo que es bastante fácil de entender e incluye comentarios a lo largo del código.
{ Unit que implementa las interfaces necesarias (sobre la clase TBaseShellExt) para el previsualizador. @author Germán Estévez @cat Unit } unit uPreviewHandler; interface uses Windows, Messages, ActiveX, Classes, ComObj, ComServ, ShlObj, Registry, PropSys, Types, SysUtils, Math, VCL.ExtCtrls, VCL.StdCtrls, VCL.Controls, uPreviewForm; type TBaseShellExt = class (TComObject, IPreviewHandler, IInitializeWithStream, IInitializeWithFile) strict private function IInitializeWithStream.Initialize = IInitializeWithStream_Init; function IInitializeWithFile.Initialize = IInitializeWithFile_Init; private // Permite cargar datos desde el origen especificado en una llamada de método Initialize anterior // y para comenzar la representación en la ventana del controlador de vista previa function DoPreview: HRESULT; stdcall; // Permite devolver el HWND desde una llamada a la función GetFocus. function QueryFocus(var phwnd: HWND): HRESULT; stdcall; // Permite establecer el foco en sí mismo. function SetFocus: HRESULT; stdcall; // Permite cambiar el área dentro del hwnd primario en el que se dibuja. function SetRect(var prc: TRect): HRESULT; stdcall; // Establece la ventana primaria de la ventana del previsualizador, // así como el área dentro del elemento primario que se va a usar para la // ventana del previsualizador. function SetWindow(hwnd: HWND; var prc: TRect): HRESULT; stdcall; // Permite controlar una pulsación de tecla pasada desde la bomba de mensajes // del proceso en el que se ejecuta el controlador de vista previa function TranslateAccelerator(var pmsg: tagMSG): HRESULT; stdcall; // Permite dejar de representar una vista previa y liberar todos los recursos // asignados en función del elemento pasado durante la inicialización function Unload: HRESULT; stdcall; // Inicilización para stream function IInitializeWithStream_Init(const pstream: IStream; grfMode: DWORD): HRESULT; stdcall; // Inicialización para ficheros function IInitializeWithFile_Init(pszFilePath: LPCWSTR; grfMode: DWORD): HRESULT; stdcall; private FFilePath: string; FParent: HWND; // Handle de la ventana de previsualizacion FBounds: TRect; // Define el area de la previsualizacion protected FStream:IStream; FormPreview: TFormPreview; // formuario de previsualizacion // Procedimiento que realmente realiza la previsualizacion procedure InternalDoPreview; // testear la ventana de previsualizacion procedure TestPreviewWindows; public destructor Destroy; override; end; var res: HRESULT; implementation uses uConstantes, uUtils, Vcl.AxCtrls, VCL.Graphics, VCL.Forms; { TBaseShellExt } procedure TBaseShellExt.TestPreviewWindows; begin // Si no está asignado el formulario, lo creamos... if not Assigned(FormPreview) then begin FormPreview := TFormPreview.Create(nil); FormPreview.ParentWindow := FParent; end; // Caracteristicas del form de preview FormPreview.BorderStyle := bsNone; FormPreview.Align := TAlign.alClient; FormPreview.WindowState := TWindowState.wsMaximized; FormPreview.Show; // forzar el repintado FormPreview.Invalidate; // Forzar la posicion SetWindowPos(FormPreview.Handle, 0, fBounds.left, fBounds.top, RectWidth(fBounds), RectHeight(fBounds), SWP_NOZORDER or SWP_NOACTIVATE); end; procedure TBaseShellExt.InternalDoPreview; begin // Chequear la ventana TestPreviewWindows; // Cargar la preview //... end; function TBaseShellExt.DoPreview: HRESULT; begin Result := S_OK; // si no tengo asignado parent (de la ventana) salgo.. if FParent = 0 then begin Exit; end; // Realizo el trabajo de previsualizacion TestPreviewWindows; end; destructor TBaseShellExt.Destroy; begin inherited; if Assigned(FormPreview) then FreeAndNil(FormPreview); end; function TBaseShellExt.QueryFocus(var phwnd: HWND): HRESULT; begin phwnd := GetFocus; // devuelvo el foco result := S_OK; end; function TBaseShellExt.SetFocus: HRESULT; begin Result := S_OK; end; function TBaseShellExt.SetRect(var prc: TRect): HRESULT; begin FBounds := prc; // me quedo con la nueva posicion y repinto InternalDoPreview; Result := S_OK; end; function TBaseShellExt.SetWindow(hwnd: HWND; var prc: TRect): HRESULT; begin if (hwnd <> 0) then FParent := hwnd; if (@prc <> nil) then FBounds := prc; InternalDoPreview; // forzar redibujadio Result := S_OK; end; function TBaseShellExt.TranslateAccelerator(var pmsg: tagMSG): HRESULT; begin Result := S_FALSE; // Por ahora así; False cuando no se controla nada. end; function TBaseShellExt.Unload: HRESULT; begin // liberar lo creado FreeAndNil(FormPreview); FStream := nil; FParent := 0; Result := S_OK; end; function TBaseShellExt.IInitializeWithFile_Init(pszFilePath: LPCWSTR; grfMode: DWORD): HRESULT; begin // inicializaciones FormPreview := nil; FFilePath := string.Empty; // Apuntamos el path del fichero FFilePath := pszFilePath; Result := S_OK; end; function TBaseShellExt.IInitializeWithStream_Init(const pstream: IStream; grfMode: DWORD): HRESULT; begin // inicializaciones FormPreview := nil; // Apuntamos al stream FStream := pstream; Result := S_OK; end; initialization res := OleInitialize(nil); // Permite registrar nuestro "Preview" con el ID, nombre, descripción y extensión del archivo TComObjectFactory.Create(ComServer, TBaseShellExt, IID_EXT_ShellHandler, sExtFile, sAppDescription, ciMultiInstance, tmApartment); finalization if res = S_OK then OleUninitialize(); end. |
Como veis, nuestra clase para el controlador se llama TBaseShellExt; No es casualidad. Nos ha servido bien como esqueleto y como primera aproximación, pero la idea es que más adelante ampliemos nuestro visualizador y hagamos uso de la herencia. Así que más adelante tendremos clases que deriven de esta y redefinan algunos de sus métodos (incluso que esta clase llegue a ser abstracta). En la próxima entrada continuaremos con el resto de piezas del proyecto.
Nuestra clase implementa los dos interfaces, IInitializeWithStream, IInitializeWithFile. Como hemos dicho antes, debería implementar sólo uno de ellos. En este código he añadido los dos para poder mostrar los métodos, pero si se hace así sólo tendrá efecto el IInitializeWithStream.
En la siguiente entrada veremos todo los referente al Registro de la DLL y a las claves que se deben crear para que funcione.
Como siempre los comentarios, sugerencias y demás son bienvenidos.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…