Additional LightSwitch Control Concepts

Most of the important concepts regarding control extensions for LightSwitch are covered in the two walkthrough topics, Walkthrough: Creating a Value Control Extension and Walkthrough: Creating a Detail Control Extension. There are, however, several additional concepts that may apply for certain kinds of controls.

Add DisplayMode Support to the Control

In LightSwitch, you can configure controls to behave differently when they appear in a DataGrid or similar container control. You might want this behavior to increase performance (editable controls can be more expensive to display), to achieve the correct tabbing and focus behavior, or both. If a focusable control appears inside a DataGrid, a user can, when tabbing through the grid, set the focus not only to the DataGrid cell but also to the underlying control. This behavior leads to two tab stops for each cell and additional problems when the DataGrid starts to display cached row UI because of virtualization. Therefore, the display mode of a control should never let the user give it focus or modify the data in the control. The display mode view for control is exposed to LightSwitch as a separate data template in the control factory.

If the control is not editable and performance is not a problem, the display mode template is optional. Depending on custom properties that you set on the control, You might decide to use a simple UI for the display mode (such as a TextBlock), or a version of the full control with tab stops and visual hit testing disabled.

The code-behind file for the Silverlight control contains a ControlFactory class, which provides the data template or templates for the control when it appears on a LightSwitch screen. The class contains the GetDisplayModeDataTemplate function, which returns the read-only version of the control when its data is not being edited in a DataGrid.

In the Constants section of the ControlFactory class, add a read-only data template. A TextBlock displays text instead of a TextBox. For demonstration, the text will appear in red:

' control template for datagrid display mode
Private Const DisplayModeControlTemplate As String = "<DataTemplate" + "  xmlns='https://schemas.microsoft.com/winfx/2006/xaml/presentation'>" + "  <TextBlock Text='{Binding StringValue}' Margin='3' Foreground='Red' " + "             TextAlignment=""{Binding Properties[Microsoft.LightSwitch:RootControl/TextAlignment]}""" + "             VerticalAlignment=""{Binding Properties[Microsoft.LightSwitch:RootControl/VerticalAlignment]}""" + " />" + "</DataTemplate>"
// control template for datagrid display mode
private const string DisplayModeControlTemplate =
    "<DataTemplate" +
    "  xmlns='https://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
    "  <TextBlock Text='{Binding StringValue}' Margin='3' Foreground='Red' " +
    "             TextAlignment=\"{Binding Properties[Microsoft.LightSwitch:RootControl/TextAlignment]}\"" +
    "             VerticalAlignment=\"{Binding Properties[Microsoft.LightSwitch:RootControl/VerticalAlignment]}\"" +
    " />" +
    "</DataTemplate>";

In the GetDisplayModeDataTemplate function of the ControlFactory, return the newly defined read-only template:

Public Function GetDisplayModeDataTemplate(contentItem As IContentItem) As DataTemplate
' provide the display mode template
If Me.displayModeDataTemplate Is Nothing Then
Me.displayModeDataTemplate = TryCast(XamlReader.Load(MyControlFactory.DisplayModeControlTemplate), DataTemplate)
End If
Return Me.displayModeDataTemplate
End Function

Private displayModeDataTemplate As DataTemplate
public DataTemplate GetDisplayModeDataTemplate(IContentItem contentItem)
{
    // provide the display mode template
    if (null == this.displayModeDataTemplate)
    {
        this.displayModeDataTemplate = XamlReader.Load(MyControlFactory.DisplayModeControlTemplate) as DataTemplate;
    }
    return this.displayModeDataTemplate;
}

private DataTemplate displayModeDataTemplate;

In some situations, the end user may start to type in the DataGrid when a control is still using its display mode template. In that case, the LightSwitch DataGrid will handle switching to the editable mode template. To make sure that no characters are lost when this switch is occurring, the DataGrid will cache any typed text.

In the code-behind class for the control (not the ControlFactory class), implement the ISupportTextInput interface, and add the following implementation method. This method makes sure that the new input replaces the old text and sets the cursor to the end of the new text so that the user can continue to type.

