13 marzo 2009

Crea tu propio editor de informes (III)

Hoy vamos a perfeccionar la función de mover varios componentes seleccionados a la vez. También veremos como añadir figuras gráficas al informe y como cambiar sus dimensiones.

Al igual que en artículos anteriores daré al final del mismo el enlace para descargar todo el proyecto.

MOVIENDO VARIOS COMPONENTES CON EL RATÓN

Una vez hemos conseguido mover dos o más componentes a la vez mediante el teclado tenemos que hacer lo mismo con el ratón, ya que si tenemos tres etiquetas seleccionadas e intentamos moverlas (cogiendo una) veremos que sólo se mueve esta última.

Para ello hay que modificar la primera parte del procedimiento MoverRatonPulsado:

procedure TFInforme.MoverRatonPulsado;
var
dx, dy, xAnterior, yAnterior: Integer;
Seleccion: TShape;
begin
// ¿Ha movido el ratón estándo el botón izquierdo pulsado?
if ( ( x <> Cursor.X ) or ( y <> Cursor.Y ) ) and ( Seleccionado <> nil ) then
begin
// ¿Estamos moviendo una etiqueta o una banda?
if (Seleccionado is TQRLabel) or (Seleccionado is TQRShape) then
begin
// Movemos el componente seleccionado
dx := Cursor.X - x;
dy := Cursor.Y - y;
xAnterior := Seleccionado.Left;
yAnterior := Seleccionado.Top;
Seleccionado.Left := xInicial + dx;
Seleccionado.Top := yInicial + dy;

// Movemos la selección
Seleccion := BuscarSeleccion(Seleccionado.Tag);
if Seleccion <> nil then
begin
Seleccion.Left := xInicial+dx-1;
Seleccion.Top := yInicial+dy-1;
end;

MoverOtrosSeleccionados(Seleccionado.Tag, Seleccionado.Left,
Seleccionado.Top, xAnterior, yAnterior);
end;

...


Sólo he añadido tres cosas:

1º He añadido al principio del procedimiento las variables xAnterior e yAnterior que van a encargarse de guardar la coordenada original de la etiqueta que estoy movimendo.

2º Antes de mover la etiqueta guardo sus coordenadas originales:

xAnterior := Seleccionado.Left;
yAnterior := Seleccionado.Top;
Seleccionado.Left := xInicial + dx;
Seleccionado.Top := yInicial + dy;

3º Llamo al procedimiento MoverOtrosSeleccionados que se encargará de arrastrar el resto de etiquetas seleccionadas:

MoverOtrosSeleccionados(Seleccionado.Tag, Seleccionado.Left,
Seleccionado.Top, xAnterior, yAnterior);

Le paso como parámetro el ID de la etiqueta actual (para excluirla), la nueva posición y la anterior. Este sería el procedimiento al que llamamos:

procedure TFInforme.MoverOtrosSeleccionados( ID, x, y, xAnterior, yAnterior: Integer );
var
i, dx2, dy2: Integer;
Etiqueta: TQRLabel;
Seleccion: TShape;
begin
for i := 0 to BandaActual.ComponentCount - 1 do
begin
// ¿Es una etiqueta?
if (BandaActual.Components[i] is TQRLabel) then
begin
Etiqueta := BandaActual.Components[i] as TQRLabel;

// ¿Es distinta a la etiqueta original y está seleccionada?
if (Etiqueta.Tag <> ID) and (Etiqueta.Hint <> '' ) then
begin
dx2 := x - xAnterior;
dy2 := y - yAnterior;
Etiqueta.Left := Etiqueta.Left + dx2;
Etiqueta.Top := Etiqueta.Top + dy2;

// Movemos la selección
Seleccion := BuscarSeleccion(Etiqueta.Tag);
if Seleccion <> nil then
begin
Seleccion.Left := Etiqueta.Left-1;
Seleccion.Top := Etiqueta.Top-1;
end;
end;
end;
end;
end;

Lo que hace es recorrer todas las etiquetas de la banda actual (menos la seleccionada) y las arrastra según lo que se ha movido la etiqueta original:


Si movemos la etiqueta 3 entonces tienen que venirse detrás las etiquetas 1 y 2.

CREANDO FIGURAS GRÁFICAS

Otro elemento que se hace imprescindible en todo editor de informes son las figuras gráficas (líneas y rectángulos). Vamos a seguir la misma filosofía que para crear etiquetas.

