LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C# WinForms 实现打印监听组件

admin
2025年6月22日 0:15 本文热度 100

一、组件简介

打印监听组件是一款集成于 Windows 桌面环境的打印任务管理与监控工具,适用于企业级应用场景。它不仅支持多打印机任务的实时监控,还能通过 WebSocket 与外部系统集成,实现自动化打印、任务状态反馈、远程控制等功能。

二、界面功能介绍

1. 主界面与托盘集成

主窗体:采用 WinForms 界面,包含多标签页(TabControl),每个标签页对应一台本地打印机,便于分组管理。

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

2. 打印机管理

打印机列表:自动检测本地所有已安装打印机,支持设置默认打印机、查看打印机属性。

/// <summary>/// 绑定本地打印机列表到菜单/// </summary>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);    }} /// <summary>   /// UI和系统都设置默认打印机  /// </summary>   /// <param name="printerName"></param>   private void SetDefaultPrinterUI(string printerName) {     foreach (ToolStripMenuItem item in 默认打印机ToolStripMenuItem.DropDownItems)         item.Checked = item.Text == printerName;     // 如需设置为系统默认打印机,可调用 Win32 API(可选)       SetSystemDefaultPrinter(printerName); } /// <summary>/// 显示打印机首选项对话框/// </summary>/// <param name="printerName"></param>private void ShowPrinterProperties(string printerName){    // 使用rundll32调用打印机属性对话框    //string args = $"printui.dll,PrintUIEntry /p /n \"{printerName}\"";    //•	/e 参数表示直接打开“首选项”对话框    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:每台打印机一个标签页,便于查看和管理各自的打印任务。

/// <summary>/// 绑定本地打印机列表到TabControl/// </summary>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);        // 创建DataGridView        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;        // 创建TextBox        var txtSearch = new TextBox        {            PlaceholderText = "任务ID",            Width = 120,            Anchor = AnchorStyles.Left | AnchorStyles.Bottom        };        // 创建Button        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);            }        };        // 使用Panel布局        var panel = new Panel        {            Dock = DockStyle.Bottom,            Height = 40        };        txtSearch.Location = new Point(108);        btnSearch.Location = new Point(1406);        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来实现。

/// <summary>/// 设计菜单项点击事件,启动 FastReport 设计器/// </summary>/// <param name="sender"></param>/// <param name="e"></param>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或文档名唯一标识,精确定位和管理打印作业。

using System.Management; /// <summary> /// 打印机监听方法实现 /// </summary> /// <param name="printerName"></param> /// <param name="taskId"></param> 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); // 1秒轮询                 }             }         }         catch (Exception ex)         {             UpdateTaskStatusOnUI(printerName, taskName, "状态监听失败");         }     }); }

任务状态同步

通过轮询方式定时查询打印队列,自动更新任务状态(如“正在打印”、“已完成”、“已取消”等),并在 UI 上实时反馈。

2. 打印任务操作

取消打印

通过 WMI 删除指定打印作业,确保任务被及时从队列中移除,并同步更新界面状态。

/// <summary>/// 取消打印/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void CancelPrint_Click(object sender, EventArgs e){    var dgv = GetCurrentDgv();    if (dgv == nullreturn;    var row = dgv.SelectedRows.Count > 0 ? dgv.SelectedRows[0] : null;    if (row == nullreturn;    int taskId = Convert.ToInt32(row.Cells["taskId"].Value);    string printerName = tabControl1.SelectedTab.Text;    // 查询打印队列,找到文档名包含 taskId 的作业    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 属性,重新打印时直接复用原始参数,保证打印一致性。

/// <summary>/// 重新打印/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void Reprint_Click(object sender, EventArgs e){    var dgv = GetCurrentDgv();    if (dgv == nullreturn;    var row = dgv.SelectedRows.Count > 0 ? dgv.SelectedRows[0] : null;    if (row == nullreturn;    if (row.Tag is PrintTaskInfo info)    {        // 复用原 taskName,或可选生成新 taskName        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);    }}

删除记录

支持在任务未完成时先删除打印队列中的作业,再移除界面记录,防止“假删除”导致队列堆积。

/// <summary>/// 删除打印记录/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void DeleteRecord_Click(object sender, EventArgs e){    var dgv = GetCurrentDgv();    if (dgv == nullreturn;    var row = dgv.SelectedRows.Count > 0 ? dgv.SelectedRows[0] : null;    if (row == nullreturn;    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 等),并能将打印结果、错误信息实时反馈给客户端。

socket.OnMessage = message =>{    var msg = message?.Trim().ToLowerInvariant();    // 处理不同的消息    if (msg == "ping")    {        // 回复 pong        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("{"))    {        // 反序列化为 JsonNode 便于动态访问        var json = JsonNode.Parse(msg);        var cmd = json?["cmd"]?.ToString();        string requestId = json?["requestid"]?.ToString();        //处理打印任务        if (cmd == "print")        {            // 取出 printIniInfo 和 data            var printIniInfo = json["data"]?["printiniinfo"];            var data = json["data"]?["data"];            string filePath = printIniInfo?["filepath"]?.ToString();            string realName = printIniInfo?["realname"]?.ToString();            // 获取来源IP和端口            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");            // 查找对应TabPage和DataGridView            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,                     // 任务ID                                 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        {            // 处理其他cmd            Console.WriteLine($"收到未知cmd: {cmd}");        }    }    else    {        Console.WriteLine($"收到未知消息: {message}");    }};

异常处理与反馈

打印过程中如遇异常(如文件不存在、数据格式错误等),会捕获异常并通过 WebSocket 回复详细错误信息,便于外部系统及时处理。

4. 打印文件类型支持

多格式兼容

支持 TXT、图片(JPG/PNG/BMP/GIF)、PDF、FastReport 模板(FRX)等多种文件类型的打印。

模板数据绑定

对于报表类打印,支持将 JSON、DataTable、DataSet 等多种数据源动态绑定到模板,实现灵活的数据驱动打印。

5. 用户体验优化

界面交互友好

采用右键菜单、弹窗提示、托盘集成等方式,提升用户操作便捷性。

错误提示与日志

所有关键操作均有明确的错误提示,便于用户定位问题;可扩展日志记录功能,方便后期维护。

四、总结

打印监听组件通过对打印队列的实时监控、任务的精细化管理、与外部系统的高效集成,极大提升了企业打印自动化和可控性。其灵活的界面、丰富的功能和健壮的技术架构,适用于多种业务场景,值得在企业信息化建设中推广应用。


阅读原文:https://mp.weixin.qq.com/s/x2BnsdDCol5tYkoC-E173w

源码地址:https://github.com/shenchuanchao/PrintApp


该文章在 2025/6/23 12:56:14 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved