Showing posts with label TCanvas. Show all posts
Showing posts with label TCanvas. Show all posts

Monday, 21 September 2009

Creando un editor de propiedades personalizado para TLine

Continuando con la edición de propiedades de mis componentes, aquí os traigo por fin la edición del componente TLine. Como habéis podido comprobar con los 2 artículos anteriores: Punto perteneciente a un segmento y Implementando el patrón Adapter, me han permitido llevar a cabo el siguiente punto de la implementación de Thundax Box Manager. Ahora todos los componentes visuales que aparecen en la pantalla son totalmente editables. Aún quedan cosas pendientes referentes a la edición de TBox, pero por lo menos la parte de TLine está casi acabada en la versión ThundaxBoxManager v1.0.0 build 120.exe.

Mediante la pantalla de propiedades que está implementada de igual manera que la pantalla de propiedades de TBox utilizando un patrón MVC para poder gestionar los cambios de los diferentes objetos. Además ésta pantalla de propiedades utiliza el patrón Adapter para poder devolver diferentes tipos de líneas y castear los diferentes objetos entre sí para poder dibujar así la línea que más nos interese.

Por lo tanto, ahora podemos llegar a crear gráfos como los que siguen:

La edición del gráfico la podemos realizar haciendo doble click sobre el objeto que nos interese. De momento la edición es modal (para evitar errores) y ya la iré modificando para albergar una múltiple edición por selección.



Punto perteneciente a un segmento

Continuando con el diseño de mi aplicación Thundax Box Manager, hoy os muestro la resolución de un problema típico al trabajar con selección de líneas sobre un canvas. El problema reside en calcular si un punto pertenece a una recta o no. Hacerlo a mano es bastante fácil, el problema es hacerlo con el PC. Aquí podéis ver una imagen que representa el problema en cuestión:

Verificar la ecuación manualmente es sencillo, ya que si disponemos de la ecuación de la recta del tipo y = mx + b (forma simplificada), solo tenemos que reemplazar los parámetros "x" y "y" y comprobar que hay un equilibrio en el resultado de la ecuación.

Matemáticamente, tendríamos que calcular la ecuación, y luego probar los parámetros sobre ésta y comprobar el equilibrio. Pero además lo que tenemos que hacer, es añadir un pequeño offset para que la selección de la línea ocurra cuando el puntero del mouse esté bastante cerca de la línea y no tengamos que activar el punto concreto de la línea, que puede llegar a ser molesto (intentar apuntar a un pixel es complicado).

Pues bien, con unas cuantas funciones, podemos llevar a cabo ésta configuración para que la selección de las líneas de la aplicación sea cómoda y fácil.

Por la red podréis encontrar innumerables trozos de código que solucionan éste problema, y además en cualquier lenguaje: Delphi, Java, C++, etc. Simplemente debéis escoger el que os vaya más bien.

Aquí os dejo el que he utilizado yo, una implementación de Juliano Zabeo que nos permite las opciones indicadas anteriormente.


uses
Math;

function pointBelongToLine(const target : Tpoint; line: Tline): boolean;
const
offset = 4;
var
lineInclination : TlineInclination;
max, min, calc : integer;
inclination, interception : double;
begin
result := false;
LineProperties(line, inclination, interception, lineInclination);

case lineInclination of
Hoz:
begin
min := minintvalue([line.source.x, line.target.x]);
max := maxintvalue([line.source.x, line.target.x]);
if (target.x >= min) and (target.x <= max) then
begin
calc := round( inclination * target.x + interception );
if abs(calc - target.y) <= offset then
result := true
end
end;
Vrt:
begin
min := minintvalue([line.source.y, line.target.y]);
max := maxintvalue([line.source.y, line.target.y]);
if (target.y >= min) and (target.y <= max) then
begin
calc := round( inclination * target.y + interception );
if abs(calc - target.x) <= offset then
result := true
end
end;
end
end;


El resultado lo podéis ver en la última versión de ThundaxBoxManager v1.0.0 build 116.exe:

  • Enlaces de interés:
Gráficos con Delphi.


Friday, 18 September 2009

Creando un editor de propiedades personalizado para TBox

Hace unos meses os entretuve con un editor para el componente TBox. Además os insistí con un editor concreto basado en la RTTI de manera que teníamos que hacer heredar nuestra clase de TPersistent y de ésta manera poder utilizar el RTTIInspector de la DevExpress y así hacer mucho mas fácil la edición de nuestro componente. En éste artículo os muestro un nuevo inspector de propiedades personalizado para mi Tbox. Para la edición utilizo un patrón MVC (Modelo-Vista-Controlador) para la edición de propiedades de mi componente. Aquí os dejo la última versión de ThundaxBoxManager v1.0.0 build 99.exe con los últimos patch realizados. Tengo que dar las gracias a varios de mis compañeros que me ayudaron a descubrir un bug que había en el redimensionado de las cajas y que no había notado.

