Seleccionar una carpeta

agosto 28, 2007 en Artículos, Entrada Diario, Velneo

En una de las entradas anteriores, justamente en los comentarios de “Primeros pasos tras el curso…”, AROJAS comentaba:

Buenos Días, dime tendrias alguna DLL hecha que sirva para llamar el cuadro de diálogo Buscar y Seleccionar Carpeta de Windows, como para que funcione en VELNEO, desde ya gracias

Veamos a ver que podemos hacer… ummmm :-)

En este caso concreto, no tiene mucho sentido desde la perspectiva de Delphi tener esta función dentro de una librería. Sin embargo, desde la perspectiva de Velneo la cosa cambia y posiblemente sí que nos interese incluirla, ya que al necesitar inicializar estructuras para invocar la función, puesto que recibe como parámetro un puntero a la estructura TBrowseInfo, no lo vamos a poder hacer desde Velneo. Solución: Abrimos nuestro editor de Delphi (otros compañeros sustituiran este entorno por otro cualquiera que nos permita generar la libreria) y no queda más que escribir unas lineas de código.

Vamos a necesitar la función del API de Windows, SHBrowseForFolder que se encuentra ya declarada en el modulo ShlObj.pas de Delphi, lo que nos ahorra tiempo y problemas. De no ser así, deberiamos declararla y traducir los tipos originales de C que necesita la función a los de nuestro entorno.

Básicamente, lo que hacemos es inicializar la estructura TBrowseInfo con los valores adecuados y una vez hecho esto, entregamos un puntero al registro como parámetro de entrada de la función ShBrowseForFolder. La respuesta de la función es similar a otras funciones de la Shell del mismo ámbito. Nos devuelve un puntero a una estructura (PItemIdList). Finalmente, liberamos mediante la interfaz IMalloc la asignación de memoria que hizo el sistema en su respuesta, según reza la documentación técnica.

Descargar el codigo fuente y el mapa para testear la función

Lo que ya dejo un poco a gusto de vosotros es modificar los parámetros de entrada de la función MostrarCuadroCarpetas para que muestre determinadas opciones, o por el contrario no las muestre. Podriamos querer que se visualizase un botón para permitirnos crear una nueva carpeta, mostrar solo los directorios del sistema, mostrar solo los equipos o solo las impresoras. Existen bastantes posibilidades y parece razonable que no nos sean necesarias siempre por lo que he preferido no complicar innecesariamente este pequeño ejemplo. En todo caso, una gran parte de ellas van a depender del valor que tome el campo ulFlags y convendría conocer los distintos valores de las constantes que pueden ser usadas.


BIF_RETURNONLYFSDIRS = $0001;
BIF_DONTGOBELOWDOMAIN = $0002;
BIF_STATUSTEXT = $0004;
BIF_RETURNFSANCESTORS = $0008;
BIF_EDITBOX = $0010;
BIF_VALIDATE = $0020;
BIF_NEWDIALOGSTYLE = $0040;
BIF_USENEWUI = BIF_NEWDIALOGSTYLE or BIF_EDITBOX;
BIF_BROWSEINCLUDEURLS = $0080;
BIF_UAHINT = $100;
BIF_NONEWFOLDERBUTTON = $200;
BIF_NOTRANSLATETARGETS = $400;
BIF_BROWSEFORCOMPUTER = $1000;
BIF_BROWSEFORPRINTER = $2000;
BIF_BROWSEINCLUDEFILES = $4000;
BIF_SHAREABLE = $8000;

Si quisieramos por ejemplo que además de las carpetas nos mostrara los archivos para poder seleccionar cualquiera de ellos, bastaría hacer la asignación:
ulFlags:= BIF_STATUSTEXT or BIF_BROWSEINCLUDEFILES;
Así de fácil. ;-)

function MostrarCuadroCarpetas(ACaption: PChar; ACallBack: Boolean): PChar; stdcall;
var
    fres: Array[0..MAX_PATH] of Char;
    fName: Array[0..MAX_PATH] of Char;
    IDList: PItemIDList;
    BrowseInfo: TBrowseInfo;
    Malloc: IMalloc;
