PDA

Ver la Versión Completa : Imprimiendo un StringGrid


Dexter182
31-10-2013, 01:05:29
Estoy armando un procedimiento de Impresión para imprimir un presupuesto, cuyo origen es un StringGrid.
He logrado que calcule la cantidad de hojas que necesitará imprimir, que se pueda seleccionar el intervalo de impresión, que se adecue mínimamente al tamaño de la hoja, etc.
Cuando parecía que estaba listo, me doy cuenta que el procedimiento siempre calcula el tamaño de hoja con respecto a la impresora predeterminada (por ejemplo, una de las impresoras está definida con hoja A4 y otra en un tamaño personalizado).
Si bien mediante el PrintDialog me deja elegir e imprimir en cualquiera de las dos impresoras, me encuentro con dos problemas:
- por más que elija la otra impresora, siempre intentará imprimir en el tamaño de hoja de la impresora predeterminada
- no encuentro manera de actualizar las opciones a mostrar en el PrintDialog acorde a la impresora elegida en el mismo.

Les dejo el código del procedimiento:

procedure TForm_Venta.Imprimir(StringGrid: TStringGrid; Edit: TEdit);
//Realiza las impresiones en papel de los presupuestos
var
i : byte;
Y : Integer;
Fila : byte;
AltRenglon : Single;
CantRenglon : Integer;
Paginas : Integer;

begin
try

Printer.Canvas.Font.Name := 'Arial';
Printer.Canvas.Font.Size := 10;

//Altura en pixeles de cada renglón
AltRenglon := Printer.Canvas.TextHeight('H') + (Printer.Canvas.TextHeight('H') * 0.2);
//Cantidad de renglones por página
CantRenglon := Round(Printer.PageHeight / AltRenglon) - 7;