Como podréis ver, la nueva pantalla de propiedades tiene el siguiente aspecto:

Desde ésta pantalla podremos realizar la edición del componente, cambiando el color de la caja, de la línea, de la selección e incluso añadir texto. Ahora si nos fijamos, podemos realizar diseños como los que siguen:


Como podéis ver la aplicación cada vez va cogiendo mas color y mayor funcionalidad. Si os fijáis en cada entrega, la aplicación cumple con los estándares de la OO y tengo las clases muy bien organizadas para que la escalabilidad de la aplicación pueda ser mayor. En las siguientes entregas prepararé el editor de las conexiones y mejoraré la calidad de los gráficos mostrados. Espero que os guste!.

Aquí os dejo un fragmento del código del dibujo de la Tbox:


procedure TBox.Draw();
var
NewRect: TRect;
i: integer;
begin
canvas.Brush.Style := bsSolid;
canvas.Pen.Width := 1;
canvas.Brush.Color := FboxColor;

if Inside then
canvas.Pen.Color := FselectedColor
else
canvas.Pen.Color := FlineColor;
Rectangle(Vertex1, Vertex2);
if inside then
begin
canvas.Brush.Style := bsSolid;
canvas.Brush.Color := FselectedColor;
NewRect := Rect(Vertex1.x - 2, Vertex1.y - 2, Vertex1.x + 2, Vertex1.y + 2);
canvas.FillRect(NewRect);
NewRect := Rect(Vertex2.x - 2, Vertex2.y - 2, Vertex2.x + 2, Vertex2.y + 2);
canvas.FillRect(NewRect);
NewRect := Rect(Center.x - 2, Center.y - 2, Center.x + 2, Center.y + 2);
canvas.FillRect(NewRect);
NewRect := Rect(Vertex3.x - 2, Vertex3.y - 2, Vertex3.x + 2, Vertex3.y + 2);
canvas.FillRect(NewRect);
NewRect := Rect(Vertex4.x - 2, Vertex4.y - 2, Vertex4.x + 2, Vertex4.y + 2);
canvas.FillRect(NewRect);
end;
if FDescription.text <> '' then
begin
for i := 0 to FDescription.Count - 1 do
DrawTextOrientation(Point(Vertex1.x, Vertex1.Y + (i * FFontText.Size)), 0, FFontText, FDescription[i]);
end;
end;

Thursday, 17 September 2009

Mejorando el diseño de una flecha (Parte I)

En éste artículo os mostraré un nuevo diseño de punta de flecha bastante mejorada de su antecesora: Drawing an Arrow. Últimamente he notado que el diseño que hice de ésta flecha no se acaba de ver bien cuando realizamos ciertas rotaciones y no me acaba de convencer. Así que he empezado a buscar nuevos diseños y por ejemplo Microsoft Office Word nos ofrece unos diseños de flecha bastante diferentes con la punta típica, otra más estilizada, un circulo, un rombo, etc. En éste caso me centraré en la punta de flecha más estilizada como en la que aparece en la imagen superior. Si observamos la siguiente imagen podremos ver las 2 flechas mas distintivas del Word:

Ahora, si os fijáis en mi aplicación, veréis que las flechas no se ven tan bien:

Por lo tanto, voy a intentar reemplazar la punta de flecha típica: , por una mas estilizada: . Por lo tanto me centraré en los cálculos que hay que hacer para poder dibujar ésta nueva flecha mas estilizada. En mi anterior post os mostré como se realizaba el cálculo para la punta de flecha típica:

Ahora el reto es dibujar una flecha mas estilizada de 4 puntos:

Si ampliamos la imagen podemos ver los cálculos que tenemos que realizar para poder calcular ésta nueva flecha:

Por defecto he dejado unos parámetros fijos para poder realizar el cálculo más rápido, aunque en la aplicación podremos llegar a personalizar éstos ángulos y longitudes a nuestro gusto. Aquí el tema está en construir ésta punta de flecha de 4 puntos igual que la de 3 puntos utilizando un polígono. De ésta manera la aplicación tendrá control sobre éste dibujo geométrico y será capaz de pintarlo interiormente y poner también el color que queramos:

En mi caso, el ángulo de inclinación de la flecha ya comenté como se calculaba en un post anterior utilizando el arcotangente entre el punto final y el inicial. Por defecto la inclinación hacia los puntos 2 y 4 es de 20º o sea pi/9 partes. La distancia entre los puntos 1 y 2 y 1 y 4 por defecto es de 20 píxels. El punto 3 está a 2/3 partes del punto 1, por lo tanto la generación de los puntos es la siguiente:
P1 := (Px, Py);
P2 := (Px, Py) + Move(-20 pixels) + Rotate(-pi/9);
P4 := (Px, Py) + Move(-20 pixels) + Rotate(+pi/9);
P3 := (Px, Py) + Move(-2/3 (Px,Py));
Mas o menos seria algo parecido a lo descrito anteriormente. Lo que tenemos que recordar es que en todo momento los datos son variables y podemos desplazar la flecha donde queramos, por lo tanto tenemos que tener en cuenta que éste recálculo tiene que ser lo mas rápido posible para que no se note el repintado.