begin
    //rellenamos la estructura con los datos deseados
    with BrowseInfo do begin
        hwndOwner:= 0; //en nuestro caso no existe handle y
         //se mostrara de forma no modal
         pidlRoot:= nil; //no existe origen predefinido
         pszDisplayName:= fName; //Nombre de la carpeta
         lpszTitle:= ACaption; //Titulo de la ventana
         //se podria parametrizar desde Velneo para mostrar opciones distintas
         //en este caso solo hemos seleccionado la posibilidad de mostrar el
         //texto a traves del mensaje en la función callback
         {
         Ver la unidad ShlObj para obtener una lista de todas las constantes
             disponibles.
         }

        ulFlags:= BIF_STATUSTEXT;
         //si no queremos ejecutar el callback para modificar la ventana de exploracion
         if ACallBack then
             lpfn:= @BrowseCallBackProc
         else lpfn:= nil;
         lParam:= 0;
    end;
     //Invocamos la funcion del API SHBrowseForFolder pasando como parámetro la
     //estructura que hemos inicializado
     IDList:= SHBrowseForFolder(BrowseInfo);
     //Obtenemos la ruta
     ShGetPathFromIDList(IDList, @fres);
     //segun la documentación de windows sería necesario liberar la memoria
     //que windows reserva para la estructura IDList. Nosotros seguimos la
     //mecanica aconsejada y hacemos uso de la interfaz IMalloc para ello.
     //El metodo Free libera la memoria asignada
     //Previamente comprobamos que podemos obtener un handle a la interfaz
     //para hacer uso de las funciones.

     if Succeeded(SHGetMalloc(Malloc)) then begin
         Malloc.Free(IDList);
     end;
     Result:= fres;
end;

{$R *.res}

exports
     BrowseCallBackProc,
     MostrarCuadroCarpetas;

begin
end
.

Solidaridad con las víctimas del terremoto en Perú.

agosto 24, 2007 en Entrada Diario

¡Solidaridad!
¡Qué hermosa palabra…!
La pena es que muchas veces nos quedamos en eso: tan solo en palabras.

Ya todos conoceis los efectos devastadores que ha tenido el terremoto de Perú y como, como casi siempre ocurre en estas catastrofes, se ceba sobre las capas de población más pobres y humildes.

Teneis una buena oportunidad de ser solidarios y hacer que vuestro gesto valga mucho mas que cien mil palabras. No hace falta decir mas. :-)

Hay varias entidades bancarias que han abierto cuentas en España para colaborar económicamente:

http://www.ayudaenaccion.org/index.asp?MP=&MS=&TC=I&IDC=2&noticia=857

http://www.elfarodecartagena.com/noticia.asp?ref=28571

En otros paises no se… E igualmente puede que existan otros modos de colaboración, enviando alimentos, ropa, etc…

Ahora teneis la oportunidad de actuar y poner vuestro granito de arena en que este mundo sea un poco más hermoso…

Gracias.

Algunos tutoriales y cursos para el verano…

agosto 12, 2007 en Delphi, Entrada Diario

Digo yo una cosa… :-)
¡Qué yo esté de vacaciones no significa que el resto del mundo lo esté! ;-)

Así que mientras tomaba el sol en la playa y veía volar las gaviotas revoloteando sobre la orilla, pensaba para mi: -Que no se me olvide al llegar a casa, añadir algunos enlaces a tutoriales y cursos en la proxima entrada.

Esto lo comento para la gente que se inicia en Delphi, que a fin de cuentas son los destinatarios de todas estas reflexiones. Recuerdo que yo tambien buscaba cualquier tutorial o curso, y el verano era una buena epoca porque normalmente se tiene mas tiempo libre para leer, incluso aunque uno esté trabajando. Asi que he recopilado algunos cursos y tutoriales y los he ordenado alfabeticamente.

El mas antiguo de todos, si no me equivoco es el de David Martinez (1998). Yo guardo el enlace con mucho cariño porque fue uno de los primeros tutoriales que leí de Delphi en una epoca en la que no existía demasiada documentación en castellano.

No he incluido los enlaces a boletines y revistas, como los de Síntesis (Grupo Albor), el de Jose Luis Freire en el Rinconcito, o el de Latium Software, porque no tienen la estructura de un tutorial o un curso (o un libro en el caso de I.Marteens), pero en los enlaces es facil encontrar los distintos numeros o ediciones. Buscadlos en la barra lateral derecha.
Si alguien recuerda o conoce alguno mas os invito a que los añadais en los comentarios a esta entrada.

Ariel Bustamante
http://www.q3.nu/trucomania/ftesp.html

David Martínez
http://www.hackerdude.com/courses/delphi/indice.html

Francisco Charte Ojeda
http://www.fcharte.com/Default.asp?articulos

Ian Marteens
http://www.marteens.com/caraoculta.htm

Jose Luis Freire
http://www.rinconcitodelphi.com/tutorial/tutorial.htm

Luis Roche
www.publispain.com/supertutoriales/programacion/delphi/cursos/4/ccind.htm

