监控打印队列中的作业 (.NET)
本文演示了监控打印队列作业状态变化的方法。
引言
本文演示了监控本地机器上打印队列的能力。
背景
互联网上(包括 CodeProject 和其他网站)有很多关于打印队列监控 API 调用的代码。 然而,我找不到任何能够帮助用户以最少的代码修改来启用监控一个或多个打印队列的代码。 因此,我创建了一个类 (PrintQueueMonitor
),它允许用户监控打印队列中的作业变化,并在作业添加到队列或队列中作业状态发生变化时引发事件。
使用代码
附带的 zip 文件包含两个项目
- PrintQueueMonitor
- 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 包含两个类 - PrintJobChangeEventArgs
和 PrintQueueMonitor
,以及一个委托 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;
}
}
委托 PrintJobStatusChanged
是 PrintQueueMonitor
类需要向所有者发送作业状态更改通知时引发的事件的类型。
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 和其他网站上各种帖子中的代码片段。 非常感谢所有做出贡献的人。