Defining, Overriding, and Using LightSwitch Control Properties

LightSwitch controls have properties whose values can be set at design time, or at run time via the proxy returned by a call to the FindControl extension method. Some properties are defined on RootControl, the built-in control that every control implicitly inherits as its top-most base control, and are therefore built-in and inherited by all controls. Controls can define their own properties, they can override certain attributes of the built-in properties, and they can also set properties to specific values for themselves or their children.

LightSwitch contains a property resolution system that works in design time and also at run time. This system allows the property sheet in the screen designer to be aware of the default value of a property at different points in the content tree and to display those values as they will be displayed in the run time.

Controls can manipulate a property’s default value to provide a better default experience. For example, the default label position, AttachedLabelPosition, is set to Left-aligned by the screen. AttachedLabelPosition is an inheritable property. Therefore its value will be propagated automatically to all its children, and to their children, until a control overrides the default value for that property, or until the developer has changed the value of a property on a content item.

For example, it is typical for values inside a list not to display labels. Therefore the List control changes the default label position for all its descendants to be None. It also changes the default label position for itself to be Collapsed. The developer can change one or more of these default values in the screen designer or at run time, and the changed value will be inherited down the content tree until another control either specifies a different default value or has its value changed by the developer.

Control properties are stored in the Properties collection of the content item node, IContentItem.Properties. If the property would be valid on the particular content item instance, then there will be an entry for it in the properties collection. Otherwise there will not. So if a property could be set on a content item because the property is defined on the control or one of its ancestors, or the property is defined on RootControl, or the property is an attachable property, then IContentItem.Properties will contain an entry for it. Otherwise Properties.TryGetValue will return False. If the value has not actually been set, then Properties.TryGetValue will succeed, but the collection will return Null for its value.

Defining a new control property

A LightSwitch control defines a new control property in the solution’s .lsml metadata file by using the ControlProperty element. The following example defines a property named ShowButton on a control named MyControl.

<Control Name="MyControl" ... >
    ...
    <Control.Properties>
      <ControlProperty 
Name="ShowButton"
PropertyType=":Boolean"
CategoryName="Appearance"
              EditorVisibility="PropertySheet">
        <ControlProperty.Attributes>
          <DisplayName Value="Show Button" />
          <Description Value="Determines whether the button is shown." />
        </ControlProperty.Attributes>
        <ControlProperty.DefaultValueSource>
          <ScreenExpressionTree>
            <ConstantExpression ResultType=":Boolean" Value="True"/>
          </ScreenExpressionTree>
        </ControlProperty.DefaultValueSource>
      </ControlProperty>
    </Control.Properties>
  </Control>

The following are the available attributes for ControlProperty:

  • PropertyType (required) – The type of the property. Only simple IDataTypes are supported, such as“:Int32,” “:String,” and so on. Complicated property data can be stored as a string and displayed using a custom property editor.

  • IsAttachable - If True, the property might be set on a content item whose control does not define it.

  • IsInheritable – If True, the property's value is automatically inherited by descendants in the content tree until it is overridden.

    Note

    An inheritable property must also be attachable; otherwise, it will not be valid on all possible children that would inherit it.

  • CategoryName - Specifies the name of the category in which to group the property when it is displayed in the screen designer's property window. The currently available options are empty or Default, Validation, Appearance, and Sizing.

  • UIEditorId – Specifies the property sheet editor to use: either built-in or custom. If not specified, a default editor will be used based on the property’s type.

  • EditorVisibility - Indicates how the property is treated at design time.

    • NotDisplayed (default) - This property is not displayed in the property sheet.

    • PropertySheet - Displayed in the property sheet using the specified (or default) property sheet editor.

  • AttachedPropertyAvailability - Indicates the availability of an attached property. Ignored if the property is not attachable.

    • Default – The attached property is available for all content items in the tree.

    • ImmediateChildren – The attached property is available only for the immediate child content items of a control in the content tree.

  • DefaultValueSource – An expression that must be constant that provides the default value for this property.

  • IsReadOnly – If True, specifies that the value cannot be changed by the developer.

Managing Properties for Editor and Viewer Control Pairs

A common pattern is to have two versions of a control – one that enables editing data—“editor”—and one that only displays data—“viewer”. Examples include AddressEditor/AddressViewer and TextBox/Label. Implementing a control pair is covered in the “Providing Both an Editor and a Viewer Control” section of the topic Additional LightSwitch Control Concepts.

Overriding Properties

A control’s property definitions are inherited by all descendent controls. A control can override certain attributes of inherited properties by using PropertyOverride elements.

