From: Mojo on
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



From: Dave O. on

"Mojo" <please(a)dont.spam.com> wrote in message
news:um3vclitKHA.4220(a)TK2MSFTNGP05.phx.gbl...
> 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
>

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.


From: Mike Williams on
"Mojo" <please(a)dont.spam.com> wrote in message
news:um3vclitKHA.4220(a)TK2MSFTNGP05.phx.gbl...

> 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



From: Mike Williams on
"Mojo" <please(a)dont.spam.com> wrote in message
news:um3vclitKHA.4220(a)TK2MSFTNGP05.phx.gbl...

> 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