Si analizamos el siguiente código veremos como se realizan los cálculos especificados:


uses
Math;

procedure DrawArrow(Canvas : TCanvas; Source, Target : TPoint; ArrowHeight : Integer);
function CalcPoint(p: TPoint; Angle: double; Distance: Integer): TPoint;
var
X, Y, M: Double;
begin
if Abs(Angle) <> (Pi / 2) then
begin
if Abs(Angle) < (Pi / 2) then
Distance := -Distance;
X := p.X + Distance / Sqrt(1 + Sqr(Tan(Angle)));
Y := p.Y + Tan(Angle) * (X - p.X);
Result := Point(Round(X), Round(Y));
end
else
begin
if Angle > 0 then
Distance := -Distance;
Result := Point(p.X, p.Y + Distance);
end;
end;
var
Angle : double;
PArrow: array[1..4] of TPoint;
begin
Canvas.MoveTo(Source.x, Source.y);
Canvas.LineTo(Target.x, target.y);
Angle := ArcTan2((Target.y - Source.y), (Target.x - Source.x));
PArrow[1] := Target;
PArrow[2] := CalcPoint(Target, Angle + Pi / 9, , ArrowHeight);
PArrow[3] := CalcPoint(Target, Angle, 2* ArrowHeight div 3);
PArrow[4] := CalcPoint(Target, Angle - Pi / 9, ArrowHeight);
Canvas.Polygon(PArrow);
end;


A partir de ahí, podemos ver el resultado en una pequeña aplicación de prueba:

Ahora solo falta añadir éstos cambios a la aplicación para que refleje ésta nueva flecha, mas estilizada y mas optimizada (en cálculos):

Podéis descargar la última versión estable de la aplicación desde aquí: ThundaxBoxManager v1.0.0 build 72.exe.

  • Enlaces de interés:
Funciones matemáticas con Delphi.

Tuesday, 15 September 2009

Implementación de un Canvas para modificar Objetos en Delphi parte XI

En éste artículo me dedicaré a explicaros como he resuelto el problema de las etiquetas. Dado que las líneas expresan un concepto, deberíamos poder indicarlo de alguna manera mediante una etiqueta (label) indicando una pequeña descripción de ésta. El problema empieza por saber dónde tengo que poner ese texto (ubicación exacta en coordenadas) y el ángulo exacto de inclinación que existe sobre la recta. El cálculo es relativamente fácil, ya que solo tenemos que calcular el angulo formado por dos puntos del plano, y buscar el punto médio de un segmento. Mediante éstos 2 cálculos ya tenemos el 50% del trabajo hecho. El otro 50% lo tenemos que completar buscando alguna forma para dibujar éste texto en ése punto con la inclinación dada. Pero para ésto tampoco hay ningún problema. Si os acordáis de mi post : Mostrar el texto en la orientación que queramos en un canvas, aquí describo como funciona el tema de las fuentes para dibujarlas en la orientación que queramos.

  • 1. Calcular el ángulo formado por dos puntos del plano:
Mediante éste pequeña función obtendremos el cálculo:



function TAbstractLine.CalcAngleTwoPoints(Source, Target: TPoint): double;
var
angle: double;
begin
angle := 180 * (1 + ArcTan2(Source.y - Target.y, Target.x - Source.x) / PI);
if angle >= 360.0 then
angle := angle - 360.0;
result := angle;
end;


  • 2. Calcular el punto medio de un segmento:
Con éste cálculo obtenemos el punto medio:



Position := Point((target.x + source.x) div 2, (target.y + source.y) div 2);


Ahora, la función que nos permite dibujar el tema de las descripciones es el siguiente:


procedure TAbstractLine.DrawTextOrientation(Source, Target: TPoint);