Primero añadimos la opción Figura a nuestro menú contextual:


Su implementación sería esta:

procedure TFInforme.FiguraClick(Sender: TObject);
begin
if BandaActual = nil then
begin
Application.MessageBox( 'Debe crear una banda.',
'Acceso denegado', MB_ICONSTOP );
Exit;
end;

QuitarSeleccionados;
with TQRShape.Create(BandaActual) do
begin
Parent := BandaActual;
Left := 10;
Top := 10;
Inc(UltimoID);
Tag := UltimoID;
Name := 'Figura' + IntToStr(UltimoID);
end;
end;

Al igual que con las etiquetas, creamos un objeto TQRShape y le damos el siguiente ID que le corresponda.

Si ejecutamos el programa y creamos una nueva figura, aparecerá en la parte superior izquierda de la banda con borde negro y fondo blanco:


SELECCIONANDO LAS FIGURAS

Ahora tenemos que modificar las rutinas de selección para que se adapten a nuestro nuevo objeto TQRShape. Empezamos por el procedimiento SeleccionarComponente:

procedure TFInforme.SeleccionarComponente;
var
Punto: TPoint;
begin
bPulsado := True;
x := Cursor.X;
y := Cursor.Y;

// ¿Ha pinchado una etiqueta o una figura?
if (Seleccionado is TQRLabel) or (Seleccionado is TQRShape) then
begin
xInicial := Seleccionado.Left;
yInicial := Seleccionado.Top;

if Seleccionado.Hint = '' then
begin
// ¿No esta pulsado la tacla control?
if HiWord(GetAsyncKeyState(VK_LCONTROL)) = 0 then
QuitarSeleccionados;

Seleccionado.Hint := 'X';
end;
end;
...

Realmente sólo he cambiado esta línea:

if (Seleccionado is TQRLabel) or (Seleccionado is TQRShape) then

ya que no afecta a lo demás. Lo que si hay que ampliar bien es el procedimiento encargado de dibujar los componentes seleccionados:

procedure TFInforme.DibujarSeleccionados;
var
i: Integer;
Seleccion: TShape;
Etiqueta: TQRLabel;
Figura: TQRShape;
begin
if BandaActual = nil then
Exit;

for i := 0 to BandaActual.ComponentCount-1 do
begin
// ¿Es una etiqueta?
if BandaActual.Components[i] is TQRLabel then
begin
Etiqueta := BandaActual.Components[i] as TQRLabel;

if Etiqueta.Hint <> '' then
begin
// Antes de crearla comprobamos si ya tiene selección
Seleccion := BuscarSeleccion(Etiqueta.Tag);

if Seleccion = nil then
begin
Seleccion := TShape.Create(BandaActual);
Seleccion.Parent := BandaActual;
Seleccion.Pen.Color := clRed;
Seleccion.Width := Etiqueta.Width+2;
Seleccion.Height := Etiqueta.Height+2;
Seleccion.Tag := Etiqueta.Tag;
Seleccion.Name := 'Seleccion' + IntToStr(Etiqueta.Tag);
end;

Seleccion.Left := Etiqueta.Left-1;
Seleccion.Top := Etiqueta.Top-1;
end;
end;

// ¿Es una figura?
if BandaActual.Components[i] is TQRShape then
begin
Figura := BandaActual.Components[i] as TQRShape;

if Figura.Hint <> '' then
begin
// Antes de crearla comprobamos si ya tiene selección
Seleccion := BuscarSeleccion(Figura.Tag);

if Seleccion = nil then
begin
Seleccion := TShape.Create(BandaActual);
Seleccion.Parent := BandaActual;
Seleccion.Pen.Color := clRed;
Seleccion.Width := Figura.Width+2;
Seleccion.Height := Figura.Height+2;
Seleccion.Tag := Figura.Tag;
Seleccion.Name := 'Seleccion' + IntToStr(Figura.Tag);
end;

Seleccion.Left := Figura.Left-1;
Seleccion.Top := Figura.Top-1;
end;
end;
end;
end;

Comprobamos que si es una figura creamos una selección para la misma también de color rojo. Aunque estos dos componentes podíamos haberlos metido en una sólo bloque de código, conviene dejarlos separados para futuras ampliaciones en cada uno de ellos.

Igualmente tenemos que contemplar también las figuras a la hora de quitar los seleccionados:

