sep 302010
 

Buenas,

En los eventos de ratón de un TStatusBar solemos encontrar las coordenadas X, Y en la que se produce la acción, pero no así el panel, el cual puede ser interesante saber si queremos realizar una acción determinada según el panel (mostrar un hint dependiendo del panel, un menú contextual,…). La siguiente función nos devolverá dicho panel del TStatusBar.

function GetStatusBarPanelXY(StatusBar: TStatusBar; X, Y: Integer) : Integer;
var
  i: Integer;
  R: TRect;
begin
  Result := -1;

  // Buscamos panel a panel hasta encontrar en cual está XY
  with StatusBar do
    for i := 0 to Panels.Count - 1 do
    begin
      // Obtenemos las dimensiones del panel
      SendMessage(Handle, WM_USER + 10, i, Integer(@R));
      if PtInRect(R, Point(x,y)) then
      begin
        Result := i;
        Break;
      end;
    end;
end;

Los eventos del ratón que tienen coordenadas y con los que nos servirá la función son los siguientes:

  • OnMouseActivate
  • OnMouseDown
  • OnMouseMove
  • OnMouseUp
  • OnEndDrag
  • OnEndDock
  • OnDragOver
  • OnDragDrop
  • OnContextPopup (no tiene X e Y, pero tiene un TPoint que para el caso es lo mismo)

Espero que os sirva.

Nos leemos

sep 302010
 

Buenas,

Vimos en un mensaje anterior la clase TIniFiles, la cual nos permite el acceso fácil a los ficheros de configuración. Cuando en una aplicación se hace mucho uso de este tipo de ficheros, en hecho de tener que estar todo el rato escribiendo las mismas líneas de código para crear y destruir un objeto de la clase TIniFile puede llegar a ser cansino. Lo que haremos ahora es crearnos nuestra propia clase que manipule un objeto de tipo TIniFile.

Los métodos de la nueva clase pueden llegar a ser los mismos que los que tiene TIniFile, no obstante podemos poner los que queramos o necesitemos.

Vamos a llamar a esta nueva clase TFicherosIni y la idea es crear una variable global de la unit de este tipo y controlar su creación y destrucción en la inicialización (initialization) y finalización (finalization) de la misma respectivamente.

unit FicherosIni;

interface

type
  TFicherosIni = class
  ....
  end;

var
  Ini: TFicherosIni;

implementation
.....

initialization
  Ini := TFicherosIni.Create;

finalization
  FreeAndNil(Ini);

end.

Con esto, sólo necesitaremos poner en la cláusula uses del formulario que queramos usarlo la llamada a esta unit y usar directamente el objeto Ini.

Tengo la costumbre de que los archivos ini se llamen igual que la aplicación que los usa, así pues, la clase TFicherosIni tiene una propiedad llamada NameExe la cual, al asignarse, automáticamente se asocia el fichero ini. No obstante, dado que no siempre es así, también dispone del método SetFileIni para especificar uno en concreto.

Aquí podéis descargaros la unit así como una demo de su uso.

Espero que os sea de utilidad.

Nos leemos

sep 292010
 

Buenas,

En muchas aplicaciones es habitual tener algún tipo de parámetro configurable que no puede estar almacenado en una base de datos o que simplemente el aplicativo no dispone de base de datos. Además, si no disponemos de un instalador que nos permita añadir información al registro de Windows, tenemos la opción de usar archivos de configuración o archivos ini.

Un archivo de configuración se divide en secciones, y estas secciones pueden tener una o más claves con sus respectivos valores. La estructura sería esta:

[sección 1]
clave11=valor11
clave12=valor12

[sección 2]
clave21=valor21

....

Como podemos ver, el nombre de las secciones va entre corchetes ([]) y los pares clave/valor van con un igual (=) y sin espacios de por medio.

Se pueden tener tantas secciones como se quiera/necesite, así como tantos pares de claves/valor por sección.

La gracia de este tipo archivos es que se puede acceder a una determinada clave de una sección cualquiera de forma muy sencilla y, Delphi nos proporciona una unidad (IniFiles) para la gestión de los mismos.

Veamos un ejemplo de cómo acceder:

var
  Ini: TIniFile;
begin
  Ini := TIniFile.Create('c:\MiFicheroIni.ini');
  try
    Edit1.Text := Ini.ReadString('Seccion 1', 'clave1', '');
  finally
    FreeAndNil(Ini);
  end;

