How to OPEN native PRINTER DIALOG -- Please HELP !!

P

pamelafluente

I am doing my own PrintDialog, and have placed there a combo with the
printer names, as in the PrintDialog provided by VB.NET.

Here is the question: how do I open the native windows printer dialog
for the current printer, so that my current
PrintDocument.PrinterSettings will be set according to the User choices
?

Thanks
-Pamela


My question in code. Assume you have ceated a MyPrintDialog
form with a combo box "ComboBoxPrinterNames" listing the printers and a
button "ButtonSelectedPrinterProperties"
to set the current printer properties.

---------------------------------------
Private Sub ButtonSelectedPrinterProperties_Click(ByVal sender As
System.Object, ByVal e As System.EventArgs) Handles
ButtonSelectedPrinterProperties.Click

Me.PrintDocument.PrinterSettings.PrinterName =
Me.ComboBoxPrinterNames.SelectedText
If Not Me.PrintDocument.PrinterSettings.IsValid Then
MsgBox("Invalid printer", MsgBoxStyle.Information)
Exit Sub
End If

Me.OpenPrinterPropertiesDialog(PrintDocument)
End Sub

Sub OpenPrinterPropertiesDialog(ByVal PrintDocument As
PrintDocument)

' ???????? my question: how to open the windows dialog for
setting PrintDocument.PrinterSettings

End Sub
--------------------------------------------
 
B

Bart Mermuys

Hi,

I am doing my own PrintDialog, and have placed there a combo with the
printer names, as in the PrintDialog provided by VB.NET.

Here is the question: how do I open the native windows printer dialog
for the current printer, so that my current
PrintDocument.PrinterSettings will be set according to the User choices
?

Thanks
-Pamela


My question in code. Assume you have ceated a MyPrintDialog
form with a combo box "ComboBoxPrinterNames" listing the printers and a
button "ButtonSelectedPrinterProperties"
to set the current printer properties.

---------------------------------------

Without much guarantee though it should be possible with a few native
functions:

Public Class YourPrinterSettingsDialog
' native functions
Private Declare Auto Function GlobalLock Lib "kernel32.dll" _
(ByVal handle As IntPtr) As IntPtr
Private Declare Auto Function GlobalUnlock Lib "kernel32.dll" _
(ByVal handle As IntPtr) As Integer
Private Declare Auto Function GlobalFree Lib "kernel32.dll" _
(ByVal handle As IntPtr) As IntPtr
Private Declare Auto Function DocumentProperties Lib "winspool.drv" _
(ByVal hWnd As IntPtr, ByVal hPrinter As IntPtr, _
ByVal pDeviceName As String, ByVal pDevModeOutput As IntPtr, _
ByVal pDevModeInput As IntPtr, ByVal fMode As Int32) As Integer


Private Sub ButtonSelectedPrinterProperties_Click( ... )...
' only change PrinterName when it is required, NOT each time the user
presses
' the button, setting a printername resets some of the settings
If (ComboBoxPrinterNames.SelectedIndex <> -1) Then
If (Me.PrintDocument.PrinterSettings.PrinterName <>
ComboBoxPrinterNames.Text) Then
Me.PrintDocument.PrinterSettings.PrinterName =
ComboBoxPrinterNames.Text
End If
If Me.PrintDocument.PrinterSettings.IsValid Then
Me.OpenPrinterPropertiesDialog(PrintDocument.PrinterSettings)
Else
MsgBox("Invalid printersettings", MsgBoxStyle.Information)
End If
Else
MsgBox("Choose a printer", MsgBoxStyle.Information)
End If
End Sub

Sub OpenPrinterPropertiesDialog(ByVal Settings As PrinterSettings)
' PrinterSettings+PageSettings -> hDEVMODE
Dim hDevMode As IntPtr = _
Settings.GetHdevmode(Settings.DefaultPageSettings)

