Friday, 31 July 2009

Thursday, 30 July 2009

Creando un Parser para Delphi

En éste artículo os mostraré lo fácil que es generar una pequeña gramática mediante ParserBuilder - COCO/R y crear el proyecto en Delphi para que parsee los datos entrados. En éste caso utilizaré el mismo ejemplo que viene con el ParserBuilder para crear una pequeña calculadora. La descripción del ejemplo para ésta calculadora es la siguiente:

La calculadora tiene un funcionamiento similar al de un intérprete, cuando recibe como entrada una expresión matemática sencilla como por ejemplo “12 + 5”, calcula el resultado de la operación y lo muestra por pantalla.
  • Los tipos de datos que puede manipular la calculadora son entero y real.
  • El formato de los números enteros es el habitual, es decir, un número entero es una secuencia de uno o más dígitos del 0 al 9.
  • El formato de los números reales consiste en un parte entera formada por una secuencia de cero o más dígitos del 0 al 9, a continuación un punto, y seguidamente la parte fraccionaria formada por una secuencia de uno o más dígitos del 0 al 9.
  • Las operaciones disponibles son: suma, resta, multiplicación y división para el tipo de datos real, y suma, resta y multiplicación para el tipo de datos entero. No está permitida ninguna operación aritmética con operandos de tipos diferentes.
  • Todas las operaciones se pueden agrupar con paréntesis.
  • Una expresión termina con un =.
  • El usuario puede introducir espacios en blanco y tabuladores, y todos serán ignorados. Las líneas vacías no están permitidas (una línea en blanco es un error sintáctico).
Utilizando ParserBuilder crearemos un fichero que contendrá la gramática de nuestra calculadora (.ATG). Aquí podréis descargar la versión que he utilizado ParserBuilder v0.9.0.213. Una vez tenemos el fichero en nuestro equipo, descomprimimos el fichero parserbuilder_v0_9_0_213_bin.zip e iniciamos ParserBuilder. Una vez abierto, podemos ver que el ejemplo que abre es la calculadora que vamos a utilizar.

Ahora, si nos dirigimos al final del fichero, podremos ver la gramática utilizada para parsear el lenguaje utilizado.

gramática:

CHARACTERS
digit =  "0123456789" .

TOKENS
num = digit {digit}.

NAMES

IGNORE CHR(1)..CHR(31)
PRAGMAS
PRODUCTIONS
Expr =
        (. Init ; .)
        Expression < fResult >
        "=" (. Final ; .)
        .
Expression <VAR e: INTEGER>
        (. VAR t: INTEGER ; .)
        =
        Term < e >  { '+' Term < t >       (. e := e + t  .)
        | '-' Term < t >                   (. e := e - t  .)
        }
        .
Term <VAR t: INTEGER>
        (. VAR f: INTEGER ; .)
        =
        Factor < t >  { '*' Factor < f >   (. t := t * f  .)
        | '/' Factor < f >                 (. t := t DIV f  .)
        }
        .
Factor <VAR f: INTEGER> =
        num                                (. GetNumber(f)  .)
|
        '(' Expression < f > ')'
        .


Una vez tenemos la gramática creada, generamos el proyecto para delphi:

Si nos fijamos, nos ha creado el código fuente para Delphi:


Ahora si nos dirigimos a la ruta C:\TEMP\parserbuilder_v0_9_0_213\Samples\Expr, encontraremos el proyecto Delphi para iniciarlo.

Al cargar el proyecto ExprTestHarness.dpr observaréis que puede ser que os salte un error debido a que no tenemos instalado el componente TJVDragDrop. Simplemente, ignoramos todos los errores y eliminamos el componente. En éste ejemplo no lo vamos a necesitar.

Et voilà!, ya tenemos nuestra calculadora:


Sobretodo fijaros en en código fuente generado y como crea un Parser y un Scanner para el lenguaje entrado. Espero que os haya gustado el artículo y que os sirva de ayuda!.

Wuul Software

