Show / Hide Table of Contents

Expressions

Overview

Expressions let a label compute its content and its look at rendering time. An expression can combine variables, constants, operators, functions, references to label Items and references to data fields (in a data binding scenario).

ThermalLabel supports expressions at two levels:

Level Where it is set What it does
Item Expressions Item.Expression Computes the content of one item (its text, code, image source...).
Global Expressions ThermalLabel.Expressions A list of statements that change any property of any item (visibility, color, font, position, size...).

Expressions use a VB-style syntax (And, Or, Not, <>, IIf, CStr, Left, Now()...) on top of an expression engine that follows C# rules for types and operators. Keep that in mind: it explains most of the rules below (for example, why [Items!Qty] is a string that needs a conversion before you do math with it).

Evaluation order. For every label that is generated, ThermalLabel does:

  1. Data binding, Counters and Data Masking set the content of the items (see Data Binding, Counters, Data Masking).
  2. Item Expressions are evaluated, in item order.
  3. Global Expressions are evaluated, in collection order.
  4. The label is rendered (image/PDF) or its printer commands are generated.

So an item expression sees the bound/counter/mask value of the other items, and a global expression sees the final content set by item expressions.

Expressions are evaluated only when the label has at least one item with a non-empty Expression or at least one entry in Expressions; otherwise the engine isn't touched.

Item Expressions

Set the Expression property of an item to compute its content. These are the items that support it:

Item The result of the expression is assigned to
TextItem Text
BarcodeItem Code
ImageItem SourceFile or SourceBase64, see the note below
RFIDTagItem DataToEncode
LiteralItem Text

The result is always converted to a string, so the expression must return a value (not Nothing/null).

Example: to print the current date and time in a TextItem, set its Expression property to

DateString() + " " + TimeString()

Notes:

  • If the item is data bound (or comes from a Counter/Mask), the expression result replaces the bound value for that label. The expression can read the bound value of the item with [Items!Name].
  • ImageItem: the result goes to the property the item is currently using: the data-bound file or Base64 if any; otherwise SourceBase64 if the item already has one; otherwise SourceFile.
  • Shape items (LineShapeItem, RectangleShapeItem, EllipseShapeItem, CircleShapeItem, TableShapeItem) have no content to compute, so an Expression on them has no effect. To change a shape use a Global Expression.

Global Expressions

In addition to Item Expressions, ThermalLabel supports Global Expressions, which allow advanced and dynamic changes on any item property based on the evaluation of expressions code: hide items, change colors, fonts, location, size and most of the properties available on each item type.

Global Expressions are added to the Expressions collection property of the ThermalLabel object. The collection can contain N expressions, evaluated in order, and each entry is one statement.

An assignment is written with the Set keyword:

Set [Items!Item_Name.PropertyName] = new value expression

Example: the following list of Global Expressions is part of one of the label samples shipped with the Visual Label Editor demo project and shows how to change different item properties. Each line is a single entry of the Expressions collection:

Set [Items!SampleText.BackColorHex] = "#00ff00"
Set [Items!TempText.Visible] = CBool(IIf([Items!SampleText.BackColorHex]="#00ff00", "False", "True"))
Set [Items!TempText.Font].Name = "Arial"
Set [Items!TempText.Font].Size = 12
Set [Items!BC.Code] = CStr(Now())
Set [Items!BC.Symbology] = BarcodeSymbology.DataMatrix
Set [Items!myRect.CornerRadius] = New RectangleCornerRadius(0.1)
Set [Items!Logo.X] = ((LABEL_WIDTH - [Items!Logo.Width]) / 2)

Each line does the following:

  1. Sets the BackColorHex property of a TextItem named SampleText to a new color value.
  2. Hides or shows the TextItem named TempText depending on the value set by the previous line (a conditional expression).
  3. Sets the font name of TempText (a complex property: [Items!TempText.Font].Name).
  4. Sets the font size of TempText.
  5. Sets the value to encode of the BarcodeItem named BC.
  6. Changes the barcode symbology (uses an enumeration).
  7. Sets the CornerRadius of the RectangleShapeItem named myRect using the New keyword to create an object.
  8. Centers the ImageItem named Logo using the LABEL_WIDTH variable.

