Sunday, 26 February 2012

Accessing Printer Status using WinSpool in C#

 

After help from ambience I’ve managed to get the code working to access accurate printer status by using DllImport to access Winspool.drv (as opposed to using WMI, which had very mixed results).

There are 3 key functions you need to be aware of:

  • OpenPrinter
  • GetPrinter
  • ClosePrinter



The GetPrinter function is the one that does the real work we are interested in – accessing the PrinterInfo2 structure that gives you all the information you could possibly want on your named printer – driver, status, configuration, etc

Once you have the PrinterInfo2 you can than use bitwise operators along with known constants to find out the current status of the printer. One thing to note is that to detect whether a printer is marked as offline rather than physically offline you need to check the Attributes rather than Status – I’ll put a followup post shortly with details of a Helper class round the PrinterInfo2 status.

Here’s the code to help you out if, like me you need to get accurate printer info in C#:

//Code written by Mark Middlemist - @delradie 
//Made available at http://delradiesdev.blogspot.com
//Interop details from http://pinvoke.net/
using System;
using System.Runtime.InteropServices;

namespace DelradiesDev.PrinterStatus
{
  public class WinSpoolPrinterInfo
  {
    [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int OpenPrinter(string pPrinterName, out IntPtr phPrinter, ref PRINTER_DEFAULTS pDefault);
    
    [DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool GetPrinter(IntPtr hPrinter, Int32 dwLevel, IntPtr pPrinter, Int32 dwBuf, out Int32 dwNeeded);
    
    [DllImport("winspool.drv", SetLastError = true)]
    public static extern int ClosePrinter(IntPtr hPrinter);
        
    [StructLayout(LayoutKind.Sequential)]
    public struct PRINTER_DEFAULTS
    {
      public IntPtr pDatatype;
      public IntPtr pDevMode;
      public int DesiredAccess;
    }
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct PRINTER_INFO_2
    {
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pServerName;      
      
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pPrinterName;
      
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pShareName;
      
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pPortName;
      
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pDriverName;
      
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pComment;
      
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pLocation;
      
      public IntPtr pDevMode;
      
      [MarshalAs(UnmanagedType.LPTStr)]      
      public string pSepFile;
      
      [MarshalAs(UnmanagedType.LPTStr)]      
      public string pPrintProcessor;
      
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pDatatype;
      
      [MarshalAs(UnmanagedType.LPTStr)]
      public string pParameters;
      
      public IntPtr pSecurityDescriptor;
      public uint Attributes;
      public uint Priority;
      public uint DefaultPriority;
      public uint StartTime;
      public uint UntilTime;
      public uint Status;
      public uint cJobs;
      public uint AveragePPM;
    }
    
    public PRINTER_INFO_2? GetPrinterInfo(String printerName)
    {
      IntPtr pHandle;      
      PRINTER_DEFAULTS defaults = new PRINTER_DEFAULTS();      
      PRINTER_INFO_2? Info2 = null;
      
      OpenPrinter(printerName, out pHandle, ref defaults);
      
      Int32 cbNeeded = 0;
      
      bool bRet = GetPrinter(pHandle, 2, IntPtr.Zero, 0, out cbNeeded);
      
      if (cbNeeded > 0)
      {
        IntPtr pAddr = Marshal.AllocHGlobal((int)cbNeeded);
        
        bRet = GetPrinter(pHandle, 2, pAddr, cbNeeded, out cbNeeded);
        
        if (bRet)        
        {
          Info2 = (PRINTER_INFO_2)Marshal.PtrToStructure(pAddr, typeof(PRINTER_INFO_2));
        }
        
        Marshal.FreeHGlobal(pAddr);
      }
      
      ClosePrinter(pHandle);
      
      return Info2;
    }
  }
} 

Thursday, 23 February 2012

Rebinding bug in WinForms ListBox when SelectionMode = None

Just a quick post to help out anyone who hits the same bug I have.

It appears that there are a couple of issues with WinForms ListBoxes when the SelectionMode is None (certainly in .net 2).

One that is pretty well documented is that if your SelectionMode is None then you can get problems on closing the form (https://connect.microsoft.com/VisualStudio/feedback/details/189700/listbox-selectionmode-none-bug)

One I've just hit on is that if you rebind/refresh a listbox's contents by changing the datasource then the list is cleared and left blank

The workaround is very simple (based on the one from Connect) - before manipulating the DataSource property on the ListBox change the SelectionMode to one of the other values, do your work, then change it back to None

Hope this helps