Hoy os traigo un enlace interesante de una web llamada Wuul Software, donde el autor ha dejado bastantes aplicaciones Open Source y de libre descarga hechas en Delphi (incluye código fuente) y que son muy interesantes. Aquí os dejo la lista de aplicaciones:
La verdad es que no tienen desperdicio y os aconsejo echarles un vistazo e incluso mirar el código fuente. Aquí os dejo algunas imágenes de sus aplicaciones:


Wednesday, 29 July 2009

Jugando con Elipses en Delphi

Hoy os traigo una pequeña aplicación didáctica en delphi que dibuja elipses (mejor dicho círculos, pero utilizo el metodo elipse del TCanvas para dibujarlo) y detecta las colisiones entre estas arrastrando el mouse por encima. Observareis que es muy simple pero sirve para jugar un poco con el canvas y el ratón y ver que tipo de aplicaciones visuales podemos llegar a hacer con delphi. Al pasar el mouse por encima del objeto detecta su paso y muestra un color de colisión. Además al mover una de las elipses hacia otro objeto, se detecta la colisión entre estos y se frena el movimiento si dejarte mover el objeto. La detección es muy sencilla, mediante la posición del mouse se comprueba que la distancia de éste con el centro de cualquiera de las otras elipses sea mayor que la distancia del objeto a arrastrar y las otras elipses, un principio muy simple pero que funciona muy bien y sin tener que hacer cálculos desorbitados. Aquí os dejo la aplicación Thundax Draw Ellipses, y parte del código fuente y un pantallazo de la aplicación:


La clase TEllipse:

unit ThundaxEllipse;

interface

uses
    types, Graphics, Classes, Controls;

type
    TEllipse = class(TObject)
    private
        FborderColor: TColor;
        Flength: integer;
        FlineWidth: integer;
        FCollisionColor: TColor;
        FColor: TColor;
        Fposition: TPoint;
        FCanvas: TCanvas;
        FDrag: boolean;
        Fid: string;
        Fradium: integer;
        FCenter: TPoint;
        procedure SetborderColor(const Value: TColor);
        procedure SetCollisionColor(const Value: TColor);
        procedure SetColor(const Value: TColor);
        procedure Setlength(const Value: integer);
        procedure SetlineWidth(const Value: integer);
        procedure Setposition(const Value: TPoint);
        procedure SetDrag(const Value: boolean);
        procedure SetCenter(const Value: TPoint);
        procedure Setid(const Value: string);
        procedure Setradium(const Value: integer);
    public
        property position: TPoint read Fposition write Setposition;
        property id: string read Fid write Setid;
        property Drag: boolean read FDrag write SetDrag;
        property length: integer read Flength write Setlength;
        property Color: TColor read FColor write SetColor;
        property borderColor: TColor read FborderColor write SetborderColor;
        property lineWidth: integer read FlineWidth write SetlineWidth;
        property CollisionColor: TColor read FCollisionColor write SetCollisionColor;
        constructor Create(Canvas: TCanvas; center: TPoint; length: integer);
        function PointInside(x, y: integer): boolean;
        procedure SetMousePosition(x, y: integer);
        procedure Draw(x, y: integer);
        procedure EnableDrag(x, y: integer);
        property Center: TPoint read FCenter write SetCenter;
        property radium: integer read Fradium write Setradium;
    end;
implementation

{ TEllipse }

constructor TEllipse.Create(Canvas: TCanvas; center: TPoint; length: integer);
begin
    FCanvas := Canvas;
    Fposition := center;
    FLength := length;
    FDrag := false;
    FCenter := point(Fposition.x + (Flength div 2), Fposition.y + (Flength div 2));
    FRadium := FLength div 2;
end;

procedure TEllipse.Draw(x, y: integer);
begin
    if FDrag then
        SetMousePosition(x, y);
    if PointInside(x, y) then
        FCanvas.Brush.Color := FCollisionColor