El código es simple, declaramos y creamos un objeto de tipo TIniFile. Al constructor de la clase se le pasa como parámetro el fichero de configuración al que queremos acceder. Luego, para leer, usamos el método ReadString al que se le pasa cómo parámetro la sección, la clave y un valor por defecto por si no existe dicha sección/clave.

En el ejemplo hemos visto cómo leer un string, pero se puede leer o escribir diferentes tipos de datos.

Métodos de lectura

A nivel de lectura tenemos los siguientes métodos:

    function ReadString(const Section, Ident, Default: string): string;
    function ReadInteger(const Section, Ident: string; Default: Longint): Longint;
    function ReadBool(const Section, Ident: string; Default: Boolean): Boolean;
    function ReadBinaryStream(const Section, Name: string; Value: TStream): Integer;
    function ReadDate(const Section, Name: string; Default: TDateTime): TDateTime;
    function ReadDateTime(const Section, Name: string; Default: TDateTime): TDateTime;
    function ReadFloat(const Section, Name: string; Default: Double): Double;
    function ReadTime(const Section, Name: string; Default: TDateTime): TDateTime;
    procedure ReadSection(const Section: string; Strings: TStrings);
    procedure ReadSections(Strings: TStrings);
    procedure ReadSections(const Section: string; Strings: TStrings);
    procedure ReadSubSections(const Section: string; Strings: TStrings; Recurse: Boolean = False);
    procedure ReadSectionValues(const Section: string; Strings: TStrings);

Las funciones está claro lo que hacen, así que no las comentaré. Por lo que respecta a los procedimientos…

  • ReadSection: devuelve en el TStrings las claves de la sección pasada por parámetro.
  • ReadSections: devuelve en el TStrings las secciones que contiene el fichero de configuración.
  • ReadSectionValues: devuelve en el TStrings la combinación de todas las clave/valor de la sección especificada.

Métodos de escritura

A nivel de escritura de ficheros de configuración tenemos los siguientes métodos:

    procedure WriteString(const Section, Ident, Value: String);
    procedure WriteInteger(const Section, Ident: string; Value: Longint);
    procedure WriteBool(const Section, Ident: string; Value: Boolean);
    procedure WriteBinaryStream(const Section, Name: string; Value: TStream);
    procedure WriteDate(const Section, Name: string; Value: TDateTime);
    procedure WriteDateTime(const Section, Name: string; Value: TDateTime);
    procedure WriteFloat(const Section, Name: string; Value: Double);
    procedure WriteTime(const Section, Name: string; Value: TDateTime);

Como está bastante claro lo que hacen, no hace falta comentar nada.

Otros métodos de la clase

Además de los de lectura y escritura, tenemos otros métodos para interactuar con los ficheros de configuración y son:

    function SectionExists(const Section: string): Boolean;
    procedure EraseSection(const Section: string);
    procedure DeleteKey(const Section, Ident: String);
    procedure UpdateFile;
    function ValueExists(const Section, Ident: string): Boolean;

Veamos qué hacen:

  • EraseSection: borra la sección pasada por parámetro.
  • DeleteKey: borra la clave (Ident) de la sección especificada (Section).
  • UpdateFile: fuerza la escritura a disco del fichero de configuración.
  • ValueExists: indica si una determinada clave (Ident) existe en una determinada sección (Section).

Para terminar, recomendaros la lectura de esta entrada del blog.

Nos leemos

sep 102010
 

Buenas,

Para los que nos gusta más el formato de imagen PNG, con Delphi 2010 estamos de suerte dado que trae «de serie» una unit para manipular este tipo de imágenes, la pngimage (no se si también está en Delphi 2009, si alguien puede confirmármelo, le estaría agradecido). No pretendo explicar dicha unit, pero sí os recomiendo que le deis un vistazo para saber qué podemos hacer con una imagen en este formato.

Lo común en grandes aplicaciones (y no tan grandes) es que haya mucha imagen que se repita. Imaginemos una imagen para un botón de aceptar, ¿en cuantas pantallas tenemos ese botón? En muchas. ¿Y que pasa si en cada una de esas pantallas tenemos que poner la imagen? Por pasar no pasa nada, sencillamente que el exe va aumentando de tamaño «tontamente».

La solución está clara, usar un fichero de recursos que almacene, al menos, las imágenes repetitivas. Crear un fichero de recursos es sencillo, sólo tenemos que crear un archivo de texto con extensión rc y poner tabulado nombre de recursos, tipo de recursos y nombre de la imagen. Dado que no hay un tipo de recurso específico para las imágenes PNG, usaremos el RCDATA.

