For Programmers: Free Programming Magazines  


Home > Archive > Visual Basic > January 2006 > ScaleTop / ScaleLeft problem









You are viewing an archived Text-only version of the thread. To view this thread in it's original format and/or if you want to reply to this thread please [click here]

 

Author ScaleTop / ScaleLeft problem
TxITGuy

2006-01-09, 7:16 pm

I am having a confusing issue with the behavior of Printer.ScaleLeft and
Printer.ScaleTop as compared to PictureBox.ScaleLeft and PictureBox.ScaleTop.

According to the documentation, the PictureBox control is useful to layout a
print job without using paper (and to provide Print Preview functionality)
because it's properties and methods are identical in function and behavior to
those of the Printer object. I have found this to be true, except for
ScaleLeft and ScaleTop.

In the PictureBox control, ScaleLeft and ScaleTop behave as documented,
setting the origin for all subsequent graphics methods. This makes it
effectively very easy to scroll a large document in the picturebox control,
since all methods change their drawing positions by the same amount (their
positions relative to each other remain the same).

On the printer object, however, setting ScaleLeft and ScaleTop triggers
UserDefined scale mode, and makes all subsequent graphics methods change
their drawing positions by varying amounts ... with the difference from the
normal getting larger as you move down the page (or to the right in the case
of ScaleLeft). In other words, the positions of each element change relative
to the other elements, not just relative to the page.

Has anyone encountered this problem before and found a solution?