function iif(condition: boolean; resultTrue: integer; resultFalse: integer): integer;
begin
result := resultFalse;
if Condition then
result := resultTrue
end;
var
newFont, FontSelected: integer;
ft: TFont;
angle: double;
iAngle: integer;
Position: TPoint;
begin
ft := TFont.Create;
ft.Name := 'Arial';
ft.Size := 12;
ft.Style := ft.Style - [fsBold, fsItalic, fsUnderline, fsStrikeOut];
angle := CalcAngleTwoPoints(Source, Target);
iAngle := Round(angle);
Position := Point((target.x + source.x) div 2, (target.y + source.y) div 2);
if FDescription = '' then
exit;
SetBkMode(Fcanvas.handle, transparent);
newFont := CreateFont(
-ft.Size,
0,
iAngle * 10,
0,
iif(fsBold in ft.Style, FW_BOLD, FW_NORMAL),
iif(fsItalic in ft.Style, 1, 0),
iif(fsUnderline in ft.Style, 1, 0),
iif(fsStrikeOut in ft.Style, 1, 0),
ANSI_CHARSET,
OUT_TT_PRECIS,
CLIP_DEFAULT_PRECIS,
PROOF_QUALITY,
FF_DONTCARE,
PChar(ft.Name));

FontSelected := SelectObject(Fcanvas.handle, newFont);
TextOut(Fcanvas.handle, position.x, position.y, PChar(FDescription), length(FDescription));
SelectObject(Fcanvas.handle, FontSelected);
DeleteObject(newFont);
FreeAndNil(ft);
end;


El resultado final, lo podéis comprobar en la siguiente imagen:

Aquí os dejo la última versión de la aplicación ThundaxBoxManager v1.0.0 Build 71.exe para que le echéis un vistazo y comprobéis el estado de la aplicación, que poco a poco va dando sus frutos.

Wednesday, 9 September 2009

Implementación de un Canvas para modificar Objetos en Delphi parte X

En éste décimo post (tendré que cambiar el título y empezar otro nuevo, ya que se empieza a alargar un montón) comento las mejoras sobre el diseño de dibujo de las líneas sobre el canvas. He mejorado bastante el diseño anterior utilizando Interfaces para mejorar la claridad del código y de su extensibilidad posterior. Podréis ver como dispongo de las interfaces IArrow, ILine y IDottedLine y que las extiende la clase TDrawable. Luego nuestra clase abstracta hereda y extiende la clase TDrawable y se monta la factoría abstracta a partir de éste.

En el siguiente diagrama UML veréis las modificaciones hechas sobre el concepto línea:

Ahora tanto el diagrama como el código es mucho más entendible y fácil de seguir, sin un diseño rebuscado y sin pensar en el flujo de control.

Al igual que en la novena entrega, el diagrama es el mismo pero las tripas de la aplicación han cambiado substancialmente para un mejor diseño:

El código fuente del diseño de la jerarquía de clases, es el que sigue a continuación:

type
    TPointEx = packed record
        X: Extended;
        Y: Extended;
    end;

    TTypeLine = (SimpleLine, SimpleArrowLine, SimpleDoubleArrowLine, DottedLine, DottedArrowLine, DottedDoubleArrowLine, noLine);

    IArrow = interface(IInterface)
    ['{5A1664E5-C09F-45E4-B90E-19EE1625AFF3}']
        procedure DrawArrow(Source, Target: TPoint; LenArrow: integer; Filled: boolean; Color: TColor);
    end;

    ILine = interface(IInterface)
    ['{B18EAAFF-30DA-4556-91A7-FC15720997E5}']
        procedure DrawLine(Source, Target: TPoint; Color: TColor);
    end;

    IDottedLine = interface(IInterface)
    ['{F989821E-10F1-4A25-AB3F-D4F3720D9409}']
        procedure DrawDottedLine(Source, Target: TPoint; Color: TColor);
    end;

    TDrawable = class(TInterfacedObject, IArrow, ILine, IDottedLine)
        procedure DrawArrow(Source, Target: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); virtual; abstract;
        procedure DrawLine(Source, Target: TPoint; Color: TColor); virtual; abstract;
        procedure DrawDottedLine(Source, Target: TPoint; Color: TColor); virtual; abstract;
    end;

    TAbstractLine = class(TDrawable)
        FCanvas: TCanvas;
        procedure Draw(Source, Target: TPoint; SourceI, TargetI: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); virtual; abstract;
        constructor Create(Canvas: TCanvas);
        //Interface methods
        procedure DrawArrow(Source, Target: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); override;
        procedure DrawLine(Source, Target: TPoint; Color: TColor); override;
        procedure DrawDottedLine(Source, Target: TPoint; Color: TColor); override;
    end;

    TAbstractSimpleLine = class(TAbstractLine)
        procedure Draw(Source, Target: TPoint; SourceI, TargetI: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); override;
    end;

    TAbstractSimpleArrowLine = class(TAbstractLine)
        procedure Draw(Source, Target: TPoint; SourceI, TargetI: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); override;
    end;

    TAbstractSimpleDoubleArrowLine = class(TAbstractLine)
        procedure Draw(Source, Target: TPoint; SourceI, TargetI: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); override;
    end;

    TAbstractDottedLine = class(TAbstractLine)
        procedure Draw(Source, Target: TPoint; SourceI, TargetI: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); override;
    end;

    TAbstractDottedArrowLine = class(TAbstractLine)
        procedure Draw(Source, Target: TPoint; SourceI, TargetI: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); override;
    end;

    TAbstractDottedDoubleArrowLine = class(TAbstractLine)
        procedure Draw(Source, Target: TPoint; SourceI, TargetI: TPoint; LenArrow: integer; Filled: boolean; Color: TColor); override;
    end;

    TAbstractFactory = class(TObject)
    public
        constructor Create;
        destructor Destroy; override;
        function GetLine(Canvas: TCanvas): TAbstractLine; virtual; abstract;
        function GetLineArrow(Canvas: TCanvas): TAbstractLine; virtual; abstract;
        function GetLineDoubleArrow(Canvas: TCanvas): TAbstractLine; virtual; abstract;
    end;

    TSimpleLinesFactory = class(TAbstractFactory)
    public
        constructor Create;
        destructor Destroy; override;
        function GetLine(Canvas: TCanvas): TAbstractLine; override;
        function GetLineArrow(Canvas: TCanvas): TAbstractLine; override;
        function GetLineDoubleArrow(Canvas: TCanvas): TAbstractLine; override;
    end;

    TDottedLinesFactory = class(TAbstractFactory)
    public
        constructor Create;
        destructor Destroy; override;
        function GetLine(Canvas: TCanvas): TAbstractLine; override;
        function GetLineArrow(Canvas: TCanvas): TAbstractLine; override;
        function GetLineDoubleArrow(Canvas: TCanvas): TAbstractLine; override;
    end;

