一、组件简介
打印监听组件是一款集成于 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(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来实现。
/// <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 == 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;
// 查询打印队列,找到文档名包含 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 == null) return;
var row = dgv.selectedrows.count > 0 ? dgv.selectedrows[0] : null;
if (row == null) return;
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 == 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 等),并能将打印结果、错误信息实时反馈给客户端。
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. 用户体验优化
- 界面交互友好:采用右键菜单、弹窗提示、托盘集成等方式,提升用户操作便捷性。
- 错误提示与日志:所有关键操作均有明确的错误提示,便于用户定位问题;可扩展日志记录功能,方便后期维护。
四、总结
打印监听组件通过对打印队列的实时监控、任务的精细化管理、与外部系统的高效集成,极大提升了企业打印自动化和可控性。其灵活的界面、丰富的功能和健壮的技术架构,适用于多种业务场景,值得在企业信息化建设中推广应用。
以上就是c# winforms实现打印监听组件工具的详细内容,更多关于c# winforms打印监听的资料请关注代码网其它相关文章!
发表评论