//Calcula la cantidad de páginas que necesitará el presupuesto
If CantRenglon > (StringGrid.RowCount - 1) then
Paginas := 1
Else
If CantRenglon > 0 then
Paginas := Ceil((StringGrid.RowCount - 1) / CantRenglon)
Else begin
Paginas := 0;
MessageBox(Handle, 'Imposible imprimir.'+#13#10+'Tamaño de hoja incorrecto.', 'Error!', MB_OK+MB_ICONERROR);
end;

PrintDialog.FromPage := 1;
PrintDialog.MinPage := 1;
PrintDialog.ToPage := Paginas;
PrintDialog.MaxPage := Paginas;

If PrintDialog.Execute then begin

//Define desde que página comenzará la impresión
If PrintDialog.FromPage = 1 then
Fila := 1
Else
Fila := ((PrintDialog.FromPage - 1) * CantRenglon) + 1;

Printer.BeginDoc;

While Fila < StringGrid.RowCount do begin

Printer.Canvas.Font.Name := 'Arial';
Printer.Canvas.Font.Size := 10;
Printer.Canvas.Font.Style := [fsBold, fsUnderline];
Printer.Canvas.TextOut(80, Round(AltRenglon * 2), 'Descripción');
Printer.Canvas.TextOut(Round(Printer.PageWidth * 0.68) - Printer.Canvas.TextWidth('Cant.'), Round(AltRenglon * 2), 'Cantidad');
Printer.Canvas.TextOut(Round(Printer.PageWidth * 0.83) - Printer.Canvas.TextWidth('Precio'), Round(AltRenglon * 2), 'Precio');
Printer.Canvas.TextOut(Printer.PageWidth - 80 - Printer.Canvas.TextWidth('Subtotal'), Round(AltRenglon * 2), 'Subtotal');

Printer.Canvas.MoveTo(0, Round(AltRenglon * 2.5));
Printer.Canvas.Font.Style := [];

For i := 1 to CantRenglon do
begin
Y := Printer.Canvas.PenPos.Y + Round(AltRenglon);
Printer.Canvas.TextOut(80, Y, StringGrid.Cells[1,Fila]);
Printer.Canvas.TextOut(Round(Printer.PageWidth * 0.68) - Printer.Canvas.TextWidth(StringGrid.Cells[2,Fila]), Y, StringGrid.Cells[2,Fila]);
Printer.Canvas.TextOut(Round(Printer.PageWidth * 0.83) - Printer.Canvas.TextWidth(StringGrid.Cells[3,Fila]), Y, StringGrid.Cells[3,Fila]);
Printer.Canvas.TextOut(Printer.PageWidth - 84 - Printer.Canvas.TextWidth(StringGrid.Cells[4,Fila]), Y, StringGrid.Cells[4,Fila]);
Fila := Fila + 1;
end;

Printer.Canvas.Font.Style := [fsBold];
Printer.Canvas.TextOut(80, Printer.PageHeight - Round(AltRenglon * 2 ), 'Hoja Nº ' + IntToStr(Printer.PageNumber));
Printer.Canvas.Font.Size := 14;
Printer.Canvas.TextOut(Printer.PageWidth - 80 - Printer.Canvas.TextWidth('TOTAL: ' + Edit.Text), Printer.PageHeight - Round(AltRenglon * 2 ), 'TOTAL: '+Edit.Text);

If Printer.PageNumber < Paginas then
Printer.NewPage;

end; {while}

Printer.EndDoc;
end;

except
on EPrinter do
MessageBox(Handle, 'Imposible imprimir.'+#13#10+'No se encuentra impresora.', 'Error!', MB_OK+MB_ICONERROR);
end;
end;

Desde ya muchas gracias de antemano!

Saludos! ^\||/

ecfisa
31-10-2013, 02:47:25
Hola Dexter182.

Mediante el componente TPrinterSetupDialog podes configurar el tamaño de la página y otras características de la impresora seleccionada, pero los cambios no son permanentes, se pierden al salir de la aplicación.

Para configurar la impresora por defecto revisa este enlace: ...get / set the default printer? (http://www.swissdelphicenter.ch/torry/showcode.php?id=660)


Saludos :)

Dexter182
31-10-2013, 14:39:28
¡Muchas gracias ecfisa por contestar! :)

Reconozco que no me expliqué bien. :(
Voy a intentar explicarlo con un ejemplo:
Supongamos que el StringGrid tiene 40 filas.
Al darle imprimir, el procedimiento Imprimir verificará en la impresora predeterminada que tamaño de hoja tiene definido y en base a eso calculará cuantas hojas necesitará y actualizará las opciones correspondientes en el PrintDialog (propiedades FromPage, MinPage, ToPage y MaxPage).
Supongamos que la impresora predeterminada tiene definido una A4 y la otra impresora tiene una hoja más pequeña. En una A4 las 40 filas entrarían sin problemas; en cambio en la otra hoja me llevaría dos páginas.
La idea sería que si en el PrintDialog elijo la otra impresora, el procedimiento vuelva a hacer los cálculos y actualice las opciones en el PrintDialog automáticamente (al ser una hoja más chica deberían cambiar MaxPage y ToPage).
Digamos, se como cambiar esas propiedades antes de que aparezca el diálogo, pero no como hacerlo en "vivo y en directo". :o

Espero que se entienda.

Muchas gracias de nuevo!

Saludos! ^\||/

Dexter182
01-11-2013, 17:07:12
Ahora también descubrí otro error... :(

Si mando a imprimir lo mismo varias veces, el valor de las variables AltRenglon y CantRenglon difiere de la primera vez que mando a imprimir al de todas las posteriores.
Cada vez entiendo menos. :(

He estado mirando ejemplos en Internet y todos se parecen a mi procedimiento.
No entiendo en que estoy fallando...

Saludos! ^\||/

ecfisa
01-11-2013, 19:03:57
Hola Dexter182.

Creo que el problema radica en que llamas al TPrintDialog después de haber asignado valores de la impresora asignada. Ejemplo:

...
AltRenglon := Printer.Canvas.TextHeight('H') + (Printer.Canvas.TextHeight('H') * 0.2);
...
If PrintDialog.Execute then begin

//Define desde que página comenzará la impresión
...


Te pongo este código (el tuyo con algunos cambios) para que lo pruebes:

procedure TForm1.Imprimir(PD: TPrintDialog; SG: TStringGrid; ED: TEdit);
var
AltRenglon: Single;
CantRenglon,Paginas, Fila, i, Y: Integer;
begin
with Printer do
begin
AltRenglon := Canvas.TextHeight('H') + (Canvas.TextHeight('H') * 0.2);
CantRenglon := Round(PageHeight / AltRenglon) - 7;
if SG.RowCount - 1 > CantRenglon then
Paginas := Ceil((SG.RowCount - 1) / CantRenglon)
else
Paginas := 1;

if PD.FromPage <= 1 then
Fila := 1
else
Fila := ((PD.FromPage - 1) * CantRenglon) + 1;
BeginDoc;
try
while Fila < SG.RowCount do
begin
Canvas.Font.Name := 'Arial';
Canvas.Font.Size := 10;
Canvas.Font.Style := [fsBold, fsUnderline];
Canvas.TextOut(80, Round(AltRenglon * 2), 'Descripción');
Canvas.TextOut(Round(PageWidth * 0.68) - Canvas.TextWidth('Cant.'),
Round(AltRenglon * 2), 'Cantidad');
Canvas.TextOut(Round(PageWidth * 0.83) - Canvas.TextWidth('Precio'),
Round(AltRenglon * 2), 'Precio');
Canvas.TextOut(PageWidth - 80 - Canvas.TextWidth('Subtotal'),
Round(AltRenglon * 2), 'Subtotal');
Canvas.MoveTo(0, Round(AltRenglon * 2.5));
Canvas.Font.Style := [];
for i := 1 to CantRenglon do
begin
Y := Canvas.PenPos.Y + Round(AltRenglon);
Canvas.TextOut(80, Y, SG.Cells[1, Fila]);
Canvas.TextOut(Round(PageWidth * 0.68) - Canvas.TextWidth(SG.Cells[2,Fila]),
Y, SG.Cells[2,Fila]);
Canvas.TextOut(Round(PageWidth * 0.83) - Canvas.TextWidth(SG.Cells[3,Fila]),
Y, SG.Cells[3,Fila]);
Canvas.TextOut(PageWidth - 84 - Canvas.TextWidth(SG.Cells[4,Fila]),
Y, SG.Cells[4,Fila]);
Fila := Fila + 1;
end;
Canvas.Font.Style := [fsBold];
Canvas.TextOut(80, PageHeight - Round(AltRenglon * 2 ),
'Hoja Nº ' + IntToStr(PageNumber));
Canvas.Font.Size := 14;
Canvas.TextOut(PageWidth - 80 - Canvas.TextWidth('TOTAL: ' + ED.Text),
PageHeight - Round(AltRenglon * 2 ), 'TOTAL: '+ED.Text);
if PageNumber < Paginas then
NewPage;
end;
finally
EndDoc;
end;
end;
end;


Llamada de prueba:

procedure TForm1.btnImprimirClick(Sender: TObject);
begin
if PrintDialog1.Execute then
Imprimir(PrintDialog1, StringGrid1, Edit1);
end;


Saludos :)

Dexter182
01-11-2013, 23:49:06
¡Muchas gracias por contestar! ^\||/

Creo que el problema radica en que llamas al TPrintDialog después de haber asignado valores de la impresora asignada.
Nop, no hay caso... :(

Te comparto unas pruebas de impresión para que veas lo que me hace:
https://www.dropbox.com/sh/b6kga2ki9ys5uey/3E3aklMxCQ

Siempre me sigue tomando la impresora predeterminada para calcular el tamaño de hoja.
Igual lo que más me está preocupando ahora es que la primera impresión hace una cosa y todas las siguientes son idénticas, pero diferentes a la primera. :eek:
Sinceramente ya no se que hacer.

Por si sirve de algo, estoy usando Delphi 7 en Windows XP.

Agradezco enormemente tu tiempo.

Saludos y gracias! :)

ecfisa
02-11-2013, 11:43:46
Hola Dexter182.

No sé que puede estar sucediendo... En mis pruebas luego de seleccionar el tamaño de hoja en el PrintDialog, queda determinado el alto y ancho correspondiente. Las pruebas las hice sobre Delphi 7, Windows 8 y PDFCreator.

¿ Has probado de visualizar los valores de alto y ancho luego de haber seleccionado el tipo de página ?, algo como:

if PrintDialog1.Execute then
with Printer do
ListBox1.Items.Add(Format('Height: %d, Width: %d',[PageHeight,PageWidth]));

de ese modo podrías comprobar si el cambio del tamaño del papel que haces en opciones avanzadas queda asentado, en mi caso sí lo hace.

Saludos :)

Dexter182
04-11-2013, 02:13:42
¡Gracias nuevamente ecfisa por responder! ^\||/

En mis pruebas luego de seleccionar el tamaño de hoja en el PrintDialog, queda determinado el alto y ancho correspondiente.
Ah, OK. Ahí probé y de esa manera si lo hace.
Es decir, si elijo la otra impresora y luego defino el tamaño de hoja desde las propiedades, si me toma los cambios (me armé un "ShowMessage" para ir viendo los valores de PageHeight y PageWidth).
Ahora, si solo elijo la impresora desde el PrintDialog pero no voy a Propiedades a definir la hoja, sigue tomando los valores de hoja definidos en la impresora predeterminada.

Y lo que más me está haciendo renegar es que la primera impresión es diferente a todas las posteriores (en la primera impresión calcula mal la cantidad de renglones por hoja, no se por qué. Eso se aprecia en los archivos que subí a DropBox).

Mis pruebas las hago sobre una HP LaserJet M1132 y una impresora de PDF llamada doPDF.

Te agradezco enormemente la ayuda que me estás brindando.

Saludos! :)

Dexter182
05-11-2013, 17:06:16
Y lo que más me está haciendo renegar es que la primera impresión es diferente a todas las posteriores (en la primera impresión calcula mal la cantidad de renglones por hoja, no se por qué).
¡¡¡Logré resolver eso al menos!!! :)