' Show Dialog ( [In,Out] pDEVMODE )
Dim pDevMode As IntPtr = GlobalLock(hDevMode)
DocumentProperties(Me.Handle, IntPtr.Zero, _
Settings.PrinterName, pDevMode, pDevMode, 14)
GlobalUnlock(hDevMode)

' hDEVMODE -> PrinterSettings+PageSettings
Settings.SetHdevmode(hDevMode)
Settings.DefaultPageSettings.SetHdevmode(hDevMode)

' cleanup
GlobalFree(hDevMode)
End Sub

End Class

HTH,
Greetings
 
P

pamelafluente

Dear Bart,

Really THANK YOU VERY MUCH !!! for your contribution.

You also provide some additional advice. Let me study and try to
implement it.

This one is very important for my work. Infact the PrintDialog provided
with .NET is too primitive
to be useful in real programs, and once one rewrite one cannot escape
the task to manually call the printer setup.

I will work on it and will probably poput later with some question, in
case I have problems.

I am very grateful,

-Pamela
 
P

pamelafluente

Dear Burt,

I have tried your code (on XP). Actually it is quite complete. You
have practically
done the all job. I also appreciated your correction about setting
the printer name.
You are really an angel!

I have not tried yet to print because now its night here and I am
not at my office,
but the printer properties dialog pops up fine. In case of problems
I will tell you.

If I may, I would need some further info for some refinements.
I am loading the printer names in a combo (or perhaps I will use a
treeview):
with something like:

-------------------------------------
Private Sub YourPrinterSettingsDialog_Load(ByVal sender...) Handles
MyBase.Load

Dim PrinterNames As New ArrayList
For Each PrinterName As String In
PrinterSettings.InstalledPrinters
Try
PrinterNames.Add(PrinterName)
Catch
End Try
Next PrinterName
Me.ComboBoxPrinterNames.Items.AddRange(PrinterNames.ToArray)

End Sub
------------------------------------

I need to recognize: 1. the Default Printer, 2. Network printers, to
change the appearance
of their icons. So, I guess, I need 2 boolean functions:

function IsDefaulPrinter(PrinterName as string) as boolean
function IsNetworkPrinter(PrinterName as string) as boolean
(others assumed local, I guess)

Do you know how to make these functions?
Any suggestion is very welcome. Thanks,

-Pamela
 
B

Bart Mermuys

Hi,

Dear Burt,

I have tried your code (on XP). Actually it is quite complete. You
have practically
done the all job. I also appreciated your correction about setting
the printer name.
You are really an angel!

I have not tried yet to print because now its night here and I am
not at my office,
but the printer properties dialog pops up fine. In case of problems
I will tell you.

If I may, I would need some further info for some refinements.
I am loading the printer names in a combo (or perhaps I will use a
treeview):
with something like:

-------------------------------------
Private Sub YourPrinterSettingsDialog_Load(ByVal sender...) Handles
MyBase.Load

Dim PrinterNames As New ArrayList
For Each PrinterName As String In
PrinterSettings.InstalledPrinters
Try
PrinterNames.Add(PrinterName)
Catch
End Try
Next PrinterName
Me.ComboBoxPrinterNames.Items.AddRange(PrinterNames.ToArray)

End Sub
------------------------------------

I need to recognize: 1. the Default Printer, 2. Network printers, to
change the appearance
of their icons. So, I guess, I need 2 boolean functions:

function IsDefaulPrinter(PrinterName as string) as boolean
function IsNetworkPrinter(PrinterName as string) as boolean
(others assumed local, I guess)

I understand you want to customize the PrintDialog, is there anything in
particular that you want to change or add, it is possible to create your
own, but it will involve a lot of pinvoke.

About default and network printers, it's not that simple, if you want to do
this for all printers then you need to think about speed. You can use the
native function EnumPrinters(see MSDN) but it gets complicated because a
different level is needed for wxp/2000 or w9x/me.