Remarks:

  • Only the first = after Set is the assignment; every other = in the same statement is a comparison (see line 2).
  • A statement without Set is simply evaluated (useful for functions with side effects such as SetVar).
  • Always set both states. In a data-bound job the same label object is reused for every record, so a property changed for one record stays changed for the next one unless an expression sets it again. Use a conditional that produces both values (Set [Items!Offer.Visible] = ([DataFields!Price] < 10)) instead of only hiding the item.
  • Both kinds of expressions are saved in the XML template: the Expression attribute of each item and an <Expressions><Expr Code="..."/></Expressions> node for the Global Expressions.

Expression Syntax

Operators

Operator Meaning Notes
+ - * / Arithmetic / returns a floating-point result. + also concatenates strings: "A" + "B".
< > <= >= Less / greater than (or equal to) <= and >= require the fixed RewriteVbToCSharp (see the note below).
= Equal to Outside of Set, a single = is a comparison.
<> Not equal to
And Or Logical conjunction/disjunction Write them with a space on both sides and put each operand in parentheses: (a = 1) And (b > 2).
Not Logical negation Not (expr). Not(expr) also works with the fixed RewriteVbToCSharp; in the original code write the space.
New Creates an object New RectangleCornerRadius(0.1)
True / False Boolean constants

Also:

  • Text is written between double quotes: "Hello".
  • The VB operator & is not supported. Concatenate with +.
  • Function and variable names are exact, as listed by SupportedExpressions (for example DateString(), not datestring()).

Types

  • The content of an item referenced as [Items!Name] is a string. Convert it before doing arithmetic or numeric comparisons: CInt([Items!Qty]) * 3, CDbl([Items!Price]) > 10.
  • A data field [DataFields!Name] keeps the type of the value in the data source (integer, decimal, DateTime, string...), so a numeric column can be used directly: [DataFields!Qty] * 3.
  • Conversion functions: CStr, CInt, CLng, CShort, CByte, CDbl, CSng, CDec, CBool, CChar, CDate.