procedure TFInforme.QuitarSeleccionados;
var
i: Integer;
begin
if BandaActual = nil then
Exit;

// Le quitamos la selección a los componentes
for i := 0 to BandaActual.ComponentCount-1 do
begin
if BandaActual.Components[i] is TQRLabel then
(BandaActual.Components[i] as TQRLabel).Hint := '';

if BandaActual.Components[i] is TQRShape then
(BandaActual.Components[i] as TQRShape).Hint := '';
end;

...


MOVIENDO LAS FIGURAS

Otra cosa que también tenemos hacer es cambiar el procedimiento encargado de arrastrar los componentes para que pueda hacer lo mismo con las figuras:

procedure TFInforme.MoverRatonPulsado;
var
dx, dy, xAnterior, yAnterior: Integer;
Seleccion: TShape;
begin
// ¿Ha movido el ratón estándo el botón izquierdo pulsado?
if ( ( x <> Cursor.X ) or ( y <> Cursor.Y ) ) and ( Seleccionado <> nil ) then
begin
// ¿Estamos moviendo una etiqueta o una figura?
if (Seleccionado is TQRLabel) or (Seleccionado is TQRShape) then
begin
...


Igualmente tenemos que ampliar el procedimiento encargado de seleccionar varios componentes:

procedure TFInforme.MoverOtrosSeleccionados(ID, x, y, xAnterior, yAnterior: Integer);
var
i, dx2, dy2: Integer;
Etiqueta: TQRLabel;
Figura: TQRShape;
Seleccion: TShape;
begin
for i := 0 to BandaActual.ComponentCount - 1 do
begin
// ¿Es una etiqueta?
if (BandaActual.Components[i] is TQRLabel) then
begin
Etiqueta := BandaActual.Components[i] as TQRLabel;

// ¿Es distinta a la etiqueta original y está seleccionada?
if (Etiqueta.Tag <> ID) and (Etiqueta.Hint <> '') then
begin
dx2 := x - xAnterior;
dy2 := y - yAnterior;
Etiqueta.Left := Etiqueta.Left + dx2;
Etiqueta.Top := Etiqueta.Top + dy2;

// Movemos la selección
Seleccion := BuscarSeleccion(Etiqueta.Tag);
if Seleccion <> nil then
begin
Seleccion.Left := Etiqueta.Left-1;
Seleccion.Top := Etiqueta.Top-1;
end;
end;
end;

// ¿Es una figura?
if (BandaActual.Components[i] is TQRShape) then
begin
Figura := BandaActual.Components[i] as TQRShape;

// ¿Es distinta a la Figura original y está seleccionada?
if (Figura.Tag <> ID) and (Figura.Hint <> '' ) then
begin
dx2 := x - xAnterior;
dy2 := y - yAnterior;
Figura.Left := Figura.Left + dx2;
Figura.Top := Figura.Top + dy2;

// Movemos la selección
Seleccion := BuscarSeleccion(Figura.Tag);
if Seleccion <> nil then
begin
Seleccion.Left := Figura.Left-1;
Seleccion.Top := Figura.Top-1;
end;
end;
end;
end;
end;

Y como no, hay que modificar la selección de varios componentes con el rectángulo azul. Eso estaba en el procedimiento ComprobarSeleccionComponetes:

procedure TFInforme.ComprobarSeleccionComponentes;
var
Sel: TShape; // Rectángulo azul de selección
i: Integer;
Eti: TQRLabel;
Fig: TQRShape;
begin
if BandaActual = nil then
Exit;

// ¿Hemos abierto una selección azul?
Sel := BandaActual.FindComponent('Seleccionar') as TShape;

if Sel <> nil then
begin
// Recorremos todas las etiquetas de esta banda en busca
// las cuales están dentro de nuestro rectángulo de selección
for i := 0 to BandaActual.ComponentCount-1 do
begin
// ¿Es una etiqueta?
if BandaActual.Components[i] is TQRLabel then
begin
Eti := BandaActual.Components[i] as TQRLabel;

// ¿Está dentro de nuestro rectángulo azul de selección?
if ( Eti.Left >= Sel.Left ) and
( Eti.Top >= Sel.Top ) and
( Eti.Left+Eti.Width <= Sel.Left+Sel.Width ) and
( Eti.Top+Eti.Height <= Sel.Top+Sel.Height ) then
Eti.Hint := 'X';
end;

