GLib es una librería/biblioteca de componentes de uso general
Son de uso gratuito y de licencia libre.
Puedes usarlos libremente y descargar el código fuente para estudiarlo o modificarlo; Puedes utilizarlos en cualquier tipo de aplicaciones; Gratuías o comerciales. Sólo te pido que si haces cambios en el código (mejoras, correcciones,…) me lo comuniques para actualizar los componentes, si son de interés para mi y para otras personas.
Puedes descargar los componentes de forma individual o todos juntos en un package, dependiendo de lo que te interese. Para ello selecciona los links individuales o para descargar el paquete completo el último que hace referencia a la Librería GLib.
HISTORIAL (v. 1.3)
A parte de algunos pequeños bugs se han compilado las librerías sobre la versión XE de Delphi sin presentar ningun problema. Se añadi el package para la versión XE.
Problemas resueltos:
(ClogDisk)
* Permitir grabación continua de ficheros (Accumulative).
* (CLogDisk) Propiedades para permitir o no grabar pie y cabecera.
(CustomizeGrid)
* Corregido problema al acceder a los títulos de las columnas.
* Corregido error de pintado de estilos y filas alternativas al recorrer el DBGrid.
* Puede pintar de forma automática las columnas de tipo Memo y Boolean como texto y checkbox respectivamente.
(EditInsideHelp)
* Changed InsideHelpColor by InsideHelpFont for more customization possibilities.
(thanks Ariel Martín from Cuba)
(DiskInfo)
* Corrected bug on select letter (Thanks Vincenzo).
* (v. 1.2) Corregido un error al obtener Número de Serie de varios discos instalados. (Thakns Peter Aschbacher)
* Añadidas propiedades para seleccionar los controles que se quieren almacenar… (SelectTaggedControls y SelectTag)
El componente TLogDisk sirve para facilitar el trabajo a un programador que necesite añadir un Log a sus programas. Basta con “soltar?? el componente en un formulario del programa y activarlo. Automáticamente el componente crea el fichero de Log, almacena datos de la aplicación (cabecera) y ofrece al programador métodos/rutinas para añadir datos de diferentes tipos al Log.
Este componente permite definir un texto de ayuda en la parte interna del control de edición (TEdit). El texto desaparace cuando el control contiene algun texto que el usuario ha introducido. Se pueden definir el color que deseamos para el texto de la ayuda de forma independiente al color definido en la fuente (propiedad Font) del componente.
Este componente (en éste caso derivado de un TPanel) muestra cómo añadir a un componente una Cuadrícula o Grid similar a la que aparece en los formularios cuando los estamos diseñando desde el IDE de Delphi.
Se puede configurar en el componente el color y el espaciado entre puntos de la cuadrícula.
Basta con soltarlo en el formulario y activarlo.
Este componente permite para recuperar información diversa sobre los Discos existentes en el de sistema; Discos duros, diskettes, discos de red, discos RAM… Colóquelo en un formulario y defina la letra del disco.
Componente derivado de un TListBox que añade la propiedad de definir ordenaciones para los diferentes elementos de la lista; Junto a cada elemento se muestra una señal para indicar la ordenación. Posee una propiedad de tipo array para consultar el estado de cada item de la lista
Permite guardar la posicion y tamaño de todos los componentes que se encuentran en el formulario. Utiliza para ello un fichero INI.
Basta con colocar el componente en el formulario y activarlo; Cuando la aplicación se cierra graba la posición/tamaño y al volver a ejecutarla los recupera de forma automática.
Permite seleccionar componentes visuales (en Runtime) que haya en un form de forma visual como se hace con las imágenes en los programas de diseño o con los controles en el IDE de Delphi.
Además se pueden mover y redimensionar los controles seleccionados. Basta con soltar el control en el formulario, asignarle el control que se desea seleccionar/mover/redimensionar y activarlo.
Permite gestionar de forma sencilla (sin código) algunas de las combinaciones de teclas más utilizadas en los formularios. Basta con soltar el componente sobre el formulario y activar las propiedades deseadas segun el comportamiento que queramos.
Permite modificar algunos aspectos en la visualización de un Grid estandard.
No deriva del DBGrid, si no que funciona como complemento al componente estandard de Delphi. (En construcción…)
La propiedad Flat permite modificar el aspecto del DBGrid.
Implementa métodos para mejorar el pintado en el Grid de las celdas de tipo Booleano y Memo;
Sustituye el texto por un checkbox y (MEMO) que aparece en los campos Memo por el texto del campo.
Además provee eventos para modificar colores del DBGrid:
OnPaintCell: Para pintar xeldas de un determinado color.
OnPaintCellExt: BIS del anterior con más parámetros.
OnPaintColumn: Permite pintar una columna de color.
OnPaintRow: Permite pintar una file de color.
OnPaintCellImage: Permite pintar imágenes en una celda.
OnChangeTitleCell: Modificar las celdas de título.
Este componente permite detectar en la aplicación donde se utiliza la inactividad de teclado y de ratón pasado un determinado tiempo (de forma similar a cómo se activa el salvapantallas del sistema).
El tiempo que se quiere detectar de inactividad es configurable por el usuario en minutos y segundos. Basta con activar el componente, configurar el tiempo de inactividad; Pasado este tiempo de inactividad «saltará» un evento de aviso.
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,…
Siguiendo con los ejempos y ahondando en las posibilidades de WMI (Windows Management Instrumentation), a raiz de este hilo en los foros del ClubDelphi, referente al software instalado en nuestro ordenador, he probado a obtener la lista de programas instalados utilizando la WMI.
Para ello podemos utilizar la clase Win32_Product.
Si accedemos a su descripción en MSDN, obtenemos la siguiente descripción de la clase:
Podemos disponer así de toda la información, referente a cada uno de los programas/aplicaciones instalados en nuestro equipo (*NOTA*).
Para ello, lo primero que debemos hacer es Importar a Delphi la librería “Microsoft WMI Scripting v1.X Library (Version 1.X)“, si no lo hemos hecho ya.
En la Introducción sobre WMI, se explica un poco más detalladamente.
Una vez importada, lo siguiente (dentro de nuestro ejemplo) es conectar con nuestro proveedor (en este caso nuestra máquina local):
1
2
3
4
5
6
// Create the Location object
Locator := CoSWbemLocator.Create();// Connect to the WMI service, with the root\cimv2 namespace
aServices := Locator.ConnectServer(STR_EMPTY,{Server}
STR_CIM2_ROOT,{user}STR_EMPTY,{password}STR_EMPTY,
STR_EMPTY,STR_EMPTY,0,nil);
// Create the Location object
Locator := CoSWbemLocator.Create();
// Connect to the WMI service, with the root\cimv2 namespace
aServices := Locator.ConnectServer(STR_EMPTY, {Server}
STR_CIM2_ROOT, {user}STR_EMPTY, {password}STR_EMPTY,
STR_EMPTY,STR_EMPTY, 0, nil);
A continuación, si la conexión ha sido correcta, realizamos la consulta sobre la clase Win32_Product, que nos dará la información que necesitamos.
1
2
3
// realizar la consulta
ObjSet := Services.ExecQuery('SELECT * FROM Win32_Product','WQL', wbemFlagReturnImmediately and wbemFlagForwardOnly ,nil);
// realizar la consulta
ObjSet := Services.ExecQuery('SELECT * FROM Win32_Product',
'WQL', wbemFlagReturnImmediately and wbemFlagForwardOnly , nil);
A partir de ahí basta con recorrer los elementos obtenidos y e nuestro caso formatear algunas propiedades para mostrarlas (Caption, InstallDate, Vendor y Version), aunque todas las demás están igualmente a disponsición del programador.
Recordad de colocar en el USES las dos units comentadas antes.
(*NOTA*): Recordemos (link), que una de las características de WMI es que permite «Administración remota», con lo que si tenemos los suficientes permisos, este código también puede servir para inventariar otras máquinas.
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,…
No hace mucho me cruce con esta “palabrota” este concepto: WMI.
A priori parece sólo eso, una “palabrota” más, de las muchas que nos encontramos en nuestro día a día “informático”. Pero rascando, rascando ha aparecido una perla oculta bajo una capa de polvo.
WMI son las iniciales de “Windows Management Instrumentation”. Se me hace complicado explicar en pocas palabras qué es, pero si tuviera que hacerlo, diría que es algo así como “Una puerta de acceso a la Administración de Sistemas Windows” ( ;-D Windows Management Instrumentation).
Junto a WMI aparecen dos “palabrotas” dos conceptos más que nos ayudan a saber de dónde viene.
WBEM (Web-Based Enterprise Management )
CMI (Common Model Information).
Si las juntamos todas nos queda que, WMI es una implementación que Microsoft ha hecho para Windows, de un estandard llamado WBEM que sirve para la administración de sistemas vía web y que está basado en CIM, que vienen a ser unas clases que nos dan acceso a diferente información del un equipo.
(Traducción al idioma Terrícola):
“A través de WMI yo puedo administrar un equipo basado en Windows, local o remoto, independientemente de la versión del sistema, utilizando las clases que me provee CIM.”
Y esto visto desde el punto de vista de un programador tiene muy buena pinta.
Este podría ser un gráfico sencillo de su estructura.
Dicho esto, yo me pregunto, ¿Qué tiene de bueno WMI y qué puede hacer por mi? (en el terreno más práctico)
Utilizando WMI podemos consultar información sobre nuestro equipo. Desde las aplicaciones instaladas, a la información de la BIOS, discos duros instalados, particiones creadas, Antivirus instalado si lo hay, nivel de carga de la batería (si conviene), procesos ejecutándose, acceso a los servicios instalados, acceso a las tareas programadas, y la lista sigue y sigue…
Cuando hablamos de administración significa que no sólo podemos consultar estos parámetros, sino también modificarlos en muchos casos.
Administración remota. Significa que lo dicho anteriormente se aplica también a administración remota. Es decir, no sólo podemos hacerlo en nuestro equipo, sino en el resto de equipos de la red. WMI está presente en sistemas Windows 2000 con SP2 en adelante. Para los anteriores (95, 98 y NT 4.0) se puede descargar un SDK para instalarlo (link).
Es extensible. Microsoft añade nuevas clases a cada versión de Windows. Además se pueden crear nuevos proveedores de WMI que amplíen funcionalidades.
Acceso desde la línea de comandos. Existe una herramienta para trabajar desde la línea de comandos con WMI (link).
Utilizándola podemos por ejemplo obtener los procesos ejecutándose si desde una ventana de MS-DOS escribimos:
1
2
3
4
WMIC PROCESS
WMIC PROCESS LIST BRIEF (mejora la visualización)
WMIC /output:”c:\procesos.txt” PROCESS LIST BRIEF
(para obtener salida a un fichero)
WMIC PROCESS
WMIC PROCESS LIST BRIEF (mejora la visualización)
WMIC /output:”c:\procesos.txt” PROCESS LIST BRIEF
(para obtener salida a un fichero)
Lenguaje de consulta similar a SQL llamado WQL (WMI Query Language). Utilizándolo podemos desde una ventana MS-DOS ejecutar comandos como estos:
1
2
3
4
5
WMIC PRINTER WHERE Default=”TRUE”
(obtener info sobre la impresora predeterminada)
WMIC PRINTER WHERE Default=”TRUE” LIST FULL
WMIC PRINTER WHERE default="TRUE" GET DriverName,PortName,PrintProcessor
(para obtener determinados datos de la impresora predeterminada)
WMIC PRINTER WHERE Default=”TRUE”
(obtener info sobre la impresora predeterminada)
WMIC PRINTER WHERE Default=”TRUE” LIST FULL
WMIC PRINTER WHERE default="TRUE" GET DriverName,PortName,PrintProcessor
(para obtener determinados datos de la impresora predeterminada)
Aquí se pueden ver unos cuantos ejemplos más a parte de la extensa documentación existente en las páginas de Microsoft.
Una vez hecha esta introducción (muy genérica) sobre WMI, me gustaría centrarme en las aplicaciones que pueden acceder a ella. Microsoft proporciona las API de WMI para scripts, aplicaciones Visual Basic, aplicaciones C++ y .NET Framework. Eso no quiere decir que no se pueda acceder desde casi cualquier lenguaje. En mi caso me voy a centrar en el acceso a ella desde Delphi.
Para acceder desde Delphi a WMI lo primero que necesitamos en Importar la librería desde el menú de Project/Import Type Library (pasos aquí):
Una vez importada la librería tendremos la unit WbemScripting_TLB.pas generada y lista para utilizar. En nuestros programas deberemos incluir en la clausula USES, esta unit junto con la de ActiveX.
Antes de acabar esta entrada (espero poder publicar más, ya que el tema me parece muy interesante) podemos ver cómo utilizar la WMI para acceder a los datos del disco duro. En este caso, para simplificar vamos a acceder a los datos del primer disco (DiskDrive) existente en el sistema. Para ello se utiliza la clase: Win32_DiskDrive
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Pues ha llovido mucho desde la primera parte de este «articulillo»; Por lo que he visto en los fuentes del ejemplo, lo empecé hace aproximadamente hace 2 años, así que eso es lo que lleva de retardo… ;-)
En ese primer artículo se daba una visión general de lo que podía ser un sistema de PlugIns. Unas ideas generales y algo de código para empezar. Quedaba en el tintero profundizar un poco más en el tema y ver un ejemplo un poco más «práctico» que pudiera servir en una aplicación real. Eso es lo que he intentado tratar en esta segunda parte, centrandome en un sistema de «PlugIns Homogéneos«, con carga bajo petición (por parte del usuario).
TIPOS DE PLUGINS
Como ya vimos en la primera entrega, podemos dividir los plugIns en dos tipos según las tareas que desempeñan en una aplicación. Así podemos hablar de plugIns o grupos de ellos y catalogarlos como homogéneos si la tarea que realizan es similar o catalogarlos como heterogéneos (no-agrupados) si las tareas que desarrollan son independientes y no “agrupables” según su funcionalidad.
Grupos homogéneos de PlugIns
PlugIns heterogéneos
Esta división no sólo es conceptual en función de las características y desempeños de cada uno, sino que afecta directamente a la estructura con que se diseñarán estos elementos. Así, los PlugIns que pertenezcan a un grupo homogéneo tendrán estructura similar y un punto de entrada común (formulario base o procedimiento de ejecución). Mientras que los heterogéneos posiblemente no tengan una estructura común y la forma se ejecutarlos sea más “tosca” y menos “integrada” que los anteriores.
PLUGINS HOMOGENEOS
En este artículo vamos a tratar más profundamente esta variante de plugIns. Como ya hemos comentado se trata de plugIns con una estructura similar, aunque con variaciones en su funcionalidad. Tal vez con un ejemplo se vea más claro.
Tomemos como grupo homogéneo de PlugIns; Los efectos aplicables a una imagen dentro de un programa de diseño. A partir de una imagen podemos desarrollar plugIns que efectúen un determinado “cambio” de forma que la imagen resultante sea diferente a la original. La estructura y los parámetros de todos ellos parece claro que serán similares o idénticos. Todos ellos toman una imagen inicial y a partir de unos parámetros la modifican, para devolver una imagen de salida.
ESTRUCTURA FÍSICA
Para trabajar con esta estructura de plugins, utilizaremos un sistema de carga dinámica. Los plugins de programarán utilizando packages (BPL) con una estructura común y dependiendo de un package principal que contiene la Clase Base. Todos los plugins derivarán (heredarán) de una clase base que estará programada en el package principal.
Al cargar la aplicación se carga (puesto que está linkado de forma estática -utilizando la clausula USES-) también el package correspondiente a la clase Base. Esto da acceso a todos los métodos que estén definidos en la clase base (y en los derivados) desde el programa principal.
El resto de packages se cargan de forma dinámica y todos deben derivar (sus clases) de la Clase Base programada en el package Base.
PROTOTIPO
El prototipo que vamos a realizar para ilustrar el artículo simula un programa para realizar gráficos y diagramas simples. El programa utilizará un sistema de plugIns para añadir bibliotecas de objetos que puedan añadirse a los gráficos. Cada pluging (BPL) añade una nueva categoría de elementos y cada categoría implementa uno o varios objetos.
Todos los objetos que implementa una categoría derivan de una Clase Base (TShapeExBase) y esta clase base se implementa en un package que está linkado estáticamente a la aplicación principal (se carga siempre al arrancar la aplicación) y es obligatorio que exista, de otra forma la aplicación fallaría al ejecutarse.
En la imagen que se ve ala derecha, vemos la ventana correspondiente al Plugin de «Arrows»; Aquí implementa la clase TShapeExArrow (que deriva de TShapeExBase) y en esta clase se han programado los objetos que se ven en la imagen.
En nuestro ejemplo para este artículo se cargan los plugIns bajo petición. Es decir, en una primera pasada la aplicación revisa la existencia de PlugIns y detecta todos los ficheros presentes. Muestra una ventana con los plugns disponibles y la descripción de cada uno de ellos y a medida que el usuario los selecciona se cargan de forma dinámica. Imagen de la derecha.
El código de la carga es el siguiente:
...// ComprobaciónifnotFileExists(AName)thenbegin
_mens(Format('No se ha podido cargar el package <%s>;'+'No existe en disco.',[AName]));
Exit;end;// Cargar
hndl :=LoadPackage(AName);
desc :=GetPackageDescription(PChar(AName));
Result := hndl;// Acceder a la clase del menu
pName :=ChangeFileExt(ExtractFileName(AName),'');
b := ExClassList.Find(pName, i);// Encontrada?if(b)thenbegin
AClass := TPersistentClass(ExClassList.Objects[i]);end;
...
// Comprobación
if not FileExists(AName) then begin
_mens(Format('No se ha podido cargar el package <%s>;' +
'No existe en disco.',[AName]));
Exit;
end;
// Cargar
hndl := LoadPackage(AName);
desc := GetPackageDescription(PChar(AName));
Result := hndl;
// Acceder a la clase del menu
pName := ChangeFileExt(ExtractFileName(AName), '');
b := ExClassList.Find(pName, i);
// Encontrada?
if (b) then begin
AClass := TPersistentClass(ExClassList.Objects[i]);
end;
CLASE BASE (TShapeExBase)
La clase base para nuestro sistema de plugins se llama TShapeExBase. Esta clase sirve como punto de partida para todas las demás. Además de contener los métodos comunes a todos los plugins nos permitirá acceder desde la aplicación principal a todas las funciones de los plugins. Para ello los métodos importantes estarán definidos en esta clase y luego sobreescritos (override) en las clases derivadas.
{: Clase base lapa las clases implementadas en los plugins.}
TShapeExBase =class(TShape)private
FShapeEx:string;// Marca el tipo de Shapeprocedure SetShapeEx(const Value:string);virtual;protected
W, H, S:Integer;
X, Y:Integer;
XW, YH:Integer;
W2, H2, W3, H3, H4, W4, H8, W8, W16, H16:Integer;// Método de pintadoprocedure Paint;override;procedure CalculateData();// PROCEDIMIENTOS DE INFORMACION//············································// Autor del packagefunction Autor():string;virtual;abstract;// Versión del Packagefunction Version():string;virtual;abstract;// Fecha de creaciónfunction FechaCreacion():TDate;virtual;abstract;public// constructor de la claseconstructor Create(AOwner: TComponent);override;// destructor de la clasedestructor Destroy;override;published// Tipo de Shapeproperty ShapeEx:stringread FShapeEx write SetShapeEx;end;
{: Clase base lapa las clases implementadas en los plugins.}
TShapeExBase = class(TShape)
private
FShapeEx: string;
// Marca el tipo de Shape
procedure SetShapeEx(const Value: string); virtual;
protected
W, H, S: Integer;
X, Y:Integer;
XW, YH:Integer;
W2, H2, W3, H3, H4, W4, H8, W8, W16, H16:Integer;
// Método de pintado
procedure Paint; override;
procedure CalculateData();
// PROCEDIMIENTOS DE INFORMACION
//············································
// Autor del package
function Autor():string; virtual; abstract;
// Versión del Package
function Version():string; virtual; abstract;
// Fecha de creación
function FechaCreacion():TDate; virtual; abstract;
public
// constructor de la clase
constructor Create(AOwner: TComponent); override;
// destructor de la clase
destructor Destroy; override;
published
// Tipo de Shape
property ShapeEx: string read FShapeEx write SetShapeEx;
end;
En nuestro caso es una clase sencilla. La función implementa el dibujo de componentes derivados de un TShape en pantalla.
La propiedad ShapeEx es la más importante, e indica el tipo (identificador) de la figura. Equivalente a lo que en los TShape son los valores stRectangle, stEllipse, stSquare,…
En nuestra clase no puede ser un elemento tipificado como lo es en TShape, puesto que los nuevos plugins irán añadiendo elementos a esta propiedad que a priori no conocemos.
Se añaden también procedimientos de información acerca del plugin como pueden ser el Autor, la fecha de creación o la versión.
El método Paint, que para la clase base está vacío, en las clases derivadas será donde se implementen las instrucciones de pintado para cada uno de los elementos.
Finalmente la clase Base implementa el procedimiento CalculateData y al utiliza algunas variables en la parte protected, que precalculan datos y los ponen a disposición de las clases derivadas (protected), para facilitar la implementación del método Paint y dar acceso a medidas ya precalculadas.
En la clase Base además se definen dos Listas (TStringList) que nos servirán de apoyo a la hora de acceder a los diferentes objetos de los plugIns; Tanto para las clases, como para los Shapes definidos en cada clase.
//: Lista de clases registradas en los packages dinámicos
ExClassList:TStringList;//: Lista de objetos registrados en una clase (tipos de Shapes)
ExShapeList:TStringList;
//: Lista de clases registradas en los packages dinámicos
ExClassList:TStringList;
//: Lista de objetos registrados en una clase (tipos de Shapes)
ExShapeList:TStringList;
En la primera añadiremos la referencia a la Clase y el String correspondiente al nombre del package y en la segunda, para cada Shape implementado en la Clase, su valor de la propiedad ShapeEx (comentada anteriormente) y el apuntador a su clase.
De esta forma, por ejemplo, el PlugIn que implementa la clase TshapeExArrow que corresponde a la imagen que se ve más arriba, añadirá en las lista los siguientes valores:
// Registrar los tipos
ExShapeList.AddObject('stArrorRight',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW',Pointer(TShapeExArrow));...// registrar la clase
ExClassList.AddObject('PlugArrows',Pointer(TShapeExArrow));
// Registrar los tipos
ExShapeList.AddObject('stArrorRight', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW', Pointer(TShapeExArrow));
...
// registrar la clase
ExClassList.AddObject('PlugArrows', Pointer(TShapeExArrow));
En las líneas anteriores podemos ver que el plugIn PlugArrow (1) tiene implementada la clase TShapeExArrow (1), y que dentro de esta clase hay 6 objetos diferentes de tipo ShapeEx; Cuyos identificadores son: stArrorRight, stArrorRight, stArrorRightM, stArrorLeft, stArrorUp y stArrorDown.
CLASES DERIVADAS
Tal y como está diseñada la estructura, las clases derivadas de la clase Base (TShapeExBase) deben redefinir el método Paint para definir cómo se define cada uno de los objetos de esa clase.
SISTEMA DE CARGA/DESCARGA
El sistema de carga es simple y lo único que hace de especial en este caso es comprobar primero si el package ya ha sido cargado, y si no es así llama a la función CargarPackage del formulario principal, utilizando el nombre del fichero.
Podemos ver por pasos y comentar qué hace esta función:
// Cargar
hndl :=LoadPackage(AName);
desc :=GetPackageDescription(PChar(AName));
Result := hndl;
En primer lugar (una vez hemos comprobado que el fichero existe) cargamos el package a partir de su nombre. Una vez cargado obtenemos la Descripción. Para ello se llama a la función GetPackageDescription que se encuentra en la Unidad SysUtils.pas y que develve el valor almacenado en el DPK junto a la directiva {$DESCRIPTION} o {$D} que permite almacenar hasta 255 caracteres.
Todos los packages cuentan con una sección de INITIALIZATION donde añaden a las lista de clases (ExClassList) y a la lista de Shapes (ExShapeList) los elementos que ese package implementa. Estas dos clases son importantes puesto que nos facilitan mucho el trabajo a la hora de realizar todo tipo de operaciones con los elementos de cada packages. Además se registra la clase utilizando el método RegisterClass de Delphi. Por ejemplo, el package de “Arrows” contiene esta sección de INITIALIZATION:
//===================================================================//// I N I T I A L I Z A T I O N////===================================================================initialization// Registrar la clase del formRegisterClass(TShapeExArrow);// Registrar los tipos
ExShapeList.AddObject('stArrorRight',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightM',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorLeft',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorUp',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorDown',Pointer(TShapeExArrow));// registrar la clase
ExClassList.AddObject('PlugArrows',Pointer(TShapeExArrow));//===================================================================
//===================================================================
//
// I N I T I A L I Z A T I O N
//
//===================================================================
initialization
// Registrar la clase del form
RegisterClass(TShapeExArrow);
// Registrar los tipos
ExShapeList.AddObject('stArrorRight', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightM', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorLeft', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorUp', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorDown', Pointer(TShapeExArrow));
// registrar la clase
ExClassList.AddObject('PlugArrows', Pointer(TShapeExArrow));
//===================================================================
Lo siguiente que vamos necesitamos, una vez que tenemos cargado el package, es crear la clase que se implementa en el package; Una vez hecho esto ya tendremos total acceso a los métodos que necesitemos y realmente ya habremos conseguido nuestro objetivo.
Para crear la clase utilizamos la lista de clases (ExClassList) que hemos comentado en el párrafo anterior y que hemos rellenado en la sección de inicialización; Otra opción también viable es utilizar GetClass de Delphi mediante RTTI junto con el nombre de la clase registrada (TShapeExArrow). También funcionaría, aunque en este caso, por comodidad, hemos utilizado estas listas auxiliares.
// Crear la clase
b := ExClassList.Find(pName, i);// encontrada?if(b)thenbegin
AClass := TPersistentClass(ExClassList.Objects[i]);// OTRA OPCIÓN:
BClass :=GetClass('TShapeExArrow');end;
// Crear la clase
b := ExClassList.Find(pName, i);
// encontrada?
if (b) then begin
AClass := TPersistentClass(ExClassList.Objects[i]);
// OTRA OPCIÓN:
BClass := GetClass('TShapeExArrow');
end;
Para finalizar y después de haber realizado unas comprobaciones, llamamos al método CargarCategoria, que crea de forma dinámica la ventana asociada a esa categoría (con la descripción) y también crea el elemento individual asociada a cada Shape implementado en esa clase.
En este punto ya hemos hecho uso de todo lo implementado en ese package, puesto que ya hemos creado un objeto de todos los implementados.
// Cargar los objetos de ese plugIn
CargarCategoria(AClass, desc);
En este ejemplo, no descargamos los packages, puesto que los necesitamos para seguir trabajando con los objetos que tenemos en pantalla, lo que hacemos realmente es ocultar la ventana. Si la operación que desempeña el package no necesita que posteriormente esté cargado, bastaría con descargarlo utilizando UnloadPackage.
Hasta aquí las descripción de todo el proceso. Junto con el artículo os adjunto el ejemplo completo y bastante comentado. Es sencillo, pero muestra a la perfección el manejo práctico de este tipo de ficheros.
Espero que haya quedado claro y si hay comentarios o sugerencias, ya sabéis. ¡¡Disparad!! ;-D
Imagen del programa de ejemplo.
El código del ejemplo se puede descargar desde aquí y los binarios (EXE + BPL’s) desde aquí.
Tal como me comenta Salvador, en el proyecto no se incluyen las dos BPL’s de Dephi (de la VCL) que ne necesitan para ejecutar el proyecto. Si no tenéis Delphi 6 instalado, las necesitaréis para ejecutar. Os coloco los links, con el proyecto (binarios incluyendo las BPLs) y un fichero sólo con los dos ficheros (VCL60.BPL y RTL60.BPL).
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Se conoce como Codificación Geográfica, al proceso o sistema de transformar una dirección «o similar» en coordenadas geográficas de Longitud y Latitud, para posteriormente ser utilizadas en sistemas de posicionamiento (por ejemplo).
El API de Google incluye este servicio, que es el que se utiliza cuando buscamos una dirección desde Google Maps.
Normalmente este acceso se hace desde una página web con peticiones HTTP; Lo que vamos a ver es cómo integrar este servicio en un programa Delphi.
Un poco más arriba he comentado que la codificación geográfica se basa en dar una entrada (dirección) para obtener unas coordenadas. También he añadido el «similar», puesto que en el caso de Google Maps, podemos dar como entrada otras cosas que expresamente no son una dirección.
Así, podemos utilizar como dirección:
08901,Spain
c/Balmes,214,Barcelona,Spain
Sevilla,Spain
Alhambra,Granada,Spain
…
Para hacer una prueba de lo que podemos obtener, basta con utilizar un componente TidHTTP de las Indy para realizar la petición. Coloca un componente TidHTTP en un formulario y un botón con el siguiente código:
procedure TForm1.btn1Click(Sender:TObject);const
STR_WEB ='http://maps.google.com/maps/geo?q=';
STR_OUT ='&output=csv';// formato de salidavar
Stream: TStringStream;
Str, res:string;begin// ini
Stream := TStringStream.Create('');// proteccion para liberartry
Str := STR_WEB + edt1.Text+ STR_OUT;
idhttp2.Get(Str, Stream);
edt2.Text:= Stream.DataString;finallyFreeAndNil(Stream);end;end;
procedure TForm1.btn1Click(Sender: TObject);
const
STR_WEB = 'http://maps.google.com/maps/geo?q=';
STR_OUT = '&output=csv'; // formato de salida
var
Stream: TStringStream;
Str, res:string;
begin
// ini
Stream := TStringStream.Create('');
// proteccion para liberar
try
Str := STR_WEB + edt1.Text + STR_OUT;
idhttp2.Get(Str, Stream);
edt2.Text := Stream.DataString;
finally
FreeAndNil(Stream);
end;
end;
Una imagen de lo que obtenemos una vez realizada la petición es la siguiente:
Se puede descargar el código completo de este ejemplo desde aquí.
Y lo que obtenemos como respuesta en este caso es:
200,8,41.3979638,2.1515206
Los dos últimos parámetros son los que buscabamos y nos indican las coordenadas de ese punto (latitud y longitud).
Para comprobar que so correctas, basta con ir a la web de Google Maps, copiar estas dos coordenadas en el cuadro de búsqueda y pulsar sobre el botón de <Buscar en el Mapa>.
A parte de esto, Google Maps nos devuelve, en este caso, dos números más; El primero corresponde al un «código de Estado» o «código de retorno» de la consulta que hemos realizado; En él se devuelve información por parte del servidor. Y el segundo es lo que se conoce como «Accuracy» o «Exactitud», que corresponde justamente a eso; Al nivel de exactitud que Google asigna a la respuesta, segun la dirección que le hemos dado.
El código de retorno, en este caso, es un 200, que corresponde a la constante «G_GEO_SUCCESS» (consulta correcta).
El nivel de exactitud, en este caso, es un 8 (máxima precisión).
Los niveles de exactitud con lo que trabaja Google Maps son los siguientes:
0: Ubicación desconocida.
1: Precisión a nivel de país.
2: Precisión a nivel de región.
3: Precisión a nivel de subregión.
4: Precisión a nivel de ciudad o pueblo.
5: Precisión a nivel de código postal.
6: Precisión a nivel de calle.
7: Precisión a nivel de intersección.
8: Precisión a nivel de dirección.
Así en el ejemplo que hemos visto antes obteníamos una Exactitud de 8 (a nivel de dirección), mientras que si íntroducimos una dirección del tipo;
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Hace unos días nos encontramos con el problema (no muy grande ;-D ) de añadir a una aplicación delphi existente la posibilidad de incluir una imagen seleccionada por el usuario. A priori la imagen era un JPG, de la cual se debía crear una miniatura (thumbnail) a unas dimensiones determinadas (180 x 115) y ambas debían subir a un directorio determinado. Ningun problema. Aquí mismo había un par de procedimientos de Domingo Seoane para redimensdionar una imagen.
En concreto modificando un poco el procedimiento Proporcional conseguí lo que necesitaba. Que si la imagen original no era exactamente de las mismas proporciones que la que necesitaba (miniatura) esta rellenara con un color «neutro» (en este caso el blanco) los bordes laterales.
// Esta otra mantiene la relacion entre alto y anchoprocedure Proporcional(Imagen: TGraphic; Ancho, Alto:Integer);var
Bitmap: TBitmap;
Rect:TRect;begin
Bitmap:= TBitmap.Create;try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;/// Calculos para que quede proporcionalif(Ancho/Imagen.Width) <(Alto/Imagen.Height)thenbegin
Alto:=Trunc((Ancho*Imagen.Height)/Imagen.Width);endelsebegin
Ancho:=Trunc((Imagen.Width*Alto)/Imagen.Height);end;// posición nueva// Hay que centarla para que queden márgenes iguales a ambos lados
Rect.Left:=((Bitmap.Width- Ancho)div2);
Rect.Top:=((Bitmap.Height- Alto)div2);
Rect.Right:= Rect.Left+ Ancho;
Rect.Bottom:= Rect.Top+ Alto;// Color neutro para márgenes
Bitmap.Canvas.Brush.Color:= clRed;// copiar
Bitmap.Canvas.FillRect(Bitmap.Canvas.ClipRect);
Bitmap.Canvas.StretchDraw(Rect,Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
// Esta otra mantiene la relacion entre alto y ancho
procedure Proporcional(Imagen: TGraphic; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
Rect:TRect;
begin
Bitmap:= TBitmap.Create;
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
/// Calculos para que quede proporcional
if (Ancho/Imagen.Width) < (Alto/Imagen.Height) then begin
Alto:= Trunc((Ancho*Imagen.Height)/Imagen.Width);
end
else begin
Ancho:= Trunc((Imagen.Width*Alto)/Imagen.Height);
end;
// posición nueva
// Hay que centarla para que queden márgenes iguales a ambos lados
Rect.Left := ((Bitmap.Width - Ancho) div 2);
Rect.Top := ((Bitmap.Height - Alto) div 2);
Rect.Right:= Rect.Left + Ancho;
Rect.Bottom := Rect.Top + Alto;
// Color neutro para márgenes
Bitmap.Canvas.Brush.Color := clRed;
// copiar
Bitmap.Canvas.FillRect(Bitmap.Canvas.ClipRect);
Bitmap.Canvas.StretchDraw(Rect,Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Hice un par de pruebas con imágenes y el resultado no fue exactamente lo que yo esperaba. El procedimiento era correcto, y funcionaba bien, pero las imagenes en minuatura presentaban Aliasing. Y siendo las miniaturas bastante pequeñas el efecto se notaba bastante.
Ampliando un poco la imagen y comparándola con una generada con cualquier programa sencillo de retoque fotográfico se apreciaba bastante la diferencia entre ambas.
Esto es lo que se conoce como aliasing. Se pueden encontrar múltiples definiciones y explicaciones de este problema en Internet (wiki), así que no explicaré aquí de que se trata.
APLICAR ANTIALIASING
La teoría dice que esto se soluciona aplicando algoritmos de altializasing, así me he puesto a hacer unas pruebas a ver qué resultado obtenía.Mi idea es modificar el color de cada uno de los pixels de la imagen teniendo en cuenta en color de los pixels que hay a su alrededor.
Qué pixels seleccionemos para ello y cuantos (distancia) determinará que el resultado sea más o menos satisfactorio, pero también afectará al tiempo de cálculo. Por lo que he leído esto es lo que se conoce como Supersampling/Multisampling.
Un ejemplo de diferentes selecciones de pixels se puede ver en la imagen siguiente:
En cada uno de estos casos se variará el color del pixel central teniendo en cuenta los colores de los pixels que hay a su alrededor.
A partir de aquí me he propuesto hacer algunas pruebas (sencillas) para comprobar si en los resultados se notaban cambios a simple vista.
PRUEBAS DE ALGORITMOS
Para los ejemplos he realizado una imagen sencilla, con varias líneas inclinadas, donde se aprecian bastantes «dientes de sierra» y algunas circunferencias. La imagen inicial es la que se ve en la figura siguiente con un tamaño inicial de 457 x 273 pixels.
La idea es reducir el tamaño de esa imagen hasta la mitad (más o menos) y a una cuarta parte aplicando antes un algoritmo de antialiasing sencillo escogiendo diferentes puntos para modificar el color de los pixels.
Para la reducción de tamaño, he utilizado un procedimiento estandard para reducir el tamaño de imágenes BPL utilizando (StretchDraw), pero en este caso, antes de hacer la reducción he probado a aplicar los algoritmos de AntiAliasing.
// Esta cambia el alto y ancho, estirando la imagen si es necesarioprocedure Redimensionar(Imagen:TBitmap; Ancho, Alto:Integer);var
Bitmap: TBitmap;begin
Bitmap:= TBitmap.Create;// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);// reducirtry
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
// Esta cambia el alto y ancho, estirando la imagen si es necesario
procedure Redimensionar(Imagen:TBitmap; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
begin
Bitmap:= TBitmap.Create;
// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);
// reducir
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Para modificar el color lo que he probado es a sumar los colores de los puntos escogidos al del pixel actual y luego hacer la media para obtener un color resultante; Así por ejemplo, para calcular el nuevo color de un pixel teniendo en cuenta el pixel superior y el inferior de la misma columna, utilizo un código como este:
1
2
3
4
5
6
// R1 es el componente Red del pixel actual y R2 y R3 los del sup. e inferior.
R1:=Round(R1 + R2 + R3 )div3;
G1:=Round(G1 + G2 + R3 )div3;
B1:=Round(B1 + B2 + b3 )div3;// color resultante
Result := RGB(R1,G1,B1);
// R1 es el componente Red del pixel actual y R2 y R3 los del sup. e inferior.
R1:=Round(R1 + R2 + R3 ) div 3;
G1:=Round(G1 + G2 + R3 ) div 3;
B1:=Round(B1 + B2 + b3 ) div 3;
// color resultante
Result := RGB(R1,G1,B1);
Lo que he hecho en las pruebas es aplicar a la imagen, esta mismo procedimiento, pero teniendo en cuenta diferentes selecciones de puntos.
Seleccionando 2 puntos; Superior e inferior.
Seleccionando 4 puntos; Superior, inferior, izquierda y derecha.
Seleccionando 8 puntos. Los 8 puntos que hay alrededor del pixels actual.
Seleccionando 8 puntos y aplicando ponderación al actual. Utilizar los 8 pixels que hay alrededor del actual, pero aplicando más peso (más valor) al pixels actual (a su color) que a los del resto. En mi caso el pisel actual tiene un peso de 4, mientras que el resto queda con un pero 1.
En un primer ejemplo he aplicado los dos primeros (2 y 4 pixels), pensando que no habría grandes cambios y la verdad es qe me ha sorprendido, ya que tomando tan sólo 2 puntos ya se notan algunos cambios y tomando 4 las dioferencias ya son bastante apreciables.
El resultado obtenido por este ejemplo es el siguiente:
La imagen superior es el original (redimensionado tal como lo hace delphi), y las dos inferiores son a las que se les ha aplicado el procedimiento de Antialiasing antes de redimensionarlas. En una escogiendo 2 los pixels laterales y en la otra los 4 pixels que rodean al del cálculo. Superior, inferior, izquierdo y derecho.
Como se puede ver, con dos pixels únicamente, ya hay zonas (1, 3 y 5) donde se aprecian diferencias. Seguramente en estas más que en otras porque la selección de pixels no es homogénea (de ahí que en las líneas horizontales se aprecie más mejora).
Cuando se aplica el algoritmo teniendo en cuenta los 4 pixels de alrededor, se aprecia (2, 3, 4 y 5) ya bastantes diferencias.
En el segundo ejemplo he aplicado los 4 casos comentados antes.
El resultado de este segundo ejemplo es el siguiente:
En este caso entre los dos últimos no se aprecia diferencia visible, pero sí entre escoger 4 puntos y 8 puntos. Ver los puntos marcados como 1 y 3.
Dado que no se aprecian grandes diferencias entre los dos últimos, he integrado en un último ejemplo el redimensionado y el procedimiento de Antialiasing, de forma que este segundo se realice de forma automática.
Aunque el ejemplo que se ha desarrallo aquí y el procedimiento parece que funcionan de manera aceptable, hay que tener en cuenta otros factores a la hora de realizar un algoritmo más completo.
En nuestro caso la distancia de pixel utilizada (muestreo) es una distancia 1; es decir, hemos seleccionado los pixels que hay más cercanos al que vamos a modificar. Podemos seleccionar pixels de distancias mayores (2 y 3); De esta forma el resultado puede ser más correcto, aunque esto también tiene que ver con el porcentaje de reducción del tamaño.
No es lo mismo reducir una imagen a la mitad de su tamaño, que al 10% del tamaño original. Segun el caso el resultado puede ser mejor o peor si seleccionamos pixels a distancias 1,2 y 3 del pixels a calcular.
type
TRGBTripleArray =array[0..32767]of TRGBTriple;
PRGBTripleArray =^TRGBTripleArray;...// Esta cambia el alto y ancho, estirando la imagen si es necesarioprocedure Redimensionar(Imagen:TBitmap; Ancho, Alto:Integer);var
Bitmap: TBitmap;//····························································// Procedimiento de Antialiasing con Distancia=1procedure Antialiasing(bmp1, bmp2:TBitmap);var
r1,g1,b1:Integer;
Y, X, j:integer;
SL1, SL2, SL3: PRGBTripleArray;begin// Tamaño del bitmap destino
bmp2.Height:= bmp1.Height;
bmp2.Width:= bmp1.Width;// SCANLINE
SL1 := bmp1.ScanLine[0];
SL2 := bmp1.ScanLine[1];
SL3 := bmp1.ScanLine[2];// reorrido para todos los pixelsfor Y :=1to(bmp1.Height-2)dobeginfor X :=1to(bmp1.Width-2)dobegin
R1 :=0; G1 :=0; B1 :=0;// los 9 pixels a tener en cuentafor j :=-1to1dobegin// FIla anterior
R1 := R1 + SL1[X+j].rgbtRed+ SL2[X+j].rgbtRed+ SL3[X+j].rgbtRed;
G1 := G1 + SL1[X+j].rgbtGreen+ SL2[X+j].rgbtGreen+ SL3[X+j].rgbtGreen;
B1 := B1 + SL1[X+j].rgbtBlue+ SL2[X+j].rgbtBlue+ SL3[X+j].rgbtBlue;end;// Nuevo color
R1:=Round(R1 div9);
G1:=Round(G1 div9);
B1:=Round(B1 div9);// Asignar el nuevo
bmp2.Canvas.Pixels[X, Y]:= RGB(R1,G1,B1);end;// Siguientes...
SL1 := SL2;
SL2 := SL3;
SL3 := bmp1.ScanLine[Y+1];end;end;//···························································· begin
Bitmap:= TBitmap.Create;// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);// reducirtry
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);finally
Bitmap.Free;end;end;
type
TRGBTripleArray = array[0..32767] of TRGBTriple;
PRGBTripleArray = ^TRGBTripleArray;
...
// Esta cambia el alto y ancho, estirando la imagen si es necesario
procedure Redimensionar(Imagen:TBitmap; Ancho, Alto: Integer);
var
Bitmap: TBitmap;
//····························································
// Procedimiento de Antialiasing con Distancia=1
procedure Antialiasing(bmp1, bmp2:TBitmap);
var
r1,g1,b1:Integer;
Y, X, j:integer;
SL1, SL2, SL3: PRGBTripleArray;
begin
// Tamaño del bitmap destino
bmp2.Height := bmp1.Height;
bmp2.Width := bmp1.Width;
// SCANLINE
SL1 := bmp1.ScanLine[0];
SL2 := bmp1.ScanLine[1];
SL3 := bmp1.ScanLine[2];
// reorrido para todos los pixels
for Y := 1 to (bmp1.Height - 2) do begin
for X := 1 to (bmp1.Width - 2) do begin
R1 := 0; G1 := 0; B1 := 0;
// los 9 pixels a tener en cuenta
for j := -1 to 1 do begin
// FIla anterior
R1 := R1 + SL1[X+j].rgbtRed + SL2[X+j].rgbtRed + SL3[X+j].rgbtRed;
G1 := G1 + SL1[X+j].rgbtGreen + SL2[X+j].rgbtGreen + SL3[X+j].rgbtGreen;
B1 := B1 + SL1[X+j].rgbtBlue + SL2[X+j].rgbtBlue + SL3[X+j].rgbtBlue;
end;
// Nuevo color
R1:=Round(R1 div 9);
G1:=Round(G1 div 9);
B1:=Round(B1 div 9);
// Asignar el nuevo
bmp2.Canvas.Pixels[X, Y] := RGB(R1,G1,B1);
end;
// Siguientes...
SL1 := SL2;
SL2 := SL3;
SL3 := bmp1.ScanLine[Y+1];
end;
end;
//····························································
begin
Bitmap:= TBitmap.Create;
// Aplicamos antialiasing
Antialiasing(Imagen, Bitmap);
Imagen.Assign(Bitmap);
// reducir
try
Bitmap.Width:= Ancho;
Bitmap.Height:= Alto;
Bitmap.Canvas.StretchDraw(Bitmap.Canvas.ClipRect, Imagen);
Imagen.Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Procedimiento que muestra cómo ordenar un TStringGrid a partir de los datos de una columna. El texto está basado en esta página Web donde hay un algoritmo de ordenación. El problema es que sólo es para datos numéricos. En este truco he añadido un par de parámetros para poder definir columnas de otro tipo (u ordenación de otro tipo) y además marcar si que quiere de forma Ascendente o Descendente (que tampoco está en el truco original).
Yo he añadido métodos para ordenar por enteros y Float, aunque ampliando se pueden añadir para alguno más.
// Ordena un TStringGrid.procedure SortStringGrid(var GenStrGrid: TStringGrid;
ThatCol:Integer;
ColData:TGridData=gdString;
SortOrder:TSortOrder=soASC);const
TheSeparator ='@';var
CountItem, I, J, K, ThePosition:integer;
MyList: TStringList;
MyString, TempString:string;
str:string;
vali:Integer;
valf:Double;begin
CountItem := GenStrGrid.RowCount;
MyList := TStringList.Create;
MyList.Sorted:=False;trybeginfor I :=1to(CountItem -1)dobegin
Str := GenStrGrid.Rows[I].Strings[ThatCol];if(ColData = gdInteger)thenbegin
vali :=StrToIntDef(Str,0);
Str :=Format('%*d',[15,vali]);end;if(ColData = gdFloat)thenbegin
valf :=StrToFloat(Str);
Str :=Format('%15.2f',[valf]);end;
MyList.Add(Str + TheSeparator + GenStrGrid.Rows[I].Text);end;
Mylist.Sort;for K :=1to Mylist.Countdobegin
MyString := MyList.Strings[(K -1)];
ThePosition :=Pos(TheSeparator, MyString);
TempString :='';{Eliminate the Text of the column on which we have
sorted the StringGrid}
TempString :=Copy(MyString,(ThePosition +1),Length(MyString));
MyList.Strings[(K -1)]:='';
MyList.Strings[(K -1)]:= TempString;end;if(SortOrder = soASC)thenbeginfor J :=1to(CountItem -1)dobegin
GenStrGrid.Rows[J].Text:= MyList.Strings[(J -1)];end;endelsebeginfor J :=1to(CountItem -1)dobegin
I :=(CountItem - J);
GenStrGrid.Rows[I].Text:= MyList.Strings[(J -1)];end;end;end;finally
MyList.Free;end;end;
// Ordena un TStringGrid.
procedure SortStringGrid(var GenStrGrid: TStringGrid;
ThatCol: Integer;
ColData:TGridData=gdString;
SortOrder:TSortOrder=soASC);
const
TheSeparator = '@';
var
CountItem, I, J, K, ThePosition: integer;
MyList: TStringList;
MyString, TempString: string;
str:string;
vali:Integer;
valf:Double;
begin
CountItem := GenStrGrid.RowCount;
MyList := TStringList.Create;
MyList.Sorted := False;
try
begin
for I := 1 to (CountItem - 1) do begin
Str := GenStrGrid.Rows[I].Strings[ThatCol];
if (ColData = gdInteger) then begin
vali := StrToIntDef(Str, 0);
Str := Format('%*d', [15,vali]);
end;
if (ColData = gdFloat) then begin
valf := StrToFloat(Str);
Str := Format('%15.2f',[valf]);
end;
MyList.Add(Str + TheSeparator + GenStrGrid.Rows[I].Text);
end;
Mylist.Sort;
for K := 1 to Mylist.Count do begin
MyString := MyList.Strings[(K - 1)];
ThePosition := Pos(TheSeparator, MyString);
TempString := '';
{Eliminate the Text of the column on which we have
sorted the StringGrid}
TempString := Copy(MyString, (ThePosition + 1), Length(MyString));
MyList.Strings[(K - 1)] := '';
MyList.Strings[(K - 1)] := TempString;
end;
if (SortOrder = soASC) then begin
for J := 1 to (CountItem - 1) do begin
GenStrGrid.Rows[J].Text := MyList.Strings[(J - 1)];
end;
end
else begin
for J := 1 to (CountItem - 1) do begin
I := (CountItem - J);
GenStrGrid.Rows[I].Text := MyList.Strings[(J - 1)];
end;
end;
end;
finally
MyList.Free;
end;
end;
AÑADO: Me falta una cosa.
Además habrá que definir en la unit los dos tipos que se utilizan para la ordenación:
1
2
3
4
5
Type//: Tipo de Dat de la columna por la que queremos ordenar.
TGridData =(gdString, gdInteger, gdFloat);//: Tipos de ordenación.
TSortOrder =(soASC, soDESC);
Type
//: Tipo de Dat de la columna por la que queremos ordenar.
TGridData = (gdString, gdInteger, gdFloat);
//: Tipos de ordenación.
TSortOrder = (soASC, soDESC);
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Sencillo ejemplo que muestra cómo utilizar packages dinámicos en una aplicación para obtener caraterísticas de plug-ins.
Los packages (BPL’s) con diferentes funcionalidades se crean de forma independiente y el programa se encarga de cargarlos al inicio y «colgarlos» o añadirlos a un menú en la aplicación principal.
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 este ejemplo se muestra de forma sencilla cómo «pasar» o convertir un Menu existente (componente TMainMenu) en un TTreeView; Conservando la jerarquía de los elementos y asignando las imágenes que ya existan en el ejemplo.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Se ha ampliado el ejemplo anterior sobre threads, para mostrar cómo se puede configurar el número de threads que se deseen para ejecutar una tarea concreta. Se puede definir un número máximo (por el usuario) y se muestra cómo se van ejecutando threads.
A medida que los primeros van acabando, se lanzan nuevos hasta completar todo el proceso.
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,…