Vous êtes sur la page 1sur 7

Un Componente VB6 para Printer - Parte 1

Las Deficiencias y Falta de Codificacin Limpia para Printer se Pueden Solventar Escribiendo un Componente Eficiente por Harvey Triana, Enero 4 de 2000
La salida a impresora desde programas Visual Basic no siempre es fcil. Visual Basic dispone del objeto Printer, pero ste a la vista de programadores principiantes no parece de alto nivel y suscita ms preguntas que respuestas. Para citar algunas cuestiones comunes, tenemos: Cmo imprimir un marco de texto o un TextBox Multiline?, Cmo justificar el texto impreso?, Cmo imprimir imgenes?. Todas estas preguntas tienen respuesta, sin embargo se requiere de un esfuerzo que quita mritos a Visual Basic como herramienta RAD. La API da ciertas soluciones, pero para programadores noveles no es nada atractivo, y menos la API para Printer, de hecho desde Visual Basic se ve bien extraa y difcil. Atacar los problemas desde la API para Printer podra dar soluciones oscuras y no perecederas con la evolucin de Windows (la impresin vertical es un ejemplo de este caso). El objeto Printer no es malo, si acaso tiene mala fama por ser poco comprendido. Es ms, el objeto Printer es suficiente para escribir un componente de alto nivel para aplicaciones normales. Hace algo ms de un ao escrib un componente para Printer altamente competente, con ms cdigo plano que API. Con este solo componente se han escrito salidas complejas para Plotter y reportes de acceso a datos. Este articulo delinea los pasos bsicos para escribir un componente de complemento a Printer, y da solucin eficiente a las tres primeras preguntas planteadas en el primer prrafo de este escrito.

El Objeto Printer
El objeto Printer permite comunicarnos con la impresora predeterminada en Windows. La coleccin Printers permite obtener informacin acerca de todas las impresoras del sistema disponibles (incluyendo las de red instaladas). El objeto Printer, al igual que otros objetos (como Screen), no son objetos globales en el sistema, esto quiere decir que no comparten una instancia entre aplicaciones. Para compartir una instancia, esta debe ser pasada por referencia al componente. Por ejemplo si especifica una fuente para Printer en el cliente, y en el componente escribe Printer.Print "Hola", esta fuente no es reconocida en la impresin (tanto el cliente como el componente tiene objetos Printer diferentes). Pasar por referencia objetos es lo primero que debe saber para escribir un componente para Printer. Realmente es sencillo pasar por referencia el objeto Printer del cliente a un componente, en la clase del componente escribiremos un procedimiento Property Set (mas adelante ver el cdigo). Luego de iniciar el objeto en el Cliente, lo primero que debe hacer es pasar la instancia Printer de la aplicacin al componente, esto se logra al asignar la propiedad, por ejemplo, Set obj.ClientPrinter = Printer

Un Componente de Servicios para Printer


Bien creemos el componente. Cree un nuevo proyecto DLL ActiveX. La clase predeterminada se llamar cls_PrinterUtilities. El cdigo inicial es el siguiente: Option Explicit Private m_ClientPrinter As Printer Public Property Set ClientPrinter(v As Object) If TypeName(v) = "Printer" Then Set m_ClientPrinter = v End If End Property Private Sub Class_Terminate() Set m_ClientPrinter = Nothing End Sub Note que la propiedad usa un parmetro objeto, esto es debido a que un objeto como Printer no es global de la norma ActiveX, es un objeto exclusivo de Visual Basic. Nuestro componente se encarga de usar la referencia y asignarla a un objeto Printer privado y asi mantener la asignacin en tiempo de compilacin. Para depurar el componente, agreguemos un proyecto EXE Estndar y la establecemos como inicial (clic secundario sobre el nombre del proyecto y del men contextual escogemos Establecer como Inicial). Para seguir la lnea de este ejemplo, es conveniente asignar los siguientes nombres para los proyectos y sus objetos:

Ya se ha creado el cuerpo inicial del componente, ahora agregaremos unos procedimientos interesantes para resolver las siguientes cuestiones: Como justificar el texto de impreso, procedimiento PutText Como imprimir un marco de texto o un TextBox Multiline, procedimiento PutParagraph Cmo imprimir imgenes, procedimiento PutImage

Como Justificar el Texto de Impreso