// ¿Es una figura?
if BandaActual.Components[i] is TQRShape then
begin
Fig := BandaActual.Components[i] as TQRShape;

// ¿Está dentro de nuestro rectángulo azul de selección?
if ( Fig.Left >= Sel.Left ) and
( Fig.Top >= Sel.Top ) and
( Fig.Left+Fig.Width <= Sel.Left+Sel.Width ) and
( Fig.Top+Fig.Height <= Sel.Top+Sel.Height ) then
Fig.Hint := 'X';
end;
end;

DibujarSeleccionados;
FreeAndNil(Sel); // Eliminamos la selección
end;
end;


El mismo rollo he tenido que hacer en el procedimiento MoverComponentes (el que movía los componentes con el teclado). De este modo podemos seleccionar y arrastrar a la vez tanto etiquetas como figuras:


REDIMENSIONANDO EL TAMAÑO DE LOS COMPONENTES

Ya tenemos hecho que si el usuario tiene seleccionado uno o más componentes y pulsa los cursores entonces mueve los componentes. Ahora vamos a hacer que si pulsa también la tecla SHIFT entonces lo que hace es redimensionarlos. Esto los controlamos en el evento FormKeyDown:

procedure TFInforme.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
// ¿Está pulsada la tecla SHIFT?
if ssShift in Shift then
begin
// Redimensionamos el tamaño de los componentes
case key of
VK_RIGHT: AmpliarComponentes( 1, 0 );
VK_LEFT: AmpliarComponentes( -1, 0 );
VK_UP: AmpliarComponentes( 0, -1 );
VK_DOWN: AmpliarComponentes( 0, 1 );
end;
end
else
begin
// Los movemos
case key of
VK_RIGHT: MoverComponentes( 1, 0 );
VK_LEFT: MoverComponentes( -1, 0 );
VK_UP: MoverComponentes( 0, -1 );
VK_DOWN: MoverComponentes( 0, 1 );
end;
end;
end;

El procedimiento AmpliarComponentes es similar al de moverlos:

procedure TFInforme.AmpliarComponentes(x, y: Integer);
var
i: Integer;
Etiqueta: TQRLabel;
Figura: TQRShape;
Seleccion: TShape;
begin
if BandaActual = nil then
Exit;

// Recorremos todos los componentes
for i := 0 to BandaActual.ComponentCount-1 do
begin
// ¿Es una etiqueta?
if BandaActual.Components[i] is TQRLabel then
begin
Etiqueta := BandaActual.Components[i] as TQRLabel;

// ¿Está seleccionada?
if Etiqueta.Hint <> '' then
begin
// Movemos la etiqueta
Etiqueta.Width := Etiqueta.Width + x;
Etiqueta.Height := Etiqueta.Height + y;

// Movemos su selección
Seleccion := BandaActual.FindComponent('Seleccion'+IntToStr(Etiqueta.Tag)) as TShape;
Seleccion.Width := Etiqueta.Width+2;
Seleccion.Height := Etiqueta.Height+2;
end;
end;

// ¿Es una figura?
if BandaActual.Components[i] is TQRShape then
begin
Figura := BandaActual.Components[i] as TQRShape;

// ¿Está seleccionada?
if Figura.Hint <> '' then
begin
// Movemos la Figura
Figura.Width := Figura.Width + x;
Figura.Height := Figura.Height + y;

// Movemos su selección
Seleccion := BandaActual.FindComponent('Seleccion'+IntToStr(Figura.Tag)) as TShape;
Seleccion.Width := Figura.Width+2;
Seleccion.Height := Figura.Height+2;
end;
end;
end;
end;

También se podría haber hecho con el ratón cogiendo las esquinas del componente y estirándolas, pero me llevaría un artículo sólo para eso (y la vaca no da para más).

Ya veis la movida que hay que hacer para hacer un simple editor de informes. En el próximo artículo vamos a añadir el componente TQRDBText para conectarnos con bases de datos Internase/Firebird.

DESCARGA DEL PROYECTO

Aquí van los enlaces para bajarse todo el proyecto en RapidShare y Megaupload:

http://rapidshare.com/files/208654755/EditorInformes3_DelphiAlLimite.rar.html

http://www.megaupload.com/?d=3BPPEI7H


Pruebas realizadas en RAD Studio 2007.

Publicidad