For example, all controls inherit the VerticalAlignment property from RootControl. The following example shows how a control can also override the default value for VerticalAlignment when it is used on that control, or a control derived from it.

<Control Name="MyControl" ... >
    ...
    <Control.PropertyOverrides>
      <ControlPropertyOverride
          Property=":RootControl/Properties[VerticalAlignment]">
        <ControlPropertyOverride.DefaultValueSource>
          <ScreenExpressionTree>
            <ConstantExpression ResultType=":String" Value="Top"/>
          </ScreenExpressionTree>
        </ControlPropertyOverride.DefaultValueSource>
      </ControlPropertyOverride>
    </Control.PropertyOverrides>
  </Control>

The following are the available attributes on ControlPropertyOverride:

  • Property (required) - Specifies the full ID of the property to override.

  • DefaultValueSource – An overridden default value.

  • UIEditorId – An overridden editor ID.

  • IsReadOnly – An overridden value for the IsReadOnly property.

Supporting the Built-in Common Properties in an Extension Control

LightSwitch defines a set of frequently useful properties on RootControl. Some properties are supported automatically by LightSwitch and appear automatically in the property sheet for new controls. LightSwitch generally handles the functionality of these properties automatically as well. Therefore, the control does not have to do anything to support the properties, although there might be options that the control can set. You can choose to hide the properties from the property sheet for a control by setting EditorVisibility to NotDisplayed.

Properties that require the control to do something—binding in XAML, for example—are hidden by default from the property sheet. To support these, an extension control must implement the necessary behavior and also override EditorVisibility to PropertySheet. The following sections cover the common properties and how to support each one from your control.

Opt-out Control Properties (Displayed by Default)

Opt-out properties are enabled in the property sheet by default and also have automatic behavior provided by LightSwitch for the control.

  • AttachedLabelPosition (Inheritable) - LightSwitch will automatically display a label for an extension control with default settings. It might be preferable for some controls, for example, Button controls, to display their own attached labels. In that case, set the control’s AttachedLabelSupport attribute to DisplayedByControl, as follows.

    <Control Name="MyCheckBox"
               AttachedLabelSupport="DisplayedByControl"
        ... />
    

    You might also want to hide the AttachedLabelPositionproperty by overriding EditorVisibility. Just overriding EditorVisibility to NotDisplayed isn’t enough, however, because even if the property isn’t displayed in the property sheet, its value is still inherited from the parent, and LightSwitch will display an appropriate label automatically if AttachedLabelSupport is left as the default DisplayedByContainer.

  • Common sizing properties:

    • HeightSizingMode

    • WidthSizingMode

    • MinHeight

    • MaxHeight

    • MinWidth

    • MaxWidth

    • Height

    • Width

    These properties are all displayed by a single editor associated with the property HeightSizingMode.  For most cases, we recommend that you leave these enabled in the property sheet for consistency, because LightSwitch automatically handles the sizing of controls for the general case.

    If for some reason a control has to hide these properties, you should override the EditorVisibility attribute for HeightSizingMode to NotDisplayed, as follows.

    <!--Hide all of the common sizing properties by hiding HeightSizingMode-->  
          <ControlPropertyOverride Property=":RootControl/Properties[HeightSizingMode]"
                                   EditorVisibility="NotDisplayed">
    
  • Rows and Characters

    These are not actually common properties. However, if your control defines its own properties that use these names, the common sizing properties editor will also display them. Their EditorVisibility attribute should be set to NotDisplayed so that LightSwitch does not display an additional editor for them. For example:

    <Control Name="MyTextBox" ... >
        ...
        <Control.Properties>
          <ControlProperty Name="Lines"
                           PropertyType=":Int32"
                           CategoryName="Sizing"
                           EditorVisibility="NotDisplayed">
            <ControlProperty.Attributes>
              <DisplayName Value="$(MyTextBox_Lines_DisplayName)" />
              <Description Value="$(MyTextBox_Lines_Description)" />
            </ControlProperty.Attributes>
            <ControlProperty.DefaultValueSource>
              <ScreenExpressionTree>
                <ConstantExpression ResultType=":Int32" Value="1"/>
              </ScreenExpressionTree>
            </ControlProperty.DefaultValueSource>
          </ControlProperty>
    
          <ControlProperty Name="Characters"
                           PropertyType=":Int32"
                           CategoryName="Sizing"
                           EditorVisibility="NotDisplayed">
            <ControlProperty.Attributes>
              <DisplayName Value="$(TextBox_Characters_DisplayName)" />
            </ControlProperty.Attributes>
            <ControlProperty.DefaultValueSource>
              <ScreenExpressionTree>
                <ConstantExpression ResultType=":Int32" Value="1"/>
              </ScreenExpressionTree>
            </ControlProperty.DefaultValueSource>
          </ControlProperty>      
    
        </Control.Properties>
    
  • Common alignment properties:

    • HorizontalAlignment

    • VerticalAlignment

    These properties are both displayed by a single editor associated with the VerticalAlignment property. To hide them, although it’s not ordinarily recommended, override the EditorVisibility attribute of VerticalAlignment to NotDisplayed.

    Similar to Rows and Characters, if an extension control defines its own properties that use the names WeightedRowHeight and WeightedRowWidth, they will also be displayed by this same editor.

