65.9K
CodeProject 正在变化。 阅读更多。
Home

监控打印队列中的作业 (.NET)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.06/5 (21投票s)

2010年1月8日

CPOL

2分钟阅读

viewsIcon

302959

downloadIcon

17445

本文演示了监控打印队列作业状态变化的方法。

引言

本文演示了监控本地机器上打印队列的能力。

背景

互联网上(包括 CodeProject 和其他网站)有很多关于打印队列监控 API 调用的代码。 然而,我找不到任何能够帮助用户以最少的代码修改来启用监控一个或多个打印队列的代码。 因此,我创建了一个类 (PrintQueueMonitor),它允许用户监控打印队列中的作业变化,并在作业添加到队列或队列中作业状态发生变化时引发事件。

使用代码

附带的 zip 文件包含两个项目

  1. PrintQueueMonitor
  2. PrintSpooler

PrintQueueMonitor 项目包含两个主要文件

  • PrintSpoolAPI.cs
  • PrintQueueMonitor.cs

PrintSpoolAPI.cs 包含 PrintQueueMonitor 使用的数据类型和结构。 此 API 不是由我创建的,而是从互联网上获取的(不确定具体位置)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Globalization;
namespace Atom8.API.PrintSpool
{
    public enum JOBCONTROL
    ......
    ......
}

PrintQueueMonitor.cs 包含两个类 - PrintJobChangeEventArgsPrintQueueMonitor,以及一个委托 PrintJobStatusChanged

PrintJobChangeEventArgs 继承自 EventArgs。 在引发作业状态添加、状态更改或删除事件时,将传递此对象。

public class PrintJobChangeEventArgs : EventArgs
{
    #region private variables
    private int _jobID=0;
    private string _jobName = "";
    private JOBSTATUS _jobStatus = new JOBSTATUS();
    private PrintSystemJobInfo _jobInfo = null;
    #endregion
    public int JobID { get { return _jobID;}  }
    public string JobName { get { return _jobName; } }
    public JOBSTATUS JobStatus { get { return _jobStatus; } }
    public PrintSystemJobInfo JobInfo { get { return _jobInfo; } }
    public PrintJobChangeEventArgs(int intJobID, string strJobName, 
           JOBSTATUS jStatus, PrintSystemJobInfo objJobInfo )
        : base()
    {
        _jobID = intJobID;
        _jobName = strJobName;
        _jobStatus = jStatus;
        _jobInfo = objJobInfo;
    }
}

委托 PrintJobStatusChangedPrintQueueMonitor 类需要向所有者发送作业状态更改通知时引发的事件的类型。

public delegate void PrintJobStatusChanged(object Sender, 
                     PrintJobChangeEventArgs e);

PrintQueueMonitor 类导入 Win32 API 函数,向 Win32 队列管理器注册以获取更改通知,并等待通知。 收到通知后,它会检查通知是否与作业相关,如果是,则使用适当的参数引发 OnJobStatusChange() 事件。

