StrataFrame Forum

Is the printer ready ?

http://forum.strataframe.net/Topic17151.aspx

By Charles R Hankey - 6/18/2008

This sound like it should be easy but it isn't.  I need to find out from with a VFP app if a particular printer is ready to print. i.e it's current status  (Ready, out of paper, off-line whatever)  This code, offered up on  Universal Thread, doesn't work because if the printer is installed the handle will happen successfully (assuming the printer server box is on line) no matter what the status of the printer is.

(code below is a VFP function with syntax adjusted from VB)

LPARAMETERS tcprintername

&&#DEFINE cJobs_OFFSET 77

DECLARE INTEGER OpenPrinter IN winspool.drv;

STRING pPrinterName, INTEGER @phPrinter, INTEGER pDefault

DECLARE INTEGER ClosePrinter IN winspool.drv INTEGER hPrinter

cPrinter=tcprintername

hPrinter=0

IF OpenPrinter(m.cPrinter, @hPrinter, 0) = 0

&&? "Unable to obtain handle for the printer."

RETURN -1

ENDIF

= ClosePrinter(hPrinter)

RETURN 0

I found this http://www.merrioncomputing.com/Programming/PrintStatus.htm and I think my answer for creating a dll in .net may be in here but putting it together is a little beyound my current understanding of how these pieces fit.

Anyone who has solved this, knows of a third party dll that can be obtained or purchased, or who has already invented this wheel - you guidance will be greatly appreciated.

Charles

By Edhy Rijo - 6/18/2008

Hi Charles,

I found the code below in google http://bytes.com/forum/thread351305.html

Private Enum PrinterStatus

PrinterIdle = 3

PrinterPrinting = 4

PrinterWarmingUp = 5

' For more states see WMI docs.

End Enum

Private Function PrinterStatusToString(ByVal ps As PrinterStatus) As String

Dim s As String

Select Case ps

Case PrinterStatus.PrinterIdle

s = "waiting (idle)"

Case PrinterStatus.PrinterPrinting

s = "printing"

Case PrinterStatus.PrinterWarmingUp

s = "warming up"

Case Else

s = "unknown state"

End Select

PrinterStatusToString = s

End Function

Private Sub Form1_Load( _

ByVal sender As System.Object, _

ByVal e As System.EventArgs _

) Handles MyBase.Load

Dim strPrintServer As String

strPrintServer = "localhost"

Dim WMIObject As String, PrinterSet As Object, Printer As Object

WMIObject = "winmgmts://" & strPrintServer

PrinterSet = GetObject(WMIObject).InstancesOf("win32_Printer")

For Each Printer In PrinterSet

MsgBox( _

Printer.Name & ": " & _

PrinterStatusToString(Printer.PrinterStatus) _

)

Next Printer

End Sub

Copy the above code to an empty form and test the result.  I don't know if this is the best way to deal with this, but it may get you started.

By Charles R Hankey - 6/18/2008

Thanks very much Edhy.  That did indeed get me started.  I think I can figure out from here how to find the printer I want in the collection and then I need to figure out how to make this a dll I can call from VFP with the param of the name of the printer I want and return a value for it's status.  I know that is just basic .net stuff I should learn anyway so this is a good chance to figure it out on a very simple scenario.

Thanks again.  I'll share results here for anyone interested.

By Edhy Rijo - 6/19/2008

Hi Charles,

Oh, I did not understood you wanted to make this a .dll to be used in VFP.  I have done that for a library I use in one of my VFP project and you will need to do some registration on every computer this .dll will be use:

  • In your MyProject Compile page, check the "Register for COM interop" option.  That will make your library available to be use as a COM object in VFP.
  • You will need to have the .Net runtime installed in each computer this library will be used.
  • You need to Register this library using the RegAsm.exe utility so it can be seen by the VFP application.  The code to register it will be something like this:
    • RegAsm.exe MyLibraryName.dll /silent /codebase
    • The codebase parameter is very important since without it your VFP will not be able to see the COM classes.  What I did was to include a copy of RegAsm.exe with my application and copied to the same folder where the library is installed and then created a batch file with above code to register the library and it worked just fine.

There are many sample code in goggle on how to make a .NET library as COM, and it is basically pretty easy, then in your VFP application you will create the COM object like this:

** This is a VFP Code....

TRY

     LOCAL loPrinterStatusLib AS AssemblyName.className

     loPrinterStatusLib = CREATEOBJECT("AssemblyName.className")

     IF VARTYPE(loPrinterStatusLib) = "O"

          * Add your code here...

     ENDIF

CATCH TO oX

     MESSAGEBOX("Printer Status Library Failed." + CHR(13) + ox.MESSAGE, 64, "Printer Status Check...")

