27 febrero 2009

Crea tu propio editor de informes (II)

En el artículo de hoy vamos a conseguir seleccionar varios componentes a la vez pulsando la tecla control así como poder seleccionarlos con un rectángulo. También vamos a crear una selección lateral para saber con que banda estamos trabajando.

Una vez seleccionados los componentes vamos a dar la posibilidad de arrastrarlos todos a la vez utilizando los cursores del teclado:


Pero antes de nada, vamos a comenzar a pegar hachazos en el código fuente para dejarlo todo bien organizado. Al igual que en el artículo anterior daré al final del mismo los enlaces a RapidShare y Megaupload para que no tengáis que picar código, ya que veréis como se va complicando el asunto.

MODIFICANDO EL NUCLEO DE LA APLICACIÓN

Vamos a comenzar modificando el evento OnTimer de nuestro Temporizador simplificando el código fuente de este modo:

procedure TFInforme.TemporizadorTimer(Sender: TObject);
begin
GetCursorPos( Cursor );

// ¿Está pulsado el botón izquierdo del ratón?
if HiWord( GetAsyncKeyState( VK_LBUTTON ) ) <> 0 then
begin
if Seleccionado = nil then
Seleccionado := FindControl( WindowFromPoint( Cursor ) );

// ¿No estaba pulsado el botón izquierdo del ratón anteriormente?
if not bPulsado then
SeleccionarComponente
else
MoverRatonPulsado;
end
else
begin
Seleccionado := nil;
bPulsado := False;
ComprobarSeleccionComponentes;
end;
end;

Primero comprobamos si el usuario ha pulsado el botón izquierdo del ratón. En ese caso guardamos en la variable Seleccionado el componente donde ha pinchado y llamamos al procedimiento SeleccionarComponente que se encarga de comprobar que componente hemos pulsado (banda o etiqueta).

Si el ratón ya estaba pulsado de antes significa que vamos a arrastrar un componente o que vamos a abrir una selección. Esto se hace con el procedimiento MoverRatonPulsado.

En el caso de que el usuario suelte el botón izquierdo del ratón entonces anulamos tanto la selección individual de componentes como la colectiva mediante el procedimiento ComprobarSeleccionComponentes.

Veamos la implementación de cada procedimiento.

SELECCIONANDO COMPONENTES

El procedimiento SeleccionarComponente comprobará si hemos pulsado una etiqueta o la banda para efectuar una acción u otra:

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

// ¿Ha pinchado sobre una etiqueta?
if Seleccionado is TQRLabel 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;

// ¿Ha pinchado sobre una banda?
if Seleccionado is TQRBand then
begin
BandaActual := Seleccionado as TQRBand;
SeleccionarBandaActual;

// Transformamos las coordenadas del cursor del formulario a la banda
Punto.X := x;
Punto.Y := y;
Punto := ScreenToClient( Punto );
xInicial := Punto.X-BandaActual.Left;
yInicial := Punto.Y-BandaActual.Top;
QuitarSeleccionados;
end;

DibujarSeleccionados;
end;

Primero lee las coordenadas del ratón. Si hemos pinchado sobre una etiqueta entonces la seleccionamos y deseleccionamos las demás, pero aquí hemos añadido una novedad. Si el usuario mantiene pulsado la tecla CONTROL entonces seguimos seleccionando sin quitar la selección a las otras etiquetas.

En el caso de que pulsemos sobre una banda significa que vamos a deseleccionar todas las etiquetas y abrir un rectángulo de selección. Por eso guardo en las variables xInicial e yInicial donde comienza la selección. Aquí he tenido que convertir las coordenadas del cursor del ratón a coordenadas del formulario para que no se desplacen las coordenadas mediante la función ScreenToClient.

El procedimiento DibujarSeleccionados no ha cambiado.

SELECCIONANDO LA BANDA SOBRE LA QUE ESTAMOS

El procedimiento SeleccionarBandaActual crea un rectángulo a la izquierda de la banda actual de color verde:

procedure TFInforme.SeleccionarBandaActual;
var
SeleccionBanda: TShape;
begin
// Si ya había seleccionada una banda la eliminamos (la selección)
SeleccionBanda := Informe.FindComponent('SeleccionBanda') as TShape;
if SeleccionBanda <> nil then
FreeAndNil(SeleccionBanda);

// Seleccionamos de nuevo la banda
SeleccionBanda := TShape.Create(Informe);
with SeleccionBanda do
begin
Name := 'SeleccionBanda';
Parent := Informe;
Left := 20;
Top := BandaActual.Top;
Width := 4;
Pen.Width := 2;
Pen.Color := clGreen;
Height := BandaActual.Height;
end;
end;

A este procedimiento lo vamos a llamar también al crear una banda:

procedure TFInforme.BandaClick(Sender: TObject);
begin
BandaActual := TQRBand.Create(Informe);

