一、组件简介
打印监听组件是一款集成于 Windows 桌面环境的打印任务管理与监控工具,适用于企业级应用场景。它不仅支持多打印机任务的实时监控,还能通过 WebSocket 与外部系统集成,实现自动化打印、任务状态反馈、远程控制等功能。
二、界面功能介绍
1. 主界面与托盘集成
- 主窗体:采用 WinForms 界面,包含多标签页(TabControl),每个标签页对应一台本地打印机,便于分组管理。

- 托盘图标:程序最小化后驻留于系统托盘,双击可快速还原主界面,支持右键菜单操作(如退出、重启、服务设置等)。

2. 打印机管理
- 打印机列表:自动检测本地所有已安装打印机,支持设置默认打印机、查看打印机属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
internal void BindPrintersToMenu() { 默认打印机ToolStripMenuItem.DropDownItems.Clear(); string defaultPrinter = new System.Drawing.Printing.PrinterSettings().PrinterName;
var defaultItem = new ToolStripMenuItem(defaultPrinter) { Checked = true }; defaultItem.Click += (s, e) => SetDefaultPrinterUI(defaultPrinter); var prefItem = new ToolStripMenuItem("首选项"); prefItem.Click += (s, e) => ShowPrinterProperties(defaultPrinter); defaultItem.DropDownItems.Add(prefItem); 默认打印机ToolStripMenuItem.DropDownItems.Add(defaultItem);
foreach (string printer in System.Drawing.Printing.PrinterSettings.InstalledPrinters) { if (printer == defaultPrinter) continue;
var item = new ToolStripMenuItem(printer) { Checked = false }; item.Click += (s, e) => SetDefaultPrinterUI(printer);
var prefItem2 = new ToolStripMenuItem("首选项"); prefItem2.Click += (s, e) => ShowPrinterProperties(printer); item.DropDownItems.Add(prefItem2);
默认打印机ToolStripMenuItem.DropDownItems.Add(item); }
} private void SetDefaultPrinterUI(string printerName) { foreach (ToolStripMenuItem item in 默认打印机ToolStripMenuItem.DropDownItems) item.Checked = item.Text == printerName;
SetSystemDefaultPrinter(printerName); }
private void ShowPrinterProperties(string printerName) { string args = $"printui.dll,PrintUIEntry /e /n \"{printerName}\""; var psi = new System.Diagnostics.ProcessStartInfo { FileName = "rundll32.exe", Arguments = args, UseShellExecute = false, CreateNoWindow = true }; try { System.Diagnostics.Process.Start(psi); } catch (Exception ex) { MessageBox.Show("无法打开打印机首选项窗口:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
|
- TabControl:每台打印机一个标签页,便于查看和管理各自的打印任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
|
private void BindPrintersToTabControl() { tabControl1.TabPages.Clear();
string defaultPrinter = new System.Drawing.Printing.PrinterSettings().PrinterName; List<string> printers = new List<string>();
printers.Add(defaultPrinter);
foreach (string printer in System.Drawing.Printing.PrinterSettings.InstalledPrinters) { if (printer != defaultPrinter) printers.Add(printer); }
foreach (string printer in printers) { var tabPage = new TabPage(printer); var dgv = new DataGridView { Dock = DockStyle.Fill, ReadOnly = true, AllowUserToAddRows = false, AllowUserToDeleteRows = false, RowHeadersVisible = false, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill };
dgv.Columns.Add("clientIp", "来源"); dgv.Columns.Add("taskId", "任务ID"); dgv.Columns.Add("taskName", "任务名称"); dgv.Columns.Add("realName", "模板"); dgv.Columns.Add("requestTime", "开始时间"); dgv.Columns.Add("status", "任务状态");
dgv.ContextMenuStrip = dgvContextMenu; dgv.MouseDown += Dgv_MouseDown;
var txtSearch = new TextBox { PlaceholderText = "任务ID", Width = 120, Anchor = AnchorStyles.Left | AnchorStyles.Bottom };
var btnSearch = new Button { Text = "查找", Width = 60, Anchor = AnchorStyles.Left | AnchorStyles.Bottom };
btnSearch.Click += (s, e) => { string searchId = txtSearch.Text.Trim(); bool found = false; foreach (DataGridViewRow row in dgv.Rows) { if (row.IsNewRow) continue; if (row.Cells["taskId"].Value?.ToString() == searchId) { row.Selected = true; dgv.CurrentCell = row.Cells["taskId"]; found = true; } else { row.Selected = false; } } if (!found) { MessageBox.Show("未找到对应任务ID!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } };
var panel = new Panel { Dock = DockStyle.Bottom, Height = 40 }; txtSearch.Location = new Point(10, 8); btnSearch.Location = new Point(140, 6); panel.Controls.Add(txtSearch); panel.Controls.Add(btnSearch);
tabPage.Controls.Add(panel); tabPage.Controls.Add(dgv); tabControl1.TabPages.Add(tabPage); } }
|
3. 打印任务监控
- 任务列表:每个打印机标签页下方为 DataGridView,实时显示当前打印任务,包括来源、任务ID、任务名称、模板、开始时间、任务状态等信息。

- 右键菜单:支持对单个任务进行“取消打印”、“重新打印”、“删除记录”等操作。

- 任务搜索:支持按任务ID快速定位任务。
4. 其他功能
- 服务控制:可一键启动/停止 WebSocket 服务,支持与外部系统通信。
- 模板设计与预览:集成 FastReport 设计器和预览器,方便模板维护。
因为FastReport.Net 是需要购买授权的,所以我使用的是FastReport.OpenSource(开源版),开源版功能太少,不能直接从程序内部调用FastReport设计器和预览器,只能通过启动本地安装的.exe来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
private void 设计ToolStripMenuItem_Click(object sender, EventArgs e) { string designerPath = GetConfigValue("designer_path"); string templatePath = GetTemplatePathFromConfig(); if (string.IsNullOrEmpty(designerPath) || !System.IO.File.Exists(designerPath)) { MessageBox.Show("未找到 FastReport 设计器,请检查 config.ini 配置!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (!System.IO.File.Exists(templatePath)) { MessageBox.Show("未找到模板文件,请检查路径!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } try { System.Diagnostics.Process.Start(designerPath, $"\"{templatePath}\""); } catch (Exception ex) { MessageBox.Show("启动 FastReport 设计器失败:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void 预览ToolStripMenuItem_Click(object sender, EventArgs e) { string viewerPath = GetConfigValue("viewer_path"); string templatePath = GetTemplatePathFromConfig(); if (string.IsNullOrEmpty(viewerPath) || !System.IO.File.Exists(viewerPath)) { MessageBox.Show("未找到 FastReport 预览器,请检查 config.ini 配置!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (!System.IO.File.Exists(templatePath)) { MessageBox.Show("未找到模板文件,请检查路径!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } try { System.Diagnostics.Process.Start(viewerPath, $"\"{templatePath}\""); } catch (Exception ex) { MessageBox.Show("启动 FastReport 预览器失败:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
|
- 自动更新:支持在线检查和自动更新程序版本。
这里使用的是:Autoupdater.NET
- 帮助与支持:内置开发者联系方式,便于用户反馈和技术支持。

三、技术要点
1. 打印任务监听与管理
- WMI 打印作业监控
通过 System.Management
命名空间,使用 WMI
查询 Win32_PrintJob
,实现对打印队列的实时监控。可根据任务ID或文档名唯一标识,精确定位和管理打印作业。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| using System.Management;
private void StartMonitorPrintJob(string printerName, int taskId, string taskName) { Task.Run(() => { try { string query = $"SELECT * FROM Win32_PrintJob WHERE Name LIKE '%{printerName}%'"; using (var searcher = new ManagementObjectSearcher(query)) { while (true) { var jobs = searcher.Get(); bool found = false; foreach (ManagementObject job in jobs) { found = true; int JobId = Convert.ToInt32(job["JobId"]); if (JobId == taskId) { string jobStatus = job["JobStatus"]?.ToString() ?? ""; string status = job["Status"]?.ToString() ?? ""; string displayStatus = string.IsNullOrEmpty(jobStatus) ? status : jobStatus; UpdateTaskStatusOnUI(printerName, taskName, displayStatus); if (displayStatus.Contains("Printed") || displayStatus.Contains("Completed") || displayStatus.Contains("Deleted")) return; } } if (!found) { UpdateTaskStatusOnUI(printerName, taskName, "已完成"); return; } Thread.Sleep(1000); } } } catch (Exception ex) { UpdateTaskStatusOnUI(printerName, taskName, "状态监听失败"); } }); }
|
- 任务状态同步
通过轮询方式定时查询打印队列,自动更新任务状态(如“正在打印”、“已完成”、“已取消”等),并在 UI 上实时反馈。
2. 打印任务操作
- 取消打印
通过 WMI 删除指定打印作业,确保任务被及时从队列中移除,并同步更新界面状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
private void CancelPrint_Click(object sender, EventArgs e) { var dgv = GetCurrentDgv(); if (dgv == null) return; var row = dgv.SelectedRows.Count > 0 ? dgv.SelectedRows[0] : null; if (row == null) return; int taskId = Convert.ToInt32(row.Cells["taskId"].Value); string printerName = tabControl1.SelectedTab.Text; string query = $"SELECT * FROM Win32_PrintJob WHERE Name LIKE '%{printerName}%'"; using (var searcher = new System.Management.ManagementObjectSearcher(query)) { foreach (System.Management.ManagementObject job in searcher.Get()) { int JobId = Convert.ToInt32(job["JobId"]); if (JobId == taskId) { try { job.Delete(); row.Cells["status"].Value = "已取消"; MessageBox.Show($"已取消打印任务:{taskId}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show("取消打印失败:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } return; } } } MessageBox.Show("未找到对应的打印任务,可能已完成或被清除。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); }
|
- 重新打印
首次打印时将所有参数(文件路径、数据、模板名等)保存在 DataGridView 行的 Tag 属性,重新打印时直接复用原始参数,保证打印一致性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
private void Reprint_Click(object sender, EventArgs e) { var dgv = GetCurrentDgv(); if (dgv == null) return; var row = dgv.SelectedRows.Count > 0 ? dgv.SelectedRows[0] : null; if (row == null) return; if (row.Tag is PrintTaskInfo info) { string taskName = row.Cells["taskName"].Value.ToString(); string status = row.Cells["status"].Value.ToString(); if (status == "已完成") PrintFile(info.FilePath, info.Data, taskName); } else { MessageBox.Show("未找到原始打印信息,无法重新打印。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
|
- 删除记录
支持在任务未完成时先删除打印队列中的作业,再移除界面记录,防止“假删除”导致队列堆积。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
private void DeleteRecord_Click(object sender, EventArgs e) { var dgv = GetCurrentDgv(); if (dgv == null) return; var row = dgv.SelectedRows.Count > 0 ? dgv.SelectedRows[0] : null; if (row == null) return;
int taskId = Convert.ToInt32(row.Cells["taskId"].Value); string status = row.Cells["status"].Value?.ToString(); string printerName = tabControl1.SelectedTab.Text; if (status != "已完成" && status != "已取消") { string query = $"SELECT * FROM Win32_PrintJob WHERE Name LIKE '%{printerName}%'"; using (var searcher = new System.Management.ManagementObjectSearcher(query)) { foreach (System.Management.ManagementObject job in searcher.Get()) { int JobId = Convert.ToInt32(job["JobId"]); if (JobId == taskId) { try { job.Delete(); } catch (Exception ex) { MessageBox.Show("删除打印任务失败:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } break; } } } } dgv.Rows.Remove(row); }
|
3. WebSocket 通信
- Fleck 组件集成
使用 Fleck
实现 WebSocket 服务端,支持外部系统通过网络下发打印任务、查询状态、远程控制等。
- 消息协议设计
采用 JSON 协议,支持多种命令(如 print
、show
、ping
等),并能将打印结果、错误信息实时反馈给客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| socket.OnMessage = message => { var msg = message?.Trim().ToLowerInvariant(); if (msg == "ping") { socket.Send("pong"); } else if (msg == "show") { this.Invoke(() => { this.Show(); this.WindowState = FormWindowState.Normal; this.ShowInTaskbar = true; this.Activate(); }); } else if (msg != null && msg.TrimStart().StartsWith("{")) { var json = JsonNode.Parse(msg); var cmd = json?["cmd"]?.ToString(); string requestId = json?["requestid"]?.ToString(); if (cmd == "print") { var printIniInfo = json["data"]?["printiniinfo"]; var data = json["data"]?["data"]; string filePath = printIniInfo?["filepath"]?.ToString(); string realName = printIniInfo?["realname"]?.ToString();
string clientIp = socket.ConnectionInfo.ClientIpAddress; int clientPort = socket.ConnectionInfo.ClientPort; string requestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string status = "作业正在后台处理"; string printerName = new System.Drawing.Printing.PrinterSettings().PrinterName; int taskId = 0; string taskName = DateTime.Now.ToString("yyyyMMddHHmmssfff"); this.Invoke(() => { foreach (TabPage tab in tabControl1.TabPages) { if (tab.Text.StartsWith(printerName)) { var dgv = tab.Controls.OfType<DataGridView>().FirstOrDefault(); if (dgv != null) { int rowIndex = dgv.Rows.Add( $"{clientIp}:{clientPort}", taskId, taskName, realName, requestTime, status ); var row = dgv.Rows[rowIndex]; row.Tag = new PrintTaskInfo { FilePath = filePath, Data = data }; dgv.Sort(dgv.Columns["requestTime"], ListSortDirection.Descending); } break; } } }); this.Invoke(() => PrintFile(filePath, data, taskName, socket, requestId));
StartMonitorPrintJob(printerName, taskId, taskName); } else { Console.WriteLine($"收到未知cmd: {cmd}"); } } else { Console.WriteLine($"收到未知消息: {message}"); } };
|
- 异常处理与反馈
打印过程中如遇异常(如文件不存在、数据格式错误等),会捕获异常并通过 WebSocket 回复详细错误信息,便于外部系统及时处理。
4. 打印文件类型支持
- 多格式兼容
支持 TXT、图片(JPG/PNG/BMP/GIF)、PDF、FastReport 模板(FRX)等多种文件类型的打印。


- 模板数据绑定
对于报表类打印,支持将 JSON、DataTable、DataSet 等多种数据源动态绑定到模板,实现灵活的数据驱动打印。
5. 用户体验优化
- 界面交互友好
采用右键菜单、弹窗提示、托盘集成等方式,提升用户操作便捷性。
- 错误提示与日志
所有关键操作均有明确的错误提示,便于用户定位问题;可扩展日志记录功能,方便后期维护。
四、总结
打印监听组件通过对打印队列的实时监控、任务的精细化管理、与外部系统的高效集成,极大提升了企业打印自动化和可控性。其灵活的界面、丰富的功能和健壮的技术架构,适用于多种业务场景,值得在企业信息化建设中推广应用。