zplCore.comzplCore.com

12Print jobs & bulk printing

A print job is one ZPL stream: an optional leading label, one label per record and an optional trailing label. With CutAfterLastLabel() the whole job runs in tear-off mode and the cut happens only after the last label.

Leading label, trailing label and cut (LabelBinding)

// header and trailer labels are bound to PrintJobInfo:
// JobName, Created, RecordCount, LabelCount, FirstKey, LastKey
var leading = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { X = 5, Y = 5, Text = "DRUCKAUFTRAG", FontSize = 22 })
    .Add(new TextItem { X = 5, Y = 20, DataField = "JobName", FontSize = 14 })
    .Add(new TextItem { X = 5, Y = 32, Expression = "Format([Created], \"dd.MM.yyyy HH:mm\")" })
    .Add(new TextItem { X = 5, Y = 42, Expression = "[RecordCount] + \" Sendungen\"" });
var trailing = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { X = 5, Y = 5, Text = "ENDE DES AUFTRAGS", FontSize = 18 })
    .Add(new TextItem { X = 5, Y = 20, Expression = "[LabelCount] + \" Labels gedruckt\"" })
    .Add(new TextItem { X = 5, Y = 30, Expression = "\"von \" + [FirstKey] + \" bis \" + [LastKey]", FontSize = 9 });
var label = new Label(LabelUnit.Mm, 100, 60)
    .Add(new TextItem { X = 4, Y = 4, DataField = "Name", FontSize = 14 })
    .Add(new TextItem { X = 4, Y = 12, DataField = "City" })
    .Add(new BarcodeItem { X = 4, Y = 22, DataField = "TrackingNo", BarHeight = 18 });

string zpl = label.For<Parcel>()
    .Job("Versand Wien", p => p.TrackingNo)   // name + key for FirstKey/LastKey
    .Copies(p => p.Parcels)                   // LabelCount includes copies
    .WithLeadingLabel(leading)
    .WithTrailingLabel(trailing)
    .CutAfterLastLabel()                      // 1st format ^MMT (tear-off), last ^MMC (cut)
    .ToZpl(parcels);                          // list → RecordCount already known on the header label
Method / fieldMeaning
.Job(name, keySelector)job name and per-record key (e.g. tracking number) for FirstKey/LastKey
.WithLeadingLabel(label)leading label, bound to PrintJobInfo
.WithTrailingLabel(label)trailing label with totals, bound to PrintJobInfo
.CutAfterLastLabel()the first format gets ^MMT (tear-off, applies to all following), only the last format ^MMC (cut)
PrintJobInfo.JobName, Createdname and creation time of the job
PrintJobInfo.RecordCountnumber of records – known in the leading label when a list/collection is passed; always set in the trailing label
PrintJobInfo.LabelCountprinted labels including copies (Copies)
PrintJobInfo.FirstKey, LastKeykey of the first and last record

All fields can be used as DataField or in expressions ([RecordCount] + " Sendungen"). The result for three shipments (one of them with two copies): 5 formats, 6 labels.

Leading label

Leading label: JobName, Created, RecordCount

Record 2 (^PQ2)

Record 2, copy 1

Trailing label (^MMC)

Trailing label: LabelCount, FirstKey, LastKey
Show generated ZPL

PrintJobTemplate: one job from JSON templates

PrintJobTemplate combines the leading, record and trailing label together with options. Load from three label templates (FromFiles(leading, label, trailing), leading/trailing optional) or from one JSON file (ToJson/FromJson); SaveFiles splits it back into three files. For<T>() returns the fully configured LabelBinding<T>.

// three JSON templates (e.g. from a designer) → ONE print job
var job = PrintJobTemplate.FromFiles("templates/leading.json", "templates/shipping.json", "templates/trailing.json");
job.JobName = "Tagesversand";
job.CutAfterLastLabel = true;          // cut only after the trailer label
job.StoredFormatName = "R:SHIP.ZPL";   // template once via ^DF, then only ^XF + ^FN values
job.KeyField = "TrackingNo";           // FirstKey/LastKey on the trailer
job.CopiesField = "Parcels";           // ^PQ per record

