No hace mucho me cruce con esta “palabrota” este concepto: WMI.
A priori parece sólo eso, una “palabrota” más, de las muchas que nos encontramos en nuestro día a día “informático”. Pero rascando, rascando ha aparecido una perla oculta bajo una capa de polvo.
WMI son las iniciales de “Windows Management Instrumentation”. Se me hace complicado explicar en pocas palabras qué es, pero si tuviera que hacerlo, diría que es algo así como “Una puerta de acceso a la Administración de Sistemas Windows” ( ;-D Windows Management Instrumentation).
Junto a WMI aparecen dos “palabrotas” dos conceptos más que nos ayudan a saber de dónde viene.
WBEM (Web-Based Enterprise Management )
CMI (Common Model Information).
Si las juntamos todas nos queda que, WMI es una implementación que Microsoft ha hecho para Windows, de un estandard llamado WBEM que sirve para la administración de sistemas vía web y que está basado en CIM, que vienen a ser unas clases que nos dan acceso a diferente información del un equipo.
(Traducción al idioma Terrícola):
“A través de WMI yo puedo administrar un equipo basado en Windows, local o remoto, independientemente de la versión del sistema, utilizando las clases que me provee CIM.”
Y esto visto desde el punto de vista de un programador tiene muy buena pinta.
Este podría ser un gráfico sencillo de su estructura.
Dicho esto, yo me pregunto, ¿Qué tiene de bueno WMI y qué puede hacer por mi? (en el terreno más práctico)
Utilizando WMI podemos consultar información sobre nuestro equipo. Desde las aplicaciones instaladas, a la información de la BIOS, discos duros instalados, particiones creadas, Antivirus instalado si lo hay, nivel de carga de la batería (si conviene), procesos ejecutándose, acceso a los servicios instalados, acceso a las tareas programadas, y la lista sigue y sigue…
Cuando hablamos de administración significa que no sólo podemos consultar estos parámetros, sino también modificarlos en muchos casos.
Administración remota. Significa que lo dicho anteriormente se aplica también a administración remota. Es decir, no sólo podemos hacerlo en nuestro equipo, sino en el resto de equipos de la red. WMI está presente en sistemas Windows 2000 con SP2 en adelante. Para los anteriores (95, 98 y NT 4.0) se puede descargar un SDK para instalarlo (link).
Es extensible. Microsoft añade nuevas clases a cada versión de Windows. Además se pueden crear nuevos proveedores de WMI que amplíen funcionalidades.
Acceso desde la línea de comandos. Existe una herramienta para trabajar desde la línea de comandos con WMI (link).
Utilizándola podemos por ejemplo obtener los procesos ejecutándose si desde una ventana de MS-DOS escribimos:
1
2
3
4
WMIC PROCESS
WMIC PROCESS LIST BRIEF (mejora la visualización)
WMIC /output:”c:\procesos.txt” PROCESS LIST BRIEF
(para obtener salida a un fichero)
WMIC PROCESS
WMIC PROCESS LIST BRIEF (mejora la visualización)
WMIC /output:”c:\procesos.txt” PROCESS LIST BRIEF
(para obtener salida a un fichero)
Lenguaje de consulta similar a SQL llamado WQL (WMI Query Language). Utilizándolo podemos desde una ventana MS-DOS ejecutar comandos como estos:
1
2
3
4
5
WMIC PRINTER WHERE Default=”TRUE”
(obtener info sobre la impresora predeterminada)
WMIC PRINTER WHERE Default=”TRUE” LIST FULL
WMIC PRINTER WHERE default="TRUE" GET DriverName,PortName,PrintProcessor
(para obtener determinados datos de la impresora predeterminada)
WMIC PRINTER WHERE Default=”TRUE”
(obtener info sobre la impresora predeterminada)
WMIC PRINTER WHERE Default=”TRUE” LIST FULL
WMIC PRINTER WHERE default="TRUE" GET DriverName,PortName,PrintProcessor
(para obtener determinados datos de la impresora predeterminada)
Aquí se pueden ver unos cuantos ejemplos más a parte de la extensa documentación existente en las páginas de Microsoft.
Una vez hecha esta introducción (muy genérica) sobre WMI, me gustaría centrarme en las aplicaciones que pueden acceder a ella. Microsoft proporciona las API de WMI para scripts, aplicaciones Visual Basic, aplicaciones C++ y .NET Framework. Eso no quiere decir que no se pueda acceder desde casi cualquier lenguaje. En mi caso me voy a centrar en el acceso a ella desde Delphi.
Para acceder desde Delphi a WMI lo primero que necesitamos en Importar la librería desde el menú de Project/Import Type Library (pasos aquí):
Una vez importada la librería tendremos la unit WbemScripting_TLB.pas generada y lista para utilizar. En nuestros programas deberemos incluir en la clausula USES, esta unit junto con la de ActiveX.
Antes de acabar esta entrada (espero poder publicar más, ya que el tema me parece muy interesante) podemos ver cómo utilizar la WMI para acceder a los datos del disco duro. En este caso, para simplificar vamos a acceder a los datos del primer disco (DiskDrive) existente en el sistema. Para ello se utiliza la clase: Win32_DiskDrive
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,…
Pues ha llovido mucho desde la primera parte de este «articulillo»; Por lo que he visto en los fuentes del ejemplo, lo empecé hace aproximadamente hace 2 años, así que eso es lo que lleva de retardo… ;-)
En ese primer artículo se daba una visión general de lo que podía ser un sistema de PlugIns. Unas ideas generales y algo de código para empezar. Quedaba en el tintero profundizar un poco más en el tema y ver un ejemplo un poco más «práctico» que pudiera servir en una aplicación real. Eso es lo que he intentado tratar en esta segunda parte, centrandome en un sistema de «PlugIns Homogéneos«, con carga bajo petición (por parte del usuario).
TIPOS DE PLUGINS
Como ya vimos en la primera entrega, podemos dividir los plugIns en dos tipos según las tareas que desempeñan en una aplicación. Así podemos hablar de plugIns o grupos de ellos y catalogarlos como homogéneos si la tarea que realizan es similar o catalogarlos como heterogéneos (no-agrupados) si las tareas que desarrollan son independientes y no “agrupables” según su funcionalidad.
Grupos homogéneos de PlugIns
PlugIns heterogéneos
Esta división no sólo es conceptual en función de las características y desempeños de cada uno, sino que afecta directamente a la estructura con que se diseñarán estos elementos. Así, los PlugIns que pertenezcan a un grupo homogéneo tendrán estructura similar y un punto de entrada común (formulario base o procedimiento de ejecución). Mientras que los heterogéneos posiblemente no tengan una estructura común y la forma se ejecutarlos sea más “tosca” y menos “integrada” que los anteriores.
PLUGINS HOMOGENEOS
En este artículo vamos a tratar más profundamente esta variante de plugIns. Como ya hemos comentado se trata de plugIns con una estructura similar, aunque con variaciones en su funcionalidad. Tal vez con un ejemplo se vea más claro.
Tomemos como grupo homogéneo de PlugIns; Los efectos aplicables a una imagen dentro de un programa de diseño. A partir de una imagen podemos desarrollar plugIns que efectúen un determinado “cambio” de forma que la imagen resultante sea diferente a la original. La estructura y los parámetros de todos ellos parece claro que serán similares o idénticos. Todos ellos toman una imagen inicial y a partir de unos parámetros la modifican, para devolver una imagen de salida.
ESTRUCTURA FÍSICA
Para trabajar con esta estructura de plugins, utilizaremos un sistema de carga dinámica. Los plugins de programarán utilizando packages (BPL) con una estructura común y dependiendo de un package principal que contiene la Clase Base. Todos los plugins derivarán (heredarán) de una clase base que estará programada en el package principal.
Al cargar la aplicación se carga (puesto que está linkado de forma estática -utilizando la clausula USES-) también el package correspondiente a la clase Base. Esto da acceso a todos los métodos que estén definidos en la clase base (y en los derivados) desde el programa principal.
El resto de packages se cargan de forma dinámica y todos deben derivar (sus clases) de la Clase Base programada en el package Base.
PROTOTIPO
El prototipo que vamos a realizar para ilustrar el artículo simula un programa para realizar gráficos y diagramas simples. El programa utilizará un sistema de plugIns para añadir bibliotecas de objetos que puedan añadirse a los gráficos. Cada pluging (BPL) añade una nueva categoría de elementos y cada categoría implementa uno o varios objetos.
Todos los objetos que implementa una categoría derivan de una Clase Base (TShapeExBase) y esta clase base se implementa en un package que está linkado estáticamente a la aplicación principal (se carga siempre al arrancar la aplicación) y es obligatorio que exista, de otra forma la aplicación fallaría al ejecutarse.
En la imagen que se ve ala derecha, vemos la ventana correspondiente al Plugin de «Arrows»; Aquí implementa la clase TShapeExArrow (que deriva de TShapeExBase) y en esta clase se han programado los objetos que se ven en la imagen.
En nuestro ejemplo para este artículo se cargan los plugIns bajo petición. Es decir, en una primera pasada la aplicación revisa la existencia de PlugIns y detecta todos los ficheros presentes. Muestra una ventana con los plugns disponibles y la descripción de cada uno de ellos y a medida que el usuario los selecciona se cargan de forma dinámica. Imagen de la derecha.
El código de la carga es el siguiente:
...// ComprobaciónifnotFileExists(AName)thenbegin
_mens(Format('No se ha podido cargar el package <%s>;'+'No existe en disco.',[AName]));
Exit;end;// Cargar
hndl :=LoadPackage(AName);
desc :=GetPackageDescription(PChar(AName));
Result := hndl;// Acceder a la clase del menu
pName :=ChangeFileExt(ExtractFileName(AName),'');
b := ExClassList.Find(pName, i);// Encontrada?if(b)thenbegin
AClass := TPersistentClass(ExClassList.Objects[i]);end;
...
// Comprobación
if not FileExists(AName) then begin
_mens(Format('No se ha podido cargar el package <%s>;' +
'No existe en disco.',[AName]));
Exit;
end;
// Cargar
hndl := LoadPackage(AName);
desc := GetPackageDescription(PChar(AName));
Result := hndl;
// Acceder a la clase del menu
pName := ChangeFileExt(ExtractFileName(AName), '');
b := ExClassList.Find(pName, i);
// Encontrada?
if (b) then begin
AClass := TPersistentClass(ExClassList.Objects[i]);
end;
CLASE BASE (TShapeExBase)
La clase base para nuestro sistema de plugins se llama TShapeExBase. Esta clase sirve como punto de partida para todas las demás. Además de contener los métodos comunes a todos los plugins nos permitirá acceder desde la aplicación principal a todas las funciones de los plugins. Para ello los métodos importantes estarán definidos en esta clase y luego sobreescritos (override) en las clases derivadas.
{: Clase base lapa las clases implementadas en los plugins.}
TShapeExBase =class(TShape)private
FShapeEx:string;// Marca el tipo de Shapeprocedure SetShapeEx(const Value:string);virtual;protected
W, H, S:Integer;
X, Y:Integer;
XW, YH:Integer;
W2, H2, W3, H3, H4, W4, H8, W8, W16, H16:Integer;// Método de pintadoprocedure Paint;override;procedure CalculateData();// PROCEDIMIENTOS DE INFORMACION//············································// Autor del packagefunction Autor():string;virtual;abstract;// Versión del Packagefunction Version():string;virtual;abstract;// Fecha de creaciónfunction FechaCreacion():TDate;virtual;abstract;public// constructor de la claseconstructor Create(AOwner: TComponent);override;// destructor de la clasedestructor Destroy;override;published// Tipo de Shapeproperty ShapeEx:stringread FShapeEx write SetShapeEx;end;
{: Clase base lapa las clases implementadas en los plugins.}
TShapeExBase = class(TShape)
private
FShapeEx: string;
// Marca el tipo de Shape
procedure SetShapeEx(const Value: string); virtual;
protected
W, H, S: Integer;
X, Y:Integer;
XW, YH:Integer;
W2, H2, W3, H3, H4, W4, H8, W8, W16, H16:Integer;
// Método de pintado
procedure Paint; override;
procedure CalculateData();
// PROCEDIMIENTOS DE INFORMACION
//············································
// Autor del package
function Autor():string; virtual; abstract;
// Versión del Package
function Version():string; virtual; abstract;
// Fecha de creación
function FechaCreacion():TDate; virtual; abstract;
public
// constructor de la clase
constructor Create(AOwner: TComponent); override;
// destructor de la clase
destructor Destroy; override;
published
// Tipo de Shape
property ShapeEx: string read FShapeEx write SetShapeEx;
end;
En nuestro caso es una clase sencilla. La función implementa el dibujo de componentes derivados de un TShape en pantalla.
La propiedad ShapeEx es la más importante, e indica el tipo (identificador) de la figura. Equivalente a lo que en los TShape son los valores stRectangle, stEllipse, stSquare,…
En nuestra clase no puede ser un elemento tipificado como lo es en TShape, puesto que los nuevos plugins irán añadiendo elementos a esta propiedad que a priori no conocemos.
Se añaden también procedimientos de información acerca del plugin como pueden ser el Autor, la fecha de creación o la versión.
El método Paint, que para la clase base está vacío, en las clases derivadas será donde se implementen las instrucciones de pintado para cada uno de los elementos.
Finalmente la clase Base implementa el procedimiento CalculateData y al utiliza algunas variables en la parte protected, que precalculan datos y los ponen a disposición de las clases derivadas (protected), para facilitar la implementación del método Paint y dar acceso a medidas ya precalculadas.
En la clase Base además se definen dos Listas (TStringList) que nos servirán de apoyo a la hora de acceder a los diferentes objetos de los plugIns; Tanto para las clases, como para los Shapes definidos en cada clase.
//: Lista de clases registradas en los packages dinámicos
ExClassList:TStringList;//: Lista de objetos registrados en una clase (tipos de Shapes)
ExShapeList:TStringList;
//: Lista de clases registradas en los packages dinámicos
ExClassList:TStringList;
//: Lista de objetos registrados en una clase (tipos de Shapes)
ExShapeList:TStringList;
En la primera añadiremos la referencia a la Clase y el String correspondiente al nombre del package y en la segunda, para cada Shape implementado en la Clase, su valor de la propiedad ShapeEx (comentada anteriormente) y el apuntador a su clase.
De esta forma, por ejemplo, el PlugIn que implementa la clase TshapeExArrow que corresponde a la imagen que se ve más arriba, añadirá en las lista los siguientes valores:
// Registrar los tipos
ExShapeList.AddObject('stArrorRight',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW',Pointer(TShapeExArrow));...// registrar la clase
ExClassList.AddObject('PlugArrows',Pointer(TShapeExArrow));
// Registrar los tipos
ExShapeList.AddObject('stArrorRight', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW', Pointer(TShapeExArrow));
...
// registrar la clase
ExClassList.AddObject('PlugArrows', Pointer(TShapeExArrow));
En las líneas anteriores podemos ver que el plugIn PlugArrow (1) tiene implementada la clase TShapeExArrow (1), y que dentro de esta clase hay 6 objetos diferentes de tipo ShapeEx; Cuyos identificadores son: stArrorRight, stArrorRight, stArrorRightM, stArrorLeft, stArrorUp y stArrorDown.
CLASES DERIVADAS
Tal y como está diseñada la estructura, las clases derivadas de la clase Base (TShapeExBase) deben redefinir el método Paint para definir cómo se define cada uno de los objetos de esa clase.
SISTEMA DE CARGA/DESCARGA
El sistema de carga es simple y lo único que hace de especial en este caso es comprobar primero si el package ya ha sido cargado, y si no es así llama a la función CargarPackage del formulario principal, utilizando el nombre del fichero.
Podemos ver por pasos y comentar qué hace esta función:
// Cargar
hndl :=LoadPackage(AName);
desc :=GetPackageDescription(PChar(AName));
Result := hndl;
En primer lugar (una vez hemos comprobado que el fichero existe) cargamos el package a partir de su nombre. Una vez cargado obtenemos la Descripción. Para ello se llama a la función GetPackageDescription que se encuentra en la Unidad SysUtils.pas y que develve el valor almacenado en el DPK junto a la directiva {$DESCRIPTION} o {$D} que permite almacenar hasta 255 caracteres.
Todos los packages cuentan con una sección de INITIALIZATION donde añaden a las lista de clases (ExClassList) y a la lista de Shapes (ExShapeList) los elementos que ese package implementa. Estas dos clases son importantes puesto que nos facilitan mucho el trabajo a la hora de realizar todo tipo de operaciones con los elementos de cada packages. Además se registra la clase utilizando el método RegisterClass de Delphi. Por ejemplo, el package de “Arrows” contiene esta sección de INITIALIZATION:
//===================================================================//// I N I T I A L I Z A T I O N////===================================================================initialization// Registrar la clase del formRegisterClass(TShapeExArrow);// Registrar los tipos
ExShapeList.AddObject('stArrorRight',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightM',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorLeft',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorUp',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorDown',Pointer(TShapeExArrow));// registrar la clase
ExClassList.AddObject('PlugArrows',Pointer(TShapeExArrow));//===================================================================
//===================================================================
//
// I N I T I A L I Z A T I O N
//
//===================================================================
initialization
// Registrar la clase del form
RegisterClass(TShapeExArrow);
// Registrar los tipos
ExShapeList.AddObject('stArrorRight', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightM', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorLeft', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorUp', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorDown', Pointer(TShapeExArrow));
// registrar la clase
ExClassList.AddObject('PlugArrows', Pointer(TShapeExArrow));
//===================================================================
Lo siguiente que vamos necesitamos, una vez que tenemos cargado el package, es crear la clase que se implementa en el package; Una vez hecho esto ya tendremos total acceso a los métodos que necesitemos y realmente ya habremos conseguido nuestro objetivo.
Para crear la clase utilizamos la lista de clases (ExClassList) que hemos comentado en el párrafo anterior y que hemos rellenado en la sección de inicialización; Otra opción también viable es utilizar GetClass de Delphi mediante RTTI junto con el nombre de la clase registrada (TShapeExArrow). También funcionaría, aunque en este caso, por comodidad, hemos utilizado estas listas auxiliares.
// Crear la clase
b := ExClassList.Find(pName, i);// encontrada?if(b)thenbegin
AClass := TPersistentClass(ExClassList.Objects[i]);// OTRA OPCIÓN:
BClass :=GetClass('TShapeExArrow');end;
// Crear la clase
b := ExClassList.Find(pName, i);
// encontrada?
if (b) then begin
AClass := TPersistentClass(ExClassList.Objects[i]);
// OTRA OPCIÓN:
BClass := GetClass('TShapeExArrow');
end;
Para finalizar y después de haber realizado unas comprobaciones, llamamos al método CargarCategoria, que crea de forma dinámica la ventana asociada a esa categoría (con la descripción) y también crea el elemento individual asociada a cada Shape implementado en esa clase.
En este punto ya hemos hecho uso de todo lo implementado en ese package, puesto que ya hemos creado un objeto de todos los implementados.
// Cargar los objetos de ese plugIn
CargarCategoria(AClass, desc);
En este ejemplo, no descargamos los packages, puesto que los necesitamos para seguir trabajando con los objetos que tenemos en pantalla, lo que hacemos realmente es ocultar la ventana. Si la operación que desempeña el package no necesita que posteriormente esté cargado, bastaría con descargarlo utilizando UnloadPackage.
Hasta aquí las descripción de todo el proceso. Junto con el artículo os adjunto el ejemplo completo y bastante comentado. Es sencillo, pero muestra a la perfección el manejo práctico de este tipo de ficheros.
Espero que haya quedado claro y si hay comentarios o sugerencias, ya sabéis. ¡¡Disparad!! ;-D
Imagen del programa de ejemplo.
El código del ejemplo se puede descargar desde aquí y los binarios (EXE + BPL’s) desde aquí.
Tal como me comenta Salvador, en el proyecto no se incluyen las dos BPL’s de Dephi (de la VCL) que ne necesitan para ejecutar el proyecto. Si no tenéis Delphi 6 instalado, las necesitaréis para ejecutar. Os coloco los links, con el proyecto (binarios incluyendo las BPLs) y un fichero sólo con los dos ficheros (VCL60.BPL y RTL60.BPL).
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,…
Después de las dos entradas sobre Codificacióin Geográfica (*), toca entrar de lleno en la Visualización e Integración de Google Maps en nuestro programa, que por otra parte era el objetivo inicial de esta serie de entradas. La Codificación Geográfica (obtención de Latitud y Loingitud a partir de una dirección postal) era un «escollo» a salvar en nuestro camino, hasta nuestro verdadero objetivo.
(*) Lo normal es tener en nuestros programas/aplicaciones una serie de direcciones postales o direcciones de correo que deseamos mostrar o señalar en un Mapa. Para utilizar esas direccones con la API de Google Maps necesitamos obtener las coordenadas de Latitud y Longitud apropiadas. Para ello ya vimos aquí (Codificación Geográfica – I) y aquí (Codificación Geográfica – II) cómo Google provee métodos para obtener las coordenadas de posicionamiento a partir de direcciones postales.
La idea para conseguir la integración es sencilla. Utilizar un componente TWebBrowser para mostrar una página HTML. Desde Delphi debemos «modificar» esa página para que muestre lo que nosotros deseemos.
Utilizando una sencilla página HTML con un Script, podemos visualizar el mapa correspondiente a coordenadas de Latitud y Longitud. Con un componente TWebBrowser podemos visualizar esa página dentro de una aplicación delphi.
La página en sí sólo contiene el Script para Visualizar un Mapa.
Dentro del Script vemos cómo se crea un elemento de tipo «map»:
map =new GMap2(document.getElementById("map"));
map = new GMap2(document.getElementById("map"));
Se añaden los controles que queremos visualizar en el mapa; En este caso, la selección para el tipo de mapa y control de zoom y posición grande.
// Controles del mapa
map.addControl(new GMapTypeControl());
map.addControl(new GLargeMapControl());
// Controles del mapa
map.addControl(new GMapTypeControl());
map.addControl(new GLargeMapControl());
El tipo de mapa que queremos que inicialmente aparezca (Mapa, visión por satélite o lo que Google llama híbrido).
// Tipo de mapa
map.setMapType(G_NORMAL_MAP);
// Tipo de mapa
map.setMapType(G_NORMAL_MAP);
Y por último colocamos la posición (latitud y longitud) donde queremos que el mapa salga centrado.
Pero no acaban aquí (ni mucho menos) las posibilidades de Google Maps. Una de las opciones, que dentro de lo sencillo, es muy interesante, es la inclusión de marcas señalando puntos del mapa o incluso ventanas de información no es mucho más complejo.
Para añadir una nueva marca, basta con conocer el punto exacto donde queremos situarla y utilizar un objeto GMarker para mostrarla en el mapa. En cuanto a la ventana, de información, sólo necesitamos el código HTML necesario para crearla.
En este ejemplo la ventana tiene un código sencillo (texto, imagen y link) que se almacena en una variable.
Para mostrarla basta con que utilicemos en método OpenInfoWindowHtml (cuyo nombre es bastante descriptivo, por cierto ;-D ) de la siguiente forma:
// Visualización de la mrcavar point =new GLatLng(41.381655,2.122829);var marker =new GMarker(point);
marker.openInfoWindowHtml(html);
// Visualización de la mrca
var point = new GLatLng(41.381655,2.122829);
var marker = new GMarker(point);
marker.openInfoWindowHtml(html);
Hay más opciones y basta con echarle una vistazo a la API de Google Maps para conocerlas. Además hay muchos ejemplos de utilización que pueden servir para aclarar los conceptos y para ver más cláramente cómo se utilizan los diferentes elementos.
Todo lo visto hasta aquí, está puesto en práctica en un ejemplo Delphi.
Podéis descargar el código completo (incluye EXE -comprimido UPX-) desde aquí:
<Descargar Ejemplo>
Y aquí acabamos esta entrada, que no será la última. Me dejo para una segunda parte, algunos detalles finales, como interactuar con el mapa y obtener datos de él y fusionar los dos conceptos vistos hasta ahora.
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,…
Se conoce como Codificación Geográfica, al proceso o sistema de transformar una dirección «o similar» en coordenadas geográficas de Longitud y Latitud, para posteriormente ser utilizadas en sistemas de posicionamiento (por ejemplo).
El API de Google incluye este servicio, que es el que se utiliza cuando buscamos una dirección desde Google Maps.
Normalmente este acceso se hace desde una página web con peticiones HTTP; Lo que vamos a ver es cómo integrar este servicio en un programa Delphi.
Un poco más arriba he comentado que la codificación geográfica se basa en dar una entrada (dirección) para obtener unas coordenadas. También he añadido el «similar», puesto que en el caso de Google Maps, podemos dar como entrada otras cosas que expresamente no son una dirección.
Así, podemos utilizar como dirección:
08901,Spain
c/Balmes,214,Barcelona,Spain
Sevilla,Spain
Alhambra,Granada,Spain
…
Para hacer una prueba de lo que podemos obtener, basta con utilizar un componente TidHTTP de las Indy para realizar la petición. Coloca un componente TidHTTP en un formulario y un botón con el siguiente código:
procedure TForm1.btn1Click(Sender:TObject);const
STR_WEB ='http://maps.google.com/maps/geo?q=';
STR_OUT ='&output=csv';// formato de salidavar
Stream: TStringStream;
Str, res:string;begin// ini
Stream := TStringStream.Create('');// proteccion para liberartry
Str := STR_WEB + edt1.Text+ STR_OUT;
idhttp2.Get(Str, Stream);
edt2.Text:= Stream.DataString;finallyFreeAndNil(Stream);end;end;
procedure TForm1.btn1Click(Sender: TObject);
const
STR_WEB = 'http://maps.google.com/maps/geo?q=';
STR_OUT = '&output=csv'; // formato de salida
var
Stream: TStringStream;
Str, res:string;
begin
// ini
Stream := TStringStream.Create('');
// proteccion para liberar
try
Str := STR_WEB + edt1.Text + STR_OUT;
idhttp2.Get(Str, Stream);
edt2.Text := Stream.DataString;
finally
FreeAndNil(Stream);
end;
end;
Una imagen de lo que obtenemos una vez realizada la petición es la siguiente:
Se puede descargar el código completo de este ejemplo desde aquí.
Y lo que obtenemos como respuesta en este caso es:
200,8,41.3979638,2.1515206
Los dos últimos parámetros son los que buscabamos y nos indican las coordenadas de ese punto (latitud y longitud).
Para comprobar que so correctas, basta con ir a la web de Google Maps, copiar estas dos coordenadas en el cuadro de búsqueda y pulsar sobre el botón de <Buscar en el Mapa>.
A parte de esto, Google Maps nos devuelve, en este caso, dos números más; El primero corresponde al un «código de Estado» o «código de retorno» de la consulta que hemos realizado; En él se devuelve información por parte del servidor. Y el segundo es lo que se conoce como «Accuracy» o «Exactitud», que corresponde justamente a eso; Al nivel de exactitud que Google asigna a la respuesta, segun la dirección que le hemos dado.
El código de retorno, en este caso, es un 200, que corresponde a la constante «G_GEO_SUCCESS» (consulta correcta).
El nivel de exactitud, en este caso, es un 8 (máxima precisión).
Los niveles de exactitud con lo que trabaja Google Maps son los siguientes:
0: Ubicación desconocida.
1: Precisión a nivel de país.
2: Precisión a nivel de región.
3: Precisión a nivel de subregión.
4: Precisión a nivel de ciudad o pueblo.
5: Precisión a nivel de código postal.
6: Precisión a nivel de calle.
7: Precisión a nivel de intersección.
8: Precisión a nivel de dirección.
Así en el ejemplo que hemos visto antes obteníamos una Exactitud de 8 (a nivel de dirección), mientras que si íntroducimos una dirección del tipo;
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,…
Personalmente a veces me sería útil que el formulario que estoy utilizando tuviera algun sistema para detectar cuando se está minimizando el formularo; Y mejor aun que permitiera interactuar con esta acción.
Utilizo a menudo una opción de configuración que llamo: «Minimizar al Tray». Muchas aplicaciones lo utilizan y se trata simplemente de, en lugar de minimizar la aplicación, ocultarla y mostrar un icono junto al reloj en la barra de tareas de Windows.
Para ellos la forma más sencilla que he encontrado es la que explico a continuación. Hay que decir que estoy usando Delphi 5, así que tal vez en alguna versión posterior (que además cuentan con el componnte para el Tray) habrá alguna solución más sencilla.
1
2
3
// Capturar mensajes al formprocedure WMSysCommand(var Msg: TWMSysCommand);message WM_SYSCOMMAND;
// Capturar mensajes al form
procedure WMSysCommand(var Msg: TWMSysCommand);
message WM_SYSCOMMAND;
// Capturar mensajes....
procedure TFormMain.WMSysCommand(var Msg: TWMSysCommand);
begin
// Minimizando?
if (Msg.CmdType = SC_MINIMIZE) then begin
actionOcultar.Execute;
end
else begin
DefaultHandler(Msg);
end;
end;
En mi caso, lo que hago en el procedimiento es llamar al método de ocultar. Importante que en mi caso no deseo que se realice el Minimizar, por eso, la llamada a DefaultHandler está en el else. Si se desea que igualmente se realice esa llamada, esta debe estar fuera del IF.
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,…
Utilizando ADO se puede acceder a casi toda la información de la Base de Datos, Tablas y Columnas, pero no a toda. En el caso de las Bases de datos de Access, por ejemplo, la propiedad descripción, que utilizamos para añadir un texto descriptivo a las columnas de las tablas, no es accesible.
Para obtenerla, hace falta acceder directamente a la información del «catálogo» utiliando ADOX. Para poder utilizar ADOX, lo primero que debemos hacer es importar la librería a en Delphi;
Esto se puede hacer desde el menú de: Proyect/Import Type Library.
La librería en concreto es la llamada «Microsoft ADO Ext. for DDL and Security» y proceso paso a paso, podéis verlo es esta página de Zarko Gajic.
Una vez importada la librería, basta con abrir la Base de Datos, acceder a una tabla y a un columna; A partir de ahí ya tenemos todos los datos(propiedades) referentes a esa columna.
El código es sencillo (basta con tener un formulario con un memo (Memo1) y un botón (button1)):
procedure TForm1.Button2Click(Sender:TObject);const
DB_CONNECTION='Provider=Microsoft.Jet.OLEDB.4.0;'+'Data Source=%s';
DATABASENAME ='c:\Archivos de '+'programa\Archivos comunes\Borland Shared\Data'+'\dbdemos.mdb';var
i, j:Integer;
Con:OleVariant;
fCatalog:Catalog;
Column: _Column;
Table:_Table;
Str1, Str2:string;begin// Limpiar la salida
Memo1.Lines.Clear;// Conectar con la Base de Datos
Con := CreateOleObject('ADODB.Connection');// Abrir
Con.Open(Format(DB_CONNECTION,[DATABASENAME]));// protecciontry// Acceder a la Base de Datos
fCatalog := CoCatalog.Create;
fCatalog._Set_ActiveConnection(Con);// Acceder a la tabla de empleados
Table := fCatalog.Tables['employee'];// recorrer las columnasfor i :=0to(Table.Columns.Count-1)dobegin// Acceder a la columna
Column := Table.Columns[i];// Datos de columna
Memo1.Lines.Add(' ');
Memo1.Lines.Add(Format('Columna: %s',[Column.Name]));
Memo1.Lines.Add('---------------------------------');
Memo1.Lines.Add(Format(' Tamaño: %d',[Column.DefinedSize]));
Memo1.Lines.Add(Format(' Precisión: %d',[Column.Precision]));// recorrer las propiedades de la columnafor j :=0to(Column.Properties.Count-1)dobegin// Cada propiedad, Nombre y valor
Str1 := Column.Properties[j].Name;
Str2 := Column.Properties[j].Value;// Saltamos las propiedades Jet...if(Length(Str1) > 0)thenbegin// Saltar las Jetif(Str1[1] <> 'J')thenbegin
Memo1.Lines.Add(Format(' %s: %s',[Str1, Str2]))end;//ifend;//ifend;// forend;// forfinally// Liberar y cerrar
Column :=nil;
Table :=nil;
fCatalog :=nil;
Con.Close;end;end;
procedure TForm1.Button2Click(Sender: TObject);
const
DB_CONNECTION='Provider=Microsoft.Jet.OLEDB.4.0;' +
'Data Source=%s';
DATABASENAME = 'c:\Archivos de ' +
'programa\Archivos comunes\Borland Shared\Data' +
'\dbdemos.mdb';
var
i, j:Integer;
Con:OleVariant;
fCatalog:Catalog;
Column: _Column;
Table:_Table;
Str1, Str2:string;
begin
// Limpiar la salida
Memo1.Lines.Clear;
// Conectar con la Base de Datos
Con := CreateOleObject('ADODB.Connection');
// Abrir
Con.Open(Format(DB_CONNECTION,[DATABASENAME]));
// proteccion
try
// Acceder a la Base de Datos
fCatalog := CoCatalog.Create;
fCatalog._Set_ActiveConnection(Con);
// Acceder a la tabla de empleados
Table := fCatalog.Tables['employee'];
// recorrer las columnas
for i := 0 to (Table.Columns.Count - 1) do begin
// Acceder a la columna
Column := Table.Columns[i];
// Datos de columna
Memo1.Lines.Add(' ');
Memo1.Lines.Add(Format('Columna: %s',[Column.Name]));
Memo1.Lines.Add('---------------------------------');
Memo1.Lines.Add(Format(' Tamaño: %d',[Column.DefinedSize]));
Memo1.Lines.Add(Format(' Precisión: %d',[Column.Precision]));
// recorrer las propiedades de la columna
for j := 0 to (Column.Properties.Count - 1) do begin
// Cada propiedad, Nombre y valor
Str1 := Column.Properties[j].Name;
Str2 := Column.Properties[j].Value;
// Saltamos las propiedades Jet...
if (Length(Str1) > 0) then begin
// Saltar las Jet
if (Str1[1] <> 'J') then begin
Memo1.Lines.Add(Format(' %s: %s',[Str1, Str2]))
end; //if
end; //if
end; // for
end; // for
finally
// Liberar y cerrar
Column := nil;
Table := nil;
fCatalog := nil;
Con.Close;
end;
end;
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,…
Procedimiento que muestra cómo ordenar un TStringGrid a partir de los datos de una columna. El texto está basado en esta página Web donde hay un algoritmo de ordenación. El problema es que sólo es para datos numéricos. En este truco he añadido un par de parámetros para poder definir columnas de otro tipo (u ordenación de otro tipo) y además marcar si que quiere de forma Ascendente o Descendente (que tampoco está en el truco original).
Yo he añadido métodos para ordenar por enteros y Float, aunque ampliando se pueden añadir para alguno más.
// Ordena un TStringGrid.procedure SortStringGrid(var GenStrGrid: TStringGrid;
ThatCol:Integer;
ColData:TGridData=gdString;
SortOrder:TSortOrder=soASC);const
TheSeparator ='@';var
CountItem, I, J, K, ThePosition:integer;
MyList: TStringList;
MyString, TempString:string;
str:string;
vali:Integer;
valf:Double;begin
CountItem := GenStrGrid.RowCount;
MyList := TStringList.Create;
MyList.Sorted:=False;trybeginfor I :=1to(CountItem -1)dobegin
Str := GenStrGrid.Rows[I].Strings[ThatCol];if(ColData = gdInteger)thenbegin
vali :=StrToIntDef(Str,0);
Str :=Format('%*d',[15,vali]);end;if(ColData = gdFloat)thenbegin
valf :=StrToFloat(Str);
Str :=Format('%15.2f',[valf]);end;
MyList.Add(Str + TheSeparator + GenStrGrid.Rows[I].Text);end;
Mylist.Sort;for K :=1to Mylist.Countdobegin
MyString := MyList.Strings[(K -1)];
ThePosition :=Pos(TheSeparator, MyString);
TempString :='';{Eliminate the Text of the column on which we have
sorted the StringGrid}
TempString :=Copy(MyString,(ThePosition +1),Length(MyString));
MyList.Strings[(K -1)]:='';
MyList.Strings[(K -1)]:= TempString;end;if(SortOrder = soASC)thenbeginfor J :=1to(CountItem -1)dobegin
GenStrGrid.Rows[J].Text:= MyList.Strings[(J -1)];end;endelsebeginfor J :=1to(CountItem -1)dobegin
I :=(CountItem - J);
GenStrGrid.Rows[I].Text:= MyList.Strings[(J -1)];end;end;end;finally
MyList.Free;end;end;
// Ordena un TStringGrid.
procedure SortStringGrid(var GenStrGrid: TStringGrid;
ThatCol: Integer;
ColData:TGridData=gdString;
SortOrder:TSortOrder=soASC);
const
TheSeparator = '@';
var
CountItem, I, J, K, ThePosition: integer;
MyList: TStringList;
MyString, TempString: string;
str:string;
vali:Integer;
valf:Double;
begin
CountItem := GenStrGrid.RowCount;
MyList := TStringList.Create;
MyList.Sorted := False;
try
begin
for I := 1 to (CountItem - 1) do begin
Str := GenStrGrid.Rows[I].Strings[ThatCol];
if (ColData = gdInteger) then begin
vali := StrToIntDef(Str, 0);
Str := Format('%*d', [15,vali]);
end;
if (ColData = gdFloat) then begin
valf := StrToFloat(Str);
Str := Format('%15.2f',[valf]);
end;
MyList.Add(Str + TheSeparator + GenStrGrid.Rows[I].Text);
end;
Mylist.Sort;
for K := 1 to Mylist.Count do begin
MyString := MyList.Strings[(K - 1)];
ThePosition := Pos(TheSeparator, MyString);
TempString := '';
{Eliminate the Text of the column on which we have
sorted the StringGrid}
TempString := Copy(MyString, (ThePosition + 1), Length(MyString));
MyList.Strings[(K - 1)] := '';
MyList.Strings[(K - 1)] := TempString;
end;
if (SortOrder = soASC) then begin
for J := 1 to (CountItem - 1) do begin
GenStrGrid.Rows[J].Text := MyList.Strings[(J - 1)];
end;
end
else begin
for J := 1 to (CountItem - 1) do begin
I := (CountItem - J);
GenStrGrid.Rows[I].Text := MyList.Strings[(J - 1)];
end;
end;
end;
finally
MyList.Free;
end;
end;
AÑADO: Me falta una cosa.
Además habrá que definir en la unit los dos tipos que se utilizan para la ordenación:
1
2
3
4
5
Type//: Tipo de Dat de la columna por la que queremos ordenar.
TGridData =(gdString, gdInteger, gdFloat);//: Tipos de ordenación.
TSortOrder =(soASC, soDESC);
Type
//: Tipo de Dat de la columna por la que queremos ordenar.
TGridData = (gdString, gdInteger, gdFloat);
//: Tipos de ordenación.
TSortOrder = (soASC, soDESC);
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,…
Función para buscar ficheros en un directorio de forma recursiva.
Devuelve una lista de nombres de fichero encontrados a partir de la carpeta inicial StartDir, que cumplen el patrón especificado por FileMask.
Mediante recursively se indica si se desea
hacer la busqueda en los subdirectorios.
El resultado se devuelve en FilesList (TStringList), que es la lista que se rellena con los nombres de fichero encontrados.
{: Devuelve una lista de nombres de fichero encontrados a partir
de la carpeta inicial StartDir, que cumplen el patrón especificado
por FileMask.Mediante recursively se indica si se desea hacer la
busqueda en los subdirectorios.
StartDir Carpeta desde la que empezar a buscar.
FileMask Patrón que han de cumplir los ficheros.
Recursively Si hay que continuar la búsqueda en los subdirectorios.
FilesList Lista con los nombres de fichero encontrados.
}procedure FindFiles(StartDir, FileMask:string;
recursively:boolean;var FilesList: TStringList);const
MASK_ALL_FILES ='*.*';
CHAR_POINT ='.';var
SR: TSearchRec;
DirList: TStringList;
IsFound:Boolean;
i:integer;beginif(StartDir[length(StartDir)] <> '\')thenbegin
StartDir := StartDir +'\';end;// Crear la lista de ficheos en el dir. StartDir (no directorios!)
IsFound :=FindFirst(StartDir + FileMask,
faAnyFile - faDirectory, SR)=0;// MIentras encuentrewhile IsFound dobegin
FilesList.Add(StartDir + SR.Name);
IsFound :=FindNext(SR)=0;end;FindClose(SR);// Recursivo?if(recursively)thenbegin// Build a list of subdirectories
DirList := TStringList.Create;// protecciontry
IsFound :=FindFirst(StartDir + MASK_ALL_FILES,
faAnyFile, SR)=0;while IsFound dobeginif((SR.Attrand faDirectory) <> 0)and(SR.Name[1] <> CHAR_POINT)thenbegin
DirList.Add(StartDir + SR.Name);
IsFound :=FindNext(SR)=0;end;// ifend;// whileFindClose(SR);// Scan the list of subdirectoriesfor i :=0to DirList.Count-1dobegin
FindFiles(DirList[i], FileMask, recursively, FilesList);end;finally
DirList.Free;end;end;end;
{: Devuelve una lista de nombres de fichero encontrados a partir
de la carpeta inicial StartDir, que cumplen el patrón especificado
por FileMask.Mediante recursively se indica si se desea hacer la
busqueda en los subdirectorios.
StartDir Carpeta desde la que empezar a buscar.
FileMask Patrón que han de cumplir los ficheros.
Recursively Si hay que continuar la búsqueda en los subdirectorios.
FilesList Lista con los nombres de fichero encontrados.
}
procedure FindFiles(StartDir, FileMask: string;
recursively: boolean; var FilesList: TStringList);
const
MASK_ALL_FILES = '*.*';
CHAR_POINT = '.';
var
SR: TSearchRec;
DirList: TStringList;
IsFound: Boolean;
i: integer;
begin
if (StartDir[length(StartDir)] <> '\') then begin
StartDir := StartDir + '\';
end;
// Crear la lista de ficheos en el dir. StartDir (no directorios!)
IsFound := FindFirst(StartDir + FileMask,
faAnyFile - faDirectory, SR) = 0;
// MIentras encuentre
while IsFound do begin
FilesList.Add(StartDir + SR.Name);
IsFound := FindNext(SR) = 0;
end;
FindClose(SR);
// Recursivo?
if (recursively) then begin
// Build a list of subdirectories
DirList := TStringList.Create;
// proteccion
try
IsFound := FindFirst(StartDir + MASK_ALL_FILES,
faAnyFile, SR) = 0;
while IsFound do begin
if ((SR.Attr and faDirectory) <> 0) and
(SR.Name[1] <> CHAR_POINT) then begin
DirList.Add(StartDir + SR.Name);
IsFound := FindNext(SR) = 0;
end; // if
end; // while
FindClose(SR);
// Scan the list of subdirectories
for i := 0 to DirList.Count - 1 do begin
FindFiles(DirList[i], FileMask, recursively, FilesList);
end;
finally
DirList.Free;
end;
end;
end;
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,…
Muestra como utilizar un BITMAP (en este caso extraído de un TImage) para modificar el cursor activo. Modificando un poco el Tip/Truco es fácil obtener la imagen de disco o desde un recurso.
Se utiliza la API CreateIconIndirect para generar el cursor que posteriormente se activará.
Se puede usar un color de fondo como transparente para obtener un cursos «opaco» o un segundo BITMAP para combinarlo con el primero y así obtener también efectos de transparencia en el cursor generado.
// Crear los bitmaps
BitmapMask := TBitmap.Create;
Bitmap := TBitmap.Create;// protecciontry// Cargar las imágenes
BitmapMask.Assign(Image2.Picture.Bitmap);
Bitmap.Assign(Image1.Picture.Bitmap);// Crear el icono del cursorwith iconInfo dobegin
fIcon :=false;
xHotspot :=(Bitmap.Widthdiv4);
yHotspot :=(Bitmap.Heightdiv3);
hbmMask := BitmapMask.Handle;
hbmColor := Bitmap.Handle;end;// Asignar el icono
Screen.Cursors[1]:= CreateIconIndirect(iconInfo);Self.Cursor:=1;finally// Liberar
BitmapMask.Free;
Bitmap.Free;end;
// Crear los bitmaps
BitmapMask := TBitmap.Create;
Bitmap := TBitmap.Create;
// proteccion
try
// Cargar las imágenes
BitmapMask.Assign(Image2.Picture.Bitmap);
Bitmap.Assign(Image1.Picture.Bitmap);
// Crear el icono del cursor
with iconInfo do begin
fIcon := false;
xHotspot := (Bitmap.Width div 4);
yHotspot := (Bitmap.Height div 3);
hbmMask := BitmapMask.Handle;
hbmColor := Bitmap.Handle;
end;
// Asignar el icono
Screen.Cursors[1] := CreateIconIndirect(iconInfo);
Self.Cursor := 1;
finally
// Liberar
BitmapMask.Free;
Bitmap.Free;
end;
Es ejemplo completo se puede descargar <AQUÍ>. Descargar.
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,…
Para cualquier fichero que se encuentra en el sistema de archivos de Windows se almacenan varias fechas. Para acceder a todas ellas puede utilizar el siguiente truco:
// ================================================================// Return the three dates (Created,Modified,Accessed)// of a given filename. Returns FALSE if file cannot// be found or permissions denied. Results are returned// in TdateTime VAR parameters// ================================================================// ================================================================// Devuelve las tres fechas (Creación, modificación y último acceso)// de un fichero que se pasa como parámetro.// Devuelve FALSO si el fichero no se ha podido acceder, sea porque// no existe o porque no se tienen permisos. Las fechas se devuelven// en tres parámetros de ipo DateTime// ================================================================function GetFileTimes(FileName :string;var Created :TDateTime;var Modified :TDateTime;var Accessed :TDateTime):boolean;var
FileHandle :integer;
Retvar :boolean;
FTimeC,FTimeA,FTimeM : TFileTime;
LTime : TFileTime;
STime : TSystemTime;begin// Abrir el fichero
FileHandle :=FileOpen(FileName,fmShareDenyNone);// inicializar
Created :=0.0;
Modified :=0.0;
Accessed :=0.0;// Ha tenido acceso al fichero?if FileHandle < 0then
RetVar :=falseelsebegin// Obtener las fechas
RetVar :=true;
GetFileTime(FileHandle,@FTimeC,@FTimeA,@FTimeM);// CerrarFileClose(FileHandle);// Creado
FileTimeToLocalFileTime(FTimeC,LTime);if FileTimeToSystemTime(LTime,STime)thenbegin
Created :=EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Created := Created +EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);end;// Accedido
FileTimeToLocalFileTime(FTimeA,LTime);if FileTimeToSystemTime(LTime,STime)thenbegin
Accessed :=EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Accessed := Accessed +EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);end;// Modificado
FileTimeToLocalFileTime(FTimeM,LTime);if FileTimeToSystemTime(LTime,STime)thenbegin
Modified :=EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Modified := Modified +EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);end;end;
Result := RetVar;end;
// ================================================================
// Return the three dates (Created,Modified,Accessed)
// of a given filename. Returns FALSE if file cannot
// be found or permissions denied. Results are returned
// in TdateTime VAR parameters
// ================================================================
// ================================================================
// Devuelve las tres fechas (Creación, modificación y último acceso)
// de un fichero que se pasa como parámetro.
// Devuelve FALSO si el fichero no se ha podido acceder, sea porque
// no existe o porque no se tienen permisos. Las fechas se devuelven
// en tres parámetros de ipo DateTime
// ================================================================
function GetFileTimes(FileName : string; var Created : TDateTime;
var Modified : TDateTime; var Accessed : TDateTime) : boolean;
var
FileHandle : integer;
Retvar : boolean;
FTimeC,FTimeA,FTimeM : TFileTime;
LTime : TFileTime;
STime : TSystemTime;
begin
// Abrir el fichero
FileHandle := FileOpen(FileName,fmShareDenyNone);
// inicializar
Created := 0.0;
Modified := 0.0;
Accessed := 0.0;
// Ha tenido acceso al fichero?
if FileHandle < 0 then
RetVar := false
else begin
// Obtener las fechas
RetVar := true;
GetFileTime(FileHandle,@FTimeC,@FTimeA,@FTimeM);
// Cerrar
FileClose(FileHandle);
// Creado
FileTimeToLocalFileTime(FTimeC,LTime);
if FileTimeToSystemTime(LTime,STime) then begin
Created := EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Created := Created + EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);
end;
// Accedido
FileTimeToLocalFileTime(FTimeA,LTime);
if FileTimeToSystemTime(LTime,STime) then begin
Accessed := EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Accessed := Accessed + EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);
end;
// Modificado
FileTimeToLocalFileTime(FTimeM,LTime);
if FileTimeToSystemTime(LTime,STime) then begin
Modified := EncodeDate(STime.wYear,STime.wMonth,STime.wDay);
Modified := Modified + EncodeTime(STime.wHour,STime.wMinute,
STime.wSecond, STime.wMilliSeconds);
end;
end;
Result := RetVar;
end;
Para llamar a ésta función se puede utilizar un código como éste:
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,…