Cambié esto:
Printer.Canvas.Font.Name := 'Arial';
Printer.Canvas.Font.Size := 10;
//Altura en pixeles de cada renglón
AltRenglon := Printer.Canvas.TextHeight('H') + (Printer.Canvas.TextHeight('H') * 0.2);

Por esto:
//Altura en pixeles de cada renglón
AltRenglon := AltText + (AltText * 0.2);

AltText es una constante que la definí en 48.

Saludos! :)

Dexter182
05-11-2013, 23:51:51
Estaba pensando que pareciera que la única manera de hacer lo que quiero es armándome un diálogo (o un Form) de impresión propio, porque con un PrintDialog solo puedo definir variables antes de mostrarlo o después de darle clic al botón de Imprimir.
Yo necesito cambiar variables mientras se está ejecutando y de momento no se me ocurre como. :(

Saludos! :)

Dexter182
12-06-2016, 22:20:38
Después de mucho, mucho tiempo, y porque quería hacerle varios cambios, volví a ponerme las pilas con solucionar de una manera más elegante varios de los problemas que tenía mi código de impresión.
Luego de leer bastante a través de este foro y varias páginas en Internet, pero por sobre todo luego de muchísimas pruebas, logré que funcione como yo quería.
Finalmente decidí dejar de usar el PrintDialog y creé mi propio formulario de impresión, que consiste en un simple ComboBox para elegir la impresora y un pequeño GroupBox dónde están embebidos los componentes que me permiten decidir las opciones del intervalo de impresión.

