Expressions are objects that derive from the NExpression abstract class. Expressions can be assigned to the properties of all elements. The purpose of expressions is to allow the automatic calculation of an element explicit local value, that can be a function of other element local values.
Expressions are assigned to element properties via the NElement-SetFx method. To get the expression that is associated with an element:property you can use the NElement-GetFx method. Expressions are automatically evaluated during the document evaluation as explained by the Documents topic.
Expressions were integrated in the DOM in order to allow the building of complex formula sheets, that mimic the functional behavior of certain Microsoft Office products, such as Excel and Visio. Naturally for such a requirement the DOM supports a specific type of expression that is constructed from a formula string. The DOM is however not restricted only to formula expressions.
To better understand how expressions work consider the following example:
My First Expression |
Copy Code
|
public class MyElement : NElement
{
public MyElement() { }
static MyElement()
{
MyElementASchema = NSchema.Create(typeof(MyElement), NElement.NElementSchema);
MyElementASchema.MakeContainer();
AProperty = MyElementASchema.AddSlot("A", NDomType.Double, 0.0d);
BProperty = MyElementASchema.AddSlot("B", NDomType.Double, 0.0d);
CProperty = MyElementASchema.AddSlot("C", NDomType.Double, 0.0d);
}
public double A
{
get { return (double)GetValue(AProperty); }
set { SetValue(AProperty, value); }
}
public double B
{
get { return (double)GetValue(BProperty); }
set { SetValue(BProperty, value); }
}
public double C
{
get { return (double)GetValue(CProperty); }
set { SetValue(CProperty, value); }
}
public static readonly NProperty AProperty;
public static readonly NProperty BProperty;
public static readonly NProperty CProperty;
public static readonly NSchema MyElementASchema;
}
public static void TestExpressions()
{
NGenericDocument<MyElement> myDoc = new NGenericDocument<MyElement>();
MyElement myElement = new MyElement();
myDoc.Content = myElement;
myElement.A = 10;
myElement.B = 20;
myElement.SetFx(MyElement.CProperty, "A+B");
OutputMyElementProperties("Before eval", myElement);
myDoc.Evaluate();
OutputMyElementProperties("After eval", myElement);
// The output is
// Before eval A=10
// Before eval B=20
// Before eval C=0
// After eval A=10
// After eval B=20
// After eval C=30
}
public static void OutputMyElementProperties(string phase, MyElement myElement)
{
Console.WriteLine(phase + " A=" + myElement.A.ToString());
Console.WriteLine(phase + " B=" + myElement.B.ToString());
Console.WriteLine(phase + " C=" + myElement.C.ToString());
}
|
The MyElement class defines three properties of type Double - A, B and C. In the TestExpressions method, we have created an instance of the MyElement class, that resides in a document. Before the document evaluation we have set values of 10 and 20 to the A and B properties of the MyElement instance.
The C property was assigned with a formula expression that is represented by the "A+B" string. Notice that before the document evaluation the value of property C is 0 (the default property value). This is because the expression has not been evaluated yet, and as mentioned expressions are evaluated during the document evaluation. After the evaluation the value of the C property becomes 30 (the sum of the A and B property values).
Expressions in the DOM API are usually abbreviated with the Fx suffix. This abbreviation is introduced in order to increase the code readability. Expressions in the context of DOM need not be associated only with the Formula Expressions.
After the expression is created, the expression goes through the following lifecycle:
1. Assign to target.
The expression target is the element:property pair, the local explicit value of which is set by the expression. The NElement-SetFx method associates an expression instance with an element:property pair (i.e. assigns a target for the expression). The NExpression-TargetElement and NExpression-TargetProperty help you query for the element:property pair, which the expression currently targets.
2. Parse
The expression parsing is internally performed when the expression enters a document. The purpose of the parsing step is to determine the expression dependencies. The expression dependencies are the element:property pairs on which the expression evaluation depends on. In the context of My First Expression example, the expression assigned to the C property has two dependencies - the A and B properties of the element. When an expression dependency value changes, the expression is automatically revaluated at the next document evaluation cycle. The NExpression-IsParsed property helps you query whether the expression was successfully parsed.
3. Evaluate
The expression evaluation is automatically performed when the expression enters a document and has been parsed. It is also automatically performed when an expression dependency has changed. The expression evaluation results in a new local explicit value of the element:property that it targets.
4. Unparse
The expression unparsing is internally performed when the expression leaves a document, or when a dependency of the expression leaves a document. The purpose of the unparsing is to detach the expression from its dependencies. If the expression was unparsed because a dependency left the document, the expression will try to be parsed again on the next document evaluation cycle.
Guarded expressions are such expressions, whose Guard property is set to true. The purpose of guarding expressions is to protect them and the value they apply to the expression target, from being deleted or replaced. By default expressions are not guarded. The guarding of expressions affects the following NElement methods:
SetValue, ClearValue - when setting or clearing an explicit local value to an element, the element first tries to remove the expression that may be associated with the property. If there is such an expression and it is not guarded, it gets automatically removed (the element:property explicit local value becomes a constant one). If there is a guarded expression however these methods do not remove the expression. The local value of the element may still be changed by the method if the expression Permeable property is set to true.
SetFx - the method internally checks whether the previous expression assigned to the property (if any) is guarded. If it is guarded the method does nothing. To replace a guarded expression, you need to call the SetFxForce method, in order to remove or replace the expression by force.
Guarded expressions are useful when you expect that the explicit local value, or the expression assigned to a property will get overriden by NOV. For example: the Width property of a NOV widget is overriden by the parent widget each time it rearranges its children. If you want to set a constant value to this property you can assign a constant value guarded expression to the Width property of the widget.
Guarded expressions as a concept are identical to the Microsoft Visio GUARD function. However since NOV supports not only formula expressions, but also other types of specialized expressions, the expression guarding is generally available to all types of expressions.
Permeable expressions are useful when you need to perform TwoWay binding as shown in the Binding Expressions section.
Formula expressions are represented by instances of the NFormulaFx class (derived from NExpression). Formula expressions are implicitly created when you use the NElement-SetFx method with a string argument (as seen in the My First Expression example). The passed string needs to be a valid formula.
The strings that you pass as formula expressions, need to be valid formulas - see Formula Basics about the general formula syntax rules, operators etc. In the context of the DOM, NOV Formulas are extended to provide you with the means to reference other property values inside the formula. This is achieved with a formula extension that resolves certain formula identifiers to valid element:property references (automatically considered as dependencies of the formula expression).
An element:property reference inside a formula expression is encoded as a sequence of steps, separated by the '.' char (dot). The last step of the reference needs to be the name of a property that belongs to the current element. The reference resolution current element is initially the expression target element. You can switch the current element with the following types of steps:
Predefined Steps:
$Parent - selects the parent element of the current element
$PrevSibling - selects the previous sibling element of the current element
$NextSibling - selects the next sibling element of the current element
$FirstChild - selects the first child element of the current element
$LastChild - selects the last child element of the current element
$Cur- selects the current element (provided so that you can select a child at index - for example:$Cur.1.X - selects the second child element of the target element and gets its X property value).
$Sheet- selects the current element formula sheet. Selects the current element if it is marked as formula sheet. Otherwise selects the first ancestor element that is marked as formula sheet.
$ParentSheet - selects the parent sheet of the current formula sheet element.
Children Steps:
Child At Index Step - a step that represents a number can appear as a middle step in the reference. It selects the child at the specified index of the current target element. For example: $Parent.1.X - selects the second child element of the parent element and gets its X property value.
Named Child Step - a step that represents a valid identifier can appear as a middle step in the reference. It selects a named child with the specified name. For example: $Parent.Content.X - selects a child element of the parent element, with the name "Content" and gets its X property value.
We have already stated that the last step of the reference needs to be the name of a property of the current target element. However if the reference consists only of a single step (i.e. a property name) and the current target element does not have such a property, the expression will try to find that property in the element formula sheet element.
Certain types of element instances can be marked as formula sheet. This is achieved by calling the SetFormulaSheet(true) method of the element schema. By default elements are not marked as formula sheets. The concept of a formula sheet is needed in order to write more compact formulas that belong to descendant elements, not marked as formula sheets, that belong to a certain ancestor element that is marked as a formula sheet.
Consider a NOV diagram shape. The shape has Width and Height properties and a collection of plotter geometry commands that often need to refer to the Width and Height of the shape. For example: if you want to make the X coordinate of the plotter command to be the middle of the shape you can write a formula expression such as: $Parent.$Parent.Width * 0.5. This is however too long for a formula expression. Now because the shape is marked as a formula sheet and because the geometry collection and the plotter command is are not marked as formula sheets you can simply write: Width*0.5.
The purpose of the formula sheet is to simplify the syntax of formula expressions in the context of element tree hierarchies, where certain types of elements will often refer to property values of a specific ancestor.
The concept for a formula sheet is a generalization of the ShapeSheet concept of Microsoft Visio. In Microsoft Visio, the ShapeSheet is constructed by Sections, Rows and Cells that natively map to the more general DOM Child Slots, Collection Children and Properties. In Microsoft Visio important objects such as Shapes and Pages are associated with a ShapeSheet, which in DOM context are simply elements marked as formula sheets. NOV Diagram uses formula expressions extensively to achieve the Smart Shapes functionality of Visio. However each NOV Widget or other type of element can benefit of formula expressions and hence all NOV Elements are Smart.
Speaking of Microsoft Visio, NOV generalizes another Visio pattern, that is widely used in ShapeSheets – the scratches. The Visio Scratch Section is typically used to isolate repeated complex calculations and has the meaning of mathematical substitution. For example, if you need to calculate:
A = 10 + SQRT(100);
B = 20 + SQRT(100);
C = 30 + SQRT(100);
It will be faster to calculate:
T = SQRT(100);
A = 10 + T;
B = 20 + T;
C = 30 + T;
In this very simple example, the T variable is a scratch variable – it is used to cache the result of the SQRT(100) operation.
In order to implement this principle in NOV expressions, you need a property that stores the T value evaluation. In the Properties topic we already said that you can declare extended properties, which are such properties that can be applied to any node. So in NOV you do not need a specialized Scratch Section just to hold temporary values – you can use extended properties instead.
The NScratchPropertyEx static class defines several extended properties that you can use for formula scratching. They are:
X, X1, Y, Y1 – a set of Double extended properties that you can use for scratching. Named X and Y because in NOV you will most likely use scratches for dynamic geometries and positioning.
A, B, C, D – a set of Variant extended properties that you can use for scratching.
Of course scratching is not limited to the properties in the NScratchPropertyEx - you can use any extended property for that purpose and you can define your own extended properties. The NScratchPropertyEx static class simply defines some extended properties for you.
The following example demonstrates scratching:
Scratch Example |
Copy Code
|
myElement.SetFx(NScratchPropertyEx.XPropertyEx, "SQRT(100)");
myElement.SetFx(MyElement.AProperty, "10+" + NScratchPropertyEx.XPropertyEx.Name);
myElement.SetFx(MyElement.BProperty, "20+" + NScratchPropertyEx.XPropertyEx.Name);
myElement.SetFx(MyElement.CProperty, "30+" + NScratchPropertyEx.XPropertyEx.Name);
myDoc.Evaluate();
OutputMyElementProperties("After eval", myElement);
// The output is
// After eval A=20
// After eval B=30
// After eval C=40
|
Binding expressions are represented by instances of the NBindingFx class (derived from NExpression). It is a simple expression that lets you bind the expression target to an arbitrary other element property value (source). In the context of the My First Expression example you can bind property A to be the value of property B like this:
OneWay Binding |
Copy Code
|
myElement.SetFx(MyElement.AProperty, new NBindingFx(myElement, MyElement.BProperty));
myElement.B = 50;
myDoc.Evaluate();
OutputMyElementProperties("After eval", myElement);
// The output is
// After eval A=50
// After eval B=50
// After eval C=100
|
This is how you make OneWay binding. To perform TwoWay binding you need two binding expressions - one for each binding direction. To protect the expressions from being automatically cleared when the respective properties are set, you need to set their Guard property to true. To allow the local value modification you of the respective properties, you need to set their Permeable property to true. The NBindingFx.TwoWayBind method does that for you:
TwoWay Binding |
Copy Code
|
NBindingFx.TwoWayBind(myElement, MyElement.AProperty, myElement, MyElement.BProperty);
myElement.B = 30;
myDoc.Evaluate();
OutputMyElementProperties("After eval1", myElement);
myElement.A = 20;
myDoc.Evaluate();
OutputMyElementProperties("After eval2", myElement);
// The output is
// After eval1 A=30
// After eval1 B=30
// After eval1 C=60
// After eval2 A=20
// After eval2 B=20
// After eval2 C=40
|