Opt-in Control Properties

Opt-in properties are hidden in the property sheet by default either because LightSwitch cannot provide their behavior for a control automatically or because not all controls would want this functionality.  Opt-in properties can be overridden to display in the property sheet, but might require the control to do additional work for the property to have the effect that you want.

  • ShowAsLink

    To support this property, first override EditorVisibility="PropertySheet" in the control’s metadata:

    <Control.PropertyOverrides>
          <!-- Support Show As Link property -->
          <ControlPropertyOverride
            Property=":RootControl/Properties[ShowAsLink]"
            EditorVisibility="PropertySheet"/>
    </Control.PropertyOverrides>
    

    Use the built-in presentation framework control LinkableLabel (Microsoft.LightSwitch.Presentation.Framework.LinkableLabel) in your control’s visual tree to have automatic label-or-hyperlink behavior as with Label. Or, use your own UI and use the functions in the helper class ShowAsLinkPropertyHelper (Microsoft.LightSwitch.Presentation.Framework.Helpers.ShowAsLinkPropertyHelper) to implement this functionality.

  • FontStyle (Inheritable)

    To support FontStyle, which is recommended only for read-only controls like Label, just override its EditorVisibility to PropertySheet, and its functionality will be handled automatically by LightSwitch.

  • TextAlignment

    To support TextAlignment, a control must override EditorVisibility and also provide its own implementation, as follows.

    <TextBox
           Text="{Binding StringValue, Mode=TwoWay}"
           TextAlignment="{Binding Properties[MyControlExtension:MyControl/TextAlignment]}"
           IsReadOnly="{Binding IsReadOnly}"
           AutomationProperties.AutomationId="{Binding Name, StringFormat=Control: \{0\}}"
           AutomationProperties.Name="{Binding DisplayName}"
           AutomationProperties.HelpText="{Binding Description}"
           ToolTipService.ToolTip="{Binding Description}"
       />
    
  • BrowseOnly (Inheritable)

    This property corresponds to the Use read-only controls property in the property sheet. To support it, which is recommended for group and smart layout controls only, override EditorVisibility to PropertySheet and LightSwitch will handle it automatically.

  • IsReadOnly (Inheritable) and IsEnabled (Inheritable)

    These two properties are for internal use only and should not be displayed in the property sheet.

  • ContainerState (Inheritable)

    ContainerState is an internal property that should never be displayed in the property sheet.  It is used to enable controls to change their UI or behavior based on context in the tree.  The only standard values currently defined are None and Cell.  Cell indicates that the control is being used inside another control that displays items like cells (for example, a DataGrid). As an example, the built-in TextBox control changes its appearance when ContainerState is Cell.

    If you are creating a collection or group control and want to have controls change their appearance as they do inside the DataGrid, set the value of this property to Cell on the control’s children, as follows.

    <Control.ChildItemPropertySources>
          <!-- Set ContainerState for descendants to "Cell", so they can draw 
               themselves differently inside a DataGrid or similar control -->
          <ControlPropertySource Property=":RootControl/Properties[ContainerState]">
            <ControlPropertySource.Source>
              <ScreenExpressionTree>
                <ConstantExpression ResultType=":String" Value="Cell" />
              </ScreenExpressionTree>
            </ControlPropertySource.Source>
          </ControlPropertySource>
        </Control.ChildItemPropertySources>
    

    If you are creating a control that changes its behavior in response to its ContainerState, you can check IContentItem.ContainerState, or find it in IContentItem.Properties, and respond in code, or perhaps in XAML via a visual state.

  • Image

    To support the Image property, a control must both override EditorVisibility to PropertySheet and then bind its UI to the Image. Do not bind directly to the value of the Image property. Instead, you should use the helper class ImagePropertyHelper in Microsoft.LightSwitch.Presentation.Framework.Helpers to interpret the property settings in IContentItem and retrieve the correct image to display. The helper class knows how to retrieve the image and get correct default images for certain bound data, such as button methods.