public class PrintQueueMonitor  
{
    #region DLL Import Functions
    [DllImport("winspool.drv", 
      EntryPoint = "OpenPrinterA",SetLastError = true, 
      CharSet = CharSet.Ansi,ExactSpelling = true, 
      CallingConvention = CallingConvention.StdCall)]
    public static extern bool OpenPrinter(String pPrinterName,
    out IntPtr phPrinter,
    Int32 pDefault);
    [DllImport("winspool.drv", 
        EntryPoint = "ClosePrinter",
        SetLastError = true,
        ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall)]
    public static extern bool ClosePrinter
    (Int32 hPrinter);
    [DllImport("winspool.drv",
    EntryPoint = "FindFirstPrinterChangeNotification",
    SetLastError = true, CharSet = CharSet.Ansi,
    ExactSpelling = true,
    CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr FindFirstPrinterChangeNotification
                        ([InAttribute()] IntPtr hPrinter,
                        [InAttribute()] Int32 fwFlags,
                        [InAttribute()] Int32 fwOptions,
                        [InAttribute(), 
                         MarshalAs(UnmanagedType.LPStruct)] 
                         PRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions);
    [DllImport("winspool.drv", EntryPoint = 
               "FindNextPrinterChangeNotification",
    SetLastError = true, CharSet = CharSet.Ansi,
    ExactSpelling = false,
    CallingConvention = CallingConvention.StdCall)]
    public static extern bool FindNextPrinterChangeNotification
                        ([InAttribute()] IntPtr hChangeObject,
                         [OutAttribute()] out Int32 pdwChange,
                         [InAttribute(), 
                          MarshalAs(UnmanagedType.LPStruct)] 
                          PRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions,
                        [OutAttribute()] out IntPtr lppPrinterNotifyInfo
                             );
        #endregion
        #region Constants
        const int PRINTER_NOTIFY_OPTIONS_REFRESH = 1;
        #endregion
        #region Events
        public event PrintJobStatusChanged OnJobStatusChange;
        #endregion
        #region private variables
        private IntPtr _printerHandle = IntPtr.Zero;
        private string _spoolerName = "";
        private ManualResetEvent _mrEvent = new ManualResetEvent(false);
        private RegisteredWaitHandle _waitHandle = null;
        private IntPtr _changeHandle = IntPtr.Zero;
        private PRINTER_NOTIFY_OPTIONS _notifyOptions = 
                new PRINTER_NOTIFY_OPTIONS();
        private Dictionary<int, string> objJobDict = 
                new Dictionary<int, string>();
        private PrintQueue _spooler = null;
        #endregion
        #region constructor
        public PrintQueueMonitor(string strSpoolName) 
        {
            // Let us open the printer and get the printer handle.
            _spoolerName = strSpoolName;
            //Start Monitoring
            Start();
        }
        #endregion
        #region destructor
        ~PrintQueueMonitor() 
        {
            Stop();
        }
        #endregion
        #region StartMonitoring
        public void Start()
        {
            OpenPrinter(_spoolerName, out _printerHandle, 0);
            if (_printerHandle != IntPtr.Zero)
            {
                //We got a valid Printer handle. 
                //Let us register for change notification....
                _changeHandle = FindFirstPrinterChangeNotification(
                  _printerHandle, (int)PRINTER_CHANGES.PRINTER_CHANGE_JOB, 0, 
                  _notifyOptions);
                // We have successfully registered for change
                // notification. Let us capture the handle...
                _mrEvent.Handle = _changeHandle;
                //Now, let us wait for change notification from the printer queue....
                _waitHandle = ThreadPool.RegisterWaitForSingleObject(_mrEvent, 
                  new WaitOrTimerCallback(PrinterNotifyWaitCallback), 
                  _mrEvent, -1, true);
            }
            _spooler = new PrintQueue(new PrintServer(), _spoolerName);
            foreach (PrintSystemJobInfo psi in _spooler.GetPrintJobInfoCollection())
            {
                objJobDict[psi.JobIdentifier] = psi.Name;
            }
        }
        #endregion
        #region StopMonitoring
        public void Stop()
        {
            if (_printerHandle != IntPtr.Zero)
            {
                ClosePrinter((int)_printerHandle);
                _printerHandle = IntPtr.Zero;
            }
        }
        #endregion
        #region Callback Function
        public void PrinterNotifyWaitCallback(Object state,bool timedOut)
        {
            if (_printerHandle == IntPtr.Zero) return;
            #region read notification details
            _notifyOptions.Count = 1;
            int pdwChange = 0;
            IntPtr pNotifyInfo = IntPtr.Zero;
            bool bResult = FindNextPrinterChangeNotification(_changeHandle, 
                           out pdwChange, _notifyOptions, out pNotifyInfo);
            //If the Printer Change Notification Call did not give data, exit code
            if ((bResult == false) || (((int)pNotifyInfo) == 0)) return;
            //If the Change Notification was not relgated to job, exit code
            bool bJobRelatedChange = 
              ((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_ADD_JOB) == 
                PRINTER_CHANGES.PRINTER_CHANGE_ADD_JOB) || 
              ((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_SET_JOB) == 
                PRINTER_CHANGES.PRINTER_CHANGE_SET_JOB) ||
              ((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_DELETE_JOB) == 
                PRINTER_CHANGES.PRINTER_CHANGE_DELETE_JOB) ||
              ((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_WRITE_JOB) == 
                PRINTER_CHANGES.PRINTER_CHANGE_WRITE_JOB);
            if (!bJobRelatedChange) return;
            #endregion 
            #region populate Notification Information
            //Now, let us initialize and populate the Notify Info data
            PRINTER_NOTIFY_INFO info = 
              (PRINTER_NOTIFY_INFO)Marshal.PtrToStructure(pNotifyInfo, 
               typeof(PRINTER_NOTIFY_INFO));                        
            int pData = (int)pNotifyInfo + 
                         Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO));
            PRINTER_NOTIFY_INFO_DATA[] data = 
                    new PRINTER_NOTIFY_INFO_DATA[info.Count];
            for (uint i = 0; i < info.Count; i++)
            {
                data[i] = (PRINTER_NOTIFY_INFO_DATA)Marshal.PtrToStructure(
                          (IntPtr)pData, typeof(PRINTER_NOTIFY_INFO_DATA));
                pData += Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO_DATA));
            }
            #endregion 
            
            #region iterate through all elements in the data array
            for (int i = 0; i < data.Count(); i++)
            {
                if ( (data[i].Field == 
                       (ushort)PRINTERJOBNOTIFICATIONTYPES.JOB_NOTIFY_FIELD_STATUS) &&
                     (data[i].Type == (ushort)PRINTERNOTIFICATIONTYPES.JOB_NOTIFY_TYPE)
                    )
                {
                   JOBSTATUS jStatus  = (JOBSTATUS)Enum.Parse(typeof(JOBSTATUS), 
                                         data[i].NotifyData.Data.cbBuf.ToString());
                    int intJobID = (int)data[i].Id;
                    string strJobName = ""; 
                    PrintSystemJobInfo pji = null;
                    try
                    {
                        _spooler = new PrintQueue(new PrintServer(), _spoolerName);
                        pji = _spooler.GetJob(intJobID);
                        if (!objJobDict.ContainsKey(intJobID))
                            objJobDict[intJobID] = pji.Name;
                        strJobName = pji.Name;
                    }
                    catch
                    {
                        pji = null;
                        objJobDict.TryGetValue(intJobID, out strJobName);
                        if (strJobName == null) strJobName = "";
                    }
                    if (OnJobStatusChange != null)
                    {
                        //Let us raise the event
                        OnJobStatusChange(this, 
                          new PrintJobChangeEventArgs(intJobID,strJobName,jStatus,pji));
                    }
                }
            }
            #endregion
            #region reset the Event and wait for the next event
            _mrEvent.Reset();
            _waitHandle = ThreadPool.RegisterWaitForSingleObject(_mrEvent, 
              new WaitOrTimerCallback(PrinterNotifyWaitCallback), _mrEvent, -1, true);
            #endregion 
        }
        #endregion
    }

示例源代码和用法

PrintSpooler 项目是一个示例(包含代码),它使用 PrintQueueMonitor 类,并将从 PrintQueueMonitor 类接收到的作业更改通知附加到列表框中。

pqm = new PrintQueueMonitor(cmbPrinters.Text.Trim());
pqm.OnJobStatusChange += 
    new PrintJobStatusChanged(pqm_OnJobStatusChange);
........
........
........

void pqm_OnJobStatusChange(object Sender, PrintJobChangeEventArgs e)
{

    MethodInvoker invoker = () => {
        lbSpoolChanges.Items.Add(e.JobID + " - " + 
        e.JobName + " - " + e.JobStatus);
    };

    if (lbSpoolChanges.InvokeRequired)
        Invoke(invoker);
    else
        invoker();
}

历史

为了构建这个类和示例,我使用了来自 CodeProject 和其他网站上各种帖子中的代码片段。 非常感谢所有做出贡献的人。

© . All rights reserved.