else
        FCanvas.Brush.Color := FColor;

    FCanvas.Pen.Color := FborderColor;
    FCanvas.Pen.Width := 1;
    FCanvas.Ellipse(Fposition.x, Fposition.y, Fposition.x + FLength, Fposition.y + FLength);
    FCanvas.Font.Color := clWhite;
    FCanvas.TextOut(Fposition.x + (Flength div 2) - 5, Fposition.y + (Flength div 2) - 7, FId);
end;

procedure TEllipse.EnableDrag(x, y: integer);
begin
    if PointInside(x, y) then
        Fdrag := true;
end;

function TEllipse.PointInside(x, y: integer): boolean;
var
    x0, y0, a, b: integer;
    point2: TPoint;
begin
    point2 := Point(Fposition.x + Flength, FPosition.y + Flength);
    x0 := (Fposition.x + point2.x) div 2;
    y0 := (Fposition.y + point2.y) div 2;
    a := (point2.x - Fposition.x) div 2;
    b := (point2.y - Fposition.y) div 2;
    result := (Sqr((x - x0) / a) + Sqr((y - y0) / b) <= 1);
end;

procedure TEllipse.SetborderColor(const Value: TColor);
begin
    FborderColor := Value;
end;

procedure TEllipse.SetCenter(const Value: TPoint);
begin
    FCenter := Value;
end;

procedure TEllipse.SetCollisionColor(const Value: TColor);
begin
    FCollisionColor := Value;
end;

procedure TEllipse.SetColor(const Value: TColor);
begin
    FColor := Value;
end;

procedure TEllipse.SetDrag(const Value: boolean);
begin
    FDrag := Value;
end;

procedure TEllipse.Setid(const Value: string);
begin
    Fid := Value;
end;

procedure TEllipse.Setlength(const Value: integer);
begin
    Flength := Value;
end;

procedure TEllipse.SetlineWidth(const Value: integer);
begin
    FlineWidth := Value;
end;

procedure TEllipse.SetMousePosition(x, y: integer);
begin
    FPosition := point(x - (Flength div 2), y - (Flength div 2));
    FCenter := point(Fposition.x + (Flength div 2), Fposition.y + (Flength div 2));
end;

procedure TEllipse.Setposition(const Value: TPoint);
begin
    Fposition := Value;
end;

procedure TEllipse.Setradium(const Value: integer);
begin
    Fradium := Value;
end;

end.


El cálculo de las colisiones:

procedure TForm3.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
    i, j: integer;
    Collide: boolean;
    textCollision: string;
    Collisions: array[1..20] of integer;
begin
    DrawRectangle(clWhite, clWhite);
    Memo1.lines.Clear;
    textCollision := '';
    Collide := false;
    for i := 1 to 20 do
        Collisions[i] := -1;

    for i := 1 to number do
    begin
        for j := 1 to number do
            if Ellipses[i].drag and
                distanciaMouse(Point(x, y), Ellipses[i].center, Ellipses[j].center) and
                Collision(ellipses[i], ellipses[j]) then
            begin
                Memo1.lines.add('Collide True ->' + inttostr(i) + ' ' + inttostr(j));
                Collide := true;
                Collisions[i] := i;
            end;
    end;

    for i := 1 to number do
    begin
        if Collisions[i] = -1 then
            ellipses[i].Draw(x, y)
        else
            ellipses[i].draw(ellipses[i].center.x, ellipses[i].center.y);
        Memo1.lines.add('Collision ' + inttostr(Collisions[i]));
    end;

    if collide then
        textCollision := 'Collision!';

    StatusBar1.Panels[0].Text := 'x : ' + inttostr(x) + ' y : ' + inttostr(y) + ' ' + textCollision;
end;

function TForm3.DistanciaMouse(p1, p2, p3: TPoint): boolean;
begin
    result := false;
    if (p2.x <> p3.x) and (p2.y <> p3.y) then
        result := (distancia(p1, p3) < distancia(p2, p3))
end;

function TForm3.Collision(p1, p2: TEllipse): boolean;
begin
    result := false;
    if p1 <> p2 then
        result := distancia(p1.center, p2.center) < (2 * (p1.radium));
end;

