335 lines
11 KiB
C#
335 lines
11 KiB
C#
using Microsoft.SqlServer.Server;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace linesider_screen_tool
|
|
{
|
|
public sealed class BartenderPrintHelper : IDisposable
|
|
{
|
|
private const string BarTenderProgId = "BarTender.Application";
|
|
private object? _btApp; // 声明为可空类型
|
|
private int _disposedValue;
|
|
|
|
public BartenderPrintHelper()
|
|
{
|
|
// 延迟初始化,在首次使用时创建 Bartender 应用实例
|
|
}
|
|
|
|
/// <summary>
|
|
/// 打印单个标签(同步)
|
|
/// </summary>
|
|
public bool PrintLabel(
|
|
string templatePath,
|
|
Dictionary<string, string> subStringValues,
|
|
int copies = 1,
|
|
int serializedLabels = 1)
|
|
{
|
|
ValidateParameters(templatePath, subStringValues);
|
|
|
|
return ExecuteBartenderAction(format =>
|
|
{
|
|
SetPrintSettings(format, copies, serializedLabels);
|
|
SetSubStringValues(format, subStringValues);
|
|
InvokeMethod(format, "PrintOut", false, false);
|
|
return true;
|
|
}, templatePath);
|
|
}
|
|
|
|
|
|
public Task<List<string>> GetNamedSubStrings(string templatePath)
|
|
{
|
|
dynamic format =null;
|
|
dynamic btApp = null;
|
|
try
|
|
{
|
|
btApp = Activator.CreateInstance(Type.GetTypeFromProgID(BarTenderProgId));
|
|
format = btApp.Formats.Open(
|
|
templatePath,
|
|
false,
|
|
null
|
|
);
|
|
format.PrintSetup.IdenticalCopiesOfLabel = 1;
|
|
format.PrintSetup.NumberSerializedLabels = 1;
|
|
List<string> subStrings = new List<string>();
|
|
dynamic namedSubStrings = format.NamedSubStrings;
|
|
|
|
foreach (var iteam in namedSubStrings)
|
|
{
|
|
subStrings.Add(iteam.Name);
|
|
}
|
|
|
|
return Task.FromResult(subStrings);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
Console.WriteLine($"发生错误: {ex.Message}");
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
// 清理资源
|
|
if (format != null)
|
|
{
|
|
Marshal.ReleaseComObject(format);
|
|
}
|
|
if (btApp != null)
|
|
{
|
|
btApp.Quit(0);
|
|
Marshal.ReleaseComObject(btApp);
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 批量打印标签(高性能实现)
|
|
/// </summary>
|
|
public bool PrintBatchLabels(
|
|
string templatePath,
|
|
IEnumerable<Dictionary<string, string>> labelsData,
|
|
int copiesPerLabel = 1,
|
|
int serializedLabels = 1)
|
|
{
|
|
ValidateParameters(templatePath, labelsData);
|
|
|
|
return ExecuteBartenderAction(format =>
|
|
{
|
|
SetPrintSettings(format, copiesPerLabel, serializedLabels);
|
|
bool allSuccess = true;
|
|
|
|
foreach (var data in labelsData)
|
|
{
|
|
try
|
|
{
|
|
SetSubStringValues(format, data);
|
|
InvokeMethod(format, "PrintOut", false, false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
allSuccess = false;
|
|
LogError($"打印标签时出错: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
return allSuccess;
|
|
}, templatePath);
|
|
}
|
|
|
|
private T ExecuteBartenderAction<T>(Func<object, T> action, string templatePath)
|
|
{
|
|
if (IsDisposed)
|
|
throw new ObjectDisposedException(nameof(BartenderPrintHelper));
|
|
|
|
EnsureBartenderInitialized();
|
|
object? format = null;
|
|
|
|
try
|
|
{
|
|
format = OpenFormat(templatePath);
|
|
return action(format!);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogError($"执行 Bartender 操作失败: {ex.Message}");
|
|
throw new InvalidOperationException("Bartender 操作失败", ex);
|
|
}
|
|
finally
|
|
{
|
|
ReleaseFormat(format);
|
|
}
|
|
}
|
|
|
|
private void EnsureBartenderInitialized()
|
|
{
|
|
if (_btApp == null)
|
|
{
|
|
try
|
|
{
|
|
_btApp = Activator.CreateInstance(Type.GetTypeFromProgID(BarTenderProgId));
|
|
SetProperty(_btApp, "Visible", false); // 确保应用程序不可见
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogError($"初始化 Bartender 失败: {ex.Message}");
|
|
throw new InvalidOperationException("无法初始化 Bartender 应用程序", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
private object OpenFormat(string path)
|
|
{
|
|
EnsureBartenderInitialized();
|
|
|
|
try
|
|
{
|
|
var formats = GetProperty(_btApp!, "Formats");
|
|
return InvokeMethod(formats, "Open", path, false, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogError($"打开标签模板失败: {path}, 错误: {ex.Message}");
|
|
throw new FileNotFoundException($"无法打开标签模板: {path}", ex);
|
|
}
|
|
}
|
|
|
|
private void SetPrintSettings(object format, int copies, int serializedLabels)
|
|
{
|
|
var printSetup = GetProperty(format, "PrintSetup");
|
|
SetProperty(printSetup, "IdenticalCopiesOfLabel", copies);
|
|
SetProperty(printSetup, "NumberSerializedLabels", serializedLabels);
|
|
}
|
|
|
|
private void SetSubStringValues(object format, Dictionary<string, string> values)
|
|
{
|
|
if (values == null) return;
|
|
|
|
foreach (var kv in values)
|
|
{
|
|
try
|
|
{
|
|
InvokeMethod(format, "SetNamedSubStringValue", kv.Key, kv.Value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogError($"设置标签变量失败: {kv.Key}={kv.Value}, 错误: {ex.Message}");
|
|
throw new ArgumentException($"无法设置标签变量: {kv.Key}", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ReleaseFormat(object? format)
|
|
{
|
|
if (format == null) return;
|
|
|
|
try
|
|
{
|
|
InvokeMethod(format, "Close", 0); // 0 = BarTender.BtSaveOptions.btDoNotSaveChanges
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogError($"关闭标签格式失败: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
ReleaseComObject(format);
|
|
}
|
|
}
|
|
|
|
private void ReleaseComObject(object obj)
|
|
{
|
|
if (obj == null || !Marshal.IsComObject(obj))
|
|
return;
|
|
|
|
try
|
|
{
|
|
// 循环调用直到引用计数为 0
|
|
int refCount;
|
|
while ((refCount = Marshal.ReleaseComObject(obj)) > 0)
|
|
{
|
|
LogError($"COM 对象引用计数: {refCount}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogError($"释放 COM 对象失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void ValidateParameters(string templatePath, object data = null)
|
|
{
|
|
if (string.IsNullOrEmpty(templatePath))
|
|
throw new ArgumentNullException(nameof(templatePath), "标签模板路径不能为空");
|
|
|
|
if (!File.Exists(templatePath))
|
|
throw new FileNotFoundException("指定的标签模板文件不存在", templatePath);
|
|
|
|
if (data is IEnumerable<Dictionary<string, string>> labelsData && labelsData == null)
|
|
throw new ArgumentNullException(nameof(labelsData), "标签数据不能为空");
|
|
|
|
if (data is Dictionary<string, string> singleLabelData && singleLabelData == null)
|
|
throw new ArgumentNullException(nameof(singleLabelData), "标签数据不能为空");
|
|
}
|
|
|
|
private void LogError(string message)
|
|
{
|
|
// 这里可以添加实际的日志记录逻辑
|
|
Console.WriteLine($"[ERROR] {message}");
|
|
}
|
|
|
|
private bool IsDisposed => Interlocked.CompareExchange(ref _disposedValue, 0, 0) != 0;
|
|
|
|
public void Dispose()
|
|
{
|
|
// 使用 Interlocked.Exchange 确保线程安全
|
|
if (Interlocked.Exchange(ref _disposedValue, 1) != 0)
|
|
return;
|
|
|
|
try
|
|
{
|
|
// 使用临时变量确保线程安全
|
|
var btApp = Interlocked.Exchange(ref _btApp, null);
|
|
|
|
if (btApp != null && Marshal.IsComObject(btApp))
|
|
{
|
|
try
|
|
{
|
|
// 使用反射调用 Quit 方法
|
|
InvokeMethod(btApp, "Quit", 0);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogError($"退出 Bartender 应用程序失败: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
ReleaseComObject(btApp);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
|
|
~BartenderPrintHelper() => Dispose();
|
|
|
|
// 反射辅助方法
|
|
private object GetProperty(object obj, string propertyName)
|
|
{
|
|
return obj.GetType().InvokeMember(
|
|
propertyName,
|
|
BindingFlags.GetProperty,
|
|
null,
|
|
obj,
|
|
null
|
|
)!;
|
|
}
|
|
|
|
private void SetProperty(object obj, string propertyName, object value)
|
|
{
|
|
obj.GetType().InvokeMember(
|
|
propertyName,
|
|
BindingFlags.SetProperty,
|
|
null,
|
|
obj,
|
|
new[] { value }
|
|
);
|
|
}
|
|
|
|
private object InvokeMethod(object obj, string methodName, params object?[]? parameters)
|
|
{
|
|
return obj.GetType().InvokeMember(
|
|
methodName,
|
|
BindingFlags.InvokeMethod,
|
|
null,
|
|
obj,
|
|
parameters ?? Array.Empty<object>()
|
|
)!;
|
|
}
|
|
}
|
|
} |