La última aplicación disponible es la siguiente: ThundaxBoxManager v1.0.0 build 44.exe.

Tuesday, 8 September 2009

Implementación de un Canvas para modificar Objetos en Delphi parte IX

En éste pequeño artículo os muestro los cambios realizados en la factoría de clases para la gestión del pintado de las líneas. Ahora he hecho una pequeña división entre los diferentes tipos de líneas y su parametrización. De ésta manera, podemos dibujar los 6 tipos diferentes de líneas que hay en la aplicación y que cada uno tendrá un comportamiento diferente. Si ahora os fijáis en la paleta de herramientas, veréis como aparecen éstos nuevos botones con sus funcionalidades. El nuevo diagrama UML es el que sigue:

Ahora la aplicación permite la siguiente configuración:

Podréis descargar la última versión de prueba en el siguiente enlace: ThundaxBoxManager v1.0.0 build 26.exe. Espero que os guste. Ya veis que poco a poco voy perfilando varias cosas y aprovechando más y más el diseño OO y los patrones para realizar una aplicación relativamente fácil.

Monday, 7 September 2009

Implementación de un Canvas para modificar Objetos en Delphi parte VIII

En ésta octava entrega del modificador de Objetos, he implementado una Factoría de clases en la construcción de las líneas, de ésta manera no tengo que estar codificando cada línea independientemente creando cada vez más y más código. De ésta manera, no indico de ninguna manera el tipo de línea a dibujar ya que queda indicado en la creación de la línea y utilizan el mismo método de pintado.

La configuración de la factoría es la siguiente:

El resultado de la ejecución de la aplicación es el mismo que el de la séptima parte, aunque tiene ésta mejora en la estructura de líneas:



Podéis descargar la última versión ThundaxBoxManager v1.0.0 build 23.exe aquí.

De ésta manera la codificación es mucho más sencilla, y nos permite crear las líneas sin tener que estar pensando en qué voy a dibujar:

Points := GetPointInter(Connector.SourceBox, Connector.TargetBox);
Connector.Line.Draw(Connector.SourceBox.Center, Points, 5, true, clBlack);

Aún hay que mejorar muchas cosas, pero el tema es que la estructura esté potenciada utilizando OO y diseño de patrones.

Friday, 4 September 2009

Implementación de un Canvas para modificar Objetos en Delphi parte VII

En ésta entrega, os muestro una pequeña modificación que he tenido que realizar en el diseño de la aplicación, ya que al intentar crear diversos tipos de conectores, he tenido que quitar la referencia que tenia cada caja para su conexión y extraerla fuera para poder realizar diversos tipos de enlaces. De ésta manera permito a la aplicación poder crecer en materia de enlaces. Ahora mismo como veréis en el siguiente diagrama UML, dispongo de la clase TConnector que es la que me permite disponer de una referencia entre cajas e informar de su enlace, ya sea del tipo mental o físico. De ésta manera luego puedo acabar de implementar los diversos tipos de enlaces y mostrarlos en el canvas:

Si visualizamos la aplicación, podremos generar gráficos como los siguientes:

Aquí os dejo también el enlace de la aplicación para que la podáis probar: Thundaxboxmanager v1.0.0 build 20.exe. Poco a poco podéis ver que voy perfilando la aplicación, y sobretodo utilizando mucho la abstracción y la orientación a objetos que es lo que nos interesa en ésta aplicación.