function TForm3.distancia(p1, p2: TPoint): double;
begin
    result := sqrt(sqr(p1.x - p2.x) + sqr(p1.y - p2.y));
end;


Obtener las claves instaladas en nuestro ordenador

Muchas veces nos podemos encontrar en situaciones desesperadas donde hemos perdido el serial de alguna aplicación instalada y necesitamos recuperar esa key de alguna manera. Pues bien, aquí os traigo una aplicación muy interesante llamada Magical Jelly Bean Keyfinder y está escrita en Delphi. Es un proyecto Open Source y encontraréis las fuentes en Source Forge. Podréis descargar la aplicación y el código fuente en el siguiente enlace: KeyFinder v2.0.6. Una vez iniciamos la aplicación, podremos visualizar los programas instalados y que Keyfinder detecta, y nos mostrará los serials de cada aplicación para que podamos guardar esas claves y ponerlas a buen recaudo.


Si queremos encontrar las claves por ejemplo en nuestro windows xp sp2, podremos encontrarla si nos dirigimos a C:\WINDOWS\system32\oobe\oobeinfo.ini. En éste fichero encontraremos la clave instalada en nuestro sistema.

Tuesday, 28 July 2009

Convertir ficheros Excel 2007 a dBase (DBF)

En mi anterior post, indiqué que desde excel 2007 no se podían guardar los cambios de un excel a un fichero dBase (DBF). Pues bien, hay una manera de hacerlo pero con la ayuda de nuestro Access 2007. Primero creamos nuestro fichero Excel y lo guardamos en una ruta conocida.


Luego iniciamos Microsoft Access 2007, y creamos una nueva base de datos en blanco:


Luego creamos nuestra BD y importamos nuestro fichero Excel:

Ahora marcamos la ruta desde donde importaremos el excel que hemos creado:

Seguimos con la importación, y elegimos la hoja que queremos importar:

Una vez importada la tabla, la podemos visualizar dentro de Microsoft Access:

Ahora, ya estamos preparados para generar nuestro fichero dBase (.DBF). Nos situamos encima de la Hoja1 y con el botón derecho hacemos Exportar -> a Archivo de dBase:

Ahora guardamos el fichero en el formato especificado:

Ahora podemos visualizar la tabla desde mi aplicación Thundax DBF Editor:

Espero que os sirva de ayuda.

Editando ficheros dBase (DBF) con Delphi

Debido a las mejoras de Microsoft Office (versión 2007), desaparece la parte de edición y guardado de las tablas en .dbf (DBase IV). Por eso, he creado una pequeña aplicación con delphi que permite realizar una series de consultas SQL y además permite editar la tabla en sí. De esta manera podemos seguir conservando nuestros ficheros DBase y con una aplicación sencilla realizar potentes consultas sobre la table mediante SQL. La aplicación utiliza los componentes típicos de conexión a la BD, y mediante ADO realizo la conexión sobre una carpeta para simular el ConnectionString hacia un ODBC con la ruta del los ficheros DBF. Luego tengo una pequeña grid enlazada al ClientDataSet que recoge los datos y los muestra. Aquí os dejo unas imágenes de la aplicación Thundax DBF Editor.

Aquí os dejo parte del código fuente para que veáis lo simple que es la aplicación:

procedure TForm1.Button1Click(Sender: TObject);
var
    strDBFolder, TextExecute: string;
begin
    if Edit1.text = '' then
        exit;
    if listbox1.Count = 0 then
        exit;
    Screen.Cursor := crSQLWait;
    strDBFolder := Edit1.text;
    ADOConnection1.Close;
    ADOConnection1.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=' +
        strDBFolder +
        ';Extended Properties=dBASE IV;User ID=' + Edit2.text + ';Password=' + Edit3.text + ';';
    ADOConnection1.Open;
    if SelectedText = '' then
        TextExecute := SynEdit1.text