Marco Cantú
http://www.marcocantu.com/epascal/Spanish/default.htm

Seguro que todos estos enlaces huelen un poco a mar…

La fotografías no tienen demasiada calidad. Están hechas desde el móvil mientras paseaba por la orilla. Eran las últimas horas de la tarde y el sol se estaba escondiendo. ¡Me encanta el mar!

;-)




PD: No se puede hacer siempre las cosas a gusto de todo el mundo. A muchos les puede molestar que se incluyan cosas personales, como puedan ser estas mismas fotos. Yo no dejo de ver el blog como algo muy personal e intransferible…
:-)

La Granja

agosto 2, 2007 en Artículos, Delphi

Anoche, era una de esas noches en las que uno acaba por hacer de todo…, quiero decir de todo menos lo que se tiene que hacer… El calor de este final de Julio y la humedad, me invitaron finalmente a salirme a la terraza, junto a los geranios y la enredadera, buscando un poco de aire fresco, y como habían pocas o ninguna ganas de trabajar, me puse a ojear los enlaces que figuran en el panel situado a la derecha del blog, cayendo por casualidad en la web de Neftalí.

Pasé un buen rato recorriendo las secciones pero sin ningun destino concreto hasta que mi vista se detuvo, por unos segundos, en uno de los articulos de Neftalí (German Estevez), que hablaba sobre la ejecución de metodos a través del nombre del mismo. Es un artículo que tiene algo de tiempo, pero independientemente de estó, como en otros muchos articulos, pueden desprenderse algunas reflexiones sobre puntos básicos, que con toda seguridad resultan curiosos e interesantes, no solo para la persona que se inicia, ya que son comunes a todas las versiones de Delphi. Al menos a mi, siempre me fascinó el halo de misterio que rodea al polimorfismo, a lo que pueda oler a RTTI que siempre acaban siendo punteros y direcciones de memoria misteriosas… :-)

Lo siguiente que hice fue abrir el entorno de Delphi. Efectivamente no tenía ganas de trabajar. :-)
¿Quien puede tener ganas de trabajar en una noche así?

¿Qué pensaba…?

Os confieso que estaba dandole vueltas a la cabeza sobre cómo podría sacar alguna utilidad de lo leido en el artículo, si es que realmente me valía para algo. De hecho, en el framework que estamos viendo en el Boletin, nos acercamos a conceptos similares cuando registramos una clase, y era el nombre de la clase, el que nos valia por ejemplo para gestionar su creación a traves de una metaclase especializada para tal fin.

O bien era una de esas cosas que se leen y que formalmente no sabes sacarle la utilidad.

Así que empecé a contar una historia con el único fin de pensar unos minutos sobre todo esto:…. “Erase una vez una granja

TGranja = class(TComponent)
end;

y un montón de animales que iban a vivir en ella. (Dibujemos los animales).

TAnimal = class(TComponent)
end;

Mi hijo me miraba como diciendo: ¡este cada vez está peor!

Ya estamos acabando…: Nos faltan las especies concretas.

Podemos tener un caballo, una vaca, quizas varios cerdos y cientos de gallinas. La propuesta es que no dejen de ser animales… :-)

TVaca = class(TAnimal)
end;

Tras un buen rato tecleando acabé completando mi idea de Granja y de Animal, representados en las dos clases correspondientes. La clase TGranja basicamente se encargaba de gestionar la entrada de animales a la misma, manteniendo internamente una lista de las distintas especies y de cada uno de los ejemplares. Además, disponía como cualquier casa o empresa de un presupuesto, que mermaría en cada compra de animales asi como cada vez que los alimentaban. Sin dinero no se pueden comprar animales, ¿no?.

Veamos como ha quedado…

TGranja = class
private
    FTipoDeAnimales: TList;
    FAnimales: TList;
protected
    function RegisterClass(AAnimal: TClassAnimal): Integer; virtual;
    procedure UnRegisterClass(AAnimal: TClassAnimal); virtual;
public
    constructor Create; override;
    destructor Destroy; override;
    function ComprarAnimal(AAnimal: TAnimal): Integer;
    procedure AlimentarTodos;
    procedure Alimentar(AAnimal: TAnimal);
    procedure IncrementarEurosAlPresupuesto(FEuros: Double);
    procedure DisminuirEurosAlPresupuesto(FEuros: Double);
    property Name: String read FName write SetName;
    property Presupuesto: Double read FPresupuesto;
    property AnimalTipoCount: Integer read GetAnimalTipoCount;
    property AnimalTipo[Index: Integer]: TClassAnimal read GetAnimalTipo;
    property AnimalCount: Integer read GetAnimalCount;
    property Animal[Index: Integer]: TAnimal read GetAnimal;