La justificacin de texto impreso se logra fcilmente al hacer el calculo matemtico de las dimensiones del texto y la determinacin del punto inicial de salida a impresora (CurrentX y CurrentY). Es conveniente dar unas constantes enumeradas para que el Cliente use con facilidad la funcin, he aqu el cdigo: '//ENUMS (Procedure PutText) Public Enum ptr_PutText

ptr_LeftToPoint ptr_CenterPoint ptr_RightToPoint ptr_BootomOfPoint ptr_TopOfPoint End Enum Public Sub PutText( _ ByVal x As Single, _ ByVal y As Single, _ s As String, _ Optional AlignmentPointX As ptr_PutText = ptr_RightToPoint, _ Optional AlignmentPointY As ptr_PutText = ptr_BootomOfPoint _ ) With m_ClientPrinter Select Case AlignmentPointX Case ptr_RightToPoint '//Default Case ptr_LeftToPoint: x = x - .TextWidth(s) Case ptr_CenterPoint: x = x - .TextWidth(s) / 2 End Select Select Case AlignmentPointY Case ptr_BootomOfPoint '//Default Case ptr_TopOfPoint: y = y - .TextHeight(s) Case ptr_CenterPoint: y = y - .TextHeight(s) / 2 End Select .CurrentX = x .CurrentY = y End With m_ClientPrinter.Print s; End Sub Los parmetros x e y de PutText son los puntos de referencia del cliente, por ejemplo si deseamos un texto en el punto 4 cm desde el margen izquierdo, 8 cm desde el margen superior, y que este texto este justo a la derecha de los 4 cm y justo debajo de los 8 cm, entonces usaremos las constantes enumeradas ptr_LeftToPoint y ptr_BootomOfPoint. La eficiencia y calidad de la funcin PutText es impresionante, podis colocar texto en cualquier parte de la hoja impresa con precisin y limpieza. Es el pilar para construir funciones ms complejas para crear reportes. Valga comentar que la justificacin de texto impreso con API es bastante complicado, con llamadas a funciones de la DLL GDI32.

Como Imprimir un Marco de Texto o un TextBox Multiline


Este titulo es una cuestin interminable en las consultas de programadores Visual Basic. Si bien, la MSDN da una solucin API para la impresin de un TextBox Multiline, en el articulo Q140886 (HOWTO: Print MultiLine TextBox Using Windows API Functions), esta solucin no cubre todos los casos y se limita al control TextBox. La solucin debe resolver el problema de poder imprimir en las dimensiones de un marco deseado. La funcin PutParagraph, en cdigo plano, solventa este problema de manera eficiente. El procedimiento PutParagraph se basa inicialmente en lograr programar el WordWrap (recorte de lineas segun el ancho de salida) del texto dentro de las dimensiones deseadas. A medida que se logran las lneas del WordWrap, se van enviando a la impresora. El cdigo es el siguiente: Private Const SP As String = " "

Public Sub PutParagraph( _ ByVal ParagraphLeft As Single, _ ByVal ParagraphTop As Single, _ ByVal ParagraphWidth As Single, _ ByVal ParagraphHeight As Single, _ ByVal Text As String _ ) Dim pos As Long Dim Word As String Dim SumTem As String Dim SumWord As String Dim hgt As Single Dim pY As Single Dim s As String hgt = m_ClientPrinter.TextHeight("H") s = RTrim$(Text) + SP pY = ParagraphTop Do pos = InStr(s, SP) If pos Then Word = Left$(s, pos - 1) '//Cut word (unfrequent case) If m_ClientPrinter.TextWidth(Word) > ParagraphWidth Then CutWord Word, ParagraphWidth End If SumTem = SumTem + Word + SP If m_ClientPrinter.TextWidth(SumTem) <= ParagraphWidth Then s = Mid$(s, pos + 1) SumWord = SumTem Else PutText ParagraphLeft, pY, SumWord pY = pY + hgt SumTem = vbNullString If (pY + hgt) > (ParagraphTop + ParagraphHeight) Then pos = 0 '//Force exit End If End If Else PutText ParagraphLeft, pY, SumWord End If Loop Until pos = 0 End Sub Private Sub CutWord(ByRef Word As String, dx As Single) Dim CutWord As String, i As Integer CutWord = vbNullString i = 0 Do i = i + 1 If m_ClientPrinter.TextWidth(CutWord + _ Mid(Word, i, 1) + "....") < dx Then CutWord = CutWord + Mid(Word, i, 1)