else
        TextExecute := SelectedText;

    if TextExecute = '' then
        exit;

    if not AnsiContainsStr(AnsiUpperCase(TextExecute), 'SELECT') then
        ADOConnection1.Execute(TextExecute)
    else
    begin
        ADOQuery1.Close;
        ClientDataSet1.close;
        ADOQuery1.SQL.Text := TextExecute;
        ADOQuery1.Open;
        ClientDataSet1.open;
    end;
    ADOConnection1.Close;
    Screen.Cursor := crDefault;
    StatusBar1.SimpleText := 'Done.';
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
    if clientdataset1.Delta <> null then
        if messagedlg('Save changes ? ', mtConfirmation, mbOKCancel, -1) = mrOK then
            clientdataset1.ApplyUpdates(0)
        else
            abort;
end;

  • Enlaces de interés:
Formatos de archivo que permite Excel 2007.

Mostrar el Dialogo de carpetas

Con delphi no tenemos ningún dialogo para mostrar solo las carpetas. Disponemos de los componentes TDialog que nos permiten abrir ficheros, seleccionarlos o abrir otros tipos de dialogo como los de colores, fuentes, etc. Para cargar solo el dialogo de carpetas, lo tenemos que hacer como siempre se ha hecho, utilizando la Win32 API Shell objects Interface Unit (ShlObj). En ésta unit encontraremos toda la información para ejecutar nuestro dialogo. Aquí os dejo el código fuente sacado de Scalabium Software y unas imágenes de muestra del dialogo.

Con el siguiente código fuente, obtendremos el dialogo mostrado a continuación:




uses
SysUtils
, ShlObj;

function BrowseCallbackProc(hwnd: HWND; uMsg: UINT; lParam: LPARAM; lpData: LPARAM): Integer; stdcall;
begin
if (uMsg = BFFM_INITIALIZED) then
SendMessage(hwnd, BFFM_SETSELECTION, 1, lpData);
BrowseCallbackProc := 0;
end;

function GetFolderDialog(Handle: Integer; Caption: string; var strFolder: string): Boolean;
const
BIF_STATUSTEXT = $0004;
BIF_NEWDIALOGSTYLE = $0040;
BIF_RETURNONLYFSDIRS = $0080;
BIF_SHAREABLE = $0100;
BIF_USENEWUI = BIF_EDITBOX or BIF_NEWDIALOGSTYLE;
var
BrowseInfo: TBrowseInfo;
ItemIDList: PItemIDList;
JtemIDList: PItemIDList;
Path: PAnsiChar;
begin
Result := False;
Path := StrAlloc(MAX_PATH);
SHGetSpecialFolderLocation(Handle, CSIDL_DRIVES, JtemIDList);
with BrowseInfo do
begin
hwndOwner := GetActiveWindow;
pidlRoot := JtemIDList;
SHGetSpecialFolderLocation(hwndOwner, CSIDL_DRIVES, JtemIDList);
pszDisplayName := StrAlloc(MAX_PATH);
lpszTitle := PChar(Caption);
lpfn := @BrowseCallbackProc;
lParam := LongInt(PChar(strFolder));
end;
ItemIDList := SHBrowseForFolder(BrowseInfo);
if (ItemIDList <> nil) then
if SHGetPathFromIDList(ItemIDList, Path) then
begin
strFolder := Path;
Result := True
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
begin
s := 'C:\';
if GetFolderDialog(Application.Handle, 'Select a folder', s) then
Edit1.text := s;
end;





Friday, 24 July 2009

Implementando Diagramas de Voronoi en Delphi

Hoy os muestro uno de los últimos problemas de geometría computacional que me queda por mostrar y resolver. En éste caso se trata de los diagramas de Voronoi o Polígonos de Thiessen. Éste es el que me ha representado dificultad para su resolución, pero bueno con una buena base matemática es bastante posible. Además me he servido de mis anteriores aplicaciones Thundax Convex Hull y Spanning Tree, las cuales aparecen en ésta aplicación también. Los diagramas de voronoi se aplican en estudios en los que hay que determinar áreas de influencia (centros hospitalarios, estaciones de bomberos, bocas de metro, centros comerciales, control del tráfico aéreo, telefonía móvil, análisis de poblaciones de especies vegetales, etc.). Es una de las funciones de análisis básicas en los Sistemas de información geográfica. La aplicación que os dejo aquí aún está en fase de desarrollo, pero ya se pueden ver y calcular los diagramas correctamente. Aquí os dejo unas imágenes de la aplicación Thundax Voronoi.

