Framework / DOM / DOM Basics / Properties
In This Topic
    Properties
    In This Topic
     Properties Overview

    The properties of a specific type of node are represented by singleton instances of the NProperty class. These instances are internally created when you call the AddMember or AddSlot methods of the schema that is associated with the node type to which you want to add the properties, in the specific node static constructor. The returned NProperty instances need to be stored as public static readonly fields, that follow the PropertyName + "Property" naming convention.

    In the DOM properties serve for the following purposes:

    • Access to Property Values - you can use the GetValue and SetValue methods of a node to get/set the value for specific property without using reflection.
    • Behavior Markup - different property metadata flag help you declare whether the property is serializable, recordable or affects a specific node behavior aspect, like its rendering, measure, arrange etc.
     Creating Properties

    The following code sample demonstrates how to create one member and one slot property:

    My First Node With Properties
    Copy Code
    public class MyNode : NNode
    {
     public MyNode()
     {
      m_MyInt32 = 0;
     }
     static MyNode()
     {
      MyNodeSchema = NSchema.Create(typeof(MyNode), NNode.NNodeSchema);
      // declare properties
      MyInt32Property = MyNodeSchema.AddMember("MyInt32", NDomType.Int32, 0,
       delegate(NNode node) { return ((MyNode)node).m_MyInt32; },       // property getter
       delegate(NNode node, object value) { ((MyNode)node).m_MyInt32 = (int)value; });  // property setter
      MyStringProperty = MyNodeSchema.AddSlot("MyString", NDomType.String, "");
     }
     // clr property exposing MyInt32 property
     public int MyInt32
     {
      get  {   return (int)GetValue(MyInt32Property);   }
      set  {   SetValue(MyInt32Property, value);  }
     }
     // clr property exposing MyString property
     public string MyString
     {
      get  {   return (string)GetValue(MyStringProperty);  }
      set  {   SetValue(MyStringProperty, value);  }
     }
     // fields
     private int m_MyInt32;
     // schema references
     public static readonly NSchema MyNodeSchema;
     public static readonly NProperty MyInt32Property;
     public static readonly NProperty MyStringProperty;
    }
    
    The property instances returned by the AddSlot and AddMember methods, must be stored in a public static readonly field of the node type. The field name should follow the PropertyName + "Property" naming convention as shown in the example.
     Property Backing

    Property backing refers to the way in which the node stores the local value for a specific property. There are two ways in which the local value for a property can be backed:

    • By a Member - the property is backed by a pair of getter and setter functions.
    • By a Slot - the property is backed by a slot in the internal node property storage.

    When you create a property with the AddMember method, this property is said to be backed by a member. It is actually backed by a pair of getter and setter functions, but because in most cases these functions are getting and setting the value of a node field member, this way of property backing is called member backing and the respective properties are called member properties. Typically you will create member properties when the value of the property is usually locally specified, and in the case of elements, when it cannot be inherited or specified via CSS. For example: the Text of a TextBox, or the SelectedIndex of a ListBox are usually specified for nearly all TextBox/ListBox instances, and their values cannot be inherited or specified via CSS. That is why these properties are backed by a member.

    When you create a property with the AddSlot method, this property is said to be backed by a slot and the respective properties are called slot properties. The node will internally store a local value for the property, only if you call the SetLocalValue or SetValue function (difference explained in the next section). In cases the local value is not explicitly specified by the user, the node has no additional memory overhead for the local property value and the default value for the property is returned by the GetLocalValue (and in some cases the GetValue method - difference explained in the next section). Typically you will create slot properties when the value of the property is usually not locally specified, and in the case of elements, when it can be inherited or specified via CSS. For example: the Padding of a Widget is usually not specified by the user, but is styleable and can be specified via CSS.

    The NProperty IsBackedByMember property returns true if the property is backed by a member and returns false, if the property is backed by a slot.

     Getting and Setting Property Values

    The getting and setting of property values to nodes (also called DOM Property System or simply Property System) can at first be confusing to developers not familiar with the deferred computing paradigm that the DOM uses. This is because the DOM actually has two implementations of its property system:

    1. Default Property System - implemented by the NNode class and by default used by all nodes that do not derive from NElement (e.g. simple nodes, attributes, styling nodes etc.)
    2. Elements Property System - implemented by the NElement class by overriding the default property system. It is by default used by all nodes that derive from NElement (i.e. by all elements).

    The two property systems implementations are actually working with the same set of methods, originally defined by the NNode class, however the implementation of some of these methods is overridden by the NElement class. In its core the property system allows for two values for each property - local and computed (effective), both of which can come from different sources:

    Local Values 
    The local value can come from two sources:

    • default - when the property is a slot property and its default value is used.
    • local (explicit user source) - when the user explicitly sets a local value to a certain property.

    When you set or clear local values, the local value immediately changes. 

    Computed Values
    The computed value is the value that is computed by the DOM Styling and Inheritance. It is used in the element measure, arrange and display. It can come from the following sources:

    • default - when the property is a slot property and its default value is used.
    • inherited - when the value is inherited and its value was inherited from an ancerstor element.
    • local - when the user explicitly set local value is used.
    • styling - when the value was computed via Styling (CSS).

    You cannot directly set a computed value. In most cases however the value that you explicitly set locally, becomes the computed value (see Styling and Inheritance for a complete discussion of value weights). The computed value may not change immediately.

    Both the default and the elements property system threat the local value in the same way. That is why the NNode methods dealing with local values are not marked as virtual. The following table describes these methods:

    Method Description

    object GetLocalValue(NProperty property)

    Gets the local value for the specified property.

    object GetLocalValue(NProperty property, out bool isDefault)

    Gets the local value for the specified property, and also gets whether the local value was explicitly set by the user (isDefault is false), or is the default one (isDefault is true).
    NOTE: For properties backed by member isDefault is always set to false.

    void SetLocalValue(NProperty property, object value)

    Sets an explicit local value to the specified property.

    void ClearLocalValue(NProperty property)

    Clears any previously set explicit local value for the specified property and reverse the local value to its default one.
    NOTE: Has no effect on properties backed by member.

    bool ContainsLocalValue(NProperty property)

    Returns true if the node has an explicit local value for the specified property.
    NOTE: Always returns false for properties backed by member.

    The default property system does not support computed values. That is why the methods that may return or in any way affect the computed value for a property are marked as virtual in the NNode class, and their default implementation simply delegates to the respective local value methods. The NElement class however overrides these methods to perform additional tasks, as described in the following table:

    Method NNode Implementation NElement Implementation

    object GetValue(NProperty property)

    Gets the local value for the specified property. Gets the current computed value for the specified property.

    object GetValue(NProperty property, out ENValueSource source)

    Gets the local value for the specified property. The source is always set to either local or default. Gets the current computed value for the specified property and also gets the source from which the computed value was obtained.

    void SetValue(NProperty property, object value)

    Sets an explicit local value of the specified property. Tries to set a constant explicit local value to the specified property, by automatically removing any unguarded expression assigned to the property (if any), and then setting an explicit local value to the specified property. If there is a guarded expression assigned to the property, the method does nothing.

    void ClearValue(NProperty property)

    Clears any previously set explicit local value for the specified property and reverse the local value to its default one. Tries to clear any previously set explicit local value, or potential such, that can be set by an expression, by automatically removing any unguarded expression assigned to the property (if any), and then clearing the local value. If there is a guarded expression assigned to the property, the method does nothing.

    Regardless of whether you are authoring a simple NNode derivate or a NElement derivate, it is recommended to back exposing CLR properties by calling the GetValue and SetValue methods.

     Extended Properties

    The NProperty instances created by the AddSlot and AddMember methods of the NSchema instance associated with a specific type of nodes, are considered to be owned by the schema. Such properties are also called self-properties, because you cannot use these properties to get and set property values to nodes that are not instances of the specific node type (or a derived one).

    In some cases however you need to declare such properties, which you can use to assign values to different types of node, without them actually knowing about it. The solution for such cases is to use extended properties. Extended properties are created by the NProperty.CreateExtended method. All extended properties are owned by the NNode.NNodeSchema and are backed by a slot.

    Extended properties cannot be marked as styleable or inheritable.

    The IsExtened property of NProperty returns true if the property is an extended one, otherwise it returns false (the property is a self-property).

    The name of all extended properties is automatically prefixed with the "+"Declaring Type Full Name+ "-" prefix. This automatically makes it impossible to create a self-property with the same name, because the self property needs to be exposed by a CLR property, but CLR Properties cannot contain the "+" symbol. The following code sample creates an extended property and assigns it to a button widget.

    My First Extended Property
    Copy Code
    public static class MyExtendedProperties
    {
     static MyExtendedProperties()
     {
      MyInt32PropertyEx = NProperty.CreateExtended(typeof(MyExtendedProperties), "MyInt32", NDomType.Int32, 0);
     }
     public static readonly NProperty MyInt32PropertyEx;
    }
    
    void SomeMethod()
    {
     NButton btn = new NButton("Extended properties");
    
     int val = (int)btn.GetValue(MyExtendedProperties.MyInt32PropertyEx);
     Debug.Assert(val == 0);
    
     btn.SetValue(MyExtendedProperties.MyInt32PropertyEx, 12);
     val = (int)btn.GetValue(MyExtendedProperties.MyInt32PropertyEx);
     Debug.Assert(val == 12);
    
     btn.ClearValue(MyExtendedProperties.MyInt32PropertyEx);
     val = (int)btn.GetValue(MyExtendedProperties.MyInt32PropertyEx);
     Debug.Assert(val == 0);
    }
    
    The extended property instance returned by the NProperty.CreateExtened method, must be stored in a public static readonly field of the declaring type. The field name should follow the PropertyName + "PropertyEx" naming convention as shown in the example.
     DOM Properties

    If you start to think generally about properties, you will soon notice that the term "property" refers to a Name:Type pair, that represents a contract between the object, to which the property belongs and the rest of the world that wants to get/set the value of this property.

    Whenever you create a self or extended property, the DOM assigns the new NProperty instance with an instance of a NDomProperty class. This is internally done by calling the NDomProperty.Register method, which returns either an existing DOM property or a new one, depending on whether a DOM property with same Name:Type as the Name:Type of the newly created NProperty has already been registered. In this way all NProperty instances with the same Name:Type are pointing to the same NDomProperty instance. The NDomProperty itself also keeps track of the NProperty instances that point to it. You can get all NProperty instances that point to a specific DOM property via the Instances property of each NDomProperty.

    The fact that all properties with the same Name and Type have something in common - their NDomProperty - is essential for the way in which the DOM CSS works. Both rules and declarations work with DOM properties, thus decoupling the CSS from the actual types of nodes to which the rules and declarations apply. Inheritance also works with DOM properties, thus allowing a node to inherit a property value from an ancestor node that simply declares another instance of the same DOM property. This greatly reduces the need to make unnecessarily deep hierarchies just to allow inheritance of property values.

     Observing Property Value Changes

    There are several ways in which you can observe property changes:

    • Property Value Callback Functions - this is the recommended way to monitor for property changes when you author nodes that always need to know when the value of a certain property has changed. The following example demonstrate the technique:
      Register callback functions for property changes
      Copy Code
      public class MyNode : NNode{
      ...
      static MyNode(){
       ...
       MyStringProperty.AddValueChangedCallback(delegate(NNode target, NValueChangeData data) { ((MyNode)target).OnMyStringPropertyChanged(data); });
      }
      protected void OnMyStringPropertyChanged(NValueChangeData data){
       if (data.HasLocalValueChange) {
        // Local value has changed
       }
       if (data.HasComputedValueChange) {
        // Computed value has changed
       }
      }
      }
      
    • Override the OnPropertyValueChanged method - the OnPropertyValueChanged protected virtual method is called each time a local or computed property value has changed for the node instance. This is the easiest way to handle multiple property changes when you author nodes that always need to know when the value of certain properties has changed. The following example demonstrate the technique:
      Override OnValueChanged
      Copy Code
      public class MyNode : NNode{
      ...
      protected override void OnValueChanged(NValueChangeData data){
       base.OnValueChanged(data);
       if (data.Property == MyStringProperty) {
        if (data.HasLocalValueChange)  {
         // Local value has changed
        }
        if (data.HasComputedValueChange)  {
         // Computed value has changed
        }
       }
      }
      }
      
    • Subscribe for Property Events - you can subscribe for the respective property ValueChangedEvent. This is the way to observe any node for a specific property change, without it knowing about it. The following example demonstrate the technique:
      Add Event Handlers for Property Events
      Copy Code
      ...
      MyNode myNode = new MyNode();
      myNode.AddEventHandler(MyNode.MyStringProperty.ValueChangedEvent, new Function<NValueChangeEventArgs>(OnMyStringPropertyChanged));
      ...
      void OnMyStringPropertyChanged(NValueChangeEventArgs args){
       if (args.HasLocalValueChange) {
        // Local value has changed
       }
       if (args.HasComputedValueChange) {
        // Computed value has changed
       }
      }
      
     Property Metadata

    The metadata available for each property includes the property default value, flags and meta units. The following table summarizes the metadata API of NProperty.

    Getter and Setter Description

    Default Value

    object GetDefaultValue(NSchema schema)
    void SetDefaultValue(NSchema schema, object value)

    Gets or sets the default value for the property.

    Flags applicable to all nodes

    bool GetSerializable(NSchema schema)
    void SetSerializable(NSchema schema, bool value)

    Gets or sets whether the property value needs to be serialized. By default true.

    bool GetNullable(NSchema schema)
    void SetNullable(NSchema schema, bool value)

    Gets or sets whether the property value can be null. By default true.

    bool GetDeeplyCloneable(NSchema schema)
    void SetDeeplyCloneable(NSchema schema, bool value)

    Gets or sets whether the property value needs to be deeply cloned when the node is deeply cloned. By default true.

    Flags applicable to NDocumentNode derivates

    bool GetRecordable(NSchema schema)
    void SetRecordable(NSchema schema, bool value)

    Gets or sets whether the property value needs to be deeply cloned when the node is deeply cloned. By default true.

    Flags applicable to NElement derivates

    bool GetStyleable(NSchema schema)
    void SetStyleable(NSchema schema, bool value)

    Gets or sets whether the computed property value can be specified with styling. By default false.

    bool GetInherited(NSchema schema)
    void SetInherited(NSchema schema, bool value)

    Gets or sets whether the computed property value can be inherited. By default false.

    bool GetAffectsParentMeasure(NSchema schema)
    void SetAffectsParentMeasure(NSchema schema, bool value)

    Gets or sets whether the property value affects the measure of the parent measure element. By default false.
    The parent measure element is by default the first ancestor that implements the INMeasureElementParent interface.

    bool GetAffectsParentArrange(NSchema schema)
    void SetAffectsParentArrange(NSchema schema, bool value)

    Gets or sets whether the property value affects the arrange of the parent arrange element. By default false.
    The parent arrange element is by default the first ancestor that implements the INArrangeElementParent interface.

    bool GetAffectsParentDisplay(NSchema schema)
    void SetAffectsParentDisplay(NSchema schema, bool value)

    Gets or sets whether the property value affects the display of the parent display element. By default false.
    The parent display element is by default the first ancestor that implements the INDisplayNode interface.

    Flags applicalbe to NElement derivates that implement specific interfaces.

    bool GetAffectsMeasure(NSchema schema)

    void SetAffectsMeasure(NSchema schema, bool value)

    Gets or sets whether the computed property value affects the element measure. By default false.
    The element must implement the INMeasureElement interface.

    bool GetAffectsArrange(NSchema schema)
    void SetAffectsArrange(NSchema schema, bool value)

    Gets or sets whether the computed property value affects the element arrange. By default false.
    The element must implement the INArrangeElement interface.

    bool GetAffectsTransform(NSchema schema)
    void SetAffectsTransform(bool value)

    Gets or sets whether the computed property value affects the element display transform. By default false.
    The element must implement the INDisplayNode interface.

    bool GetAffectsDisplay(NSchema schema)
    void SetAffectsDisplay(NSchema schema, bool value)

    Gets or sets whether the computed property value affects the element display. By default false.
    The element must implement the INDisplayNode interface.

    Custom Flags - not used by NOV

    bool GetCustomX(NSchema schema)
    void SetCustomX(NSchema schema, bool value)
    where X in [1: 5
    ] range

    A set of 5 custom flags you can use as you wish.

    Each specific property flag, the default value and each specific meta unit can be overriden for a specific schema, that needs to be derived from the property owner schema (the schema that originally declared the property).

    Why is property metadata overridable per schema basis? Suppose the following scenario: node of type B derives from node of type A, and the schema of node type A declares a slot Int32 property named C with a default value of 0, but node of type B wants the default value to be equal to 1. Without property metadata overrides it is impossible to achieve this. The same also applies to the flags and to the meta units.

    Meta units are the DOM equivalent of CLR attributes. You can associate meta units with each property instance via the SetMetaUnit method. You can use the GetMetaUnit method to obtain a previously set meta unit by its key.

    See Meta Units for more info.