Enum-Valued Properties

Since property types must be IDataType types, and LightSwitch does not currently support an enum type, a property whose value is from a known enum property value must be stored as a string. The control must do any necessary conversions when inspecting the property value.

The default property editor for strings can handle enum-valued properties and will display a ComboBox with values if the SupportedValue and SupportedValuesExclusive attributes are specified. For example:

<ControlProperty Name="TextAlignment"
                       PropertyType=":String"
                       CategoryName="Appearance"
                       EditorVisibility="PropertySheet">
        <ControlProperty.Attributes>
          <DisplayName Value="$(MyControl_TextAlignment_DisplayName)" />
          <Description Value="$(MyControl_TextAlignment_Description)" />
          <SupportedValuesExclusive />
          <SupportedValue DisplayName="$(TextAlignment_StandardValues_Left)" 
                          Value="Left" />
          <SupportedValue DisplayName="$(TextAlignment_StandardValues_Right)" 
                          Value="Right" />
          <SupportedValue DisplayName="$(TextAlignment_StandardValues_Center)" 
                          Value="Center" />
        </ControlProperty.Attributes>
        <ControlProperty.DefaultValueSource>
          <ScreenExpressionTree>
            <ConstantExpression ResultType=":String" Value="Left"/>
          </ScreenExpressionTree>
        </ControlProperty.DefaultValueSource>
      </ControlProperty>

Setting Property Values Automatically

Controls can set property values automatically on themselves and/or their children directly from the metadata. Controls can set properties in the following locations:

  • Control.PropertySources - Sets property values directly on a control.

  • Control.ChildItemPropertySources - Sets property values on all child content items of the control.

  • Placeholder.PropertySources - Sets property values on a content item child inside a specific control placeholder.

The following example shows all three of these property sources in action.

<Control Name="MySmartLayout"
           SupportedContentItemKind="Group">

    <!-- Set property values on the control itself -->
    <Control.PropertySources>
      <!-- Override AttachedLabelPosition on the control itself to Collapsed – 
            this is the label that is shown for the smart layout itself. 
            Note that this value will also be propagated to all children 
            by default because this property is inheritable, except that you are 
            setting a different value for the children below. -->
      <ControlPropertySource Property=":RootControl/Properties[AttachedLabelPosition]">
        <ControlPropertySource.Source>
          <ScreenExpressionTree>
            <ConstantExpression ResultType=":String" Value="Collapsed" />
          </ScreenExpressionTree>
        </ControlPropertySource.Source>
      </ControlPropertySource>

    </Control.PropertySources>

    <!-- Set property values on all direct children in the content tree -->
    <Control.ChildItemPropertySources>
      
      <!-- Set default AttachedLabelPosition to None for all of the children. -->
      <ControlPropertySource Property=":RootControl/Properties[AttachedLabelPosition]">
        <ControlPropertySource.Source>
          <ScreenExpressionTree>
            <ConstantExpression ResultType=":String" Value="None" />
          </ScreenExpressionTree>
        </ControlPropertySource.Source>
      </ControlPropertySource>
      
    </Control.ChildItemPropertySources>

    <!-- Define two placeholders (or “buckets”).  You will set a value for the
         property FontSize in only one of the placeholders. This value will be
         applied to whatever content item is in that placeholder in the content
         tree. -->
    <Control.Placeholders>
      <Placeholder DisplayName="$(MySmartLayout_Title_DisplayName)" 
                   Name="Title">
        <Placeholder.PropertySources>
          <ControlPropertySource Property=":RootControl/Properties[FontStyle]">
            <ControlPropertySource.Source>
              <ScreenExpressionTree>
                <ConstantExpression ResultType=":String" Value="Heading1" />
              </ScreenExpressionTree>
            </ControlPropertySource.Source>
          </ControlPropertySource>
        </Placeholder.PropertySources>
      </Placeholder>
      <Placeholder DisplayName="$(MySmartLayout_Description_DisplayName)" 
                   Name="Description" />
    </Control.Placeholders>

    ...
  </Control>

In general practice, there is no real difference between overriding a control property’s default value and setting a property value on the control itself using Control.PropertySources. The same cannot be said for Placerholder.PropertySources and Control.ChildItemPropertySources, however, as the values are not propogated.

Property Evaluation