Aquí os dejo un documento muy interesante que explica el cálculo de las celdas: voronoi.pdf. También os dejo unos cuantos applets los cuales podréis probar online:

Voronoi/Delaunay Applet:
Voronoi Game Applet:

  • Enlaces de interés:
Colección applets voronoi.

Separando units en Delphi

Delphi, a diferencia de Java, puede albergar diferentes clases definidas en una misma unidad (.pas), por lo tanto al final puede que tengamos un fichero con miles de líneas de código que pueden hacernos ir un poco mal. En este caso la opción para separar nuestras clases en diferentes ficheros es bastante sencillo, solo tenemos que poner la clausula {$I file1.pas}. De esta manera, podemos tener creadas nuestras clases en un fichero y separar la definición de sus métodos en otros archivos. Para acceder al fichero con la clausula $I, solo hay que ejecutar CTRL + ENTER mientras el cursor está encima del nombre del fichero.
Con el siguiente código lo veréis más claro:




unit myGraph;

interface

uses
Classes;

type
TMyPoint = class(TObject)
private
x,y :extended;
public
closeDist :extended;
constructor Create(x,y:extended;L:TList;C:TCanvas);
function Clone:TGraphObject;override;
procedure draw;override;
procedure clear;override;
function getX:extended;
function getY:extended;
function DistanceTo(p:TMyPoint):extended;overload;
function DistanceTo(x,y:extended):extended;overload;
procedure MoveTo(x,y:extended);
function Match(p:TMyPoint):boolean;overload;
function Match(x,y:extended):boolean;overload;
function Angle(p:TMyPoint):extended;
function IsRightTurn(p1,p2:TMyPoint):boolean;
function areCollinear(a,b:TMyPoint):boolean;
function Bisector(p:TMyPoint):TGLine;
function CircleCenter(a,b:TGPoint):TMyPoint;
end;

implementation

uses
types,sysutils,dialogs,math, main, forms;

{$I Graph.pas}
{$I Lines.pas}
{$I Pointer.pas}

end.



Luego en los ficheros definidos al final de la unit, creamos la implementación de nuestra clase.

Thursday, 23 July 2009

Implementando automated linked objects con Delphi parte III

Bueno, continuando con la misma línea de ayer, esta vez he hecho un par de cambios que determinan que las conexiones o enlaces también son objetos. De ésta manera al seleccionar un enlace este se marca como seleccionado y nos aparecen sus propiedades aún muy austeras pero la base ya está fabricada. Los cambios más importantes de diseño radican en la modificación de la clase TLinkPainter que ahora recoge los datos de TConnector que éste es el que recoge los puntos de la conexión. Si observamos el diagrama de diseño obtenemos:

Como podéis observar en el diagrama, ahora existe TConector que tiene una lista de vértices. Éstos vértices son los puntos de conexión creados por el Controlador. Una vez tengo la lista de los puntos, o sea que he calculado los vectores de conexión entre líneas, los puedo enviar al TLinkPainter para que los dibuje sobre el canvas. La modificación es importante ya que he eliminado parte importante del TLinkPainter ya que éste solo dibujaba líneas sobre el canvas y ahora es un "objeto".

Si observamos la aplicación, nos aparecen las propiedades del enlace de la siguiente manera:

Ahora, al hacer click sobre una de las cajas o sobre la línea de unión, podemos reconocer el objeto que tenemos bajo el mouse. Aquí os dejo la última versión de la aplicación ThundaxGeometricLinks v1.0.0 build 36.

Aquí os dejo también la definición de la clase TConnector:




type
TVertexList = class(TObjectList)
protected
function GetItem(Index: Integer): TVertex; overload;
procedure SetItem(Index: Integer; AObject: TVertex);
public
property Items[Index: Integer]: TVertex read GetItem write SetItem; default;
end;