with BandaActual do
begin
Parent := Informe;
Height := 200;
end;

SeleccionarBandaActual;
end;

Cuando creamos una nueva banda la damos por la banda de trabajo actual.

ARRASTRANDO UN COMPONENTE O LA SELECCIÓN

El procedimiento MoverRatonPulsado se ejecutará cuando dejamos pulsado el botón izquierdo del ratón sobre un componente o si estamos abriendo un rectángulo de selección:

procedure TFInforme.MoverRatonPulsado;
var
dx, dy: 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?
if Seleccionado is TQRLabel then
begin
// Movemos el componente seleccionado
dx := Cursor.X - x;
dy := Cursor.Y - y;
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;
end;

// ¿Estamos sobre una banda?
if Seleccionado is TQRBand then
begin
// si no existe un rectángulo de selección lo creamos
Seleccion := BandaActual.FindComponent('Seleccionar') as TShape;

if Seleccion = nil then
begin
Seleccion := TShape.Create(BandaActual);
Seleccion.Parent := BandaActual;
Seleccion.Pen.Color := clBlue;
Seleccion.Name := 'Seleccionar';
end;

dx := Cursor.X - x;
dy := Cursor.Y - y;
Seleccion.Left := xInicial;
Seleccion.Top := yInicial;
Seleccion.Width := dx;
Seleccion.Height := dy;
Seleccion.Brush.Style := bsClear;
end;
end;
end;

Si lo que arrastramos es una etiqueta, el código fuente no cambia mucho respecto al artículo anterior. Pero si lo hacemos sobre el fondo de una banda entonces comprobamos si existe un rectángulo de selección. Si no lo hay abrimos uno mediante un componente TShape de color azul.

La selección tendrá lugar cuando el usuario suelte el botón izquierdo del ratón.

SELECCIONANDO UNO O MAS COMPONENTES

El procedimiento ComprobarSeleccionComponentes se ejecuta inmediatamente después de soltar el botón izquierdo del ratón:

procedure TFInforme.ComprobarSeleccionComponentes;
var
Sel: TShape; // Rectángulo azul de selección
i: Integer;
Eti: TQRLabel;
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
// ¿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;

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

Comprueba si hay un rectángulo azul llamado Seleccionar y en ese caso recorre todas las etiquetas de la banda y comprueba si alguna está dentro del rectángulo de selección. Si es así, le metemos una X en el Hint. Luego llamamos al procedimiento DibujarSeleccionados para que se encargue del resto y eliminamos la selección.

MOVIENDO LOS COMPONENTES CON LOS CURSORES

Para poder mover los componentes seleccionados con el teclado podemos aprovechar el evento OnKeyDown del formulario:

procedure TFInforme.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
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;


El procedimiento MoverComponentes desplaza todos las etiquetas seleccionadas horizontal o verticalmente según los parámetros:

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

// Recorremos todos los componentes
for i := 0 to BandaActual.ComponentCount-1 do
// ¿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.Left := Etiqueta.Left + x;
Etiqueta.Top := Etiqueta.Top + y;

// Movemos su selección
Seleccion := BandaActual.FindComponent('Seleccion'+IntToStr(Etiqueta.Tag)) as TShape;
Seleccion.Left := Etiqueta.Left-1;
Seleccion.Top := Etiqueta.Top-1;
end;
end;
end;

Aparte de mover la etiqueta también tenemos que arrastrar detrás su selección (TShape).

MODIFICANDO LA DESELECCION DE COMPONENTES

Tenemos que modificar también la deselección de componentes ya que si no también eliminaría la selección verde de la banda:

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

// Quitamos las etiquetas seleccionadas
for i := 0 to BandaActual.ComponentCount-1 do
if BandaActual.Components[i] is TQRLabel then
(BandaActual.Components[i] as TQRLabel).Hint := '';

// Eliminamos las selecciones
for i := BandaActual.ComponentCount-1 downto 0 do
if BandaActual.Components[i] is TShape then
if BandaActual.Components[i].Name <> 'SeleccionBanda' then
BandaActual.Components[i].Free;
end;

Lo hacemos sólo para ese caso en especial.

EL PROYECTO EN INTERNET

Aquí va el proyecto comprimido con rar y subido a RapidShare y Megaupload:

http://rapidshare.com/files/203119248/EditorInformes2_DelphiAlLimite.rar.html

http://www.megaupload.com/?d=2FCA8P9C

CONTINUARA

Como podéis ver con estos ejemplos, crear un editor de informes no es cosa fácil (hacer algo como Microsoft Word tiene que doler). Aunque no me meteré a fondo con todo lo que engloba un editor de informes, todavía nos quedan muchas características básicas que implementar. Eso será en el próximo artículo.

Pruebas realizadas en RAD Studio 2007.

Publicidad