Else Exit Do End If Loop Word = CutWord + "..." End Sub El procedimiento PutParagraph aplica para enviar texto a la impresora en el rea deseada por el programador. Como caso particular, para imprimir el contenido de un TextBox (suponiendo escalas iguales en Printer y el Form cliente), usaramos pu.PutParagraph x, y, txtX.Width, txt.Height, txtX.Text. NOTA. La impresin de un TextBox con la API (articulo Q140886), tiene el siguiente cdigo (depurado). No use el siguiente cdigo en el componente, pues el procedimiento PutParagraph es superior. Publico este cdigo para su informacin: '// FIRST SOURCE : HOWTO: Print MultiLine TextBox Using Windows API Funct ions '// Last reviewed: June 29, 1998 '// Article ID: Q140886 '// Modified by : Harvey T. Option Explicit Private Declare Function SendMessageAsLong Lib "user32" _ Alias "SendMessageA" ( _ ByVal hWnd As Long, _ ByVal wMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long _ ) As Long Private Declare Function SendMessageAsString Lib "user32" _ Alias "SendMessageA" ( _ ByVal hWnd As Long, _ ByVal wMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As String _ ) As Long Private Const EM_GETLINE As Long = &HC4 Private Const EM_GETLINECOUNT As Long = &HBA Public Sub PrintTextBox(txt As TextBox) Dim i As Long Dim n As Long n = GetLineCount(txt) For i = 1 To n Printer.Print GetLine(txt, i - 1) Next Printer.EndDoc End Sub '// This function fills the buffer with a line of text '// specified by LineNumber from the text-box control. '// The first line starts at zero.

Private Function GetLine(txt As TextBox, LineNumber As Long) As String '// Scale this to size of text box. Const MAX_CHAR_PER_LINE As Long = 80 Dim Dim Dim Dim ByteLo ByteHi rtn Buffer As As As As Integer Integer Long String

ByteLo = MAX_CHAR_PER_LINE And (255) '// [changed 5/15/92] ByteHi = Int(MAX_CHAR_PER_LINE / 256) '// [changed 5/15/92] Buffer = Chr$(ByteLo) + Chr$(ByteHi) + Space$(MAX_CHAR_PER_LINE - 2) rtn = SendMessageAsString(txt.hWnd, EM_GETLINE, LineNumber, Buffer) GetLine = Left$(Buffer, rtn) End Function '// This function will return the number of lines currently '// in the text-box control. Private Function GetLineCount(txt) As Long GetLineCount = SendMessageAsLong(txt.hWnd, EM_GETLINECOUNT, 0, 0) End Function

Cmo Imprimir Imgenes


Lo logramos usando la instruccin PainPicture y enviamos la imgen en un tipo stdPicture: Public Sub PutImage( _ ImageLeft As Single, _ ImageTop As Single, _ Image As StdPicture, _ Optional ShowError As Boolean = False _ ) On Error GoTo ErrHandler m_ClientPrinter.PaintPicture Image, _ ImageLeft, ImageTop, _ , , , , , , vbSrcAnd Exit Sub ErrHandler: If ShowError Then MsgBox "Error in Procedire PutImage: " & _ vbCrLf & Err.Description, vbInformation End If End Sub Ejemplos (pu es la instancia de la clase cls_PrinterUtilities): Imprimir un PictureBox: pu.PutImage 0, 0, picX.Picture Imprimir un archivo: pu.PutImage 0, 0, LoadPicture(FileName) Por ultimo, anexamos los procedimiento PutText, PutParagraph y Put Image a la clase cls_PrinterUtilities para lograr un interesante componente. De aqu en adelante, agregar ms y ms procedimientos har del componente algo realmente til. Tambin, en casos en donde la codificacin BASIC resulta obsoleta y extraa, se puede dar una solucin, tal es el caso de dibujar un rectngulo, he aqu un procedimiento ms natural:

Public Sub Rectangle( _ x1 As Single, y1 As Single, _ x2 As Single, y2 As Single, _ Optional Width As Long = 1 _ ) m_ClientPrinter.DrawWidth = Width m_ClientPrinter.Line (x1, y1)-(x2, y1) m_ClientPrinter.Line -(x2, y2) m_ClientPrinter.Line -(x1, y2) m_ClientPrinter.Line -(x1, y1) End Sub

Vous aimerez peut-être aussi