I've been having trouble automatically selecting a Tray (PaperSource) to print from on a specific printer (HP OfficeJet Pro 9020 series) using VB.NET. I have reduced my code to a bare minimum below to indicate the process I'm following.
After the print dialog has popped up and you select 'Tray 2' from the list. The line of code prn.PrinterSettings = pd.PrinterSettings
applies that setting, and it subsequently prints from the correct tray. If, however, you just click ok on the dialog it prints from the wrong tray.
The strange thing is that, as you can see, I set the 'Tray 2' option in code, so, in theory that object should be exactly the same. I used Newtonsoft.JSON
to serialise the pd.PrinterSettings
object before and after I had selected 'Tray 2' in the dialog so I could compare the differences and they were both identical. The only differences at first were the ones I addressed in the code earlier on. They didn't make any difference though. Those being;
pd.PrinterSettings.PrintFileName = "IP_192.168.1.50"
pd.PrinterSettings.PrinterName = "Dispatch Office"
pd.PrinterSettings.Collate = False
But for whatever reason, it only works if I explicitly open the dialog and select the desired Tray and click ok. prn.PrinterSettings = pd.PrinterSettings
is the only line of code that has any effect on what printer is used, so somewhere in pd.PrinterSettings
there must be something missing that I couldn't see in the JSON string. I've had success with this code on other printers, but this specific printer seems to be different, but the problem has to be with VB.NET I would think.
Has anyone come across anything like this before? Here is my full code below.
Private Sub Print()
Dim pd As New PrintDialog
pd.PrinterSettings.PrintFileName = "IP_192.168.1.50"
pd.PrinterSettings.PrinterName = "Dispatch Office"
pd.PrinterSettings.Collate = False
For Each paperSource As Printing.PaperSource In pd.PrinterSettings.PaperSources
If paperSource.SourceName = "Tray 2" Then
pd.PrinterSettings.DefaultPageSettings.PaperSource = paperSource
Exit For
End If
Next
If pd.ShowDialog = DialogResult.OK Then
Dim prn As New Printing.PrintDocument
prn.PrinterSettings = pd.PrinterSettings
AddHandler prn.PrintPage, AddressOf Me.PrintPageHandler
prn.Print()
RemoveHandler prn.PrintPage, AddressOf Me.PrintPageHandler
End If
End Sub
Private Sub PrintPageHandler(ByVal sender As Object, ByVal args As Printing.PrintPageEventArgs)
Dim myFont As New Font("Courier New", 9)
Dim strTextFile = IO.File.ReadAllText("C:\textfile.txt")
args.Graphics.DrawString(strTextFile, New Font(myFont, FontStyle.Regular), Brushes.Black, 50, 50)
End Sub
Also, just to confirm, this is independent of the print dialog. The code below which is in the shortest form possible also does not work.
Private Sub PrintAuto()
Dim ps As New Printing.PrinterSettings With {.PrinterName = "Dispatch Office"}
ps.DefaultPageSettings.PaperSource = ps.PaperSources(3) ' Tray 2
Dim prn As New Printing.PrintDocument With {.PrinterSettings = ps}
AddHandler prn.PrintPage, AddressOf PrintPageHandler
prn.Print()
RemoveHandler prn.PrintPage, AddressOf PrintPageHandler
End Sub
CodePudding user response:
Afternoon All,
I do believe I have found a solution myself on this one. It is horrendously complicated and revolves around the Devmode structure within the .PrinterSettings
object.
I owe this answer to Richard Dean who I stumbled across on Code Project. Have a look at this link for all the details
https://www.codeproject.com/articles/488737/storing-and-recalling-printer-settings-in-csharp-n
I took the project and ported it over to VB.NET and then simplified it to the point that I could use it to achieve the results I'm after. The software that Richard wrote basically allows you to save this structure that the printer uses and recall it at a later date. I saved the .bin file with his application and now refer to said file in my code below.
The function GetSettings
returns a PrinterSettings
object linked to the specified printer and then overwrites the Devmode data that I saved earlier. This now prints to Tray 2 on my printer with only one click of a button.
Imports System.Runtime.InteropServices
Imports System.Drawing.Printing
Imports System.IO
Public Class GetPrinterSettings
<DllImport("kernel32.dll", ExactSpelling:=True)>
Public Shared Function GlobalFree(ByVal handle As IntPtr) As IntPtr
End Function
<DllImport("kernel32.dll", ExactSpelling:=True)>
Public Shared Function GlobalLock(ByVal handle As IntPtr) As IntPtr
End Function
<DllImport("kernel32.dll", ExactSpelling:=True)>
Public Shared Function GlobalUnlock(ByVal handle As IntPtr) As IntPtr
End Function
Public Function GetSettings(PrinterName As String) As PrinterSettings
Dim ps As PrinterSettings = New PrinterSettings()
ps.PrinterName = PrinterName
Dim Filename As String = "C:\Devmode.bin"
If File.Exists(Filename) Then
' Gets the data from the arraylist and loads it back into memory
' int mode
' 1 = Load devmode structure from file
' 2 = Load devmode structure from arraylist
Dim hDevMode As IntPtr = IntPtr.Zero ' a handle to our current DEVMODE
Dim pDevMode As IntPtr = IntPtr.Zero ' a pointer to our current DEVMODE
Dim TempArray As Byte()
Try
' Obtain the current DEVMODE position in memory
hDevMode = ps.GetHdevmode(ps.DefaultPageSettings)
' Obtain a lock on the handle and get an actual pointer so Windows won't move
' it around while we're messing with it
pDevMode = GlobalLock(hDevMode)
' Overwrite our current DEVMODE in memory with the one we saved.
' They should be the same size since we haven't like upgraded the OS
' Or anything.
' Load devmode structure from file
Dim fs As FileStream = New FileStream(Filename, FileMode.Open, FileAccess.Read)
TempArray = New Byte(fs.Length - 1) {}
fs.Read(TempArray, 0, TempArray.Length)
fs.Close()
fs.Dispose()
For i As Integer = 0 To TempArray.Length - 1
Marshal.WriteByte(pDevMode, i, TempArray(i))
Next
' We're done messing
GlobalUnlock(hDevMode)
' Tell our printer settings to use the one we just overwrote
ps.SetHdevmode(hDevMode)
ps.DefaultPageSettings.SetHdevmode(hDevMode)
' It's copied to our printer settings, so we can free the OS-level one
GlobalFree(hDevMode)
Catch ex As Exception
If hDevMode <> IntPtr.Zero Then
MessageBox.Show(ex.Message)
GlobalUnlock(hDevMode)
GlobalFree(hDevMode) ' And to boot, we don't need that DEVMODE anymore, either
hDevMode = IntPtr.Zero
End If
End Try
End If
Return ps
End Function
End Class
I hope this is useful to anyone in the same boat. Thanks again to Richard for publishing the source code. Please look at Richard's post for more details as it is a very useful and powerful tool.
Regards,
Henry