대량의 비정형 가변 데이터를 지속적으로 출력해야 하는 환경에서 BarTender와 C#을 연동하는 방법을 다룹니다. BarTender 10.0 Professional 이상이 필요하며, 상업적 사용 시 정품 라이선스가 필수입니다.
설치 시 주의사항
일부 바코드 프린터에 번들로 제공되는 무료 BarTender(SDK 미포함)가 설치된 적 있는 경우, 상위 버전 설치 시 진행 화면에서 멈추는 현상이 발생할 수 있습니다. 이 경우 시스템 재설치 후 바로 상위 버전을 설치하는 것이 유일한 해결책입니다.
API 연동 방식 비교
| 방식 | 장점 | 단점 |
|---|---|---|
| .NET SDK (Seagull.BarTender.Print.dll) | 직관적인 API, 썸네일 생성 등 내장 기능 | .NET Framework 전용, .NET Core 미지원 |
| COM Interop | 모든 .NET 버전 호환 | 직접 설정해야 할 부분이 많음 |
본 예제에서는 호환성을 고려하여 COM 방식을 사용합니다.
핵심 설계: JSON 기반 데이터 바인딩
BarTender의 내장 데이터베이스 연결 기능을 활용하여 JSON 파일을 데이터 소스로 사용합니다. 엔티티 목록을 JSON으로 직렬화하고, BarTender 템플릿에서 데이터베이스 필드로 매핑 후 Named Data Source(具名数据源)에 연결하면 템플릿 내 모든 객체(텍스트, 바코드, QR 코드 등)가 해당 소스를 참조할 수 있습니다.
데이터 모델 정의
public sealed class ProductLabel
{
public string SkuCode { get; set; } = string.Empty;
public string ProductName { get; set; } = string.Empty;
public decimal UnitPrice { get; set; }
public DateTime ManufactureDate { get; set; }
public CategoryInfo Category { get; set; } = new();
}
public sealed class CategoryInfo
{
public int DeptId { get; set; }
public string DeptName { get; set; } = string.Empty;
}
JSON 직렬화
using System.Text.Json;
static class JsonDataGenerator
{
public static string GenerateSampleFile()
{
var labels = new List<ProductLabel>
{
new()
{
SkuCode = "SKU-2024-001",
ProductName = "무선 블루투스 이어폰",
UnitPrice = 129000,
ManufactureDate = DateTime.Now,
Category = new CategoryInfo { DeptId = 3, DeptName = "전자기기" }
}
};
var options = new JsonSerializerOptions { WriteIndented = true };
var jsonContent = JsonSerializer.Serialize(labels, options);
var outputPath = Path.Combine(AppContext.BaseDirectory, "label_data.json");
File.WriteAllText(outputPath, jsonContent);
return outputPath;
}
}
BarTender 템플릿 설정
- BarTender Designer에서 새로운 템플릿 생성
- 데이터베이스 설정 마법사 실행 → 데이터베이스 유형: JSON 선택
- 생성된
label_data.json파일 선택 - 필드 선택 단계에서 필요한 필드 체크 (중첩 객체 내 필드는 수동으로 선택 필요)
- Named Data Source 생성: 유형을 데이터베이스 필드로 설정하고 대상 필드 매핑
- 템플릿 객체(텍스트/바코드 등)의 데이터 소스를 기존 Named Data Source 연결로 설정
COM 연동 출력 유틸리티
using BarTender;
public sealed class LabelPrintEngine : IDisposable
{
private Application? _engine;
private Format? _currentFormat;
private bool _disposed;
public async Task<IReadOnlyList<string>> ExecutePrintJobAsync(
string btwFile,
string jsonDataFile,
string targetPrinter,
int duplicateCount = 1,
int serialIncrement = 1,
int timeoutMilliseconds = -1)
{
return await Task.Run(() =>
{
var capturedErrors = new List<string>();
try
{
EnsureEngineRunning();
LoadTemplate(btwFile);
var db = _currentFormat!.Databases.GetDatabase(1);
db.JSONDatabase.FileName = jsonDataFile;
_currentFormat.Save();
_currentFormat.PrintSetup.Printer = targetPrinter;
_currentFormat.IdenticalCopiesOfLabel = duplicateCount;
_currentFormat.NumberSerializedLabels = serialIncrement;
Messages bartenderMessages;
_currentFormat.Print(
jobName: "BatchLabelPrint",
waitForCompletion: true,
timeout: timeoutMilliseconds,
messages: out bartenderMessages);
foreach (Message msg in bartenderMessages)
{
capturedErrors.Add(msg.Text);
}
}
catch (Exception ex)
{
capturedErrors.Add($"[예외] {ex.Message}");
}
return capturedErrors;
});
}
private void EnsureEngineRunning()
{
_engine ??= new Application();
}
private void LoadTemplate(string path)
{
EnsureEngineRunning();
if (_currentFormat != null)
{
if (_currentFormat.FileName.Equals(path, StringComparison.OrdinalIgnoreCase))
return;
_currentFormat.Close(BtSaveOptions.btDoNotSaveChanges);
}
_currentFormat = _engine!.Formats.Open(path);
}
public void Dispose()
{
if (_disposed) return;
if (_currentFormat != null)
{
_currentFormat.Close(BtSaveOptions.btDoNotSaveChanges);
_currentFormat = null;
}
if (_engine != null)
{
_engine.Quit(BtSaveOptions.btDoNotSaveChanges);
_engine = null;
}
_disposed = true;
GC.SuppressFinalize(this);
}
}
사용 예시
var jsonPath = JsonDataGenerator.GenerateSampleFile();
var printEngine = new LabelPrintEngine();
var errors = await printEngine.ExecutePrintJobAsync(
btwFile: @"C:\Templates\ProductLabel.btw",
jsonDataFile: jsonPath,
targetPrinter: "ZDesigner ZT230",
duplicateCount: 2);
if (errors.Count == 0)
Console.WriteLine("출력 완료");
else
Console.WriteLine(string.Join(Environment.NewLine, errors));
printEngine.Dispose();
성능 최적화 포인트
BarTender의 내부 직렬화 엔진을 활용하면 단일 프린트 잡 내에서 연속 출력이 가능합니다. C# 측에서 반복문으로 개별 호출을 수행할 경우 프린터 초기화가 반복되어 대폭적인 성능 저하가 발생합니다. 템플릿 단에서 데이터 소스 매핑을 완료하면 BarTender가 리스트의 모든 항목을 단일 세션으로 처리하므로, 대용량 배치 출력 시에도 안정적인 고속 처리가 보장됩니다.