Private Sub ISupportTextInput_SetText(text As String) Implements ISupportTextInput.SetText
If Me.TextBox IsNot Nothing Then
Me.TextBox.Text = text
Me.TextBox.SelectionStart = text.Length
End If
End Sub
private void ISupportTextInput_SetText(string text) 
{ 
if (this.TextBox != null) 
{ 
this.TextBox.Text = text; 
this.TextBox.SelectionStart = text.Length; 
} 
} 
void ISupportTextInput.SetText(string text) 
{ 
ISupportTextInput_SetText(text); 
}

Specifying that a Control is a Viewer

If you do not want the user to change data in a control, you should make it a viewer control by adding the IsViewer flag to the control’s metadata in the .lsml file:

<Control Name="MyValueControl"
      SupportedContentItemKind="Value"
      IsViewer="True"
... >
  ...
  </Control>

By adding this flag, you change whether the control appears in some cases.

Providing Both an Editor Control and a Viewer Control

A common pattern is to have two versions of a control. One version enables editing data (“editor”), and one version only displays data (“viewer”). Examples include AddressEditor/AddressViewer and TextBox/Label.

An editor control that displays read-only data differs from a viewer control that displays read-only data. Many editor controls become unavailable in a read-only state. All editable controls must support a read-only mode (IContentItem.IsReadOnly), even if they have a paired viewer control. This read-only mode often but not always looks different from the viewer version.

When a screen is first loaded, LightSwitch determines which controls to use for all content items in the screen. If the developer has not specified a particular control to use, the set of default view mappings is used to determine the default control for a node. This assessment is based on several factors, which include parent controls, the schema of the bound data (such as whether it is a calculated field, a read-only database table, or a view), and the setting of the Use Read-Only Controls property. These factors are determined at design time, and you cannot change them at run time. Based on these considerations, either a viewer control or an editor control will be selected, assuming both are available.

Other factors can change at run time, such as the IsReadOnly property, the X_IsReadOnly property for an entity field, or authorization settings, based on user code and other factors. A content item’s control is never changed at run time. Therefore, if an editor control was selected, it may appear in read-only mode based on these factors. If a viewer was selected at screen-load time or the developer specified a viewer at design time, the viewer should ignore IContentItem.IsReadOnly and never enable editing.

When you create a pair of controls for a business type, you can set the default mappings so that the either the viewer or editor will be picked when the developer has not specified a control.

Editor and viewer controls can frequently share properties. For example, the ImageViewer and ImageEditor controls both expose a Stretch property. If each control were to define a Stretch property, LightSwitch will see these two properties as different and will not propagate the value of one property to the other. To resolve this issue, you should create a base control from which both the editor and viewer control inherit, and then define the property on the base control:

<Control Name="MyBaseControl">
    <Control.Properties>
      <ControlProperty Name="MyCommonProperty"
        PropertyType=":String"
        CategoryName="Appearance"
        EditorVisibility="PropertySheet">
    </Control.Properties>
  </Control>

  <Control
      Name="MyViewerControl"
      BaseControl="MyBaseControl"
      SupportedContentItemKind="Value"
      IsViewer="True" 
      ...>
    <Control.Attributes>
      <DisplayName Value="My Viewer Control"/>
    </Control.Attributes>
    <Control.SupportedDataTypes>
      <SupportedDataType DataType="String"/>
    </Control.SupportedDataTypes>
...
  </Control>

  <Control
      Name="MyEditorControl"
      BaseControl="MyBaseControl"
      SupportedContentItemKind="Value" 
      ... >
    <Control.SupportedDataTypes>
      <SupportedDataType DataType="String"/>
    </Control.SupportedDataTypes>
...
  </Control>

Notice the IsViewer property on the viewer and that only properties are inherited from the base control. Other attributes, such as AttachedLabelSupport of SupportedDataTypes, are not inherited. In this case, the property ID is MyModule:MyBaseControl/Properties[MyCommonProperty].

See Also

Tasks

How to: Create a LightSwitch Control

Walkthrough: Creating a Value Control Extension

Walkthrough: Creating a Detail Control Extension

Walkthrough: Creating a Business Type Extension

How to: Create a LightSwitch Extension Project

Concepts

Supported Data Types for LightSwitch Extensions

LightSwitch Extensibility Toolkit for Visual Studio 2013