The following sample should demonstrate the problem
(if it doesn't, I can email the *.frm file in question):

Private Sub Form_Load()
Dim intLoops As Integer
Dim intTwips As Integer

Printer.ScaleMode = ScaleModeConstants.vbTwips
Printer.FontName = "Tahoma"
Printer.FontSize = 8

Printer.ScaleTop = 0
Printer.ScaleLeft = 0

intLoops = 1
For intTwips = 0 To Printer.ScaleHeight
Select Case (intLoops Mod 4)
Case 0
Printer.Line (0, intTwips)-Step(495, 0)
Call PrintText(objOutput, CStr(intLoops * 15), 555, intTwips - 60)
Case 1
Printer.Line (0, intTwips)-Step(990, 0)
Call PrintText(objOutput, CStr(intLoops * 15), 1050, intTwips - 60)
Case 2
Printer.Line (0, intTwips)-Step(1485, 0)
Call PrintText(objOutput, CStr(intLoops * 15), 1545, intTwips - 60)
Case 3
Printer.Line (0, intTwips)-Step(1980, 0)
Call PrintText(objOutput, CStr(intLoops * 15), 2040, intTwips - 60)
End Select
intTwips = intTwips + (intLoops * 15)
intLoops = intLoops + 1
Next
Printer.EndDoc
End Sub

Private Sub PrintText(objOutput As Object, strText As String, intX As
Integer, intY As Integer, Optional blnRightAlign As Boolean = False)

Dim blnIsBold As Boolean

If InStr(1, strText, "<b>") Then
strText = Left(strText, Len(strText) - 4)
strText = Right(strText, Len(strText) - 3)
blnIsBold = objOutput.FontBold
objOutput.FontBold = True
End If

If blnRightAlign Then
objOutput.CurrentX = intX - objOutput.TextWidth(strText)
Else
objOutput.CurrentX = intX
End If
objOutput.CurrentY = intY
objOutput.Print strText
objOutput.FontBold = blnIsBold
End Sub

Put this into the declarations section of a new form, and run it several
times, changing the values for ScaleLeft and ScaleTop each time. Try 720 for
ScaleTop, this produces a 1/2 inch displacement at the top, and over 1 inch
at the bottom.
Stefan Berglund

2006-01-09, 7:16 pm

On Wed, 28 Dec 2005 14:27:02 -0800, "TxITGuy" <txitguy.nospam@earthlink.net> wrote:
in <E124B2EA-04B0-4C3A-8C24-C59DE9889101@microsoft.com>

>I am having a confusing issue with the behavior of Printer.ScaleLeft and
>Printer.ScaleTop as compared to PictureBox.ScaleLeft and PictureBox.ScaleTop.
>
>According to the documentation, the PictureBox control is useful to layout a
>print job without using paper (and to provide Print Preview functionality)
>because it's properties and methods are identical in function and behavior to
>those of the Printer object. I have found this to be true, except for
>ScaleLeft and ScaleTop.
>
>In the PictureBox control, ScaleLeft and ScaleTop behave as documented,
>setting the origin for all subsequent graphics methods. This makes it
>effectively very easy to scroll a large document in the picturebox control,
>since all methods change their drawing positions by the same amount (their
>positions relative to each other remain the same).
>
>On the printer object, however, setting ScaleLeft and ScaleTop triggers
>UserDefined scale mode, and makes all subsequent graphics methods change
>their drawing positions by varying amounts ... with the difference from the
>normal getting larger as you move down the page (or to the right in the case
>of ScaleLeft). In other words, the positions of each element change relative
>to the other elements, not just relative to the page.
>
>Has anyone encountered this problem before and found a solution?
>
>The following sample should demonstrate the problem
>(if it doesn't, I can email the *.frm file in question):
>
>Private Sub Form_Load()
>Dim intLoops As Integer
>Dim intTwips As Integer
>
>Printer.ScaleMode = ScaleModeConstants.vbTwips
>Printer.FontName = "Tahoma"
>Printer.FontSize = 8
>
>Printer.ScaleTop = 0
>Printer.ScaleLeft = 0
>
>intLoops = 1
>For intTwips = 0 To Printer.ScaleHeight
> Select Case (intLoops Mod 4)
> Case 0
> Printer.Line (0, intTwips)-Step(495, 0)
> Call PrintText(objOutput, CStr(intLoops * 15), 555, intTwips - 60)
> Case 1
> Printer.Line (0, intTwips)-Step(990, 0)
> Call PrintText(objOutput, CStr(intLoops * 15), 1050, intTwips - 60)
> Case 2
> Printer.Line (0, intTwips)-Step(1485, 0)
> Call PrintText(objOutput, CStr(intLoops * 15), 1545, intTwips - 60)
> Case 3
> Printer.Line (0, intTwips)-Step(1980, 0)
> Call PrintText(objOutput, CStr(intLoops * 15), 2040, intTwips - 60)
> End Select
> intTwips = intTwips + (intLoops * 15)
> intLoops = intLoops + 1
>Next
>Printer.EndDoc
>End Sub
>
>Private Sub PrintText(objOutput As Object, strText As String, intX As
>Integer, intY As Integer, Optional blnRightAlign As Boolean = False)
>
> Dim blnIsBold As Boolean
>
> If InStr(1, strText, "<b>") Then
> strText = Left(strText, Len(strText) - 4)
> strText = Right(strText, Len(strText) - 3)
> blnIsBold = objOutput.FontBold
> objOutput.FontBold = True
> End If
>
> If blnRightAlign Then
> objOutput.CurrentX = intX - objOutput.TextWidth(strText)
> Else
> objOutput.CurrentX = intX
> End If
> objOutput.CurrentY = intY
> objOutput.Print strText
> objOutput.FontBold = blnIsBold
>End Sub
>
>Put this into the declarations section of a new form, and run it several
>times, changing the values for ScaleLeft and ScaleTop each time. Try 720 for
>ScaleTop, this produces a 1/2 inch displacement at the top, and over 1 inch
>at the bottom.


I use a different approach. I scale the print preview window according to
the printer specs using the GetDeviceCaps API so it works for everything
including faxes and pdfs. For my low level routines I use a percentage of
the width (X distance) so the routines scale for either portrait or landscape.


Private Function GetPrinterSpecs(ByVal bPortrait As Boolean) As Boolean

On Error GoTo BigProblem
If (bPortrait) Then
Printer.Orientation = vbPRORPortrait
Else
Printer.Orientation = vbPRORLandscape
End If
Dim lngdpiX As Long: lngdpiX = GetDeviceCaps(Printer.hdc, LOGPIXELSX)
Dim lngdpiY As Long: lngdpiY = GetDeviceCaps(Printer.hdc, LOGPIXELSY)
Dim lngMarginLeft As Long: lngMarginLeft = GetDeviceCaps(Printer.hdc, PHYSICALOFFSETX)
Dim lngMarginTop As Long: lngMarginTop = GetDeviceCaps(Printer.hdc, PHYSICALOFFSETY)
Dim lngPrintAreaHorz As Long: lngPrintAreaHorz = GetDeviceCaps(Printer.hdc, HORZRES)
Dim lngPhysWidth As Long: lngPhysWidth = GetDeviceCaps(Printer.hdc, PHYSICALWIDTH)
Dim lngPrintAreaVert As Long: lngPrintAreaVert = GetDeviceCaps(Printer.hdc, VERTRES)
Dim lngPhysHeight As Long: lngPhysHeight = GetDeviceCaps(Printer.hdc, PHYSICALHEIGHT)
'Debug.Print lngMarginLeft, lngMarginTop, lngPrintAreaHorz, lngPhysWidth, lngPrintAreaVert, lngPhysHeight
Dim lngMarginRight As Long: lngMarginRight = lngPhysWidth - lngPrintAreaHorz - lngMarginLeft
Dim lngMarginBottom As Long: lngMarginBottom = lngPhysHeight - lngPrintAreaVert - lngMarginTop
mintMaxX = ((lngPhysWidth - lngMarginLeft - lngMarginRight) * Printer.TwipsPerPixelX)
mintMaxY = ((lngPhysHeight - lngMarginTop - lngMarginBottom) * Printer.TwipsPerPixelY)
'Debug.Print lngMarginRight, lngMarginBottom, mintMaxX, mintMaxY
picPreview.Width = lngPhysWidth * Printer.TwipsPerPixelX
picPreview.Height = lngPhysHeight * Printer.TwipsPerPixelY
'Debug.Print "X,Y="; picPreview.Width, picPreview.Height, "Max X,Y=", mintMaxX, mintMaxY
mintMarginLeft = lngMarginLeft * Printer.TwipsPerPixelX
mintMarginTop = lngMarginTop * Printer.TwipsPerPixelY
mxcY = mintMaxY - (0.5 * cintUnitsPerInch)
GetPrinterSpecs = True
Exit Function

BigProblem:
Err.Clear
mintMaxX = 11520
mintMaxY = 15024
mxcY = 14304
mintMarginLeft = 360
mintMarginTop = 360
picPreview.Width = 12240
picPreview.Height = 15840
' MsgBox Err.Description
GetPrinterSpecs = False

End Function

Private Sub WriteTextRightJustify(ByRef objPrint As Object, ByVal sOutput As String, ByVal sngXPercent As Single, ByVal intY As Integer)

WriteText objPrint, sOutput, sngXPercent, intY, -objPrint.TextWidth(sOutput)

End Sub

Private Sub WriteText(ByRef objPrint As Object, ByVal sOutput As String, ByVal sngXPercent As Single, ByVal intY As Integer, Optional ByVal intOffset
As Integer)

If (Not mbPrinter) Then
Dim intMarginLeft As Integer: intMarginLeft = mintMarginLeft
Dim intMarginTop As Integer: intMarginTop = mintMarginTop
End If
objPrint.CurrentX = (sngXPercent * mintMaxX) + intOffset + intMarginLeft
objPrint.CurrentY = intY + intMarginTop
objPrint.Print sOutput

End Sub

---
Stefan Berglund
Mike Williams

2006-01-09, 7:17 pm

"TxITGuy" <txitguy.nospam@earthlink.net> wrote in message
news:E124B2EA-04B0-4C3A-8C24-C59DE9889101@microsoft.com...

> On the printer object, however, setting ScaleLeft and ScaleTop
> triggers UserDefined scale mode, and makes all subsequent
> graphics methods change their drawing positions by varying amounts


Setting the ScaleLeft or ScaleTop property always causes the ScaleMode to
change to User Defined. This happens both with picture boxes and with the
printer. It doesn't alter the currently existing value of ScaleHeight or
ScaleWidth though, so it isn't a problem. The apparent "change of position
by varying amounts" problem you are seeing is caused by something else
entirely, which again is not a problem once you know about it.

The problem (although it really isn't a problem once you know about it) is
due to the fact that as far as VB (and also the various API printing
routines) is concerned, the printer page is not normally entirely
accessible. The printer page as far as VB is concerned is actually rectangle
that is a bit smaller than the physical paper size. For simplicity it is
useful to call this the "printable area". Moreover, the actual size of this
"printable area" is often different on different printers, even when using
exactly the same paper size. Also, the location on the physical paper of the
printable rectangle is something other than (0, 0), and this position too
can be different on different printers. In other words, the printer page has
a "printable area" which is usually (but not always) slightly smaller than
the physcial page and which is positioned close to (but not exactly at) the
top left corner of the physical page. It is physically impossible for the
printer to deposit ink outside of this "printable area". The actual size and
position of this printable area differs on different printers, and it even
differs on exactly the same printer if the user selects different settings
in the printer dialog. By default, as far as VB (and the printer APIs) is
concerned, location (0, 0) is the top left corner of this printable area. On
an A4 printer the page size would be 8.27 x 11.69 inches and the printable
area would have a size of about 8 x 11.3 inches and would be positioned at
about (0.25, 0.15) inches, but this can vary from printer to printer (and it
can also vary on the same printer at different user settings).

All this stuff is very easy to fix though, so that your printouts will
appear exactly the same in a picture box as they do on the printer.
Personally I prefer to use inches for my scale modes (you can print stuff
accurately using Singles or Doubles whatever scale mode you use). The
following code checks the location of the "printable area" of the currently
selected printer (at its currently selected settings) and it alters the
ScaleLeft and ScaleTop properties such that as far as VB is concerned
location (0, 0) points to the exact top left corner of the physical page:

Option Explicit
Private Declare Function GetDeviceCaps Lib "gdi32" _
(ByVal hdc As Long, ByVal nindex As Long) As Long
Private Const PHYSICALOFFSETX As Long = 112
Private Const PHYSICALOFFSETY As Long = 113

Private Sub SetPrinterOrigin(x As Single, y As Single)
With Printer
.ScaleLeft = .ScaleX(GetDeviceCaps(.hdc, PHYSICALOFFSETX), _
vbPixels, .ScaleMode) - x
.ScaleTop = .ScaleY(GetDeviceCaps(.hdc, PHYSICALOFFSETY), _
vbPixels, .ScaleMode) - y
.CurrentX = 0
.CurrentY = 0
End With
End Sub

Private Sub Command1_Click()
Printer.ScaleMode = vbInches
SetPrinterOrigin 0, 0 ' top left corner of physical page
' print two very small circles at exact positions on an
' A4 page
Printer.FillStyle = vbFSSolid
Printer.Circle (1.5, 1.5), 0.02, vbBlack
Printer.Circle (4.5, 4.5), 0.02, vbBlack
Printer.EndDoc
End Sub

If you paste the above code into a VB Form containing one Command Button and
click the button it will produce a printed page with two very small solid
circles on it. The centre of the first circle will be exactly 1.5 inches
form the left of the physical page and 1.5 inches from the top. The second
circle will be positions at exactly 4.5 x 4.5 inches.

As an example, if you are using A4 paper and inches as units of measurement
(as I do) and you want a picture box that displays its output exactly as it
would appear on the printed page (but scaled to fit the picture box) all you
need to do is to create a picture box that has exactly the same aspect ratio
as an A4 page (the height is the square root of two times its width) and
then set the ScaleWidth of the picture box to 8.27 and the ScaleHeight of
the picture box to 11.69 (which is the actual size of an A4 page in inches).

This is very easy if you are using a "borderless" picture box, but is very
slightly complicated if you are using a picture box with a border (because
it is the "interior" or "client" size of the picture box that is important).
So, for a picture box with (or without) a border you might do something
like:

Private Sub Command2_Click()
Dim bx As Single, by As Single
Me.ScaleMode = vbInches
Picture1.ScaleMode = Me.ScaleMode
bx = Picture1.Width - Picture1.ScaleWidth
by = Picture1.Height - Picture1.ScaleHeight
Picture1.Width = 4
Picture1.Height = Picture1.ScaleWidth * Sqr(2) + by
Print Picture1.ScaleWidth
Print Picture1.ScaleHeight
Picture1.ScaleWidth = 8.27
Picture1.ScaleHeight = 11.69
End Sub

The above code sets up a display picture box so that its client height is
exactly the square root of two times its client width, whether it has a
border or not. It also sets it up so that it's client area "appears to be" a
piece of A4 paper (except that in the example the actual physical width of
the "picture box piece of paper" is 3 logical display inches, which is 288
pixels on most machines).

Now you can set the Printer Object's ScaleMode to vbInches (and use the
SetPrinterOrigin code as shown above) and you can print to the picture box
and to the printer using exactly the same coordinates, and everythign will
be positioned properly in both cases. Obviously, for things whose size is
set in units other than the current ScaleMode (fonts, for example, whose
size is always set in Points when using VB methods) you need to "scale" them
accordingly. For example, in the above code (using a 3 inch wide picture box
as opposed to an 8.27 inch wide printer page) you would set the picture box
font sizes to 3/8.27 times the printer font size (12 points on the printer
would for example be set to 4.35 points on the picture box).

Obviously, you need to use True Type fonts to get the best results. Also,
when displaying printed pages on the screen at sizes that are much smaller
than the physical output printer size you will lose a lot of detail (mostly
because of the very limited resolution of typical displays), but that's
another story altogether :-)

By the way, the SetPrinterOrigin code above is capable of setting the origin
to something other than the top left corner of the physical page (0, 0 in
the example). For example, on an A4 page you can set the origin to the exact
centre of the physical page simply by using:

SetPrinterOrigin 4.135, 5.845 ' centre of physical page

If you do the above then thereafter location (0, 0) for the VB printer
object will "point to" the exact centre of the page. The above of course
assumes that you are using inches for your scales.

Mike




Sponsored Links







Also available: Server administration forum archive | Web Design forum archive | Software forum archive | Hardware reviews archive

Copyright 2008 codecomments.com