end;

La clase TAnimal, declaraba algunas propiedades y métodos comunes a los descendientes. Os dejo ver la especificación y seguimos reflexionando:

TAnimal = class
private
    FNombre: String;
    FCoste: Double;
    FSexo: TSexo;
    FPVP: Double;
    FEspecie: String;
    procedure SetNombre(const Value: String);
    procedure SetCoste(const Value: Double);
    procedure SetSexo(const Value: TSexo);
    procedure SetPVP(const Value: Double);
    procedure SetEspecie(const Value: String);
protected
    FGranja: TGranja;
    property Coste: Double read FCoste write SetCoste;
    procedure ExecuteMethod(AAnimal: TAnimal; const AMetodoName: String); virtual;
public
    procedure Execute(const AMetodoName: String); virtual;
    procedure Caracterizar; virtual; abstract;
    property ConsumoPorComida: Double read FConsumoPorComida write SetConsumoPorComida;
    procedure Comer; virtual; abstract;
    property Nombre: String read FNombre write SetNombre;
    property Especie: String read FEspecie write SetEspecie;
    property Sexo: TSexo read FSexo write SetSexo;
    property PVP: Double read FPVP write SetPVP;
end;

Respecto a los descendientes de la clase TAnimal, para las pruebas que quise compartir con vosotros, me bastaban cuatro métodos rápidos que al menos le permitan al animal emitir un sonido virtual y poco mas (comer también es saludable ;-) ).

Veamos por ejemplo como queda el interfaz y la implementacion de la clase TCaballo:

unit uCaballos;

interface

uses uGranja;

type
TCaballo = class(TAnimal)
public
    procedure Execute(const AMetodoName: String); override;
    procedure Caracterizar; override;
    procedure Comer; override;
    procedure Relinchar;
published
    procedure Trotar; //< - Interesante (ver comentarios)
end;

implementation

uses Dialogs;

{ TCaballo }

procedure TCaballo.Execute(const AMetodoName: String);
begin
    ExecuteMethod(Self, AMetodoName);
end;

procedure TCaballo.Caracterizar;
begin
    Relinchar;
end;

procedure TCaballo.Comer;
begin
    FGranja.DisminuirEurosAlPresupuesto(ConsumoporComida);
    Coste:= Coste + ConsumoporComida;
end;

procedure TCaballo.Relinchar;
begin
    ShowMessage(‘IIIIAAAAAAAAAAA…’);
end;

procedure TCaballo.Trotar;
begin
    ShowMessage(‘Caballo trotando…’);
end;

end.

* Desgargar código fuente del ejemplo

Para situarnos en condicion de reflexionar, preparé rápidamente un pequeño ejemplo lo mas sencillo posible, bastante tonto todo hay que decirlo, con la idea de que se vieran explicitamente los comentarios. Si ya descargasteis el zip con las unidades, en el interior existe un exe para que se pueda ejecutar sin tener que compilar los módulos (el fichero comprimido ha sido revisado con ZoneAlarm Antivirus antes de ser subido al servidor pero no obstante siempre debeis tomar precauciones y revisar cualquier descarga). El codigo se ha compilado con Delphi 2007.

Ahora vienen los comentarios y con vuestro permiso, vamos a seguir ya en tiempo presente. Lo más fácil es que contrasteis lo que se comenta, con el codigo fuente, para así evitar tener que incluirlo tamben en la entrada.

¿Que representa TGranja…?

La clase TGranja es similar a otras clases que nos planteamos en el Framework pero un poco mas sencilla. La idea que perseguimos es crear una clase especializada en cualquier descenciente de una clase base cualquiera, en este caso concreto TAnimal, que es una abstracción que aglutina a todos los animales que podemos encontrar dentro del dominio de TGranja. Así pues, TGranja conoce cuantos animales contiene la factoria o cuantas especies distintas existen. Asimismo, tiene metodos que permiten representar un acto común a todos ellos como lo puede ser el de alimentarse. Hasta aquí todo perfecto, ¿no?

Veamosssss… :-)

Hay un problema y es el mismo que liga al interfaz principal del Framework con los Módulos de la gestión. Para ser funcional el interfaz, necesitamos desligarlo de los módulos, puesto que si este los “conoce” se ligará a los mismos en un vinculo de dependencia. Lo ideal es que la ventana principal no conozca la clase final a la que accede el usuario. Un detalle como éste, suele identificar la rigidez de algunos desarrollos y el grado de rigidez es directamente proporcional a las relaciones de dependencia entre los modulos.

