Inicio > Delphi, Ejemplos > I – Creando una DLL de previsualización de ficheros (Preview Handler)

I – Creando una DLL de previsualización de ficheros (Preview Handler)

miércoles, 22 de mayo de 2024 Dejar un comentario Ir a comentarios
Share Button

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:

  1. Crear una Previsualización de ficheros en Windows
  2. «Registros» de una DLL de previsualización
  3. Generando previsualización funcional (PreviewHandler)
  4. 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)»

  1. Nos dice los pasos a seguir para que se muestre el archivo.
  2. 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:

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.

5/5 - (1 voto)
Subscribe
Notify of
guest

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

0 Comments
Inline Feedbacks
Ver todos los comentarios
0
Would love your thoughts, please comment.x
()
x