Un ejemplo de fichero podría ser esto:

RES_IMG_OK_16X     			RCDATA  "btn_16_ok.png"
RES_IMG_CANCEL_16X			RCDATA	"btn_16_cancel.png"
RES_IMG_MAIL_16X			RCDATA	"btn_16_email.png"
RES_IMG_SEARCH_16X			RCDATA	"btn_16_search.png"

Ahora sólo queda compilarlo para crear un archivo de recurso (.res) con el compilador de recursos brcc32.exe (suele estar en el path).

Para usar el nuevo fichero, en la unit que deseemos tenemos que añadir

{$R MisRecursos.res}

Donde MisRecursos.res es el nombre del fichero de recursos.

Ahora que ya tenemos el fichero de recursos creado, tan solo nos quedará añadir las imágenes a nuestros botones o TImages. Con el siguiente par de funciones cargaremos en la propiedad del componente que toque la imagen del fichero de recursos que deseemos

procedure LoadPNGFromResource(Picture: TPicture; const ResName: string); overload;
var
  Png: TPngImage;
begin
  Png := TPngImage.Create;
  try
    Png.LoadFromResourceName(HInstance, ResName);
    Picture.Assign(Png);
  finally
    if Assigned(Png) then  FreeAndNil(Png);
  end;
end;

procedure LoadPNGFromResource(Bitmap: TBitmap; const ResName: string); overload;
var
  Png: TPngImage;
begin
  Png := TPngImage.Create;
  try
    Png.LoadFromResourceName(HInstance, ResName);
    Bitmap.Assign(Png);
  finally
    if Assigned(Png) then  FreeAndNil(Png);
  end;
end;

Y un pequeño ejemplo de llamada

  LoadPNGFromResource(imgImage.Picture, 'RES_IMG_MAIL_16X');
  LoadPNGFromResource(bAccept.Glyph, 'RES_IMG_OK_16X');
  LoadPNGFromResource(bCancel.Glyph, 'RES_IMG_CANCEL_16X');

Para terminar, podemos poner esas dos funciones en una unit a parte y así, donde queramos cargar un PNG del fichero de recursos, sólo tendremos que usar dicha unidad.

Nos leemos

sep 092010
 

Buenas,

Los que no tengáis hijos, no sabéis la de papeles que hay que hacer para un recién nacido y el rato que te tiras en cada uno de estos sitios. Vamos, que pierdes un par de días sólo con el papeleo (por supuesto hablo de España).

Lo primero son los papeles de alta del hospital, tanto de la madre como del niño que, el día del alta, pues como que no tienes ganas de hacer nada porque estás cansado (la madre por lo que ha pasado y el padre por estar durmiendo en un sofá durante dos días) o sencillamente porque están las administraciones cerradas.

Al día siguiente, a primera hora si es que quieres aprovechar el día, es ir al Registro Civil para actualizar el Libro de Familia y dar de alta al niño como ciudadano de España 😀 Ahí, como tengas la suerte de depender de una ciudad medianamente grande, te tiras la mañana. En Tarragona, con un par de horas lo solucionamos.

Si nos centramos sólo en el papeleo obligatorio, se tiene que ir a la empresa a por unos papeles que hay que llevar a la Seguridad Social, tanto para la madre como para el padre. La madre para sus 122 días de baja y para el padre para sus 13 días de baja (los otros son según convenio – suelen ser 2 – ). En la Seguridad Social también puedes pasarte un buen rato.

Una vez llegado aquí, realmente sólo quedaría ir al CAP (Centro de Atención Primaria) a pedir un pediatra para el crío e ir al médico de la madre para pedir la baja por maternidad.

No obstante, y dado que el Gobierno da ayudas, ¡qué menos que ir a pedirlas! Así pues tendremos que ir a Hacienda para pedir la ayuda del Gobierno Central y a la Generalitat para la ayuda que da el Gobierno Autonómico.

Total, dos días perdidos haciendo papeles, parece una vergüenza viviendo en la era de la informática, pero es lo que hay (y vale la pena por el que ha nacido ;))

Nos leemos

sep 052010
 

Buenas,

Este mensaje es para dar la bienvenida a este mundo a Arnau que, con 3kg350gr, vino a este mundo ayer día 4 a las 0h40m.

Tu madre y yo ya te queremos un montón y, como a tu hermana, te daré el primer consejo, no te dediques a la informática jejeje