A property’s value for a particular content item can come from multiple sources. The following is the order of evaluation from these sources, ordered from lowest to highest priority:

  1. Default value specified in a ControlProperty defined on or inherited by the content item’s control.

  2. Value inherited from the parent's effective value of this property, if it’s inheritable.

  3. Default value overridden via a ControlPropertyOverride.

  4. Value specified in the control's PropertySources.

  5. Value specified in the parent control's ChildItemPropertySources.

  6. Value specified in the placeholder’s PropertySources of the parent control, if the value is specified in a placeholder.

  7. Value set by the developer at design time, ContentItem.PropertySources.

  8. Value set by the developer at runtime by means of FindControl().

  9. State determined by the content item at run time and which can change; for properties such as IsReadOnly, IsEnabled, overriding a table item’s property’s IsReadOnly method, and so on.

  10. State from the bound data's schema, such as read-only tables, calculated fields, and so on.

The first six items are default values or values set in metadata, items seven and eight are values set by the LightSwitch developer, and items nine and ten are values determined by the data binding state.

Accessing Property Values

Binding to Property Values from XAML

Property values may be accessed from a content item’s Properties collection using the fully qualified property name. To avoid ambiguity, the properties dictionary is keyed off of fully qualified property names. Because of limitations in XAML binding notation, standard property IDs such as “:RootControl/Properties[AttachedLabelPosition]” cannot be used. Therefore, an alternative notation is used for keying into the Properties collection: Microsoft.LightSwitch:RootControl/AttachedLabelPosition. Note also that the shortcut “:” for “Microsoft.LightSwitch” cannot be used here. The following example shows the syntax.

TextAlignment="{Binding Properties[Microsoft.LightSwitch:RootControl/TextAlignment]}"

Accessing Property Values from Code

You can also access property values from code. In this case, the fully qualified property name is still required. The following example shows how to access a property value.

Friend NotInheritable Class MyContentItemExtensions
    Private Sub New()
    End Sub

    <System.Runtime.CompilerServices.Extension> _
    Public Shared Function TryGetPropertyValue(Of T)(contentItem As IContentItem, qualifiedPropertyName As String, ByRef value As T) As Boolean
        Dim objValue As Object
        If contentItem.Properties.TryGetValue(qualifiedPropertyName, objValue) AndAlso objValue IsNot Nothing Then
            If GetType(T).IsEnum Then
                Try
                    Dim objectEnumValue As Object = [Enum].Parse(GetType(T), DirectCast(objValue, String))
                    value = DirectCast(objectEnumValue, T)
                    Return True
                Catch generatedExceptionName As ArgumentException
                End Try
            Else
                value = DirectCast(objValue, T)
                Return True
            End If
        End If

        value = Nothing
        Return False
    End Function

    <System.Runtime.CompilerServices.Extension> _
    Public Shared Function GetPropertyValueOrDefault(Of T)(contentItem As IContentItem, qualifiedPropertyName As String, Optional defaultValue As T = Nothing) As T
        Dim result As T
        If contentItem.TryGetPropertyValue(Of T)(qualifiedPropertyName, result) Then
            Return result
        Else
            Return defaultValue
        End If
    End Function
End Class
internal static class MyContentItemExtensions
{
        public static bool TryGetPropertyValue<T>(this IContentItem contentItem, string qualifiedPropertyName, out T value)
        {
            object objValue;
            if (contentItem.Properties.TryGetValue(qualifiedPropertyName, out objValue) && objValue != null)
            {
                if (typeof(T).IsEnum)
                {
                    try
                    {
                        object objectEnumValue = Enum.Parse(typeof(T), (string)objValue), false;
                        value = (T)objectEnumValue;
                        return true;
                    }
                    catch (ArgumentException)
                    {
                    }
                }
                else
                {
                    value = (T)objValue;
                    return true;
                }
            }

            value = default(T);
            return false;
        }

        public static T GetPropertyValueOrDefault<T>(this IContentItem contentItem, string qualifiedPropertyName, T defaultValue = default(T))
        {
            T result;
            if (contentItem.TryGetPropertyValue<T>(qualifiedPropertyName, out result))
                return result;
            else
                return defaultValue;
        }
}

Handling Property Changes

When accessing property values from code, you must correctly handle property changes by using the IContentItem.PropertyChanged event, in which case an event is fired both for “Properties” and also for the qualified property name specifically. This is important for all properties, because the developer can change property values at run time through FindControl() or through the run-time screen designer, “Customize Screen”. Generally, if a control correctly handles property changes in the run-time screen designer, it will correctly handle the FindControl() case.

See Also

Tasks

How to: Create a LightSwitch Control

Walkthrough: Creating a Value Control Extension

Walkthrough: Creating a Detail Control Extension

Walkthrough: Creating a Smart Layout Control Extension

Walkthrough: Creating a Stack Panel Control Extension

Concepts

LightSwitch Extensibility Toolkit for Visual Studio 2013