Recuperando el tiempo que he estado de vacaciones, escribo esta entrada. Son enlaces y referencias que tengo “apiladas” y que os las presento sin más espera.
Ya hace unos días que se pueden descargar las Trial de las diferentes versiones y paquetes de la última versión lanzada desde Embarcadero; Desde ahí mismo se pueden consultar las características de las diferentes versiones, FAQ’s, datos relacionados,…
Puede ser que te guste más o menos (como las lentejas o la coliflor), que te enganche más o menos, pero como mínimo hay que probarlo. ;-)
Ya hace unos días también que está en marcha el Grupo Delphi Solidario en Facebook; Una iniciativa de Salvador Jover (inquieto, como siempre). Si no te has apuntado, pásate por allí y hazlo. Punto de encuentro de desarrolladores y personas relacionadas con Delphi; Enlaces, documentación, White papers y en general todo tipo de información interesante.
Muchas de las presentaciones del RAD Studio XE2 Word Tour (la nueva versión RAD Studio) las está realizando Pawel Glowacky; En su blog, además de hacer un recorrido por las diferentes presentaciones que va haciendo, podéis encontrar esta lista de recursos sobre Delphi XE2 y Rad Studio.
Hay muchas características en la nueva versión de Delphi. Entre ellas, una de las más “visuales” son los estilos. Si quieres empezar con ellos y saber cómo funcionan un buen punto de partida puede ser esta entrada del Blog de Rodrigo Ruz:
He leído también que ya se puede descargar la versión “no oficial” de las RxLib con soporte para XE2. La podéis encontrar en Torry y la añadiré a las diferentes versiones que ya hay disponibles en mi web en el apartado:
También se puede encontrar ya, la release de las JEDI con soporte para XE2 (torry) o en la página de SourceForge.
Para los nostálgicos y los que nos interesan las curiosidades, Zarko Gajic ha hecho una pequeña recopilación de las características de las diferentes versiones de Delphi, desde sus inicios (página 1 y página 2).
Algo de historia nunca va mal… ;-D
Se dice que esta versión de Delphi (y de RAD Studio) es una de las que más cambios trae, de las que más novedades aporta. A tenor de esta lista no parece que sea una exageració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,…
He actualizado la librería de componentes Glib a la versión 1.3
Se han corregido algunos pequeños errores y se han implementado algunas sugerencias de usuarios que los están utilizando. En el fichero leeme.txt que se adjunta con el paquete hay descripción detallada de los cambios, así como en el código de los componentes, donde están comentados igualmente los que afecta a cada uno.
He compilado el paquete en Delphi XE, sin mayores problemas (como era de esperar ;-D ) y he dejado el fichero del package, junto con el resto.
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,…
IBPIndexCalc es una aplicación que permite obtener multitud de datos (en general «el grado de dificultad») de un Track/Ruta realizado en bicicleta (sea de carretera o de montaña). Aunque los datos podrían ser aplicados a cualquier track realizado en otra actividad.
Para ello el programa utiliza la web de IBPIndex (http://www.IBPIndex.com); Imprescindible para todos aquellos que salís en bici y utilizáis/generáis track con GPS.
Este índicxe IBP (Interactive Bicycling Parameters) se obtiene analizando los datos de latitud, longitud y altura en multitud de puntos del recorrido. A partir de estos puntos se calculan las distancias recorridas en los diferentes tramos de subidas y bajadas (1%, 5%, 10% etc..) se computan los % que representan sobre el total, los metros totales ascendidos, descendidos, los ratios medios de subida y de bajada, los Km. totales, y la distribución de los tramos de subida. Si queréis saber más y ampliar información sobre el tema visitad la web de IBPIndex (proceso de cálculo, corrección de errores, formato de puntuación,…).
IBPIndexCalc utiliza los componentes Indy para el acceso a la Web. Además implementa una clase derivada de TThread que permite que el pograma principal no se bloquee mientras espera la respuesta desde la web. Estructura de la Clase TWebThread:
{ : Clase para realizar consultas utilizando thread a la Web. }
TWebThread =class(TThread)private
IdHTTP: TidHTTP;
Stream: TIdMultipartFormDataStream;
FTrackFileName:string;
FwebBrowser: TWebBrowser;
FResponse:string;private
FURLNavigate:string;
FURLPDF:string;function _RepairLinks(StrPage:string;var URLpage:string;var UrlPDF:string):string;protectedpublic// ejecutar el procedimniento.procedure Execute;override;// componente de navegación.property webBrowser: TWebBrowser read FwebBrowser write FwebBrowser;// Fichero de Track/ruta.property TrackFileName:stringread FTrackFileName write FTrackFileName;// : Propiedad para recoger la respuesta desde la Web.property Response:stringread FResponse write FResponse;// URL de navegación.property URLNavigate:stringread FURLNavigate write FURLNavigate;// URL del PDF de respuesta.property UrlPDF:stringread FURLPDF;// : Constructor redefinido.constructor Create(AFileName:string; wb: TWebBrowser);end;
{ : Clase para realizar consultas utilizando thread a la Web. }
TWebThread = class(TThread)
private
IdHTTP: TidHTTP;
Stream: TIdMultipartFormDataStream;
FTrackFileName: string;
FwebBrowser: TWebBrowser;
FResponse: string;
private
FURLNavigate: string;
FURLPDF: string;
function _RepairLinks(StrPage: string; var URLpage: string;
var UrlPDF: string): string;
protected
public
// ejecutar el procedimniento.
procedure Execute; override;
// componente de navegación.
property webBrowser: TWebBrowser read FwebBrowser write FwebBrowser;
// Fichero de Track/ruta.
property TrackFileName: string read FTrackFileName write FTrackFileName;
// : Propiedad para recoger la respuesta desde la Web.
property Response: string read FResponse write FResponse;
// URL de navegación.
property URLNavigate: string read FURLNavigate write FURLNavigate;
// URL del PDF de respuesta.
property UrlPDF: string read FURLPDF;
// : Constructor redefinido.
constructor Create(AFileName: string; wb: TWebBrowser);
end;
Una vez que el programa obtiene respuesta de la Web, la analiza para detectar posibles errores y para extraer los links útiles al usuario. En este caso el PDF generado y la página de resultados.
La aplicación y la clase UTWebThread puede se útil, modificando determinados, para realizar un trabajo similar en otras páginas webs.
Es totalmente gratuíta y el código fuente está disponible.
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,…
TrackInfo es una aplicación que permite obtener información y visualizar sobre un mapa, rutas en formato GPX (GPS eXchange Format). Está pensado (o enfocado) a la carga de archivos que provienen desde un dispositivo GPS. Permite la conversión de rutas entre diferentes formatos.
NEW:
Actualizados los formatos de conversión; KML (Google Earth), TCX (Garmin training center), GTM (GPS TrackMaker), PLT (Ozi explorer), GDB (Mapsource), TRK (CompeGPS), XOL (Swiss Map), …
Animación de recorrido de la ruta
Gráficos de Alturas y velocidad sincronizados con la animación.
Información extendida para los puntos que forma la ruta (Velocidad puntual, distancia acumulada, ascenso acunulado, descenso acumulado,…)
Corrección de errores y bugs respecto a la versión anterior.
Mayor rapidez en la carga de rutas y en la conversió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,…
Esta semana se ha publicado un nueva versión «no-oficial» de las RxLib que soporta Delphi XE; En mi caso hace unos días había hablado sobre una versión «portada» (con pequeños cambios) para que funcionara en la última versión de Delphi. Esta incluye nuevas funciones y nuevos componentes (17 según leo). Podéis ver la los cambios principales introducidos en esta versión revisando esta página. Desde ahí mismo o desde esta página de recopilación de versiones podéis descargar esta versión.
Por cierto, el link de descarga aparece en la página de recopilación o podéis user el que aparece debajo de estas líneas:
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,…
Esta entrada se podría considerar como una «ampliación» de la última publicada relacionada con los ficheros KML, Generar ficheros KML de rutas; Tracks en Google Maps. A partir de un tema surgido en los foros del Clubdelphi de cómo visualizar estos ficheros directamente sobre Google Maps dentro de una aplicación Delphi, he modificado el programa anterior (que incluía la conversión desde GPX) para poder visualizarlos directamente.
En un ejemplo más simplificado, y enfocado a los ficheros KML. Como ya dije anteriormente, este tipo de ficheros pueden almacenar otros tipos de información y en este ejemplo se leen los datos directamente desde este tipo de ficheros (en este caso sólo la ruta).
Además el mapa está optimizado para visualizarse a tamaño completo, y no según el tamaño de la ventana como estaba anteriormente (esto implica la optimización de que no se recarga la página al cambiar el tamaño).
También se ha modificado el recurso de la página Web, para que automáticamente se calcule en «centrado» y el «zoom» del mapa a partir del Track que se va a visualizar. Para ello se utiliza el siguiente código:
var polylineBounds = polyline.getBounds();
var ZoomLevel = map.getBoundsZoomLevel(polylineBounds) - 1;
map.setCenter(polylineBounds.getCenter(), ZoomLevel);
var polylineBounds = polyline.getBounds();
var ZoomLevel = map.getBoundsZoomLevel(polylineBounds) - 1;
map.setCenter(polylineBounds.getCenter(), ZoomLevel);
Se puede descargar el ejemplo desde aquí (incluye los fuentes).
Debido a un cambio en la definición de la función EnumResourceModules, que pasa de usar LongInt a NativeInt, hay que realizar un cambio en el procedimiento que se le pasa como parámetro, modificando este dato.
La definición del procedimiento, ahora queda así:
function _MyEnumResourceModules (
Instance:{$IFDEF VER230}NativeInt{$ELSE}Pointer{$ENDIF};
Data:Pointer):Boolean;
function _MyEnumResourceModules (
Instance:{$IFDEF VER230}NativeInt{$ELSE}Pointer{$ENDIF};
Data:Pointer):Boolean;
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,…
En esta entrada mostraré el código nesario para generar ficheros KML (Keyhole Markup Language) a partir de datos que provienen de un DataSet o de un fichero GPX (GPX eXchage Format).
Los ficheros KML se utilizan para representar datos geográficos en 3 dimensiones (además de la longitud y la Latitud incluyen también la altura) y fue desarrollado para ser utilizado por un programa que a posteriori se convertiría en Google Earth.
En una entrada anterior ya vimos como importar un fichero GPX, con posiciones geográficas a un Dataset para posteriormente poder tratar y manejar estas posiciones (Cargar fichero GPX (XML) y acceder a los datos). Partiendo de lo visto en esa entrada como base, debe ser sencillo generar un fichero KML con los puntos que forma la ruta.
Hay que decir que los ficheros KML permiten muchas más cosas de las vistas en esta entrada, y para ver todas las posibilidades podemos revisar los ejemplos que se muestran aquí (Tutorial KML en Google); En este entrada me voy a centrar en la creación de rutas (lista de puntos) aunque las posibilidades van mucho más allá.
La estructura de nuestro fichero se compone por una cabecera, donde se almacenan datos genéricos de la ruta, y el bloque central donde añadimos los diferentes puntos geográficos que la componen.
Un ejemplo sencillo de fichero KML podría ser este:
Como vemos en la parte superior añadimos datos de la ruta y algunas opciones de visualización. En este caso un nombre y una descripción y porteriormente se definen opciones de estilo para el track (LineStyle); Definimos que usaremos una línea para representar esos puntos con color Rojo y un a anchura de 3.
Este archivo podemos abrirlo directamente con Google Earth o importarlo a «Mis Mapas» desde Google Maps y la visualización ser algo así;
En Google Maps al importar la ruta tendríamos esto:
Y al Abrir el fichero con Google Earth obtendríamos esto:
Podemos ver que como la ruta está definida como <LineString>, esta se pinta como una línea siguiendo los parámetros definidos en el fichero (color rojo y ancho 3).
Para generar el fichero con una ruta, partiremos de que los datos han sido imprtados a un Dataset y que ya disponemos de ellos en ese formato.
Primero generaremos la cabecera del fichero con los datos genéricos de la ruta; Nombre, Descripción, y las características de estilo para la línea:
const
CAB_XML ='<!--?xml version="1.0" encoding="UTF-8"?-->';
CAB_KLM ='';
CAB_PLACEMARK ='';
CAB_NAME ='%s';
STR_STYLE ='<!--
<LineStyle>
<color>%s</color>'+'<width>%d</width></LineStyle>
-->';
CAB_LINE ='';begin// Limpiar
TSTrack.Clear;// Añadir cabeceras
TSTrack.Add(CAB_XML);
TSTrack.Add(CAB_KLM);
TSTrack.Add(CAB_PLACEMARK);// Datos de la ruta
TSTrack.Add(Format(CAB_NAME,[AtrackName]));// color{$IFDEF VER220}// transparencia + color
Str :='ff'+ RGBToWebColorStr(ATrackColor);{$ELSE}
Str :='ff'+ uProcedures.RGBToWebColorStr(ATrackColor);{$ENDIF}
Str := AnsiReplaceText(Str,'#','');// TrackColor en HEXA
TSTrack.Add(Format(STR_STYLE,[Str, ATrackWidth]));
TSTrack.Add(CAB_LINE);
Con este código generamos la cabecera del fichero. En mi caso he usado una directiva condicional, para la función RGBToWebColorStr, que no está definida en versiones antiguas de la VCL.
Lo siguiente es realizar el recorrido por los puntos, añadiendo información de cada uno de ellos.
dsPoints.First;// recorridowhilenot(dsPoints.Eof)dobegin// Info. de punto
StrLine :=Copy(dsPoints.FieldByName(lonFName).AsString,1,10)+',';
StrLine := StrLine +Copy(dsPoints.FieldByName(latFName).AsString,1,10)+',';
StrLine := StrLine +
dsPoints.FieldByName(eleFName).AsString+' ';// Añadir la linea
TSTrack.Add(StrLine);// Next
dsPoints.Next;end;
dsPoints.First;
// recorrido
while not (dsPoints.Eof) do begin
// Info. de punto
StrLine :=
Copy(dsPoints.FieldByName(lonFName).AsString,1,10) + ',';
StrLine := StrLine +
Copy(dsPoints.FieldByName(latFName).AsString, 1,10) + ',';
StrLine := StrLine +
dsPoints.FieldByName(eleFName).AsString + ' ';
// Añadir la linea
TSTrack.Add(StrLine);
// Next
dsPoints.Next;
end;
Sólo queda finalmente completar el fichero añadiendo los TAG’s de las diferentes secciones que hemos ido creando.
Para facilitar el trabajo he encapsulado la conversión en el procedimiento siguiente:
procedure ConvertTrackToKLM(
ATrackName:string;// Nombre
ATrackDesc:string;//descripción
dsPoints:TDataSet;// Dataset que contiene los datos de los puntos{Nombres de los campos en el Dataset que contienen
Latitud, Longitud, Elevación y Time}
latFieldName, lonFieldName, eleFieldName, timeFieldName:string;var TSTrack:TStrings;// Estructura de salida con el track
ATrackColor:TColor=clBlue;// Color para la linea del track
ATrackWidth:integer=2// Anchura de la línea);
procedure ConvertTrackToKLM(
ATrackName:string; // Nombre
ATrackDesc:string; //descripción
dsPoints:TDataSet; // Dataset que contiene los datos de los puntos
{Nombres de los campos en el Dataset que contienen
Latitud, Longitud, Elevación y Time}
latFieldName, lonFieldName, eleFieldName, timeFieldName:string;
var TSTrack:TStrings; // Estructura de salida con el track
ATrackColor:TColor=clBlue; // Color para la linea del track
ATrackWidth:integer=2 // Anchura de la línea);
Y una llamada de ejemplo a este procedimiento podrías ser la siguiente:
TS := TStringList.Create();try// convertir a KML
ConvertTrackToKLM(
FormData.cdsTrack.FieldByName('name').AsString,// trackName'Descripción del track',// descripción
FormData.cdsPoints,// Dataset con los puntos'lat','lon','ele','time',// Nombre de los campos
TS);// Fichero de salida
newName :=ChangeFileExt(Self.TrackPath,'.KML');
TS.SaveToFile(newName);finallyFreeAndNil(TS);end;
TS := TStringList.Create();
try
// convertir a KML
ConvertTrackToKLM(
FormData.cdsTrack.FieldByName('name').AsString, // trackName
'Descripción del track', // descripción
FormData.cdsPoints, // Dataset con los puntos
'lat', 'lon','ele','time', // Nombre de los campos
TS);
// Fichero de salida
newName := ChangeFileExt(Self.TrackPath, '.KML');
TS.SaveToFile(newName);
finally
FreeAndNil(TS);
end;
Partiendo de la aplicación que sirvió de ejemplo en la entrada <Google Maps en Delphi>, he generado otro ejemplo que nos permitirá integrar la carga de un fichero GPX (utilizando el XML Mapper de Delphi), la conversión del fichero GPX a formato KML y la visualización de la ruta creada utilizando Google Maps integrado en nuestra aplicación, como prometí en la última entrada sobre el tema.
He probado a generar un video del ejemplo para ver el funcionamiento.
[youtube DLoX_KVSJ7c nolink]
Para las pruebas podeís descargar las rutas de ejemplo de la última entrada o buscar por internet en la infinidad de páginas que hay que las publican
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,…
A la sección de «Recopilatorio de las RxLib», he subido una nueva versión (oficiosa como las últimas) que compila bien en la nueva versión de Dephi (RAD Studio XE). Los ficheros de INCLUDE están actualizados para la nueva versión y he añadido alguna directiva de compilación para evitar errores segun diferentes configuraciones.
He modificado algunas units en la demo principal (RxDemo) de forma que compile para las últimas versiones.
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,…
Los threads (o hilos de ejecución), en general, se ven como un tema peliaudo; Son unas «cosas» que están ahí, son buenas (eso dice todo el mundo), pero cuando más se tarde en tener que tacarlos, mejor… ;-D
Recuerdo que cuando empecé en esto de la programación, los threads o la «programación con múltiples hilos de ejecución» sonaba algo así como «muy difícil». A medida que va pasando el tiempo, uno se da cuenta que que no es así, y cuando has hecho unos cuantos ejemplos te das cuenta de la potencia que aportan y de que realmente sólo hay que tener algunas «precauciones» a la hora de utilizarlos.
En este misma página podéis encontrar algunos ejemplos («ping usando threads», Ejemplo visual y Ejemplo visual ampliado) de programación de hilos de ejecución con Delphi. Ejemplos bastantes sencillos y bastante inútiles, porqué no decirlo, exceptuando la utilidad de aprender cómo funcionan y poder ver un código simple de utilización.
La idea de esta entrada es ir un poco más allá. A veces nos encontramos en una aplicación, con determinadas consultas que tardan mucho tiempo y que no es imprescindible esperar a su finalización para poder continuar con la ejecución normal del programa. Las más comunes serías típicas consultas de Listados, estadísticas o determinadas operaciones que podríamos hacer en 2º plano. Esas consultas serían las candidatas ideales para poder lanzarlas en un Thread independiente del hilo principal del programa.
Para los ejemplos voy a utilizar los componentes ADO y accederemos a la Base de Datos dbdemos.mdb que viene con Delphi.
Para trabajar con ADO utilizando threads, o para lanzar consultas dentro de threads, la única condición es que la conexión (TADOConnection) se cree dentro del mismo thread. Utilizaremos para ello una «cadena de conexión» como propiedad del Thread.
Para lanzar desde una aplicación una consulta utilizando nuestra clase TADOSQLThread , debería bastar con asignar la conexión, la cadena SQL, lanzar nuestro thread y esperar a que acabe. El código podría ser este:
//crear el Thread; Pasamos los parámetros de conexión y SQL
th := TADOSQLThread.Create(True, AConnection, ASQL);// Evento finalizacion; Al finalizar el control me llegará hasta este evento.
th.OnTerminate:= TerminateThread;// Ejecutarlo (ponerlo en marcha)
th.Resume;
//crear el Thread; Pasamos los parámetros de conexión y SQL
th := TADOSQLThread.Create(True, AConnection, ASQL);
// Evento finalizacion; Al finalizar el control me llegará hasta este evento.
th.OnTerminate := TerminateThread;
// Ejecutarlo (ponerlo en marcha)
th.Resume;
El código operativo del thread es sencillo, se encuentra en el método Execute y lo único que hace es ejecutar la consulta; En el constructor cremos la nueva Query (con una nueva conexión) y asignamos la SQL.
constructor TADOSQLThread.Create(CreateSuspended:Boolean;
AConnString:String; ASQL:string);begin// Creamos el thread inicialmente suspendido (para asignarle las props.)inherited Create(CreateSuspended);// No liberar automáticamenteSelf.FreeOnTerminate:=False;//crea el query
FADOQ := TAdoquery.Create(nil);
FADOQ.ConnectionString:= AConnString;
FADOQ.SQL.Add(ASQL);Self.FSQL:= ASQL;end;procedure TADOSQLThread.Execute();begininherited;// Ejecutar la consultaSelf.FADOQ.Open;end;
constructor TADOSQLThread.Create(CreateSuspended:Boolean;
AConnString:String; ASQL:string);
begin
// Creamos el thread inicialmente suspendido (para asignarle las props.)
inherited Create(CreateSuspended);
// No liberar automáticamente
Self.FreeOnTerminate := False;
//crea el query
FADOQ := TAdoquery.Create(nil);
FADOQ.ConnectionString := AConnString;
FADOQ.SQL.Add(ASQL);
Self.FSQL:= ASQL;
end;
procedure TADOSQLThread.Execute();
begin
inherited;
// Ejecutar la consulta
Self.FADOQ.Open;
end;
Ahora haría falta probar si en la ejecución de una serie de sentencia SQL con y sin threads se aprecian diferencias visibles. Hay que tener en cuenta que el utilizar o no threads también implica otras cosas.
No todo en este escenario son ventajas, hay que tenerlo en cuenta y entender el funcionamiento para sopesar si en cada caso concreto es beneficioso utilizar threads. Hay 2 grandes inconvenientes que a priori se detectan fácilmente cuando se ejecuta y se prueba un ejemplo como el que vamos a realizar.
Gasto de conexiones: En una ejecución normal, las consultas que se lanzan utilizan todas la misma conexión (ADOConnection); Una premisa que hemos marcado para trabajar con threads, es que cada thread debe funcionar con su conexión propia. Esto implica que en un caso estamos utilizando una única conexión y en el otro X conexiones concurrentes. Esto puede ser un problema en segun qué sistemas.
Sobrecarga de tiempo: El segundo problema (derivado en cierta manera del primero) es la sobrecarga de tiempo que la creación y activación de las nuevas conexiones provoca. Crear, activar (sobre todo este) y liberar las conexiones de cada thread es un tiempo añadido que hay que tener en cuenta.
Estos 2 problemas no se puede solucionar (del todo), pero sí mitigar utilizando un «pool de conexiones«; No es un tema para desarrollar ahora (tal vez más adelante), pero la idea explicada de forma sencilla, es que podemos utilizar un número máximo de conexiones (no tantas como threads). De esta forma, se asigna una conexión libre a un thread cuando la necesita, mientras haya conexiones libres; Cuando ya no quedan libres, el thread debe esperar a que una finalice para que le sea asignada. De esta forma podemos fijar el número máximo de conexiones que se utilizan y además optimizar tiempo, ya que esas conexiones se pueden «reaprovechar» de forma que no exista la necesidad de crear/activar/destruir cada una de ellas.
¿Cuando usar threads y cuando no?
La regla sencilla sería: «Cuanto más grandes y pesadas sean las consultas, más a cuenta sale utilizar threads».
Si lanzamos 20 consultas que tardan muy poco tiempo, el retraso en crear/activar las conexiones de cada una de ellas puede hacer que el tiempo de preparación sea mayor que el de la propia consulta; En ese caso estaremos «gastando» mas tiempo en «preparar» que en «consultar. Por el contrario si esas 20 consultas tardan 30 segundos cada una, el tiempo de extra de conectar para cada una de ellas puede pasar desapercibido (cuando mayor sea el tiempo de consulta, más eficiente este sistema).
Resultado de las pruebas
En las pruebas he lanzado una serie de consultas de forma secuencial. Hay que notar que el tiempo total (para consultas grandes) es sensiblemente menos cuando utilizamos threads; Pero no sólo hay que tener en cuenta el tiempo total, sino el intervalo en que tenemos acceso al resultado de cada consulta.
De forma secuencial, si la primera consulta tarda 10, la segunda 5 y la tercera 7; El tiempo total es de 22, pero los tiempos de acceso a los resultados son 10, 15 y 22 segundos respectívamente; En cambio si esto se hiciera con threads, aun suponiendo que el tiempo total fuera el mismo, los tiempo de acceso a los resultados serían 10, 5 y 7 segundos.
Select * from CustomerSelect * from Employee
Select * from CountrySelect * from items
Select * from Parts
Select * from VendorsSELECT employee.* FROM employee ORDER BY Salary,
LastName DESC , FirstName DESC , HireDate DESC
SELECT employee.* FROM employee ORDER BY Salary DESC
SELECT customer.*, orders.*, items.*, parts.*, vendors.*,
vendors.State, items.Discount, orders.SaleDate, *
FROM vendors INNER JOIN (parts INNER JOIN ((customer INNER JOIN orders
ON customer.CustNo = orders.CustNo)
INNER JOIN items ON orders.OrderNo = items.OrderNo)
ON parts.PartNo = items.PartNo)
ON vendors.VendorNo = parts.VendorNo
ORDER BY vendors.State, items.Discount DESC , orders.SaleDate
La primera prueba consta de una serie de consultas que tardan muy poco tiempo, con la Base de Datos de Access DBDEMOS.MDB (que se adjunta con Delphi). En este caso se puede ver que los tiempos de las consultas individuales son sensíblemente más bajos sin threads que con threads, debido a que las consultas con threads incluyen el tiempo de conexión. Finalmente aunque los tiempos individuales son mayores (con threads) el tiempo total queda bastante igualado (se compensa la ejecución con threads con la pérdida en las conexiones).
DATOS SIN THREADS.
DATOS CON THREADS
¿Qué pasaría si lanzáramos algunas consultas que tarden más tiempo?
Para el ejemplo he utilizado datos propios conectando a SQL Server, ya que los de la Base de Datos DBDEMOS sólo nos sirven para realizar pequeñas pruebas. Os animo a que cambieis la conexión ADO que viene en el ejemplo y configuréis vuestra propia conexión y vuestras propias consultas para realizar las pruebas.
Para la conexión basta con pulsar el botón que aparece en la parte derecha de la conexión:
Y para las consultas, basta con tener la precaución de colocar el caracter @ al inicio de cada una de las SQL (sólo cuando empieza la consulta, no en el salto de línea).
En este caso, vemos que los resultados sí cambian sensiblemente; Lo primero que nos llama la atención, es la diferencia de tiempo total de la serie de consultas (con y sin threads); He realizado unas cuantas ejecuciones, alternando primero unas y luegos las otras y los resultados de tiempos son estos; Los primeros son las consultas normales y los segundos con threads:
Sin threads:
·············································
Tiempo total(todo): 01:19:359
Tiempo total(todo): 01:18:516
Tiempo total(todo): 01:04:500
Tiempo total(todo): 01:08:969
Tiempo total(todo): 01:09:718
·············································
Con threads:
·············································
Tiempo total con threads(todo): 01:00:000
Tiempo total con threads(todo): 00:46:800
Tiempo total con threads(todo): 00:45:484
Tiempo total con threads(todo): 00:53:078
·············································
Posteriormente he lanzado, para variar, 4 ejecuciones concurrentes de ejemplo; 2 con threads y 2 sin threads y el resultado ha sido similar (en cuanto a la diferencia):
·············································
Tiempo total(todo): 01:48:984
Tiempo total(todo): 01:50:860
·············································
·············································
Tiempo total con threads(todo): 01:27:593
Tiempo total con threads(todo): 01:32:860
·············································
CONCLUSIÓN: Aunque el ejemplo es bastante sencillo, y la clase que implementa los threads tiene poca «chicha» yo creo que se ven las posibilidades de utilizar esta opción. También debe quedar claro que no es algo para usar «siempre»; Hemos visto que dependiendo del escenario donde se utiliza puede resultar inútil e incluso contraproducente, ya que gasta más recursos que la técnica sin threads. Como ventaja tenemos que la utilización de threads, en general, nos aporta paralelismo y mayor control en la ejecución del programa (ya que evitamos el «bloqueo» en el caso de una consulta muy costosa).
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,…