The example shows you how you can do it using 2 additional classes:
PrinterInfo (name, isdefault, isnetwork)
PrinterApi (thin wrapper around w32 native Printer Functions)

YourPrintDialog.vb
------------------
Public Class YourPrintDialog
' ....
Private Sub LoadPrinters()
ComboBoxPrinterNames.Clear()

For Each pi As PrinterInfo In PrinterInfo.InstalledPrinters
Console.WriteLine("Name={0} Def={1} Network={2}", _
pi.Name, pi.IsDefault, pi.IsNetwork)

ComboBoxPrinterNames.Items.Add(pi.Name)
If (pi.IsDefault) Then ComboBoxPrinterNames.Text = pi.Name
Next
End Sub
' ....
End Class


PrinterInfo.vb
--------------
Public Class PrinterInfo

Public Name As String
Public IsDefault As Boolean
Public IsNetwork As Boolean

Public Shared ReadOnly Property InstalledPrinters() As PrinterInfo()
Get
Dim infos As New ArrayList
If (Environment.OSVersion.Platform = PlatformID.Win32NT) Then
' window xp,2000
Dim DefName As String = PrinterApi.GetDefPrinter()

For Each pi4 As PrinterApi.PRINTER_INFO_4 In _
PrinterApi.EnumInfo4( _
PrinterApi.PRINTER_ENUM_LOCAL Or _
PrinterApi.PRINTER_ENUM_CONNECTIONS)

Dim pi As New PrinterInfo
pi.Name = pi4.PrinterName
pi.IsDefault = Equals(pi4.PrinterName, DefName)
pi.IsNetwork = (pi4.Attributes And
PrinterApi.PRINTER_ATTRIBUTE_NETWORK) > 0
infos.Add(pi)
Next
Else
' windows 95,98,me
For Each pi5 As PrinterApi.PRINTER_INFO_5 In _
PrinterApi.EnumInfo5(PrinterApi.PRINTER_ENUM_LOCAL)
Dim pi As New PrinterInfo
pi.Name = pi5.PrinterName
pi.IsDefault = (pi5.Attributes And
PrinterApi.PRINTER_ATTRIBUTE_DEFAULT) > 0
pi.IsNetwork = (pi5.Attributes And
PrinterApi.PRINTER_ATTRIBUTE_NETWORK) > 0
infos.Add(pi)
Next
End If
Return infos.ToArray(GetType(PrinterInfo))
End Get
End Property
End Class


PrinterApi.vb
--------------
Imports System.Runtime.InteropServices
Imports System.Text

Public Class PrinterApi

Private Declare Auto Function EnumPrinters Lib "winspool.drv" ( _
ByVal flags As Int32, ByVal name As String, ByVal level As Int32, _
ByVal pPrinterEnum As IntPtr, ByVal cbBuf As Int32, _
ByRef pcbNeeded As Int32, ByRef pcReturned As Int32) As Int32

Private Declare Auto Function GetDefaultPrinter Lib "winspool.drv" ( _
ByVal szBuffer As StringBuilder, _
ByRef cchBuffer As Int32) As Int32

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Class PRINTER_INFO_4
Public PrinterName As String
Public ServerName As String
Public Attributes As Integer
End Class

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Class PRINTER_INFO_5
Public PrinterName As String
Public PortName As String
Public Attributes As Integer
Public DeviceNotSelectedTimeout As Integer
Public TransmissionRetryTimeout As Integer
End Class

Public Const PRINTER_ATTRIBUTE_NETWORK As Int32 = &H10
Public Const PRINTER_ENUM_CONNECTIONS As Int32 = &H4
Public Const PRINTER_ENUM_LOCAL As Int32 = &H2
Public Const PRINTER_ATTRIBUTE_DEFAULT As Int32 = &H4

Public Shared Function GetDefPrinter() As String
Dim sbName As New StringBuilder(32)
GetDefaultPrinter( sbName, 32 )
return sbName.ToString()