TConnector = class(TPersistent)
private
vertexList: TVertexList;
Fcolor: TColor;
FPenWidth: integer;
FSelectedColor: TColor;
FSelected: boolean;
procedure Setcolor(const Value: TColor);
procedure SetPenWidth(const Value: integer);
procedure SetSelected(const Value: boolean);
procedure SetSelectedColor(const Value: TColor);
published
property color: TColor read Fcolor write Setcolor;
property PenWidth: integer read FPenWidth write SetPenWidth;
property SelectedColor: TColor read FSelectedColor write SetSelectedColor;
public
property Selected: boolean read FSelected write SetSelected;
constructor Create(color: TColor; SelectedColor: TColor);
procedure Add(vertex: TVertex);
function isInside(vertex: TVertex): boolean;
procedure EmptyList();
destructor Destroy(); override;
end;





Palabras de búsqueda:
Connect two objects.
Draw line between two points
Draw connector
  • Enlaces de interés:
Computational Geometry.
Algorithmic Geometry.

Convertir una imagen en HTML

Hoy os traigo una aplicación muy interesante y que mucha gente utiliza para convertir una imagen en HTML aplicando un cambio en el mostrado de los pixels por texto. Es muy interesante y realmente da mucho juego para nuestras imágenes y para poder dar un toque original. La aplicación la podréis encontrar en Sulaco, dónde hay un montón de ejemplos del tipo OpenGL en Delphi.

Por ejemplo, la imagen siguiente se convertiría :

La aplicación se llama HTML Image Page generator, y la podéis encontrar aquí.

En éste enlace, también podréis encontrar un ejemplo muy interesante del Schwarzenegger.

Wednesday, 22 July 2009

Implementando automated linked objects con Delphi parte II

Como podéis comprobar la aplicación sigue adelante. En éste caso he mejorado todo el tema de las propiedades y su edición implementando un MVC (Modelo vista controlador) en el TBox. Ahora existe un TBoxView que se encarga de la visualización y de manejar los eventos de éste y mediante el TBoxManager crearemos el controlador para el TBox. Si observamos la nueva distribución de clases en nuestra aplicación tenemos:

Nuestro modelo Tbox es gestionado por el TboxManager y visualizado por el TBoxView. Encontraréis muchas referencias sobre éste patrón (MVC) en diversos libros y en las referencias que dejaré en éste artículo. El problema del diseño original es que al querer modificar las propiedades de la Tbox desde la pantalla, eso requiere que modifiques directamente este objeto y no debería de ser así. Para eso se crea un visualizador, que es el que gestiona la vista de la Tbox sobre la pantalla, y las modificaciones de estado se gestionan sobre el manager y así de vuelta al modelo. De esta manera tenemos separados la lógica de programa, la interfaz de usuario y la lógica de control. En mi caso el visor se implementa sobre un TImage, y se controlan los eventos sobre éste.

Aquí os dejo el siguiente build de la aplicación Thundax Geometric Links v1.0.0 build 20. Comprovaréis que la aplicación ha augmentado su tamaño (más de 5 Mb), eso es debido al componente RTTI que carga mucha parte de la VCL de DevExpress. Aquí os dejo unas imágenes para que podáis ver el resultado:

Aún queda mucho trabajo por hacer y muchos módulos por encajar pero ya llegará. Ya lo iréis viendo!.

Veamos un poco la codificación creada:

Creación de la aplicación MVC:




procedure TForm1.FormCreate(Sender: TObject);
begin
BoxP := TBoxPainter.Create(image1);
BoxL := TLinkPainter.Create(image1, SourceBox, TargetBox);
BoxW := TBoxView.Create(image1, SourceBox, TargetBox, BoxP, BoxM, BoxL);
BoxM := TBoxManager.Create(SourceBox, TargetBox);
BoxW.OffsetBox := offsetBox;
BoxW.SetInspector(cxRTTIInspector1);
BoxW.Draw();
end;




Modificación de la clase TBox:




