Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
Home
Discussion GroupsVB SyntaxEnterprise DevelopmentDatabase AccessControlsCOMWin APICrystal ReportDeploymentGeneralGeneral 2
Related Topics
VB.NET / ASP.NETMS SQL ServerMS AccessOther Database ProductsMore Topics ...

VB Forum / Controls / March 2010



Tip: Looking for answers? Try searching our database.

Print API instead of Common Dialog (VB6)

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Mojo - 25 Feb 2010 14:46 GMT
Hi All

I've been shyed away from using the common dialog control for my 'open' and
'print' requirements due to bundling issues (I'm trying to do a portable
app), resources, etc and although I found a great and straightforward API
routine for the 'open' aspect (using the comdlg32.dll API call) there
doesn't appear to be the same method for printing.  All I want to do is
bring up the print dialog so that they can choose their own printer.  At the
moment, I'm sending my print jobs straight to the default printer, but this
isn't always the best way in a school environment.

Any good tips without the need to use/register an ocx?

Thanks
Dave O. - 25 Feb 2010 15:02 GMT
> Hi All
>
[quoted text clipped - 13 lines]
>
> Thanks

Hi, in the same way that you use the GetOpenFileName API to open a file you
would use the ShowPrinter API to get the printer dialogue. Or if you just
want to show a list of available printers then you can use the Printer
object to iterate a list:
I just tested it by filling a listbox although in real life a combo box
would be more appropriate::

For i = 0 To Printers.Count - 1
 List1.AddItem Printers(i).DeviceName
Next

Seems to work a treat, an obvious touch would be to have the default printer
preselected.

Regards
Dave O.
Mike Williams - 25 Feb 2010 17:07 GMT
> I've been shyed away from using the common dialog control for
> my 'open' and 'print' requirements due to bundling issues . . and
> although I found a great and straightforward API routine for the
> 'open' aspect (using the comdlg32.dll API call) there doesn't
> appear to be the same method for printing.  All I want to do is
> bring up the print dialog so that they can choose their own printer.

The best way is to present your users with a standard Windows printer dialog
using the PrintDialog API and instructing it to return a hDC for you. That
method allows your users to properly select from all the available printer
options in the dialog, including those options that are special to many
printers and that would otherwise unavailable because they cannot be
transferred to the VB Printer Object and also including those options that
are not even known to the PrintDialog itself (other than the fact that it
knows there is something in the Extras section that only the printer driver
itself understands). However, the method involves doing all your printing to
the returned hDC using the various GDI printing and drawing functions, and
on the assumption that you are currently using the VB Printer Object for
your printing and that you would prefer to continue to do so then that
method would not be appropriate for you, although personally I would suggest
it as being the very best method. Post again if you would like to go that
route.

Otherwise there are still plenty options available to you, although you
should avoid at all costs any option that forces the system default printer
to change to the printer your user has selected (some methods do that and
they are to be avoided). One very simple method would be to create your own
"home brewed" printer dialog using a Modal Form containing a ListBox and
populating the ListBox by running through the built-in VB Printers
Collection, allowing the user to select whichever printer he wants and then
using Set Printer to st the VB printer to whatever he has chosen. There are
of course many limitations to that approach, and it is non standard, but it
is very easy to do and can be done in a dozen or so lines of code.

Another alternative is to show a standard Windows printer dialog using the
PrintDialog API, allowing the user to make his various selections and then
transferring them to the VB Printer Object. That method does of course limit
you to the settings that the VB Printer Object can actually "eat", but it is
probably the second best option (after the hDC option I mentioned earlier).
There is a Micro$oft DLL called vbprndlg.dll which wraps it all up nicely
for you and which can be downloaded at
http://support.microsoft.com/kb/322710 along with its various supporting
files, although since you've said you want to avoid bundling supporting
files then I imagine you won't want to use that DLL so you will probably be
better off using the PrintDialog API through straight VB6 code. As they say
in all the best cookery programs, here's one I prepared earlier ;-)

It's actually a modification of some code that was on the MSDN website
before they produced the vbprndlg.DLL, but I have modified it in various
ways to overcome some of its limitations. For example I've added code to
take account of long printer names, which can be very much longer than the
CCHDEVICENAME limit of 32 characters that can be present in the DEVMODE
dmDeviceName entry, especially when they are over networks but also
sometimes even if they are not. I've also added some code to properly set
the positioning of your printout so that it takes account of the printer's
unrpintable margins. In fact the main reason I wrote all that "waffle" above
is to alert you to the limitations of some of the other methods (although
not to the limitations of the first method I suggested, because it doesn't
really have any other than the added complexity of your printing code), and
I did so in order that you would not be put off by the length of the code I
am posting. It seems like a lot, but it really is well worth doing. Anyway,
here is the code. To try it out just paste it into a VB Form containing one
Command Button.

Mike