End Function

Public Shared Function EnumInfo4(ByVal Flags As Int32) As PRINTER_INFO_4()
' level 4 enum
Return DirectCast( _
EnumInfo(4, Flags, GetType(PRINTER_INFO_4)), _
PRINTER_INFO_4())

End Function

Public Shared Function EnumInfo5(ByVal Flags As Int32) As PRINTER_INFO_5()
' level 5 enum
Return DirectCast( _
EnumInfo(5, Flags, GetType(PRINTER_INFO_5)), _
PRINTER_INFO_5())

End Function

Private Shared Function EnumInfo(ByVal Level As Int32, _
ByVal Flags As Int32, ByVal InfoType As Type) As Object()

Dim cbNeeded As Int32, cReturned As Int32
Dim pBuffer As IntPtr, pBuf As IntPtr
Dim pi As Object
Dim infos As New ArrayList

' enumerate printers
EnumPrinters(Flags, Nothing, Level, IntPtr.Zero, 0, cbNeeded, cReturned)
pBuffer = Marshal.AllocHGlobal(cbNeeded)

EnumPrinters(Flags, Nothing, Level, pBuffer, cbNeeded, cbNeeded,
cReturned)

pBuf = pBuffer
For i As Int32 = 0 To cReturned - 1
pi = Marshal.PtrToStructure(pBuf, InfoType)
infos.Add(pi)

pBuf = New IntPtr(pBuf.ToInt64() + Marshal.SizeOf(InfoType))
Next
Marshal.FreeHGlobal(pBuffer)
Return infos.ToArray(InfoType)

End Function
End Class

HTH,
Greetings
 
P

pamelafluente

Dear Burt,

I don't now what to say.

I should probably take a flight and come to kiss you. Or send Microsoft
a petition to have you appointed MVP.
Thanks!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

I * do have to * make my own PrintDialog. I have abolutely no choice
because the application is such that there is no way to use the one
provided with the language.

So the help you are so kindly and generously providing is very crucial
for me.

Let me digest all the material you have provided. I will be back later
with possible
questions.

A presto,

-Pamela
 
P

pamelafluente

Dear Bart,

I have tried it on XP Professional with a unique default local printer.

It seems it fail to recognize the a default printer. This is the
result:

Name=Microsoft Office Document Image Writer Def=False Network=False

The function:
Public Shared Function GetDefPrinter() As String
returns an empty string.
GetDefaultPrinter is returning an empty stringbuilder.

In particular

Public Class PrinterInfo
.....

For Each pi4 As PrinterApi.PRINTER_INFO_4 In
PrinterApi.EnumInfo4(PrinterApi.PRINTER_ENUM_LOCAL Or
PrinterApi.PRINTER_ENUM_CONNECTIONS)
Dim pi As New PrinterInfo
pi.Name = pi4.PrinterName
pi.IsDefault = Equals(pi4.PrinterName, DefName)

'------------------------------------------------
when executed on XP with a unique, default, printer I have:

pi4.PrinterName = "Microsoft Office Document Image Writer"
DefName = ""

hence it does not recognize the default printer as such.
PrinterApi.GetDefPrinter is returning empty string

--------------------------------------------------

pi.IsNetwork = (pi4.Attributes And
PrinterApi.PRINTER_ATTRIBUTE_NETWORK) > 0
infos.Add(pi)
Next

This is strange it is like the native function GetDefaultPrinter it is
not doing its job (?)

-Pamela
 
B

Bart Mermuys

Hi,

Dear Bart,

I have tried it on XP Professional with a unique default local printer.

It seems it fail to recognize the a default printer. This is the
result:

Name=Microsoft Office Document Image Writer Def=False Network=False

The function:
Public Shared Function GetDefPrinter() As String
returns an empty string.
GetDefaultPrinter is returning an empty stringbuilder.