EDITO para poner las fotos prometidas 🙂

Nos leemos.

sep 022010
 

Buenas,

Vimos en un mensaje anterior cómo conseguir mostrar en una aplicación Delphi un mapa de Google Maps. Ahora toca «jugar» con este mapa y, lo primero que tenemos que saber hacer es cómo centrarlo en una posición determinada (la latitud y longitud que nosotros queramos).

Para ello tendremos que realizar algún cambio en el código base de la página web. Lo primero es hacer global la variable que controla el mapa «map«. Para ello bastará con declararla fuera de la función y, por supuesto, quitar el «var» de la línea de creación del objeto google.maps.Map. Así pues, nuestro JavaScript quedará de la siguiente manera:

  var map = null;

  function initialize() {
    var latlng = new google.maps.LatLng(-34.397, 150.644);
    var myOptions = {
      zoom: 8,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    map = new google.maps.Map(document.getElementById("map_canvas"),
        myOptions);
  }

Si nos damos una vuelta por la clase Map, veremos que tiene un método panTo definido de la siguiente forma

panTo(latLng:LatLng)

Este método recibe como parámetro un objeto de tipo LatLng que contendrá la longitud y latitud donde se centrará el mapa. Así pues, bastará con crear un objeto de éste tipo y hacer una llamada a este método para conseguir lo que buscamos.

Para construir el objeto de tipo LatLng y hacer la llamada al método Map.panTo, crearemos la siguiente función JavaScript en el código de la página:

  function CenterMap(Lat, Lng) {
   	map.panTo(new google.maps.LatLng(Lat, Lng));
  }

Ahora sólo quedará desde código Delphi hacer una llamada a esta función con los parámetros deseados. Para ello usaremos la interfaz IHTMLDocument2 la cual encapsula la información de la página web mostrada y la interfaz IHTMLWindow2 que contiene información sobre el objeto Windows (para más información consultar la ayuda de Delphi). La función de centrado del mapa podría ser algo así:

procedure TForm1.CentraMapa(const Long, Lat: string);
var
  Doc2: IHTMLDocument2;
  Win2: IHTMLWindow2;
  LatLng: String;
begin
  Doc2 := wbWeb.Document as IHTMLDocument2;
  Win2 := Doc2.parentWindow;

  LatLng := '"' + Lat + '", "' + Long + '"';

  Win2.execScript('CenterMap(' + LatLng + ')', 'JavaScript');
end;

Con ésto, sólo tenemos que ir llamando a nuestra función para ir moviéndonos por le mapa de Google a las coordenadas geográficas que queramos.

Puedes descargarte un ejemplo aquí (incluye ejecutable).

Continuará….

Nos leemos

sep 022010
 

Buenas,

Una vez visto cómo funcionan nuestras clases, ha llegado el momento de mostrar la información en un mapa.

Si le damos un vistazo al tutorial del API de JavaScript de Google Maps nos podemos dar cuenta que es muy sencillo el mostrar un mapa con los datos que queramos mostrar. Vasta con montar una pequeña página web, que ésta ejecute un script y mostrarla en un TWebBrowser.

La página web

Lo primero es ver y comprender qué hace esta sencilla página web que, incluso para gente que no está familiarizada con la programación web, llega a entenderlo fácilmente. Para todo el que quiera verla, les remito a dicho tutorial.

La parte qué realmente nos interesa es el JavaScript que hay. Lo primero es  incluir el API de Google mediante la línea

<script type="text/javascript"
   src="http://maps.google.com/maps/api/js?sensor=false"></script>

Recordad lo que explicamos en un artículo anterior sobre el parámetro sensor.

Y ahora se crea una función que será llamada en el evento OnLoad del tag body la cual realizará los siguientes pasos.

  • Crear un objeto para almacenar las coodenadas (latlng).
  • Crear un objeto de tipo map options que contenga todas las características del mapa
  • Crear un objeto «mapa» con los parámetros de dónde se mostrará y qué características tendrá

El resultado, éste:

<script type="text/javascript">
  function initialize() {
    var latlng = new google.maps.LatLng(-34.397, 150.644);
    var myOptions = {
      zoom: 8,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    var map = new google.maps.Map(document.getElementById("map_canvas"),
        myOptions);
  }

</script>

A nivel de características del mapa, decir que puede especificarse el zoom (zoom), la longitud y latitud en la que debe centrarse (center) y el tipo de mapa que se quiere visualizar (mapTypeId) que puede ser:

  • ROADMAP: mapa normal 2D
  • SATELLITE: muestra las fotografías de satélite
  • HYBRID: mixto entre ROADMAP y SATELLITE
  • TERRAIN: relieve físico del terreno

Esta es otra deferencia con la versión 2 del API. En la v2 existe una visión del mapa por defecto. En esta versión hay que especificarla. Si quieres saber más acerca de los tipos de mapa, te recomiendo una visita a Google Map Javascript API V3 – Map Types.

Ahora que se ha entendido lo que hace el código de la página, llega el momento de mostrarlo en nuestra aplicación Delphi. Lo primero que tendremos que decidir es dónde almacenaremos esta web, si en un archivo en disco y cargarlo en ejecución, si en un archivo de recursos o bien en una constante en el mismo código (o alguna otra manera que no se me ocurre ahora, jejeje). La del archivo físico en disco yo la descartaría de entrada por el simple motivo de que puede ser borrada por error, no copiada si se cambia de carpeta el contenido,….. Vamos, que es más fácil perder el archivo que otra cosa.

Y entre archivo de recursos o constante, pues ya depende de la manera en que nos guste programar. Si nos gusta un código más limpio, sin duda en un archivo de recursos. Pero si queremos poder ver en todo momento todo lo que afecta al programa y queremos tener un código algo más sencillo (no es que sea complicado usar un fichero de recursos), sin duda en una constante dentro del código. En este ejemplo, lo haremos mediante una constante (a la que llamaremos MAPA_CODE) en el código Delphi.

El objeto TWebBrowser

Para cargar nuestro mapa en el objeto TWebBroser dependerá de cómo lo hayamos guardado. Si lo hemos guardado como un archivo en disco, bastará con hacer una llamada al método Navigate pasándole como parámetro la ruta exacta de dónde se encuentra dicho fichero.

Y si lo que hemos hecho es almacenarlo en un fichero de recursos o en una constante, el proceder será algo diferente. La idea es conseguir tener un TStringStream que contenga el código fuente de la página web. Dado que nosotros almacenamos la página en una constante, bastará con crear una variable de éste tipo pasándole como parámetro la constante.

var
  StringStream: TStringStream;
begin
  StringStream := TStringStream.Create(MAPA_CODE);

Luego tenemos que asegurarnos que el objeto Document del TWebBrowser tiene «algo». Para ello basta con hacer una llamada a Navigate con la típica página de inicio.

    if not Assigned(wbWeb.Document) then wbWeb.Navigate('about:blank');

Ahora ya nos encontramos en la disposición de poder cargar nuestra página almacenada en el TStringStream. Para ello tenemos que coger la interfaz IPersistStreamInit del objeto Document, borrar el documento actual mediante una llamada al método InitNew del IPersistStreamInit, crear un objeto IStream con los datos del TStringStream y cargar este objeto IStream en el documento mediante el método Load del objeto IPersistStreamInit. Todo esto que parece una comida de olla, se resume en esta función:

procedure CargaWeb;
var
  StringStream: TStringStream;
  PersistStreamInit: IPersistStreamInit;
  StreamAdapter: IStream;
begin
  // creación del TStringStream con el string que contiene la página web
  StringStream := TStringStream.Create(MAPA_CODE);
  try
    // nos aseguramos de tener algún contenido en Document
    if not Assigned(wbWeb.Document) then wbWeb.Navigate('about:blank');

    // acceder a la interfaz IPersistStreamInit de Document
    if wbWeb.Document.QueryInterface(IPersistStreamInit, PersistStreamInit) = S_OK then
    begin
      // borrar datos actuales
      if PersistStreamInit.InitNew = S_OK then
      begin
        // creación y inicialización del IStream
        StreamAdapter:= TStreamAdapter.Create(StringStream);
        try
          Screen.Cursor := crHourGlass;
          // carga de la página web mediante IPersistStreamInit
          PersistStreamInit.Load(StreamAdapter);
        finally
          Screen.Cursor := crDefault;
        end;
      end;
    end;
  finally
    StringStream.Free;
  end;
end;

Si en el OnShow de un formulario hiciéramos una llamada a esta función, veríamos que en nuestro TWebBrowser se visualiza el mapa de Google en las coordenadas que trae por defecto (las que contiene nuestra constante).

Pero ésto no es lo que nosotros queremos. Lo que queremos es poder posicionar el mapa en un punto determinado y, ésto ya será parte del siguiente artículo.

Continuará….

Nos leemos