Expression References

  • Referencing an Item: to reference the content of an item, the item needs a unique Name, and then you use [Items!Item_Name]. For example, if an item is named ProductID, its content is [Items!ProductID].

    • The Name must start with a letter and can contain letters, digits and _ only. Items without a name can't be referenced.
    • The content is the item's text/code/source: Text (TextItem, LiteralItem), Code (BarcodeItem), DataToEncode (RFIDTagItem), the file or Base64 source (ImageItem). For any other item it is an empty string. If the item is data bound, the bound value is used.
    • Simple property of an item: [Items!Item_Name.PropName], for example [Items!ProductID.Text]. This gives the property of the item object itself (not a string copy), so it can be read and assigned.
    • Complex property of an item: [Items!Item_Name.PropName].SubPropName, for example [Items!ProductID.Font].Size.
    • Whole item object: Obj_Items_Item_Name, for example Obj_Items_ProductID.
  • Referencing a Data Field: [DataFields!DataField_Name], for example [DataFields!ProductDescription]. Data fields are available when the label has a data source (see Data Binding). The name can contain letters, digits and _ only, so a column named Product Description can't be referenced: rename the column.

  • Referencing a Complex Data Field: for complex data sources like a collection of POCO (Plain Old C# Object), use [DataFields!First_Level_Prop].Second_Level_Prop. For example:

public class ProductInfo
{
    public string Serial { get; set; }
    public double Weight { get; set; }
}

public class Product
{
    public bool Active { get; set; }
    public string Name { get; set; }
    public ProductInfo Info { get; set; }
}

List<Product> myDataSource = new List<Product>
{
    new Product
    {
        Active = true,
        Name = "Product 1",
        Info = new ProductInfo { Serial = "123456789012", Weight = 5.2 }
    },
    new Product
    {
        Active = false,
        Name = "Product 2",
        Info = new ProductInfo { Serial = "098234983999", Weight = 1.7 }
    }
};

With that data source (myDataSource), the Serial value is referenced as [DataFields!Info].Serial.

  • Unknown names. A reference to an item or data field that doesn't exist doesn't raise an error: it evaluates to null. A typo such as [Items!Nme] therefore shows up as an empty or failed result, not as a message. Check names first when an expression "does nothing".

  • Referencing Operators, Variables/Constants and Functions: just use the syntax listed below. More than 100 built-in functions based on the popular VBA syntax are provided, and you can add Custom Functions, Types and Variables/Constants through the ExpressionBuilder class.

Built-in Variables

Variable Value
PAGE_NUMBER The 1-based number of the current label in the job.
TOTAL_PAGES The total number of labels in the job.
LABEL_WIDTH The width of the current label, in the label's unit (a Double).
LABEL_HEIGHT The height of the current label, in the label's unit (a Double).
TEMP_VAR A general-purpose variable for program flow. Assign it with SetVar("TEMP_VAR", value).
vbCrLf, vbCr, vbLf Line break constants.

How PAGE_NUMBER and TOTAL_PAGES work:

  • Data-bound job (a data source, Counters and/or a Mask): PAGE_NUMBER is the record number and TOTAL_PAGES is the number of records. With several labels per row, each label counts as a page.
  • Single label without a data source: both are 1, even if you print several PrintJob.Copies. In this case the printer repeats the same label, so the SDK evaluates it only once. To number the copies, use a Counter or a data source.
  • PrintJob.Replicates repeats a label that was already generated, so it doesn't change PAGE_NUMBER/TOTAL_PAGES (verified when exporting to image/PDF).

Example, a footer on every label (TextItem.Expression):

"Label " + CStr(PAGE_NUMBER) + " of " + CStr(TOTAL_PAGES)

Built-in Functions

The engine provides these functions (about 110). Use ExpressionBuilder.SupportedExpressions to get the exact syntax, a description and an example of each one.

Category Functions
Text Asc, AscW, Chr, ChrW, Format, FormatCurrency, FormatDateTime, FormatNumber, FormatPercent, GetChar, InStr, InStrRev, LCase, Left, Len, LSet, LTrim, Mid, Replace, Right, RSet, RTrim, Space, StrDup, StrReverse, Trim, UCase
Date and time CDate, DateAdd, DateDiff, DatePart, DateSerial, DateString, DateValue, Day, Hour, Minute, Month, MonthName, Now, Second, TimeOfDay, Timer, TimeSerial, TimeString, TimeValue, Today, Weekday, WeekdayName, Year
Math Abs, Acos, Asin, Atan, Atan2, BigMul, Ceiling, Cos, Cosh, Exp, Fix, Floor, Int, IntDiv, Log, Log10, Max, Min, Mod, Pow, Round, Sin, Sinh, Sqrt, Tan, Tanh
Inspection IsDate, IsNothing, IsNumeric
Program flow IIf, SetVar, and the Set and New keywords
Conversion CBool, CByte, CChar, CDate, CDbl, CDec, CInt, CLng, CShort, CSng, CStr, Fix, Hex, Int, Oct
Barcode GetAustraliaPost4StateBarcodeChecksum, GetAustraliaPostDomesticEParcelChecksum, GetCode11Checksum, GetCode39Checksum, GetCode93Checksum, GetDeutschePostModulus11Checksum, GetDhlAwbChecksum, GetModulus10Checksum, GetPpnChecksum, GetVoicePickCode, IsGS1DataValid, GetGS1HumanReadableText, GS1FNC1, CODE128FNC1, CODE128FNC2, CODE128FNC3, CODE128FNC4

Useful details:

  • IIf(condition, whenTrue, whenFalse) evaluates both branches before choosing one, like the VB function does. Don't use it to protect a call that would fail.
  • Round(x) rounds to an integer (halves away from zero) and has no decimals parameter. To format decimals use Format(x, "0.00") or FormatNumber(...).
  • Mod(a, b) returns a Double.
  • Date functions use the system clock and the system culture.
  • The barcode functions are meant for building the value of a BarcodeItem. For example, the Code 39 mod 43 check character: [Items!Serial] + GetCode39Checksum([Items!Serial]).

Enumerations and Objects

Only the enumerations and classes that are marked as expression-enabled can be used in expressions (ExpressionableAttribute). They are listed in SupportedExpressions under the Enumerations and Objects categories. Some examples are TextAlignment, TextVerticalAlignment, TextSizing, FontUnit, Color, LineOrientation, StrokeStyle, CodePage, BarcodeSymbology, and the classes FrameThickness and RectangleCornerRadius (created with New).

The same attribute marks the item properties intended to be changed by expressions (for example X, Y, Visible, PrintAsGraphic, Tag, BorderColor, Symbology, CornerRadius, BorderThickness).

ExpressionEngine Class

The ExpressionEngine class lets you get the list of built-in Expressions and add Custom Functions, Types and Variables/Constants.

Member Description
SupportedExpressions Returns the list of built-in expressions as a 5-tuple: category, name, syntax, description and example.
SetCustomFunction(name, delegate) Adds (or replaces) a function. If the name matches a built-in function, your delegate is used.
SetCustomVariable(name, value) Adds a variable or constant.
SetCustomVariable(name, value, type) Adds a typed variable (useful for null values or to force the type).
AddTypeReference(type) Makes a .NET type available in expressions.

The SDK also ships the ExpressionBuilder class, a static instance of ExpressionEngine with the same members. PrintJob.ExpressionEngine is the engine a job uses; when it is null, the job uses ExpressionBuilder.

// Custom function, variable and type, registered once at startup
ExpressionBuilder.SetCustomFunction("Tax", new Func<double, double>(price => price * 0.21));
ExpressionBuilder.SetCustomVariable("STORE_NAME", "Main Street Store");
ExpressionBuilder.AddTypeReference(typeof(System.Math));
ExpressionBuilder.SetCustomFunction("Tax", New Func(Of Double, Double)(Function(price) price * 0.21))
ExpressionBuilder.SetCustomVariable("STORE_NAME", "Main Street Store")
ExpressionBuilder.AddTypeReference(GetType(System.Math))

Then, in an item expression: STORE_NAME + " - " + CStr(Tax(CDbl([DataFields!Price]))).

Important

The ExpressionBuilder class is not intended to be used in concurrency scenarios! All jobs that use it share one engine and one set of variables (item contents, data fields, PAGE_NUMBER...), so two jobs running at the same time overwrite each other's values. For concurrency scenarios assign a new instance of ExpressionEngine to each PrintJob:

var engine = new ExpressionEngine();
engine.SetCustomFunction("Tax", new Func<double, double>(p => p * 0.21)); // custom items are per instance

using (var pj = new PrintJob())
{
    pj.ExpressionEngine = engine;
    // ...
}

A new engine doesn't inherit what you registered on ExpressionBuilder: register your custom functions, variables and types on each instance.

Sample

The following sample prints a label for each product of a data table. It shows an Item Expression (a footer with the label number and a formatted price) and a Global Expression (an "OFFER" tag that is visible only when the price is below 10).

VB

Dim products As New DataTable()
products.Columns.Add("ProductName", GetType(String))
products.Columns.Add("Price", GetType(Decimal))
products.Rows.Add("Notebook", 4.5D)
products.Rows.Add("Backpack", 39.9D)
products.Rows.Add("Pencil case", 8D)

Dim tLabel As New ThermalLabel(UnitType.Inch, 3, 2)
tLabel.GapLength = 0.2
tLabel.DataSource = products

'Bound to a column
Dim name As New TextItem(0.1, 0.1, 2.8, 0.4, "")
name.DataField = "ProductName"

'Item Expression: content computed from a data field
Dim price As New TextItem(0.1, 0.55, 2.8, 0.4, "")
price.Expression = """$ "" + Format([DataFields!Price], ""##,##0.00"")"

'Item Expression with built-in variables
Dim footer As New TextItem(0.1, 1.5, 2.8, 0.3, "")
footer.Expression = """Label "" + CStr(PAGE_NUMBER) + "" of "" + CStr(TOTAL_PAGES)"

'This item is shown or hidden by a Global Expression
Dim offer As New TextItem(1.8, 0.1, 1.1, 0.4, "OFFER")
offer.Name = "Offer"

tLabel.Items.Add(name)
tLabel.Items.Add(price)
tLabel.Items.Add(footer)
tLabel.Items.Add(offer)

'Global Expression: sets Visible to True or False on every label
tLabel.Expressions.Add("Set [Items!Offer.Visible] = ([DataFields!Price] < 10)")

Using pj As New PrintJob()
    pj.ProgrammingLanguage = ProgrammingLanguage.ZPL
    pj.ThermalLabel = tLabel
    Dim commands As String = pj.GetNativePrinterCommands()
End Using

C#

var products = new DataTable();
products.Columns.Add("ProductName", typeof(string));
products.Columns.Add("Price", typeof(decimal));
products.Rows.Add("Notebook", 4.5m);
products.Rows.Add("Backpack", 39.9m);
products.Rows.Add("Pencil case", 8m);

var tLabel = new ThermalLabel(UnitType.Inch, 3, 2);
tLabel.GapLength = 0.2;
tLabel.DataSource = products;

// Bound to a column
var name = new TextItem(0.1, 0.1, 2.8, 0.4, "") { DataField = "ProductName" };

// Item Expression: content computed from a data field
var price = new TextItem(0.1, 0.55, 2.8, 0.4, "");
price.Expression = "\"$ \" + Format([DataFields!Price], \"##,##0.00\")";

// Item Expression with built-in variables
var footer = new TextItem(0.1, 1.5, 2.8, 0.3, "");
footer.Expression = "\"Label \" + CStr(PAGE_NUMBER) + \" of \" + CStr(TOTAL_PAGES)";

// This item is shown or hidden by a Global Expression
var offer = new TextItem(1.8, 0.1, 1.1, 0.4, "OFFER") { Name = "Offer" };

tLabel.Items.Add(name);
tLabel.Items.Add(price);
tLabel.Items.Add(footer);
tLabel.Items.Add(offer);

// Global Expression: sets Visible to true or false on every label
tLabel.Expressions.Add("Set [Items!Offer.Visible] = ([DataFields!Price] < 10)");

using (var pj = new PrintJob())
{
    pj.ProgrammingLanguage = ProgrammingLanguage.ZPL;
    pj.ThermalLabel = tLabel;
    string commands = pj.GetNativePrinterCommands();
}

You get three labels: the first and the third show the OFFER tag, the second one does not; the footers read "Label 1 of 3", "Label 2 of 3" and "Label 3 of 3".

Common mistakes

  • Math on item references. [Items!Qty] * 3 fails because [Items!Qty] is a string. Use CInt([Items!Qty]) * 3.
  • Comparing types that don't match. A data field keeps its data type, so [DataFields!Price] < 10 needs a numeric column; if the column is text use CDbl([DataFields!Price]) < 10.
  • An Item Expression that returns nothing. The result is converted to a string; a null result (for example a misspelled name, or SetVar(...)) makes the evaluation fail. Put SetVar and other statements in Expressions, not in Item.Expression.
  • Misspelled item or field names. They evaluate to null without an error message. Remember that item names must be unique and a data field name can only have letters, digits and _.
  • And/Or without spaces or parentheses. Write (a) And (b). Not is written as Not (a).
  • Hiding an item and never showing it again. Properties set by a Global Expression persist across the labels of a data-bound job. Assign both values.
  • Sharing ExpressionBuilder between threads. Use one ExpressionEngine per PrintJob.
  • Expecting a per-copy PAGE_NUMBER with PrintJob.Copies. Copies of a single label are repeated by the printer. Use a Counter or a data source.
  • Putting the value in Text when the item also has an Expression. The expression result replaces the text on every label.
Back to top Copyright © 2003- Neodynamic SRL
http://www.neodynamic.com