Home > OS >  Trouble printing to a specific Tray (PaperSource) using VB.NET PrinterSettings
Trouble printing to a specific Tray (PaperSource) using VB.NET PrinterSettings

Time:09-26

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

  • Related