Por si le sirve a alguien, les dejo el código...

Primero les paso el código de la parte que más dolores de cabeza me llevó hacer que funcione bien y tenga consistencia entre diferentes impresoras: el cálculo de cantidad de páginas. Este procedimiento lo llamo ni bien se abre el formulario de impresión y cada vez que el usuario cambia de impresora en el ComboBox:
procedure TForm_Impresion.CalcularPaginas;
//Calcula la cantidad de páginas que llevará la impresión
var
CantFilas : Integer;
begin
try

If Origen_Impresion = Venta1 then
CantFilas := Form_Venta.StringGrid_Venta_V1.RowCount
Else
CantFilas := Form_Venta.StringGrid_Venta_V2.RowCount;

Printer.Canvas.Font.Name := 'Arial';
Printer.Canvas.Font.Height := MulDiv(GetDeviceCaps(Printer.Handle, LOGPIXELSY), 10, 72);

//Altura en pixeles de cada renglón
AltRenglon := Printer.Canvas.Font.Height + (Printer.Canvas.Font.Height * 0.3);

//Cantidad de renglones por página
CantRenglon := Round(Printer.PageHeight / AltRenglon) - 6;

//Calcula la cantidad de páginas que necesitará el presupuesto
If CantRenglon > (CantFilas - 1) then
Paginas := 1
Else
If CantRenglon > 0 then
Paginas := Ceil((CantFilas - 1) / CantRenglon)
Else
Paginas := 0;

SpinEdit_Desde.MaxValue := Paginas;
SpinEdit_Hasta.MaxValue := Paginas;
SpinEdit_Desde.Value := 1;
SpinEdit_Hasta.Value := Paginas;

except
on EPrinter do
MessageBox(Handle, 'Error al calcular la cantidad de páginas.'+#13#10+
'No se encuentra impresora.', 'Error!', MB_OK+MB_ICONERROR);
end;
end;

Y finalmente el procedimiento Impresion, que es muy similar al código anterior, pero con unos pequeños cambios:
procedure TForm_Impresion.Imprimir(StringGrid: TStringGrid; Edit: TEdit);
//Realiza las impresiones en papel de los presupuestos
var
NumHoja : byte;
i : Integer;
Fila : Integer;
PosY : Integer;
CantPaginas : Integer;
begin
try

If Printer.Printing then Printer.Abort;

Printer.Title := 'Presupuesto';