TVertex = class(TPersistent)
private
Fx: integer;
Fy: integer;
procedure Setx(const Value: integer);
procedure Sety(const Value: integer);
published
property x: integer read Fx write Setx;
property y: integer read Fy write Sety;
public
constructor Create(x: integer; y: integer); overload;
constructor Create(point: TPoint); overload;
function getPoint(): TPoint;
procedure setPoint(point: TPoint);
end;

TBox = class(TPersistent)
private
FColor: TColor;
FSize: integer;
FOffset: integer;
FDirection: TDirection;
FlinkColor: TColor;
Flink: TVertex;
Fcenter: TVertex;
Fvertex: array[0..3] of TVertex;
FIntersection: TVertex;
FColisionColor: TColor;
FDrawColor: TColor;
function getIntersectionPoint(): TVertex;
procedure SetColor(const Value: TColor);
procedure SetOffset(const Value: integer);
procedure SetSize(const Value: integer);
procedure SetDirection(const Value: TDirection);
procedure SetlinkColor(const Value: TColor);
procedure SetcenterPoint(const Value: TVertex);
procedure SetlinkPoint(const Value: TVertex);
procedure Setvertex(Index: integer; value: TVertex);
function Getvertex(Index: integer): TVertex;
procedure SetLink(point: TVertex);
procedure SetColisionColor(const Value: TColor);
procedure SetDrawColor(const Value: TColor);
published
property vertex0: TVertex index 0 read Getvertex write Setvertex;
property vertex1: TVertex index 1 read Getvertex write Setvertex;
property vertex2: TVertex index 2 read Getvertex write Setvertex;
property vertex3: TVertex index 3 read Getvertex write Setvertex;
property center: TVertex read Fcenter write SetcenterPoint;
property link: TVertex read Flink write SetlinkPoint;
property Direction: TDirection read FDirection write SetDirection;
property intersection: TVertex read getIntersectionPoint;
property Offset: integer read FOffset write SetOffset;
property Size: integer read FSize write SetSize;
property Color: TColor read FColor write SetColor;
property linkColor: TColor read FlinkColor write SetlinkColor;
property ColisionColor: TColor read FColisionColor write SetColisionColor;
property DrawColor: TColor read FDrawColor write SetDrawColor;
public
function BetweenX(point: TPoint): boolean; overload;
function BetweenY(point: TPoint): boolean; overload;
function BetweenX(point: TVertex): boolean; overload;
function BetweenY(point: TVertex): boolean; overload;
procedure SetCentre(vertex: TPoint);
procedure SetLinkDirection(up, down, left, right: boolean);
function PointInside(point: TPoint): boolean;
constructor Create(centerPoint: TPoint; Size: integer; Offset: integer; Color: integer; linkColor: integer; ColisionColor: integer);
destructor Destroy(); override;
end;




Implementación de los eventos de Mouse en la vista:




procedure TBoxView.Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if FTargetBox <> nil then
if FTargetBox.pointInside(point(x, y)) then
begin
activeDragTarget := true;
cxRTTIInspector1.InspectedObject := nil;
cxRTTIInspector1.InspectedObject := FTargetBox;
end;
if FSourceBox <> nil then
if FSourceBox.pointInside(point(x, y)) then
begin
activeDragSource := true;
cxRTTIInspector1.InspectedObject := nil;
cxRTTIInspector1.InspectedObject := FSourceBox;
end;
if (not activeDragSource) and (not activeDragTarget) then
cxRTTIInspector1.InspectedObject := nil;
end;

procedure TBoxView.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if activeDragTarget then
begin
FTargetBox.SetCentre(Point(x, y));
Draw();
end;
if activeDragSource then
begin
FSourceBox.SetCentre(Point(x, y));
Draw();
end;
end;

procedure TBoxView.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
activeDragTarget := false;
activeDragSource := false;
end;



  • Enlaces de Interés:
Framework MVC para Delphi por EazySoft. Descarga directa del eMVC.
MVP por Joanna Carter. Descarga directa del MVPSource.
Java Model View Controller.