Option Explicit
Private Declare Function GetDeviceCaps Lib "gdi32" _
(ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function PrintDialog Lib "comdlg32.dll" _
 Alias "PrintDlgA" (pPrintdlg As PRINTDLG_TYPE) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias _
 "RtlMoveMemory" (hpvDest As Any, hpvSource As Any, _
 ByVal cbCopy As Long)
Private Declare Function GlobalLock Lib "kernel32" _
 (ByVal hMem As Long) As Long
Private Declare Function GlobalUnlock Lib "kernel32" _
 (ByVal hMem As Long) As Long
Private Declare Function GlobalAlloc Lib "kernel32" _
 (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Function GlobalFree Lib "kernel32" _
 (ByVal hMem As Long) As Long
Private Declare Function SetBkMode Lib "gdi32" _
 (ByVal hdc As Long, ByVal nBkMode As Long) As Long
Private Const CCHDEVICENAME = 32
Private Const CCHFORMNAME = 32
Private Const GMEM_MOVEABLE = &H2
Private Const GMEM_ZEROINIT = &H40
Private Const DM_DUPLEX = &H1000&
Private Const DM_ORIENTATION = &H1&
Private Const PD_PRINTSETUP = &H40
Private Const PD_DISABLEPRINTTOFILE = &H80000
Private Const PHYSICALOFFSETX As Long = 112
Private Const PHYSICALOFFSETY As Long = 113
Private Const OPAQUE = 0
Private Const TRANSPARENT = 1
Private Type PRINTDLG_TYPE
 lStructSize As Long
 hwndOwner As Long
 hDevMode As Long
 hDevNames As Long
 hdc As Long
 flags As Long
 nFromPage As Integer
 nToPage As Integer
 nMinPage As Integer
 nMaxPage As Integer
 nCopies As Integer
 hInstance As Long
 lCustData As Long
 lpfnPrintHook As Long
 lpfnSetupHook As Long
 lpPrintTemplateName As String
 lpSetupTemplateName As String
 hPrintTemplate As Long
 hSetupTemplate As Long
End Type
Private Type DEVNAMES_TYPE
 wDriverOffset As Integer
 wDeviceOffset As Integer
 wOutputOffset As Integer
 wDefault As Integer
 extra As String * 200
End Type
Private Type DEVMODE_TYPE
 dmDeviceName As String * CCHDEVICENAME
 dmSpecVersion As Integer
 dmDriverVersion As Integer
 dmSize As Integer
 dmDriverExtra As Integer
 dmFields As Long
 dmOrientation As Integer
 dmPaperSize As Integer
 dmPaperLength As Integer
 dmPaperWidth As Integer
 dmScale As Integer
 dmCopies As Integer
 dmDefaultSource As Integer
 dmPrintQuality As Integer
 dmColor As Integer
 dmDuplex As Integer
 dmYResolution As Integer
 dmTTOption As Integer
 dmCollate As Integer
 dmFormName As String * CCHFORMNAME
 dmUnusedPadding As Integer
 dmBitsPerPel As Integer
 dmPelsWidth As Long
 dmPelsHeight As Long
 dmDisplayFlags As Long
 dmDisplayFrequency As Long
 dmICMMethod As Long
 dmICMIntent As Long
 dmMediaType As Long
 dmDitherType As Long
 dmReserved1 As Long
 dmReserved2 As Long
 dmPanningWidth As Long
 dmPanningHeight As Long
End Type

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 Function SelectPrinter(frmOwner As Form, Optional _
 InitialPrinter As String, Optional _
 PrintFlags As Long = PD_PRINTSETUP) _
 As Boolean
Dim LongPrinterName As String
Dim PrintDlg As PRINTDLG_TYPE
Dim DevMode As DEVMODE_TYPE
Dim DevName As DEVNAMES_TYPE
Dim lpDevMode As Long, lpDevName As Long
Dim bReturn As Integer, OriginalPrinter As String
Dim p1 As Printer, NewPrinterName As String
PrintDlg.lStructSize = Len(PrintDlg)
PrintDlg.hwndOwner = frmOwner.hWnd
PrintDlg.flags = PrintFlags
On Error Resume Next
OriginalPrinter = Printer.DeviceName
If Len(InitialPrinter) > 0 Then
 For Each p1 In Printers
   If InStr(1, p1.DeviceName, InitialPrinter, _
       vbTextCompare) > 0 Then
     Set Printer = p1
     Exit For
   End If
 Next
End If
DevMode.dmDeviceName = Printer.DeviceName
DevMode.dmSize = Len(DevMode)
DevMode.dmFields = DM_ORIENTATION
DevMode.dmPaperWidth = Printer.Width
DevMode.dmOrientation = Printer.Orientation
DevMode.dmPaperSize = Printer.PaperSize
On Error GoTo 0
PrintDlg.hDevMode = GlobalAlloc(GMEM_MOVEABLE Or _
 GMEM_ZEROINIT, Len(DevMode))
lpDevMode = GlobalLock(PrintDlg.hDevMode)
If lpDevMode > 0 Then
 CopyMemory ByVal lpDevMode, DevMode, Len(DevMode)
 bReturn = GlobalUnlock(PrintDlg.hDevMode)
End If
With DevName
 .wDriverOffset = 8
 .wDeviceOffset = .wDriverOffset + 1 + Len _
   (Printer.DriverName)
 .wOutputOffset = .wDeviceOffset + 1 + Len(Printer.Port)
 .wDefault = 0
End With
With Printer
 DevName.extra = .DriverName & Chr(0) & _
   .DeviceName & Chr(0) & .Port & Chr(0)
End With
PrintDlg.hDevNames = GlobalAlloc(GMEM_MOVEABLE Or _
 GMEM_ZEROINIT, Len(DevName))
lpDevName = GlobalLock(PrintDlg.hDevNames)
If lpDevName > 0 Then
 CopyMemory ByVal lpDevName, DevName, Len(DevName)
 bReturn = GlobalUnlock(lpDevName)
End If
If PrintDialog(PrintDlg) <> 0 Then
'
' Mike's amendment to handle long printer names
CopyMemory DevName, ByVal lpDevName, Len(DevName)
LongPrinterName = Mid$(DevName.extra, _
   DevName.wDeviceOffset - DevName.wDriverOffset + 1)
LongPrinterName = Left$(LongPrinterName, _
   InStr(LongPrinterName, Chr$(0)) - 1)
 DoEvents ' allow dialog to remove itself from display
 Me.Refresh
 SelectPrinter = True
 lpDevName = GlobalLock(PrintDlg.hDevNames)
 CopyMemory DevName, ByVal lpDevName, 45
 bReturn = GlobalUnlock(lpDevName)
 GlobalFree PrintDlg.hDevNames
 lpDevMode = GlobalLock(PrintDlg.hDevMode)
 CopyMemory DevMode, ByVal lpDevMode, Len(DevMode)
 bReturn = GlobalUnlock(PrintDlg.hDevMode)
 GlobalFree PrintDlg.hDevMode
 NewPrinterName = UCase$(Left(DevMode.dmDeviceName, _
   InStr(DevMode.dmDeviceName, Chr$(0)) - 1))
' Code now handles long printer names properly
 If Printer.DeviceName <> _
   LongPrinterName Then
   For Each p1 In Printers
     If p1.DeviceName = _
       LongPrinterName Then
       Set Printer = p1
     End If
   Next
 End If
 On Error Resume Next
 ' Transfer settings from the Devmode structure to the
 ' VB printer object (this example just transfers some
 ' of them but you can of course use transfer more)
 Printer.Copies = DevMode.dmCopies
 Printer.Duplex = DevMode.dmDuplex
 Printer.Orientation = DevMode.dmOrientation
 Printer.PaperSize = DevMode.dmPaperSize
 Printer.PrintQuality = DevMode.dmPrintQuality
 Printer.ColorMode = DevMode.dmColor
 Printer.PaperBin = DevMode.dmDefaultSource
 SetBkMode Printer.hdc, TRANSPARENT
 '
 On Error GoTo 0
Else
 SelectPrinter = False ' user cancelled
 For Each p1 In Printers
   If p1.DeviceName = OriginalPrinter Then
     Set Printer = p1
     Exit For
   End If
 Next
 GlobalFree PrintDlg.hDevNames
 GlobalFree PrintDlg.hDevMode
End If
End Function

Private Sub Command1_Click()
' Note: Specifying Printer.DeviceName in the following
' line will start the dialog off with the default
' printer initially selected in the selection box, but
' you can use any other string you wish. For example,
' using "Epson" will cause the dialog to start with
' the first printer it finds with the word "Epson"
' in its device name.
If SelectPrinter(Me, Printer.DeviceName) Then
 Printer.TrackDefault = False
 Printer.ScaleMode = vbInches
 ' set origin to top left corner of physical page
 ' (otherwise it would be the top left corner of
 ' the printable area, which is not the same)
 SetPrinterOrigin 0, 0
 Printer.CurrentX = 1: Printer.CurrentY = 1
 Printer.Print "Hello World"
 Printer.EndDoc
End If
End Sub
Mike Williams - 25 Feb 2010 17:37 GMT
> Any good tips without the need to use/register an ocx?

One thing I forgot to mention in my previous response is that you should
always use error trapping with your printing code, even if you are not doing
anything that appears likely to cause an error. In fact I really should have
done so in the code I just posted. It is even possible to get a error on the
very first Printer.Print statement under certain circumstances, even if the
user does actually have printers installed. So there's one tip . . . error
trapping ;-)

Mike
Mojo - 28 Mar 2010 18:44 GMT
Hi Mike/Dave

Sorry for the late reply.  I didn't mean to be disrespectful, especially in
light of the brilliant assistance you have given me on this.

I just haven't been on my machine to check your responses until today.

Thank you once again.

> Any good tips without the need to use/register an ocx?

One thing I forgot to mention in my previous response is that you should
always use error trapping with your printing code, even if you are not doing
anything that appears likely to cause an error. In fact I really should have
done so in the code I just posted. It is even possible to get a error on the
very first Printer.Print statement under certain circumstances, even if the
user does actually have printers installed. So there's one tip . . . error
trapping ;-)

Mike
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2010 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.