Paths

In many places in markup programming you can supply a value. For example, the Return statement has a Value property. The Value property is a dependency property and as such can you can give it a value yourself or you can bind it to another value using data binding. You can also use an expression to supply a computed value using element syntax. However XAML expressions in markup programming can be very verbose and so there is an alternative way for supplying a value call the path mechanism. If Path is specified, Return will return the value of that path expression, otherwise it will evaluate and return Value. In case you are wondering, we can't simply use path expressions for Value because they might be confused with legitimate values. As a result we have this duality for values: "native" bindable values and path expressions, each with their own property.

Here is a return statement with the call expression "written out" (Value is implicit in this case):

<p:Return>
    <p:Call TypeName="String" StaticMethodName="Format">
        <p:Val Value="i = {0}"/>
        <p:Get Var="i"/>
    </p:Call>
</p:Return>
 
and here is the same thing using the path mechanism:

<p:Return Path="[String].Format('i = {0}', $i)"/>
 
If you don't need to use element syntax to specify any of the arguments, then the path expression syntax is noticably shorter and more familiar to programmers but you can use whichever method you prefer. In fact, if all that is preventing you from using the path mechanism is a binding for an interior quantity, then you can simply assign that quantity to a temporary variable with Set using a binding and then use the path mechanism using the variable that you set.

Simple Paths

Like XAML paths in data bindings, simple paths in markup programming implicitly refer to the current Context. If a single identifier is specified, it in general refers to a property by that name on the object instance that is the Context. To speak more briefly we can say that a single identifier is a property relative to the default context. This is in fact exactly how data binding works and this is why paths work the same way. If you are ever confused about what a path means, it is probably because you are uncertain what Context is.

Complex Paths

The real power of the path mechanism comes into play when you can find a "home base" that allows you to access everything you need and make it your temporary context. The you reach off to this side and that like branches on a tree to access all the data you need: not too long, not too short, just right.

Context Inheritance

The idea of context inheritance is simple and is borrowed from the DataContext tradition of XAML. Every markup programming statement and expression has an inheritable Context which defaults to the DataContext of the FrameworkElement that the outermost markup programming element is attached to. At any point, a statement or expression can set its Context property and this will change the Context for itself and all of its contained statements and expressions unless further overriden. Although markup programming is not a traditional object oriented programming language, by setting or accepting context once at the outermost level of a section of code, the context acts like a this pointer for all the path expressions in that section.

Why are Paths called Paths?

The name path might seem strange when what we're calling a path is clearly an expression. So why are paths called paths? The first answer is that some paths are indeed paths from the context to the value of the expression. This is the same sense of the word path that XAML data binding uses. The second reason is that path is a nice short word and expression is not!

Context versus DataContext

Markup programming uses the property name Context instead of DataContext because markup program elements are not FrameworkElements and so do not have a real DataContext and don't participate in its inheritance context mechanism. Furthermore, markup programming's Context contains more than data and besides it is easier to say and write.

Identifiers and Special Characters

In order to acess variables we use a dollar sign; in order to access builtins we use an at sign. Why is this? It is possible to "put all identifiers into the same namespace" the way that C# does but it creates enormous potential for ambiguity. Since the central purpose of paths is to provide effortless accessors relative to the context we solve the ambiguity problem by putting things that are not properties or methods into separate namespaces. As a result, if you see an unadorned identifier in a path, it must be a property or method or one of a handful of constants.

Context in Action


An example of using context is if you are associated with one control but you want to "work on" another control.

<p:Block Context="{Binding ElementName=OtherControl}">
    <!-- work on other control -->
    <p:Set Path="SelectedIndex" Value="0"/>
</p:Block>
 

Path Syntax

Path Expression Meaning
/* ignored */ C style comment
// ignored ; C style comment
@ Context (current inherited value and need not be specified)
true, false, null literal constants
12345 integral value
123.456 double value
'abcdef' string value
"abcdef" string value
[Type] type name looked up in common places, e.g. System, System.Windows
[Namespace.Type] fully qualified type name
[Type](arg, ...) constructor for type
[Type] { Property = value, ...} object initializer for type
[Type] { value, ...} collection initializer for type
[Type] { { key, value }, ...} dictionary initializer for type
[Type] { Property = { value, ... }, ...} collection initializer for property of type
[Type] { Property = { { key, value }, ... }, ...} dictionary initializer for property of type
context.Property property (in property context)
context.Method(arg, ...) method (in any context)
context.Method method needing arguments (in call context)
[Namspace.Type].Method static method
$parameter parameter (in property context)
$Function(arg, ...) function (in any context)
$Function function needing arguments (in call context)
@Function builtin function needing arguments (in call context)
@Function(arg, ...) builtin function (in any context)
context[index] item (e.g. array, list, dictionary)
@[index] item (if needed to disambiguate item syntax with type syntax)
lvalue = rvalue assignment of rvalue to lvalue
++lvalue, --lvalue prefix increment or decrement
lvalue++, lvalue-- postfix increment or decrement
lvalue operator rvalue assignment operator: += -= *- /= %= &= |=
operator operand unary operation: !
operand1 operator operand2 binary operation: + - * / == != < <= > >= in markup: < <= > >=
operand1 , operand2 comma operator
condition ? ifTrue : ifFalse conditional operator
(Context.path) grouping
@iterator: { stmt; ...} iterator block aka yield block
@iterator[Type]: { stmt; ...} iterator block with collection type specified
@block: { stmt; ...} block expression aka return block
context.Property1[Property2[index1].Property3][index2+42] expressions can be nested
[String].Format('Square root of 4 is {0}.', [Math].Sqrt(4)) full method calls are valid values anywhere

@hash=c63235f0@

Last edited Mar 31, 2011 at 2:45 AM by jrs, version 38

Comments

No comments yet.