En el caso que nos ocupa, el de la granja, a nosotros no nos interesa que la misma conozca el animal que acaba de acoger. Esperamos que sea el propio animal el que nos lo diga, y para eso existe o nos valemos del polimorfismo, esa relación misteriosa que nos permite invocar un método en la clase TAnimal (el ascendiente) y que por arte de birli-birloque, mágicamente, responda en el animal correcto.

Ejecutad el ejemplo que se incluye y nos situamos inicialmente en el paso 1. Esta es la figura del mismo:

Paso 1

Podemos añadir varios animales a la granja. Basta seleccionar el animal, marcar la cantidad (por defecto 1) y pulsar sobre el boton cuyo caption es “Vender a la granja”. Repetimos esta accion para que existan al menos un par de animales y podemos pasar al paso numero 2.

Paso 2

En esta ventana se aprecian dos botones en la parte inferior con el rotulo de Alimentar, bien a un animal o bien a todos. Es el ejemplo mas sencillo que comentabamos. En la implemenación de ambos, existen un metodo comun a todos los animales, independientemente de que tipo o especie sean. Comer en la clase TAnimal es una abstracción de una acción que se concreta de forma distinta en cada especie. Asi pues, al ejecutar el método Alimentar de la clase TGranja, la invocación sobre la clase TAnimal, se responde con la respuesta del individuo especifico, que recibe el método gracias a las directivas virtual/abstract en la clase base y override en la clase descendiente. Gracias a las directivas, el metodo que por defecto es Estático y cuyo tipo es declarado en la compilación se convierte en dinamico y la implementación que se invoca responde al tipo real del objeto, en tiempo de ejecución.

También en el paso dos nos encontramos un pequeño problema, que puede ser resuelto facilmente . Queremos que nuestros animales emitan un pequeño sonido virtual, representado simplemente por un mensaje de texto mediante la ShowMessage(). Sin embargo, cada método tiene una firma distinta. El caballo relincha, la vaca muge, el cerdo gruñe… y yo suspiro por coger pronto las vacaciones… :-)

Así que lo resolvemos interponiendo un método generico que los aglutine. Yo lo he llamado Caracterizar y permite que cada animal concrete su “caracterización”. Al pulsar el animal, representado en un boton del contenedor responderá a nuestra pulsación emitiendo el sonido.

Y nos queda el paso 3 y que responde al codigo fuente que leimos de Neftalí.

Paso 3

¿Como puedo ejecutar un metodo que no existe en todos los animales? Si os fijais en la clase TCaballo, hemos declarado en la interfaz el método Trotar que es característico de esta especie y que no existe en el resto de animales de la granja.

En los dos casos anteriores, hemos sabido encontrar en la clase base un método comun a todos. ¿Que hacemos ahora…?

La mayoria de los programadores que conozco pienso que responderían a esta pregunta echando manos de los interfaces. Una interfaz es un contrato entre la clase y una especificación. El contrato obliga a que la clase implemente una respuesta a los requerimientos que se le van a demandar. Si ligamos la clase TCaballo a la interfaz ITrotar las instacias de la clase responderan correctamente a los métodos que pueda proponer la interfaz. Y tendremos en nuestras manos, las herramientas adecuadas para poder interrogar a la clase y preguntarle si implementa dicha interfaz. El uso de interfaces puede ayudarnos cuando las clases no tienen un ascendente comun.

Podemos dejar el tema de las interfaces para otra entrada del blog, ya que requiere algunos detalles adicionales que se escapan del proposito de esta entrada.

Pero sí podemos valernos del código de Germán Estevez, que se puede ver implementado en la ventana del Paso 3. Si tenemos seleccionado el ejemplar Caballo en el contenedor de animales, y ejecutamos el método Trotar la instancia de TCaballo responderá correctamente a la demanda del método. Si por el contrario, seleccionamos otro animal, la aplicación nos mostrará un aviso de error, comunicandonos que el actual animal no puede ejecutar el procedimiento. Seguimos manteniendo la premisa original que es intentar desligar a la clase especializada (TGranja), de conocer la clase del modulo que maneja (p.e. TCaballo). Inevitablemente, sí tiene que conocer el nombre del método.

Y ya para finalizar un pequeño comentario a esta última posibilidad: un detalle a tener en cuenta es que se condiciona la ejecución, a que el método sea declarado como publicado (published), para que pueda hacer uso de la RTTI o informacion de tipos en tiempo de ejecución. De no haberlo hecho así, no hubieramos podido encontrar la dirección del método Trotar para poder invocarlo.

Por hoy creo que nada mas. Mi deseo de que seais muy felices.