Wednesday, 2 September 2009

Implementación de un Canvas para modificar Objetos en Delphi parte VI

Veo que esto va para largo, y después de varios días ya voy por la sexta parte de éste tema que parece que no se va a acabar nunca. Bien, continuando con la misma temática de siempre, he tenido que dibujar diferentes líneas para diferenciar el concepto de enlace que quiero sobre las cajas. Más adelante veréis como lo utilizaré. En mi caso, necesito poder realizar un tipo de conexión que sea de tipo físico (enlace real de conexión, como conectar una manguera), y otro tipo de enlace más conceptual o mental (ésto va conectado a aquello por referencia). Pues bien, en ésta última versión, modifico los tipos de enlace y los diferencio porque unos van con línea continua y otros con línea alternada.

El problema que se plantea (como siempre!), es cómo dibujo ésta línea discontinua en el canvas???. La solución es bastante sencilla, ya que la propia API de windows nos permite crear nuestras pen personalizadas a la hora de dibujar.

Por lo tanto, podemos definir los saltos entre línea y la longitud de las líneas y pasarlas a la función ExtCreatePen para personalizar nuestro propio pen.

El resultado es el siguiente:

Luego, para crear la línea punteada, lo tenemos que hacer de la siguiente manera:

var
    userPenStyle: array[1..2] of Dword = (8, 8);

/////////////////////////////////////
    hand : cardinal;
    LogBrush: TLogBrush;
begin
    Image1.Canvas.Pen.Width := 1;
    Image1.Canvas.Pen.Color := color;
    Image1.Canvas.Brush.Style := bsSolid;

    LogBrush.lbStyle := BS_SOLID;
    logBrush.lbColor := Image1.Canvas.Pen.Color;
    hand := Image1.Canvas.Pen.Handle;
    Image1.Canvas.Pen.Handle := ExtCreatePen(PS_GEOMETRIC or PS_USERSTYLE,
                                  Image1.Canvas.Pen.Width,
                                  logBrush,
                                  Length(userPenStyle), @userPenStyle);

    //Dibujamos la línea

    Image1.Canvas.MoveTo(POrigen.x, POrigen.y);
    Image1.Canvas.LineTo(PDestino.x, PDestino.y);
    Image1.Canvas.Pen.Handle := hand;
    Image1.Canvas.Pen.Width := 1;
    Image1.Canvas.Pen.Color := color;
    Image1.Canvas.Brush.Style := bsSolid;

Aquí podréis descargar la última versión estable (beta) de ThundaxBoxManager v1.0.0 build 17. exe.

Tuesday, 1 September 2009

Implementación de un Canvas para modificar Objetos en Delphi parte V

En éste artículo os describo las diferentes mejoras que trae la aplicación. Llevo un rato trabajando sobre el proyecto y he conseguido diferentes cosas. En ésta versión, he implementado la mejora anterior del movimiento del mouse (ahora ya no se escapa ningún evento), también la mejora con el redimensionado de las cajas (ocurría lo mismo que con lo de mover la caja), y además he incorporado el dibujado de flechas entre cajas. Antes se dibujaban líneas de centro a centro de la cajas y ahora hago un pequeño cálculo para encontrar la intersección entre las 2 rectas. No es muy complicado, sabiendo un poco de matemáticas se resuelve rápido. En la wikipedia podréis encontrar información sobre las rectas.

Si observamos una imagen de la aplicación veréis de lo que hablo:

Un cálculo parecido al que hay que hacer es el siguiente:

m := 0;
if (RectaP2.x - RectaP1.x) <> 0 then
    m := (RectaP2.y - RectaP1.y) / (RectaP2.x - RectaP1.x);
d := RectaP1.y - m * (RectaP1.x);

m2 := 0;
if (Recta2P2.x - Recta2P1.x) <> 0 then
    m2 := (Recta2P2.y - Recta2P1.y) / (Recta2P2.x - Recta2P1.x);
d2 := Recta2P1.y - m2 * (Recta2P1.x);

x := 0;
if (m - m2) <> 0 then
    x := (d2 - d) / (m - m2);
y := m * x + d;

s := false;
if ((RectaP1.x - x) * (x - RectaP2.x) >= 0)
    and ((RectaP1.y - y) * (y - RectaP2.y) >= 0)
    and ((Recta2P1.x - x) * (x - Recta2P2.x) >= 0)
    and ((Recta2P1.y - y) * (y - Recta2P2.y) >= 0) then
    s := true;

Solo hay que adaptarlo para los 4 laterales de la caja, ya que las rectas son conocidas. Hay que buscar la intersección entre la recta 1 = Entre centros y le recta 2 que será uno de los laterales, vértices 1-2, 2-3, 3-4, 4-1. Una vez encontramos el que intersecciona dibujamos la flecha.

