Continuamos con el tema de las 2 últimas entradas. Visto el esqueleto del proyecto y probado que funciona (la carga, descarga y registro de la DLL) ahora nos queda generar una previsualización funcional. Hasta ahora hemos visto que podemos mostrar un formulario «vacío», ahora nos toca programar realmente la carga del fichero (según su tipo).
Os adjunto los links de todas las entradas de esta serie, que iré completando a medida que avance:
Con la estructura que tenemos del proyecto anterior, lo único que tenemos que hacer ahora es programar la carga y visualización de la información del fichero en el formato que queramos ofrecerla al usuario.
Para este primer ejemplo vamos a empezar con algo sencillo.
Realizaremos una previsualización de ficheros de log (*.LOG) que vamos a tratar como ficheros de texto plano.
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,…
Continuando el tema de «Crear una Previsualización de ficheros en Windows», vamos a seguir completando esta serie, haciendo referencia al tema del registro que nos quedó pendiente en la anterior entrada.
Os adjunto los links de todas las entradas de esta serie, que iré completando a medida que avance:
Hablo de «registros» en el título de esta entrada, porque vamos a abordar por un lado, los cambios en el «registro de Windows» que debemos realizar para que nuestra DLL quede «inscrita» en el sistema (con esto le decimos al sistema, que cuando quiera visualizar un determinado fichero de extensión AAA, debe cargar nuestra DLL para mostrar ese contenido). Y en segundo lugar, veremos cómo registrar nuestra DLL en Windows a través de regsrv32.
.
CLAVES DE REGISTRO
Cuando registramos nuestra DLL (regsvr32) necesitamos modificar algunos valores en el registro de Windows para indicar al sistema que tiene una DLL disponible para utilizar cuando se le solicite una vista previa de un archivo con extensión AAA.
Para ello tenemos disponible esta documentación que explica los diferentes cambios a crear/borrar cuando realizamos el register/unregister.
Lo primero que debemos tener en cuenta, es que si nuestro controlador de Vista previa (de 32 bits) se está registrando en un sistema de 64 bits, tal y como se describe aquí, debe usar un IdApp diferente;
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,…
Hace poco publiqué una primera entrada introductoria sobre FMX Linux. En ella explicaba los primeros pasos para su instalación y configuración. En mi caso lo hice utilizando un sistema Ubuntu 18.04 desktop.
Para las pruebas estoy utilizando una máquina virtual preconfigurada sobre VirtualBox. Es una buena solución, además de práctica y rápida de comenzar con un sistema ya configurado y listo para funcionar. De esta forma me puedo centrar en lo que realmente importa para estas entradas.
Para esto he utilizado la web descargarmaquinasvirtuales.com que permite descargar (como su nombre indica) máquinas de diferentes sistemas ya configuradas (tanto para VirtualBox como para VMWare), de forma que podemos tenerlas en marcha y funcionando en unos minutos.
Una vez instalado y funcionando, la idea es ir ejecutando diferentes proyectos realizados en Delphi para ver cómo se desempeñan en Linux. Son proyectos que desde que apareció Firemonkey he ido desarrollando, algunos para entradas del blog, otros de ejemplo y otros como parte de aplicaciones más grandes, un poco de todo.
La primera prueba ya la comenté en la entrada anterior, y se trata del programa “Hola Mundo!”. En este caso funciona sin problemas (como era de esperar). También comenté que con este programa realicé un Debug paso a paso básico.
Forms y animaciones
La siguiente prueba, también bastante sencilla ha sido probar con varios formularios. Abrir, cerrar y liberarlos sin problemas. Además en el primero de ellos he añadido al color de fondo una animación.
Podéis ver que el color de fondo utiliza un gradiente y una animación en loop.
El botón de cerrar también tiene un efecto de blur aplicado.
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,…
Ya he hablado anteriormente de varios sistemas y paquetes de componentes para internacionalizar aplicaciones en el blog. Por ejemplo, aquí y aquí. A raiz de un comentario de otro usuario, esta vez, por necesidad en este proyecto (IBCC) he decidido probar los componentes DKLang.
A primera vista me han sorprendido, pues cuenta con versión para XE5 incluyendo aplicaciones móviles (Android/iPhone) así como para VCL y FMX. Más adelante hablaré de la versión para XE5 (versión 5), aunque en esta entrada y para este proyecto he usado la versión 3, que es la que soporta Delphi 6.
Para la compilación del componente, al menos en esta versión (Delphi 6), hacen falta los componentes TNT para unicode. Gratuítos y con código fuente que podéis bajar desde esta misma web o desde internet sin problemas.
La compilación e instalación de los packages se hace sin problemas. Basta con añadir un par de directorios al path para que encuentre los ficheros necesarios. Una vez instalado el package de diseño, ya me aparece el componente DKLanguageController en la pestaña de System.
He echado en falta algún manual sencillo para poder seguir a la hora de empezar con un proyecto nuevo. En la web y en la ayuda no hay nada que de una guía rápida de funcionamiento. Aun así, no lleva más de 10 minutos ver cómo se maneja el componente.
Todos los ejemplos que se incluyen están realizados utilizando los componentes TNT, aunque es fácil sustituirlos en el DFM por componentes estándar y el funcionamiento es correcto.
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,…
Otra de las nuevas funcionalidades que trae la versión de XE2 es el soporte para ficheros ZIP. No es que sea especialmente transcendental, pero en mi caso la he utilizado bastante en mis desarrollos y hasta ahora lo había tenido que hacer mediante componentes externos (he probado varios paquetes).
Para ello se ha añadido a la unit System.ZIP, todo lo necesario para el trabajo con este tipo de ficheros.
En sistema de trabajo es bastante sencillo. Para trabajar con un fichero de este tipo tenemos que crear un objeto de la clase TZIPFile y abrirlo (utilizando la función Open). Tenemos diferentes modos de apertura dependiendo de las operaciones a realizar con el fichero (read,write o readwrite).
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,…
Otra de las cosas de las que ya he hablado y que llaman la atención en esta nueva versión de Delphi, son los estilos. Es una primera implementación y es mejorable en muchos aspectos, pero es una paso más y un avance más para poder personalizar nuestras aplicaciones.
Hay que decir que el funcionamiento general es muy sencillo.
Estaría bien, por ejemplo, mejorar el generador de estilos para que se pudieran importar estilos desde otros archivos similares. En mi caso el generador se ha mostrado “poco estable” cuando he intentado hacer algo más complejo (cambio de imágenes) que intentar modificar los colores del estilo cargado.
Por internet podéis descargar varios videos sobre el uso del «VCL Style Designer», que así es como se llama la herramienta para crear y modificar los estilos. Aunque es bastante sencilla e intuitiva. Una vez cargado el estilo, podemos modificar los diferentes elementos. Se puede exportar la imagen del estilo completo, modificarla desde un editor externo y luego volverla a cargar, para poder así asignar elementos de esa nueva imagen a los diferentes componentes. Se pueden cargar varias imágenes y combinar elementos de varias de ellas; Pero como he dicho en estas operaciones se ha mostrado «inestable»
Los componentes que derivan de los estándar cogen de forma correcta los estilos. Lo que no he podido probar (y no parece que exista ahora solución -digo esto sin habérmelo mirado mucho-) es qué posibilidades hay para componentes «externos» que no deriven de uno estándar.
Una creado o modificado un estilo podemos cambiar los campos que identifican al creador, desde la misma herramienta de creación (autor, versión, página web y correo electrónico). Mientras que la previsualización desde el «Style Designer» funciona bastante bien (salvo cuando hay algun error que no muestra nada -tampoco el error-), desde el IDE de Delphi la previsualización es defectuosa (podrían haber utilizado el mismo método que desde el Designer) o «floja» por decirlo así.
En resumen, aunque el sistema tiene algunos detalles todavía faltos de pulir, en general me parece un acierto el poder dotar de esta posibilidad a las aplicaciones que se realicen. El sistema es muy sencillo de utilizar y aunque tiene algunas limitaciones, funciona bastante bien.
Actualmente al cargar instalar Delphi XE2 contamos contamos con 5 estilos que podemos utilizar.
Desde la web de Rodrigo podeís descargar algunos que él ya ha creado y es de esperar que los miembros de la comunidad vayan «colgando» los suyos propios a medida que los vayan creando (para eso supongo que se ha distribuido). Desde ahí mismo podéis consultar la explicación detallada de cómo crear nuevos estilos a partir de los existentes.
El código para cargar el estilo es bastante simple a partir de su nombre:
Var
Estilo:TStyleManager;
i:integer;begin
Estilo := TStyleManager.Create;try
i :=Random(6);
Estilo.SetStyle(lEstilos[i]);finally
Estilo.Free;end;</p>
Var
Estilo:TStyleManager;
i:integer;
begin
Estilo := TStyleManager.Create;
try
i := Random(6);
Estilo.SetStyle(lEstilos[i]);
finally
Estilo.Free;
end;</p>
En mi caso cargo los estilos de forma aleatoria a partir de un array donde los tengo definidos.
Hasta aquí esta primera visión. En general me ha parecido positivo. Algunos aspectos por pulir, pero creo que es un detalle más, que hace subir puntos a esta versión.
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 ya tengo Delphi XE2 en marcha. Que ya lo tengo instalado ¡Vamos!
No se a vosotros, pero a mi me pasa (y no sólo con esta versión) que una vez que la tengo instalada, lo primero que se me ocurre es probarla. Probar las nuevas características y ver qué tal funcionan.
¡¡Eso es lo que hace todo el mundo!! Diréis.
Bueno, sí y no. Me refiero a «probarlas en frío». Sin saber nada más. Sin leer nada más.
Se trata de abrir un proyecto en blanco y empezar a probar cosas. Cosas totalmente nuevas. Cosas que no existían hasta ahora. Cosas de las que he oído hablar, pero no he leído nada.
Eso me da una idea de cómo son de fáciles a la hora de asimilar. Difíciles, complicadas, rebuscadas,… Busco documentación y veo cómo se llega a ella, si la hay, cómo es de clara,…
Todo el mundo habla estos días de FireMonkey. ;-D ¡¡Pues allá que voy yo!!
He ido al menú de fichero y he creado una nueva aplicación de este tipo (Fire Monkey HD Application).
A priori todo parece normal; el formulario se ve algo diferente, pero todo lo demás tiene «la misma pinta».
Pongo algunos componentes, unos botones, un checkbox, una statusBar y una imagen. Mi primera sorpresa viene cuando voy a buscar la propiedad Caption del botón y no está. 8-|
¡No puede ser! Vuelvo a buscar y efectívamente no está.
En su lugar veo la propiedad Text. Pruebo y efectívamente pertenece al caption del TButton.
No parece que tenga mayor importancia…
Pongo un checkbox y al ir a buscar la propiedad Checked… ¿??¿?¿ ¡¡No está!! 8-||
Vuelvo a revisar las propiedades y… encuentro una propiedad IsChecked que pertenece a lo que normalmente se hace con la propiedad Checked.
Dejando de lado esto (cuestión de la adaptación de los nuevos componentes adquiridos por Embarcadero-me imagino-) el resto de cosas que he probado son bastante intuitivas y algunas bastante impresionantes. Aplicar un efecto (para lo que antes necesitábamos bastante código o componentes especializados) o realizar una rotación de una imagen es algo «trivial» (por su sencillez).
La velocidad es muy buena y la primera impresión muy positiva.
Os dejo este primer ejemplo; Bastante «tonto» por su funcionalidad, pero también por la complejidad que me ha conllevado (bastante poca).
Aquí los fuentes y el ejecutable, aunque sinceramente no creo que tengan mucho interés, desde en punto de vista de la codificación.
NOTA: Sigo buscando cómo asignarle un texto a la StatusBar que he puesto en el formulario… ;-D
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,…
Desde hace un tiempo estoy trabajando con ficheros de rutas o «Tracks» que provienen de dispositivos GPS (Global Positioning System). Un track o una ruta, no es más que una sucesión de coordenadas de Latitud y Longitud. Adicionalmente esos puntos pueden contener más información como Elevación, Fecha y hora,…
Existen infinidad de formatos para almacenar «Tracks» muchos de ellos provenientes de marcas concretas de receptores GPS (Garmin, DeLorme, Holux, Magellan, Tomtom…) y otros provenientes de programas de software conocidos como OziExplorer, CompeGPS o los relacionados con Google Maps.
Además existe también un formato genérico y bastante aceptado como estandard, que almacena puntos con estructura XML. Es el formato GPX (GPS eXchange Format).
La idea de esta entrada es generar código para trabajar desde delphi con un track en formato genérico GPX. Poder abrirlo y cargar los valores sin problemas y posteriormente exportarlo a KML. Aunque una vez recuperados los datos desde Delphi, la exportación a otros formatos será muy similar.
Leer un archivo GPX
Lo primero que vamos a hacer para poder leer un archivo de tipo GPX es generar una pieza intermedia que nos permita fácilmente trasladar los datos desde un XML (con una estructura determinada) a un Dataset (en nuestro caso un TClientDataset).
NOTA: Para utilizar como ejempos, en la web podéis encontrar infinidad de archivos de este tipo; Yo para las pruebas utilizaré un par de archivos de rutas distintos que podeís descargar aquí.
Para ello utilizaremos la herramienta XML Mapper de Delphi, accesible desde el menú de Tools (o también desde el Menu de programa, en el mismo grupo donde está instalado Delphi).
PASO1: Abrir fichero GPX; Aunque la extensión no es XML, sí lo es la estructura del fichero, así que la herramienta reconocerá los campos y en la parte izquierda del programa podremos ver la estructura del fichero con los diferentes campos disponibles. En este caso vemos que el fichero contiene una serie de campos correspondientes al track, como creator, versión,… y luego un «Nested Dataset»; Un Dataset «anidado», que contiene todos los puntos del track con los campos Lat (latitud), Lon (longitud), Ele (altura o elevación) y time (fecha/hora).
PASO2: Generar un Datapacket; Una vez que la estructura se ha leído correctamente podemos generar lo que XML Mapper llama un DataPacket; Que viene a ser algo así como los datos que tenemos en el XML, pero en formato de Dataset y además nos permitirá guardar el archivo de transformación (que es realmente lo importante).
Este archivo de transformación nos permitirá posteriormente leer cualquier fichero XML con la misma estructura que este y realizar la traducción a «formato dataset» de forma automática.
Para ello Desde el menú contextual seleccionamos todos los campos (Select All) y en el menú de Create escogemos Datapacket from XML (Ctrl+D). Con ello habremos generado nuestro fichero de transformación y el Dataset final.
En la parte derecha de la pantalla podemos ver la misma estructura que teníamos en el XML pero en «formato datapacket». Podemos comprobar cómo se ha realizado la transformación y que los datos se leen correctamente utilizando un botón que hay en la parte inferior de la pantalla llamado «Create and Test Transformation«.
Esto nos abre un Grid y nos muestra los datos provenientes del Dataset que se ha generado utilizando el fichero de transformación. Esto es lo que posteriormente nosotros queremos repetir en nuestro programa. Además como la estructura XML posee un «DataSet anidado», si pulsamos doble click sobre el campo trkpt, se abre un segundo Dataset que contiene todos los registros de los puntos que hay en la ruta.
En este ejemplo hemos realizado una traducción directa, pero el programa (antes de generar la transformación) nos permite modificar los campos que queremos «mapear» entre el origen (el XML) y el destino (Datapacket/Dataset), de forma que podemos aplicar determinados cambios, variaciones o filtros a los datos de entrada.
PASO3: Guardar el fichero de transformación; Una vez realizado esto sólo nos queda (lo más importante) guardar el fichero de transformación, que nos asegura que a partir de este momento podremos repetir esta misma transformación (con cualquier otro fichero de entrada que sea del mismo formato que este). Desde el Menú de File seleccionamos Save Transformation. Asignaremos anuestro fichero el nombre de GPX_To_DP.xtr (GPX to DataPacket).
Utilizar un fichero de transformación
Una vez que hemos generado nuestro fichero de tranformación GPX_To_DP.xtr, vamos a ver cómo nos puede ser útil en un programa delphi para tratar ficheros de rutas guardaos en formato GPX. Para ello utilizaremos 2 TClientDataset, no para la ruta y otro para los puntos de la ruta (a modo de Master-Detail).
Creamos un un nuevo proyecto y un formulario con los siguientes componentes:
Un DBGrid, un TDatasource y un TClientDataset para los puntos
Un DBGrid, un TDatasource, un TClientDataset y un TXMLTransformProvider para los datos del track.
Cada uno de los DBGrid va ligado al TDataset y cada uno de estos al TClientDataset.
En el XMLTransformProvider deberemos colorcar en la propiedad TransformRead, el fichero de transformación que hemos generador mediante la herramienta XML Mapper.
Como hemos visto, el campo trkpt del TClientDataset asociado al track es un campo de tipo TDataset (en concreto un TDatasetField). Para mostrarlo utilizaremos el segundo TClientDataset que hemos colocado en el formulario (cdsPoints).
En la propiedad DatasetField del TClientDataset cdsPoints asignamos el campos del primer Dataset (en este caso trkpt). Con esto visualizaremos en el segundo DBGrid los puntos que contiene el track.
Una vez hecho esto, bastará con las siguentes líneas de código para conseguir leer el contenido de cualquier fichero con formato GPX. Únicamente hay que asignar el fichero de transformación y el fichero fuente XML.
var
b:Boolean;
path:string;begin// Abrir el diálogo
b := OpenDialog1.Execute;if(b)thenbegin// Asignar fichero de transformación
path :=ExtractFilePath(Application.ExeName)+'GPX_To_DP.xtr';
XMLTransformProvider.TransformRead.TransformationFile:= path;// Fichero XML (GPX) de ruta
XMLTransformProvider.XMLDataFile:= OpenDialog1.FileName;// Abrir el Dataset
cdsTrack.Open;end;end;
var
b:Boolean;
path:string;
begin
// Abrir el diálogo
b := OpenDialog1.Execute;
if (b) then begin
// Asignar fichero de transformación
path := ExtractFilePath(Application.ExeName) + 'GPX_To_DP.xtr';
XMLTransformProvider.TransformRead.TransformationFile := path;
// Fichero XML (GPX) de ruta
XMLTransformProvider.XMLDataFile := OpenDialog1.FileName;
// Abrir el Dataset
cdsTrack.Open;
end;
end;
El resultado son los 2 DBGrid con los datos del Track:
Podéis descargar los ficheros de ejemplo de las rutas y el código fuente y binarios en los siguientes enlaces:
Una vez que tenemos los datos en formato accesible (fácil) para nosotros, lo siguiente que vamos a proponer es exportarlos a un formato KML (Google Maps) y visualizar la ruta/track sobre un Mapa de Google Maps.
Algo como lo que se ve a continuación, aunque eso lo dejo para la próxima entrada:
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,…
Hace tiempo me topé con un tema similar a este, aunque con un enfoque diferente; En aquel caso se trataba de que el usuario pudiera crear su propio menú personalizado dentro de una aplicación. Es decir, que además de las opciones propias de la aplicación, el usuario pudiera configurarse un menú con las opciones que más deseara utilizar o tener más «a mano». En este caso, y a partir de este hilo en los foros del Clubdelphi, se ha planteado la posibilidad de que un usuario pueda crearse sus propios «accesos directos» a opciones del menú.
La solución en aquel momento pasó por «volcar» el contenido del menú a otro componente (en ese caso un TreeView, de forma similar a cómo se ve en este ejemplo) y desde ese, generar la estructura del nuevo punto de menú arrastrando elementos.
Para el problema de generar accesos directos, se me antoja que se pueda usar un sistema similar.
(1) «Volcar» el contenido del menú hasta otro componente que nos permita trabajar con los elementos del menú (ya que ni el menú ni los ítems poseen opciones para arrastrar -Drag & Drop-). Este esta caso vamos a utilizar un componente (TListBox) donde almacenaremos los elementos y los apuntadores a los ítems del menú (propiedad Objects).
procedure TFormMain.Button1Click(Sender:TObject);var
i:integer;
str:string;// Recursiva para obtener los subItemsprocedure GetItems(mi:TMenuItem);var
i:Integer;beginfor i :=0to(mi.Count-1)dobegin
Str := mi.Items[i].Caption;
ListBox1.Items.AddObject(Str, mi.Items[i]);// SubItems de este
GetItems(mi.Items[i]);end;end;begin// Recorerr menu principalfor i :=0to(MainMenu1.Items.Count-1)dobegin
Str := MainMenu1.Items[i].Caption;
ListBox1.Items.AddObject(Str, MainMenu1.Items[i]);// SubItems de este
GetItems(MainMenu1.Items[i]);end;end;
procedure TFormMain.Button1Click(Sender: TObject);
var
i:integer;
str:string;
// Recursiva para obtener los subItems
procedure GetItems(mi:TMenuItem);
var
i:Integer;
begin
for i := 0 to (mi.Count - 1) do begin
Str := mi.Items[i].Caption;
ListBox1.Items.AddObject(Str, mi.Items[i]);
// SubItems de este
GetItems(mi.Items[i]);
end;
end;
begin
// Recorerr menu principal
for i := 0 to (MainMenu1.Items.Count - 1) do begin
Str := MainMenu1.Items[i].Caption;
ListBox1.Items.AddObject(Str, MainMenu1.Items[i]);
// SubItems de este
GetItems(MainMenu1.Items[i]);
end;
end;
Con este código poblamos el ListBox con los Caption(Text) de los elementos del menú, y lo que es más importante, los apuntadores a cada elementos que se guardar al utilizar AddObject.
(2) ¿Cómo crear un acceso directo que permita ejecutar una opción de menú? Para ello podemos utilizar un TImage que sobre el cual programaremos el evento OnDblClick/OnClick.
Crear el componente es sencillo, y se puede ver código de ejemplo de cómo hacerlo en estas entradas:
La idea es que cada «acceso directo» posea un apuntador al elemento de menú correspondiente para poder ejecutar el código programado en el OnClick o en la TAction asociada a ese elemento del menú. Lo lógico sería utilizar una propiedad del propio componente (Data, Object,….) que nos permitiera enlazar directamente. No es el caso del TImage, así que en el ejemplo utilizaremos el propio ListBox como el «contenedor» de los apuntadores (como una lista intermedia), aunque como he dicho, la solución ideal, sería que cada «acceso directo» tuviera un puntero «directo» al TMenItem asociado.
El código paras crear el componente y gestionar esa asociación podría ser similar a este:
procedure TFormMain.Button2Click(Sender:TObject);var
img:TImage;
mi:TMenuItem;beginIf ListBox1.ItemIndex=-1thenbegin
MessageDlg('Selecciona un elemento de la llista', mtWarning,[mbOK],0);
Exit;end;// Item del menu
mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]);// Tiene asignado el OnClick?ifAssigned(mi.OnClick)thenbegin// Nadaendelsebegin// Tiene asignada la action?ifAssigned(mi.Action)thenbegin//signado OnExecuteifAssigned(mi.Action.OnExecute)thenbegin// Nadaendelsebegin
MessageDlg('Ese elemento no tiene nada que hacer asignado',
mtWarning,[mbOK],0);
Exit;end;endelsebegin
MessageDlg('Ese elemento no tiene nada que hacer asignado',
mtWarning,[mbOK],0);
Exit;end;end;
Randomize;// Elemento seleccionado
img := TImage.Create(nil);
img.Parent:= Panel1;
img.Height:=32;
img.Width:=32;
img.Left:=Random(panel1.Width- img.Width);
img.Top:=Random(panel1.Height- img.Height);
img.Stretch:=True;
img.Transparent:=True;// El TAG es la posicion en la lista
img.Tag:= ListBox1.ItemIndex;// Item del menu
mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]);// Asignar la imagen
ImageList1.GetBitmap(mi.ImageIndex, img.Picture.Bitmap);// Asignar el evento
img.OnClick:= MyImgClick;end;
procedure TFormMain.Button2Click(Sender: TObject);
var
img:TImage;
mi:TMenuItem;
begin
If ListBox1.ItemIndex = -1 then begin
MessageDlg('Selecciona un elemento de la llista', mtWarning, [mbOK], 0);
Exit;
end;
// Item del menu
mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]);
// Tiene asignado el OnClick?
if Assigned(mi.OnClick) then begin
// Nada
end
else begin
// Tiene asignada la action?
if Assigned(mi.Action) then begin
//signado OnExecute
if Assigned(mi.Action.OnExecute) then begin
// Nada
end
else begin
MessageDlg('Ese elemento no tiene nada que hacer asignado',
mtWarning, [mbOK], 0);
Exit;
end;
end
else begin
MessageDlg('Ese elemento no tiene nada que hacer asignado',
mtWarning, [mbOK], 0);
Exit;
end;
end;
Randomize;
// Elemento seleccionado
img := TImage.Create(nil);
img.Parent := Panel1;
img.Height := 32;
img.Width := 32;
img.Left := Random(panel1.Width - img.Width);
img.Top := Random(panel1.Height - img.Height);
img.Stretch := True;
img.Transparent := True;
// El TAG es la posicion en la lista
img.Tag := ListBox1.ItemIndex;
// Item del menu
mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]);
// Asignar la imagen
ImageList1.GetBitmap(mi.ImageIndex, img.Picture.Bitmap);
// Asignar el evento
img.OnClick := MyImgClick;
end;
Primero se realizan unas comprobaciones para detectar si posee alguna acción asignada (sea OnClick o TAction) y posteriormente se crea el TImage, se configura y se asigna como TAG el ItemIndex del ListBox (que es este caso estamos utilizando como estructura intermedia para guardar el apuntador al TMenuItem).
Finalmente sólo quedar crear el procedimiento MyImgClick, que ejecutará el código asignado al elemento del menú cuando se presione sobre la imagen asociada. Se incluyen comprobaciones similares a las anteriores, por si el elemento no tiene nada asignado y se tiene en cuenta también que haya código en el OnClick del TMenuItem o exista una TAction asociada.
var
i:integer;
str:string;
mi:TMenuItem;begin// Test del senderifnot(sender is TImage)thenbegin
Exit;endelsebegin
i := TImage(Sender).Tag;
Str := ListBox1.Items[i];end;// Acceder a la opción de menú
mi := TMenuItem(ListBox1.Items.Objects[i]);// Asignado código?ifAssigned(mi.OnClick)thenbegin
mi.OnClick(nil);
Exit;endelsebegin// Tiene asignada la action?ifAssigned(mi.Action)thenbegin// Asignado OnExecuteifAssigned(mi.Action.OnExecute)thenbegin
mi.Action.OnExecute(nil);
Exit;end;endend;
MessageDlg('No hay nada asignado a esa opción...', mtInformation,[mbOK],0);end;
var
i:integer;
str:string;
mi:TMenuItem;
begin
// Test del sender
if not (sender is TImage) then begin
Exit;
end
else begin
i := TImage(Sender).Tag;
Str := ListBox1.Items[i];
end;
// Acceder a la opción de menú
mi := TMenuItem(ListBox1.Items.Objects[i]);
// Asignado código?
if Assigned(mi.OnClick) then begin
mi.OnClick(nil);
Exit;
end
else begin
// Tiene asignada la action?
if Assigned(mi.Action) then begin
// Asignado OnExecute
if Assigned(mi.Action.OnExecute) then begin
mi.Action.OnExecute(nil);
Exit;
end;
end
end;
MessageDlg('No hay nada asignado a esa opción...', mtInformation, [mbOK], 0);
end;
Se puede mejorar y «refinar» bastante más, pero creo que la idea queda clara. A partir de aquí cada uno que «añada» lo que quiera. Cualquier sugerencia será bien recibida.
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,…
He probado algunos sistema de traducción para programas Delphi, entre ellos he de destacar los componentes de TsiLang Component Suite , que me parecen muy buenos y completos.
Estos días necesitaba un paquete gratuíto y revisando páginas y foros he llegado a GNU Gettext for Delphi and C++ Builder. Me parece un paquete muy sencillo de utilizar y por lo que he probado hasta ahora, cómodo y eficaz.
En esta entrada voy a explicar desde cero los pasos que he seguido para conseguir una aplicación multiidioma.
Una vez descargado el ficchero e instalado tendremos lo necesario para generar nuestros ficheros de traducción e integrarlos en el programa.
NOTA: Hay una versión especial compatible con Delphi 2009 que podemos descargar desde aquí, si estamos utilizando esta versión de Delphi..
Si se ha instalado correctamente, nos pedirá reiniciar el sistema.
Una vez instalado, si desde el explorador de Windows pulsamos Click derecho con el ratón sobre una carpeta, nos debería aparecer una nueva opción «Extract Translations to Template«.
(2) Poedit for Windows
Necesitaremos una herramienta para poder traducir (ya seamos nosotros o una tercera persona) los ficheros de traducción a los diferentes idiomas. Esto está pensado para que esa posible tercera persona (que puede no tener Dephi instalado) pueda traducir un fichero sólo con esta herramienta (y el fichero .po como veremos después).
Una vez realizados estos dos pasos previos, crearemos un pequeño proyecto de ejemplo donde realizar las pruebas.
Para ello, desde Delphi comenzamos una nueva aplicación y en el formulario generaremos algo similar a esto:
TRADUCCIÓN
(a) Preparar el proyecto.
Una vez tenemos nuestra aplicación funcionando debemos preparnos para comentar lo necesario para traducción.
(1) Lo primero es añadir la unit gnugettext.pas a nuestro proyecto. Las units que usesn traducciones deberán tenerla en el uses.
NOTA: Si habéis realizado la instalación estandard debería estar en: «c:\Archivos de programa\dxgettext\gnugettext.pas»
(2) Añadir al OnCreate de nuestros formularios el siguiente código:
// Traducir el formulario
TranslateComponent(Self);
// Traducir el formulario
TranslateComponent(Self);
(3) Preparar la estructura de directorios para organizar las traducciones. Dentro del directorio de la aplicación crearemos una carpeta llamada locale y dentro de ella un árbol de directorios similar al que se ve en la imagen.
En este ejemplos, yo voy a utilizar 4 idiomas (es, en, fr, ca); Contando que el original va a ser el Español(es) debemos crear la estructura para los otros 3 (inglés, francés y catalán). Nos quedará un arbol de directorios como este:
(b) Generar fichero de traducciones (.po)
(1) Para generar el fichero de traducciones del proyecto se debe extraer todas las cadena «traducibles» y almacenarlas en un fichero; Para ello basta con que, desde el explorador de Windows, pulsemos Click derecho sobre la carpeta del proyecto y seleccionemos la opción «Extract Translations to template».
Esto generará un fichero default.po en el directorio de la aplicación.
(2) Copiaremos este fichero dentro de los directorios LC_MESSAGES; Tantas copias como directorios tengamos. Después de eso tendremos varios ficheros .po; Uno en el directorio de la aplicación y uno dentro de cada carpeta de traducción:
appdir/appTest.exe
appdir/default.po
appdir/locale/en/LC_MESSAGES/default.po (Traducciones al inglés)
appdir/locale/fr/LC_MESSAGES/default.po (Traducciones al francés)
appdir/locale/ca/LC_MESSAGES/default.po (Traducciones al catalán)
…
(c) Traducir los ficheros.
En este punto ya se pueden abrir cada uno de los ficheros .po y traducirlo utilizando poedit. Una vez abierto el fichero con poedit, veremos una pantalla como esta:
Una vez finalizada la traducción de palabras y cerrado el programa veremos que en cada directorio aparecen 2 ficheros; El default.po (original) y el de traducción llamado default.mo.
Repetimos esto con todos los ficheros de traducción.
(d) Cómo utilizar las traducciones en nuestro programa
Una vez los ficheros de traducción estén generados, para utilizarlos desde la aplicación añadirenmos el siguiente código a los botones de activación de los idiomas.
// para el catalán
UseLanguage('ca');
RetranslateComponent(Self);// para el inglés
UseLanguage('en');
RetranslateComponent(Self);...
// para el catalán
UseLanguage('ca');
RetranslateComponent(Self);
// para el inglés
UseLanguage('en');
RetranslateComponent(Self);
...
Si ejecutamos la aplicación veremos que al pulsar los botones cambia es aspecto del formulario.
Para finalizar, aun nos queda un detalle, que es, que la cadena que aparece al pulsar el botón «Hola Mundo!» no aparece traducida.
Para ello utilizaremos la función de traducción _().
La llamada que actualmente está así:
MessageDlg('Hola', mtInformation,[mbOK],0);
MessageDlg('Hola', mtInformation, [mbOK], 0);
Pasará a colocarse así:
MessageDlg(_('Hola'), mtInformation,[mbOK],0);
MessageDlg(_('Hola'), mtInformation, [mbOK], 0);
PASOS FINALES
Con eso nuestro programa debería quedar traducido completamente. Si movemos el EXE y la carpeta locale a cualquier otro sitio veremos que el programa funciona correctamente. Si sólo movemos el EXE, el efecto es que no aparace nada traducido.
Queda un último detalle, que es integrar las traducciones en el EXE, para que todo quede en el ejecutable y no debamos preocuparnos del directorio locale.
Basta con pulsar click derecho sobre el EXE de nuestra aplicación desde el explorador de Windows, Seleccionar la opción «Embeb Translations» y seleccionar los ficheros de traducción que queremos integrar.
Con esto debería ser todo.
UPDATE: (20/04/2011)
Pues haciendo pruebas he llegado a obtener el mismo proble que comenta Sil en sus comentarios (en mi caso utilizando Delphi XE). Se trata de que al intentar añadir las traducciones al exe se obtiene un error con la referencia: «6637DB2E-62E1-4A60-AC19-C23867046A89».
Revisando por Internet, parece que se solventa sustituyendo unas constantes que hay en gnugettext.pas.
(1) Buscar esta sección en la unit gnugettext.pas:
// DetectionSignature: used solely to detect gnugettext usage by assemble
DetectionSignature:array[0..35]ofAnsiChar='2E23E563-31FA-4C24-B7B3-90BE720C6B1A';// Embedded Header Begin Signature (without dynamic prefix written by assemble)
BeginHeaderSignature:array[0..35]ofAnsiChar='BD7F1BE4-9FCF-4E3A-ABA7-3443D11AB362';// Embedded Header End Signature (without dynamic prefix written by assemble)
EndHeaderSignature:array[0..35]ofAnsiChar='1C58841C-D8A0-4457-BF54-D8315D4CF49D';// Assemble Prefix (do not put before the Header Signatures!)
SignaturePrefix:array[0..2]ofAnsiChar='DXG';// written from assemble
// DetectionSignature: used solely to detect gnugettext usage by assemble
DetectionSignature: array[0..35] of AnsiChar='2E23E563-31FA-4C24-B7B3-90BE720C6B1A';
// Embedded Header Begin Signature (without dynamic prefix written by assemble)
BeginHeaderSignature: array[0..35] of AnsiChar='BD7F1BE4-9FCF-4E3A-ABA7-3443D11AB362';
// Embedded Header End Signature (without dynamic prefix written by assemble)
EndHeaderSignature: array[0..35] of AnsiChar='1C58841C-D8A0-4457-BF54-D8315D4CF49D';
// Assemble Prefix (do not put before the Header Signatures!)
SignaturePrefix: array[0..2] of AnsiChar='DXG'; // written from assemble
(2) Y sustituirlas por estas:
// DetectionSignature: used solely to detect gnugettext usage by assemble
DetectionSignature:array[0..35]ofAnsiChar='6637DB2E-62E1-4A60-AC19-C23867046A89';// Embedded Header Begin Signature (without dynamic prefix written by assemble)
BeginHeaderSignature:array[0..35]ofAnsiChar='';// Embedded Header End Signature (without dynamic prefix written by assemble)
EndHeaderSignature:array[0..35]ofAnsiChar='';// Assemble Prefix (do not put before the Header Signatures!)
SignaturePrefix:array[0..16]ofAnsiChar='#0#0#0#0#0#0#0#0';//'DXG'; // written from assemble
// DetectionSignature: used solely to detect gnugettext usage by assemble
DetectionSignature: array[0..35] of AnsiChar='6637DB2E-62E1-4A60-AC19-C23867046A89';
// Embedded Header Begin Signature (without dynamic prefix written by assemble)
BeginHeaderSignature: array[0..35] of AnsiChar='';
// Embedded Header End Signature (without dynamic prefix written by assemble)
EndHeaderSignature: array[0..35] of AnsiChar='';
// Assemble Prefix (do not put before the Header Signatures!)
SignaturePrefix: array[0..16] of AnsiChar='#0#0#0#0#0#0#0#0';//'DXG'; // written from assemble
Con este ambio ha desaparecido el error y se integran perfectamente las traducciones.
Espero que haya sido útil.
Espero comentarios, sugerencias, errores,… y demás.
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,…
Usamos cookies para asegurar que te damos la mejor experiencia en nuestra web. Si continúas usando este sitio, asumiremos que estás de acuerdo con ello.Aceptar