I think it is caused because the Name is longer then 32 characters (which is
currently the limit inside GetDefPrinter() but also in PRINTER_INFO_4/5), if
you can try it with a printer that has a shorter name, i will look how to
deal with this and get back to you later.

HTH,
Greetings
 
B

Bart Mermuys

Hi,

Bart Mermuys said:
Hi,



I think it is caused because the Name is longer then 32 characters (which
is currently the limit inside GetDefPrinter() but also in
PRINTER_INFO_4/5), if you can try it with a printer that has a shorter
name, i will look how to deal with this and get back to you later.

Oh, PRINTER_INFO4/5.PrinterName isn't limited to 32 character, so i
shouldn't have limited GetDefPrinter, my mistake.

Corrected code for GetDefPrinter inside PrinterApi:

Public Shared Function GetDefPrinter() As String
Dim len As Integer
Dim sbName As StringBuilder

GetDefaultPrinter(Nothing, len)
sbName = New StringBuilder(len)

GetDefaultPrinter(sbName, len)
Return sbName.ToString()
End Function

HTH,
Greetings
 
P

pamelafluente

Bart,

very good. Now it works. Tomorrow morning I will be able to do more
extensive testing on a networked environment. I will let you know the
result.

Congratulation for your knowledge. I envy you.

Where did you get all that know how? After one reads your code and the
gdi documentation everything seems even obvious, but if I should have
done it only reading the documentation, I would have probably struggled
for months to put things together. How can one become like you? :)

-Pamela
 
B

Bart Mermuys

Hi,

Bart,

very good. Now it works. Tomorrow morning I will be able to do more
extensive testing on a networked environment. I will let you know the
result.

Hope it goes well.
Congratulation for your knowledge. I envy you.

Where did you get all that know how? After one reads your code and the
gdi documentation everything seems even obvious, but if I should have
done it only reading the documentation, I would have probably struggled
for months to put things together. How can one become like you? :)

:) Like all things the beginning is hard, but after a while things begin to
make more sense. I have done lots of pinvoke and it is a lot easier if you
have used api's in unmanaged code (like c/c++) before, you really need to
know about pointers and memory allocation. When you know enough about
pointers and the like then there is enough information inside .net framework
sdk doc to get you started with pinvoke, the rest you'll need to learn from
practice, examples or other sources.

Greetings
 
P

pamelafluente

Dear Bart

I am going to go sleep. Just a few thoughs.
I am really fashinated by this kind of specific knowledge.

In my programs I have always tried to stick as much as possible
to the functionalities provided by the language, to avoid, using native
things,
that if Microsoft changes something than my program could no more work
or run into errors. So say mainly maintenance reason. On the other hand
its really exciting to put hands on that native stuff.

In this case I am forced to use them because strangey Microsoft does
not provided them in the framework. But at the same time I am afraid
that with maybe next version of windows my program would run into
errors.
Actually the class you made up PrinterInfo is very useful in general
and I don't
see why Microsoft does not include it (or something similar) in the
framework.

I am sure that a large number of programmers will have this basic need
to have control
on printing process, and your class is absolutely indispensable.

Looking at your code I have a couple of doubts. Can we rely this work
on any framework
and os (win 2003, ...)? Also about the function Equal. Is it safe to
use it or it would be more
prudent to use a case insensitive (and perhaps even trimmed) version,
just in case... ?

See you later,

-Pamela
 
P

pamelafluente

Dear Bart,
I have been doing some testing of your classes on a XP machine on my
university network.

Printers (default, network) are recognized correctly.
Printing works in general but there are 2 problems that are critical.
They are
perhaps related to each other.

Problem 1
When trying printing to Acrobat Distiller, I get always this error
message:
"Tried to access printer 'Acrobat Distiller' with invalid settings'
This error does not show with other printers, but prevents to do one
crucial task: making the pdf
This error does not appear if I print from another application such as
Word.