//Define que páginas se imprimirán
If RadioButton_Todo.Checked = True then
begin
Fila := 1;
NumHoja := 1;
CantPaginas := Paginas;
end
Else //Si RadioButton_Paginas está seleccionado
begin
//Define desde que página comenzará la impresión
If SpinEdit_Desde.Value = 1 then
begin
Fila := 1;
NumHoja := 1;
end
Else
begin
Fila := Floor((SpinEdit_Desde.Value - 1) * CantRenglon) + 1;
NumHoja := Floor(SpinEdit_Desde.Value);
end;
//Define en que que página finalizará la impresión
CantPaginas := Floor(SpinEdit_Hasta.Value - SpinEdit_Desde.Value + 1);
end;

Printer.BeginDoc;

While Fila < StringGrid.RowCount do begin

Printer.Canvas.Font.Name := 'Arial';
Printer.Canvas.Font.Height := MulDiv(GetDeviceCaps(Printer.Canvas.Handle, LOGPIXELSY), 10, 72); // Equivalente a Printer.Canvas.Font.Size := 10;
Printer.Canvas.Font.Style := [fsBold, fsUnderline];
Printer.Canvas.TextOut(80, Round(AltRenglon * 1.5), 'Descripción');
Printer.Canvas.TextOut(Round(Printer.PageWidth * 0.68) - Printer.Canvas.TextWidth('Cant.'), Round(AltRenglon * 1.5), 'Cantidad');
Printer.Canvas.TextOut(Round(Printer.PageWidth * 0.83) - Printer.Canvas.TextWidth('Precio'), Round(AltRenglon * 1.5), 'Precio');
Printer.Canvas.TextOut(Printer.PageWidth - 80 - Printer.Canvas.TextWidth('Subtotal'), Round(AltRenglon * 1.5), 'Subtotal');

Printer.Canvas.MoveTo(0, Round(AltRenglon * 2));
Printer.Canvas.Font.Style := [];

For i := 1 to CantRenglon do
If Fila <= StringGrid.RowCount then
begin
PosY := Printer.Canvas.PenPos.Y + Round(AltRenglon);
Printer.Canvas.TextOut(80, PosY, StringGrid.Cells[1,Fila]);
Printer.Canvas.TextOut(Round(Printer.PageWidth * 0.68) - Printer.Canvas.TextWidth(StringGrid.Cells[2,Fila]), PosY, StringGrid.Cells[2,Fila]);
Printer.Canvas.TextOut(Round(Printer.PageWidth * 0.83) - Printer.Canvas.TextWidth(StringGrid.Cells[3,Fila]), PosY, StringGrid.Cells[3,Fila]);
Printer.Canvas.TextOut(Printer.PageWidth - 84 - Printer.Canvas.TextWidth(StringGrid.Cells[4,Fila]), PosY, StringGrid.Cells[4,Fila]);
Fila := Fila + 1;
end;

Printer.Canvas.Font.Style := [fsBold];

If NumHoja <> Paginas then
Printer.Canvas.TextOut(80, Printer.PageHeight - Round(AltRenglon * 2 ), 'Hoja Nº ' + IntToStr(NumHoja))
Else
begin
PosY := Printer.Canvas.PenPos.Y + Round(AltRenglon);
Printer.Canvas.TextOut(80, PosY, 'Hoja Nº ' + IntToStr(NumHoja));
Printer.Canvas.Font.Height := MulDiv(GetDeviceCaps(Printer.Canvas.Handle, LOGPIXELSY), 14, 72); // Equivalente a Printer.Canvas.Font.Size := 14;
Printer.Canvas.TextOut(Printer.PageWidth - 80 - Printer.Canvas.TextWidth('TOTAL: ' + Edit.Text), PosY -10, 'TOTAL: '+Edit.Text);
end;

If Printer.PageNumber < CantPaginas then
begin
Printer.NewPage;
NumHoja := NumHoja + 1;
end

end; {while}

Printer.EndDoc;

except
on EPrinter do
MessageBox(Handle, 'Imposible imprimir.'+#13#10+'No se encuentra impresora.', 'Error!', MB_OK+MB_ICONERROR);
end;
end;

"AltRenglon", "CantRenglon" y "Paginas" son variables privadas del formulario del tipo Integer.

Ahora solo me resta solucionar un "AccessViolation" que sucede solamente con una impresora HP compartida por red al mandar la orden de impresión y la PC se encuentra apagada.
Espero no demorar tres años en solucionarlo. ;)

Saludos!