ENDTRY

By Trent L. Taylor - 6/19/2008

We have several .NET assembleis that we setup as COM and allow access through VFP.  We had to in order to allow our older VFP stuff run at the same time as out .NET stuff.  In fact, we actually have our VFP EXE running inside of our .NET app so people don't even know that a VFP app is running inside of the .NET app...it just feels like one application.

At any rate, like Edhy mentioned, WMI is a good way to go, but you want to be careful about using the winmgmts:// approach.  If you recall, in VFP you pretty much had no choice, this is what you had to do.  But now, in .NET, we can take safer approach and not rely on that instance.  Here is a class that will return to you the state of the printer using WMI:

Imports System.Management

Public NotInheritable Class Printers

    ''' <summary>
    ''' Available printer states (that I have figured out, anyway)
    ''' </summary>
    ''' <remarks></remarks>
    Public Enum PrinterStates As Integer
        Online = 0
        LidOpen = 4194432
        OutOfPaper = 144
        OutOfPaperLidOpen = 4194448
        Printing = 1024
        Initializing = 32768
        ManualFeedInProgress = 160
        Offline = 4096
        Unknown = -1
    End Enum

    ''' <summary>
    ''' Determine the state for a printer
    ''' </summary>
    ''' <param name="printerName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetPrinterStatus(ByVal printerName As String) As PrinterStates
        '-- Establish Locals
        Dim oConn As New ConnectionOptions()
        Dim oMS As New System.Management.ManagementScope("\root\CIMV2", oConn)
        'Dim oQuery As New System.Management.ObjectQuery("select PrinterState from Win32_Printer where Name = """ & printerName & """")
        Dim oQuery As New System.Management.ObjectQuery("SELECT * FROM Win32_Printer WHERE Name = """ & printerName & """")
        Dim oSearcher As ManagementObjectSearcher
        Dim oReturnCollection As ManagementObjectCollection
        Dim r As PrinterStates

        '-- Execute the query
        oSearcher = New ManagementObjectSearcher(oMS, oQuery)

        '-- Now get the results of the query
        oReturnCollection = oSearcher.Get()

        '-- Cycle through the results
        For Each oPrinter As ManagementObject In oReturnCollection
            '-- Make sure that there is a match on the name of the printer.  This should have already been addressed
            '   as part of the query, but better safe than sorry.
            If Not CType(oPrinter.Item("Name"), String).Equals(printerName, StringComparison.OrdinalIgnoreCase) Then Continue For

            '-- First determine if the printer is online.  This is required because the PrinterState doesn't always
            '   accurately return an Offline state.
            If CType(oPrinter.Item("WorkOffline"), Boolean) Then
                r = PrinterStates.Offline
            Else
                '-- Otherwise, return the state of the printer
                Try
                    r = CType(CType(oPrinter.Item("PrinterState"), Integer), PrinterStates)
                Catch ex As Exception
                    r = PrinterStates.Unknown
                End Try
            End If

            '-- One we make it this far, then we can bail
            Exit For
        Next

        '-- Return the results
        Return r
    End Function

End Class

Then you can just call the method on the desired printer.  Here is a quick sample of how to use this:

For Each printer As String In System.Drawing.Printing.PrinterSettings.InstalledPrinters
    MsgBox(printer & ControlChars.CrLf & Printers.GetPrinterStatus(printer).ToString())
Next
By Edhy Rijo - 6/19/2008

Hi Trent,

I am trying to test/implement your code above, but I am getting some missing references:

  • ConnectionOptions()
  • System.Management.ManagementScope()
  • System.Management.ObjectQuery()
  • ManagementObjectCollection
  • ManagementObjectSearcher()
  • ManagementObject

Could you please tell us what is the missing reference assembly?

By Trent L. Taylor - 6/19/2008

Add SYstem.Management as a reference.
By Edhy Rijo - 6/19/2008

Thanks, that did it!
By Charles R Hankey - 6/19/2008

Hey, thanks an lot Edhy and Trent.  i'm really glad I asked!  I had actually forgotten that by using a .NET dll I would have to make sure the boxes that ran it had .NET but since this is a feature that is going into an app we are converting to a SQLExpress backend I believe that will insure that .NET is there, no?

In any case, this is helping a lot in clarifying my understanding of how to do this stuff.  I knew that winmgmts was the old approach but couldn't wrap my brain around how to use the wmi.

It is great to be learning .NET in an environment where people speak Fox.  (I have been talking up Strataframe big-time on the UT for exactly that reason)

Both of your posts are going in the scrapbook and I'm definitely going to be making good use of this.  Thanks again.

By Trent L. Taylor - 6/19/2008

Glad it helped! Smile