string zpl = job.For<Shipment>().ToZpl(shipments.ToList());

// the same job as ONE JSON file – or split back into three files
File.WriteAllText("printjob.json", job.ToJson());
PrintJobTemplate same = PrintJobTemplate.FromJson(File.ReadAllText("printjob.json"));
same.SaveFiles("leading.json", "shipping.json", "trailing.json");
OptionEffect
JobNamename for PrintJobInfo.JobName (default "Druckauftrag")
CutAfterLastLabel^MMT for all, ^MMC only for the last label
StoredFormatNamestore the record label once via ^DF, then only ^XF + ^FN
KeyFieldproperty path for FirstKey/LastKey
CopiesFieldproperty path for the number of copies per record

leading.json (excerpt)

printjob.json – everything in one file (excerpt)

Templates: leading.json · shipping.json · trailing.json · printjob.json

Bulk data demo: EF Core + LiteDB → one .zpl file

samples/MassDataDemo loads the three templates, queries 50 shipments via LINQ from SQLite (EF Core) or LiteDB and writes one job each: leading label, ^DF template, 50 shipping labels, trailing label, cut.

// samples/MassDataDemo: 50 shipping labels + header + trailer + cut in ONE .zpl file
var job = PrintJobTemplate.FromFiles("templates/leading.json", "templates/shipping.json", "templates/trailing.json");
job.CutAfterLastLabel = true;        // tear-off for all, cut after the trailer label
job.KeyField = "TrackingNo";         // first/last shipment number on the trailer

// ---------- EF Core + SQLite ----------
using var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ShippingDb>().UseSqlite(connection).Options;
using var db = new ShippingDb(options);
db.Database.EnsureCreated();
db.Shipments.AddRange(seedData);
db.SaveChanges();

var query = db.Shipments.AsNoTracking()          // executed by SQLite
    .Where(s => !s.Printed && s.Service == "EXPRESS")
    .OrderByDescending(s => s.Id)
    .Take(50);

job.JobName = "EF Core · Express";
job.StoredFormatName = "R:SHIP.ZPL";             // template once, then only field values
string efZpl = job.For<Shipment>()
    .Map("Total", s => s.WeightKg.ToString("0.0") + " kg")
    .ToZpl(query.ToList());
File.WriteAllText("massdata-efcore.zpl", efZpl);

// ---------- LiteDB ----------
using var lite = new LiteDatabase(new MemoryStream());
var col = lite.GetCollection<Shipment>("shipments");
col.InsertBulk(seedData);
col.EnsureIndex(s => s.Country);
var docs = col.Query().Where(s => s.Country == "AT").OrderBy(s => s.TrackingNo).Limit(50).ToEnumerable();

job.JobName = "LiteDB · Österreich";
job.StoredFormatName = null;                     // full format per label
string liteZpl = job.For<Shipment>()
    .Map("Total", s => s.WeightKg.ToString("0.0") + " kg")
    .ToZpl(docs.ToList());
File.WriteAllText("massdata-litedb.zpl", liteZpl);

// preview of the whole job as PDF
var renderer = new ZplRenderer(new ZplRenderSettings { OutputFormat = ZplOutputFormat.Pdf }.WithLabelSizeMm(100, 150));
File.WriteAllBytes("massdata-efcore.pdf", renderer.ProcessCommands(efZpl)[0]);
dotnet run --project samples/MassDataDemo -- samples/output samples/templates

Leading label (^MMT)

Leading label of the demo

Shipping label 1 of 50

Shipping label of the demo

Trailing label (^MMC)

Trailing label of the demo
massdata-efcore.zpl (excerpt)
Generated files: massdata-efcore.zpl (9 KB, with ^DF/^XF) · massdata-efcore.pdf · massdata-litedb.zpl (17 KB, full formats) · massdata-litedb.pdf – 52 labels each.

More on zplCloud.com: Template: Shipping label · Template: Shipping label 100 × 150 mm with GS1-128 SSCC