La flecha, ya la tenía implementada en uno de mis post antiguos: Drawing an Arrow.

Aquí podréis descargar la última versión de la aplicación ThundaxBoxManager v1.0.0 build 16.exe, espero que la disfrutéis.

Además de las modificaciones anteriores, también he cambiado el tema del solapamiento. Cuando tenemos un objeto seleccionado, sus eventos no interaccionan con los objetos que hay no seleccionados. Antes ocurría que ibas moviendo una caja y cuando pasabas por encima de otra se movía también. Ahora queda todo el conjunto bien definido con la posibilidad de cambiar el orden.

Algo que también se me olvidó en el artículo anterior es que he utilizado el DobleBuffering en la aplicación para evitar el parpadeo o flickering.

  • Enlaces de interés:
Ecuación de la recta que pasa por 2 puntos.
Intersecting Lines algorithm.

Implementación de un Canvas para modificar Objetos en Delphi parte IV

En ésta IV parte os mostraré el problema que tiene el trabajar con los eventos del mouse sobre un componente gráfico ya sea TImage o TPaintBox o cualquiera que disponga de Canvas y eventos de mouse. Las acciones del mouse tienen un pequeño problema y es que funcionan mediante eventos. Por lo tanto ésto significa que mientras movemos uno de los objetos con el mouse, si vamos demasiado rápido, éste se nos puede escapar. Y aquí tenemos nuestro primer problema a la hora de desarrollar un canvas para la modificación de objetos. Existen varias técnicas para que ésto no ocurra o para que quede un poco más disimulado. Todo depende de que componente utilizamos y la manera en que lo utilizamos.

Hay que tener amplios conocimientos de como funcionan los mensajes y como gestionan los diversos componentes el refresco de los gráficos mediante los métodos .refresh o .invalidate. Si os fijáis en la jerarquía de clases de los elementos que contienen eventos de mouse (OnDragDrop, OnDragOver, OnEndDock, OnEndDrag, OnMouseActivate, OnMouseDown, OnMouseEnter, OnMouseLeave, OnMouseMove, OnMouseUp, OnStartDock, OnStartDrag, etc.) como TShape, TPaintBox, TImage, TBevel, etc, heredan de la clase TGraphicControl. Os recomiendo pasar por la unit ExtCtrls y observar el código de los diferentes componentes. De ahí podréis sacar bastantes ideas de como se utilizan los eventos y los mensajes.

Si cogéis la penúltima versión de Thundaxboxmanager v1.0.0 build 9.exe, podréis comprobar el tema que os comento sobre la velocidad del mouse:

Cómo podéis ver en la imagen, he desplazado el mouse muy rápido, y aunque la caja está seleccionada, no se ha desplazado con el mouse. Ésto pasa ya que los mensajes del mouse se hacen cada x tiempo, por lo tanto el código que hay en el evento (OnMouseMove) solo se llama ciertas veces. Éste problema lo genera el propio windows. Windows no acumula los mensajes del mouse en la cola de mensajes, en vez de eso lo que hace es reemplazar el último de los mensajes. Por lo tanto puede ser que el último mensaje enviado esté fuera del alcance de la caja y por eso sale de sus margenes y no es desplazado.

Aquí los trucos que podemos utilizar son los mismos que se trabajan con la creación de vídeo juegos. Lo que tenemos que hacer es ir capturando éstos mensajes lo más rápido posible y procesarlos (hay muchas técnicas para ésto). Podemos sacar ideas observando los métodos invalidate o refresh de TControl en la unit Controls. Para que quede un movimiento suave, podemos ir redibujando las zonas afectadas por el movimiento, e incluso hacer que el objeto siga el mouse y no al revés.

Si os descargáis la versión thundaxBoxManager v1.0.0 build 10.exe veréis que ahora la caja no se suelta del mouse por más rápido que lo movamos.

Es una versión en desarrollo, y puede ser que alguna de las funcionalidades de la versión anterior no estén disponibles. En ésta he modificado el comportamiento de los mensajes y ahora voy donde va el mouse, ya que es normal que se pierdan mensajes.

  • Enlaces de interés:
Handling Mouse messages.
Capture mouse event inside TImage.
Catching all mouse events.
Hooking mouse movement.
WM_PRINT Message.
Screen Grab Utility.
Hoot Keys delphi.
How to hook the mouse.
Understanding Drag Drop operations.

Sunday, 30 August 2009

Implementación de un Canvas para modificar Objetos en Delphi parte III