My settings are:
..Landscape = True
..PaperSize.Kind = Custom
..PaperSize.Width = 827
..PaperSize.Width = 1169
..Margins.Top = 50
..Margins.Left = 50
..Margins.Right = 50
..Margins.Bottom = 50


Problem 2
When the native Printer Dialog box is open it does not show the current
PrinterSettings.DefaultPageSettings
but it shows its own "default" setting (A4 with Portrait, and so fort).
For instance if PrinterSettings.DefaultPageSettings have Landscape =
true or a custom Page size
they are ignored, so the current PrinterSettings.DefaultPageSettings
are not loaded in the dialog when it is open.
Further If I set anything in the printer settings, it is ignored and
the page get printed according to the current PrinterSettings
(landscaped and so forth).
In other words there seems to be no communication between the printer
dialog and the object PrintDocument. There must
be some wrong pointer somewhere. Or perhaps some ByRef missing?

Probably something here:

Sub OpenPrinterPropertiesDialog(ByVal Settings As PrinterSettings)
' PrinterSettings+PageSettings -> hDEVMODE
Dim hDevMode As IntPtr =
Settings.GetHdevmode(Settings.DefaultPageSettings)
....

has to be changed in order to have the dialog reflecting the current
printer settings.
(PrinterSettings is both a property of DefaultPageSettings and
PrintDocument can this be relevant?)

Let me know what I can do if further testing are needed.

-Pamela
 
B

Bart Mermuys

Hi,

Dear Bart,
I have been doing some testing of your classes on a XP machine on my
university network.

Printers (default, network) are recognized correctly.
Printing works in general but there are 2 problems that are critical.

That's not good .
They are
perhaps related to each other.

Problem 1
When trying printing to Acrobat Distiller, I get always this error
message:
"Tried to access printer 'Acrobat Distiller' with invalid settings'
This error does not show with other printers, but prevents to do one
crucial task: making the pdf
This error does not appear if I print from another application such as
Word.

My settings are:
.Landscape = True
.PaperSize.Kind = Custom
.PaperSize.Width = 827
.PaperSize.Width = 1169
.Margins.Top = 50
.Margins.Left = 50
.Margins.Right = 50
.Margins.Bottom = 50

Not sure, but it could indeed be related to problem2, did you try "Acobat
Distiller" with the build-in PrintDialog and see if it works with that ?
Problem 2
When the native Printer Dialog box is open it does not show the current
PrinterSettings.DefaultPageSettings
but it shows its own "default" setting (A4 with Portrait, and so fort).
For instance if PrinterSettings.DefaultPageSettings have Landscape =
true or a custom Page size
they are ignored, so the current PrinterSettings.DefaultPageSettings
are not loaded in the dialog when it is open.
Further If I set anything in the printer settings, it is ignored and
the page get printed according to the current PrinterSettings
(landscaped and so forth).
In other words there seems to be no communication between the printer
dialog and the object PrintDocument. There must
be some wrong pointer somewhere. Or perhaps some ByRef missing?

That's strange, i have tested this and both issue worked:
1) PrinterSettings.DefaultPageSettings are applied to the dialog.
2) pages are printed according to the settings.

So i'm wondering does this happen on all pc's and for all printer's ?

If you can paste or attach a zip of your solution or the relevant code(your
PrintDialog and the code/form you use to test it).

HTH,
Greetings
 
P

pamelafluente

Hi Bart,
Not sure, but it could indeed be related to problem2, did you try "Acobat
Distiller" with the build-in PrintDialog and see if it works with that ?

Yes that worked fine.
If you can paste or attach a zip of your solution or the relevant code(your
PrintDialog and the code/form you use to test it).

Yes it requires some work because I have to extract it from my context.
May I send the zip to the
email ([email protected]) shown in the header?

We can post later the final solution to the group.

-Pamela
 
P

pamelafluente

Dear Bart,
I just sent you a tiny zip (271kb) with an excerpt of the part of prg
using the dialog.

a presto,

Pamela
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top