Continuando con la aplicación para la modificación de Objetos utilizando un Canvas, en ésta parte he implementado un modificador del z-Order, o sea, un modificador para indicar cuál de las imágenes va encima o va debajo. La implementación de mi canvas, me permite dibujar la clase TBox, y además de poder generar n-conexiones entre ellas, me permite modificar la ubicación de las cajas en el plano Z. De ésta manera puedo enviar hacia delante o hacia el fondo la caja para darle una diferente prioridad. Una vez tenga acabada correctamente la gestión de eventos, fusionaré ésta aplicación con la del post anterior y así moldear diferentes objetos con una cierta inteligencia en el Scada.

La mejora del diseño de la clase TBox es la siguiente:

Ahora dispone de diversas propiedades nuevas, así como el zOrder que indica el orden a la hora de realizar el dibujado sobre el TCanvas personalizado. Ahora solo falta unirlo al TCitectPainter para que maneje el objeto COM del IGraphicsBuilder.

Aquí os dejo la versión estable de la aplicación ThundaxBoxManager v1.0.0 Build 9.exe para que la probéis y saquéis vuestras conclusiones.

Aquí os dejo varias imágenes sobre el funcionamiento del zOrder (Send to Back):

Y (Bring to Front):

Ahora mismo, con la aplicación podríamos incluso generar grafos y calcular la ruta mínima aplicando alguno de los algoritmos típicos. Pero bueno, eso quedará para otro día, además ya hay algo implementado de eso en el blog.


Mediante ésta aplicación puedo dibujar cualquier cosa y crear diversos mapas gracias a su posibilidad de conexionado entre si. De ésta manera, podríamos llegar a crear diagramas diversos, estructuras en árbol, etc, que a veces son mejores para su entendimiento si las dibujamos, en vez de mostrar algo en texto. Se me ocurre, pues el conexionado de una red de ordenadores por ejemplo, o el viaje que realiza una factura, etc. De ésta manera podemos tener un diagrama con un poco de inteligencia y asignarle las propiedades que queramos. Solo tenemos que heredar de TBox y generar nuestro propio componente gráfico.


Friday, 21 August 2009

Implementación de un Canvas para modificar Objetos en Delphi parte II

Continuando con las mejoras de la aplicación, desde ésta se pueden dibujar diferentes conexiones entre cajas. Con la siguiente imagen veréis las cosas un poco más claras:

Ahora, podemos seleccionar una de las cajas, y realizar una conexión hacia la otra. Simplemente hay que acompañar el mouse y automáticamente generará la conexión entre centros de caja. Ahora he modificado la clase para que contenga diferentes apuntadores a otros objetos:

De ésta manera guardo un apuntador al objeto conectado y luego puedo acceder fácilmente a éste para realizar el dibujado de la línea de conexión. Ahora el sentido es único y solo admite una conexión por caja, pero bueno eso cambiará en breve. Aquí os dejo la última versión de la aplicación "Thundax Box Manager v1.0.0.6". Dentro de poco os mostraré los patrones utilizados y una versión mucho más mejorada, con mejor control de eventos que ésta versión beta en fase de desarrollo.

Thursday, 20 August 2009

Implementación de un Canvas para modificar Objetos en Delphi parte I

Poco a poco le voy dando color a mi aplicación, y ahora he desarrollado un Canvas con el que podré modificar mis objetos gráficos. Es decir, utilizaré éste canvas especial para modificar mi clase TBox, la cuál puedo desplazar y redimensionar en tiempo de ejecución. El concepto es muy simple, utilizando un componente TImage, me apodero de su Canvas y utilizo sus métodos OnMouseUp, OnMouseMove y OnMouseDown para realizar la lógica de mi aplicación. Luego añadiendo diferentes funcionalidades a mi clase, le permito al objeto saber si estoy dentro de éste o en una de sus esquinas con el correspondiente comportamiento. Luego a medida que se realizan los desplazamientos se tienen que ir recalculando los diversos puntos de los cuales está formado mi rectángulo. Una vez me sitúo encima de el objeto, el mouse cambia su estado informando de ésto mediante el icono del cursor. La Tbox tiene unas áreas bien definidas y sa van comprobando éstas cada vez mediante los eventos que os he dicho anteriormente. La configuración básica de éste experimento es la siguiente:


Por lo tanto, desde la aplicación, podemos generar lo siguiente:


Un lienzo con la posibilidad de dibujar varias TBox. La creación es directa, apretando el botón de "Draw Rectangle", nos permite directamente ir al lienzo, y mediante 2 puntos dibujar un rectángulo. Una vez hacemos click sobre la TBox, se nos selecciona marcandose en rojo y mostrandonos los vértices. Una vez dentro de la TBox, el mouse cambia para informar que podemos generar un desplazamiento.

Además podemos modificar el tamaño del objeto, situándonos en cualquiera de sus esquinas. Aquí os dejo la versión beta de ésta aplicación "Thundax Box Manager", la cuál aún está en fase de desarrollo, pero si queréis la podéis testear. Aún tengo que modificar varias cosas y mejorar cosas del redimensionado. Os iré informando.