Walkthrough: Creating a Detail Control Extension

This walkthrough demonstrates how to create a detail control extension for Visual Studio LightSwitch 2011. Detail controls are used by LightSwitch to display an entity field. This differs from a Group control, which can be used to display the whole entity by displaying each of the entity’s fields in an arranged manner. The LightSwitch Summary control is one example of a detail control.

To create a detail control extension, you must perform the following tasks:

  • Create a control extension project.

  • Update the control icon.

  • Update the control definition in the LightSwitch metadata file.

  • Add resource strings.

  • Implement the detail control.

  • Enhance the control to use the Summary property.

  • Make the control programmable.

  • Create a custom editor for the display property.

  • Handle IsComputed status in the control.

  • Prevent an item from being deleted.

  • Handle keyboard navigation in a DataGrid.

Prerequisites

  • Visual Studio 2010 SP1 (Professional, Premium, or Ultimate edition)

  • Visual Studio 2010 SP1 SDK

  • Visual Studio LightSwitch 2011

  • Visual Studio LightSwitch 2011 Extensibility Toolkit

Create a Control Extension Project

The first step is to create a project and add a Control template.

To create an extension project

  1. On the menu bar in Visual Studio, choose File, New Project.

  2. In the New Project dialog box, select the LightSwitch node, and then select LightSwitch Extension Library (Visual Basic) or LightSwitch Extension Library (C#).

  3. In the Name field, type DetailControlExtension as the name for your extension library. This name will appear on the Extensions tab of the LightSwitch Application Designer.

  4. Choose the OK button to create a solution that contains the seven projects that are required for the extension.

To choose an extension type

  1. In Solution Explorer, select the DetailControlExtension.Lspkg project.

  2. On the menu bar, choose Project, Add New Item.

  3. In the Add New Item dialog box, select Control.

  4. In the Name field, type DetailControl as the name for your extension. This name will appear in the LightSwitch screen designer.

  5. Choose the OK button. Files will be added to several projects in your solution.

Update the Control Icon

Two image files named DetailControl.png were added to your solution, one in the ControlImages folder of the DetailControlExtension.Client.Design project and the other in the ControlImages folder of the DetailControlExtension.Design project. The image in the file is used as an icon. You can replace the default image with one that uniquely identifies your control.

To modify the icon image

  1. In Solution Explorer, on the shortcut menu for the DetailControl.png file in the ControlImages folder of the DetailControlExtension.Client.Design project, choose Open With.

  2. In the Open With dialog box, select Paint, and then choose the OK button.

  3. In Paint, change the image; for example, change the color or add a shape, and then save the file and return to Visual Studio.

  4. Select the DetailControl.png file, and then on the menu bar, choose Edit, Copy.

  5. Select the ControlImages folder of the DetailControlExtension.Design project, and then on the menu bar, choose Edit, Paste. In the message that asks whether you want to replace the file, choose the Yes button.

Update the Control Definition in the LightSwitch Metadata File

Metadata that defines the control is contained in the .lsml file in the DetailControlExtension.Common project. For a detail control, the important elements are the SupportedContentItemKind, the DisplayName, and the DisplayProperty.

The SupportedContentItemKind tells LightSwitch the type of the control; for a detail control, set SupportedContentItemKindto Details.

The DisplayName property provides the name that is displayed in the screen designer. It can be defined as a String, for example “My Detail Control,” or as String resources in the ModuleResources file by using the notation $(DetailControl_DisplayName).

DisplayProperty provides a property that the developer can set at design time to specify which entity field to display.

To update the control metadata

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Common project, open the DetailControl.lsml file.

  2. Change the SupportedContentItemKind element to SupportedContentItemKind="Details".

    This tells LightSwitch that the control extension is a details control.

  3. Change the DisplayName element to <DisplayName Value="$(DetailControl_DisplayName)" />.

    In this case the control will get the display name from a string resource in a resource file that you will create in a later step.

  4. Insert the following code after the Control.Attributes block.

    <Control.Properties>
          <!-- 
            Define 'DisplayProperty' to allow the developer to select which entity field should be shown inside the detail control. 
          -->
          <ControlProperty Name="DisplayProperty"
                           PropertyType=":String"
                           CategoryName="Appearance"
                           EditorVisibility="PropertySheet">
            <ControlProperty.Attributes>
              <!-- Reference localized strings in ModuleResource.resx -->
              <DisplayName Value="$(DisplayProperty_DisplayName)" />
              <Description Value="$(DisplayProperty_Description)" />
            </ControlProperty.Attributes>
    
            <!-- Define the default value of this property to be an empty string. -->
            <ControlProperty.DefaultValueSource>
              <ScreenExpressionTree>
                <!-- Only a constant expression is supported here. -->
                <ConstantExpression ResultType=":String" Value=""/>
              </ScreenExpressionTree>
            </ControlProperty.DefaultValueSource>
    
          </ControlProperty>
        </Control.Properties>
    

    This code tells LightSwitch to display a String property in the property sheet. The DisplayName and Description attributes are again defined as String resources in a resource file. The DefaultValueSource element tells LightSwitch to display an empty String by default.

  5. Delete the Control.SupportedDataTypes block. Because a group control cannot directly display data, this block is unnecessary.

  6. The complete code for the DetailControl.lsml file should now resemble the following example.

    <?xml version="1.0" encoding="utf-8" ?>
    <ModelFragment
      xmlns="https://schemas.microsoft.com/LightSwitch/2010/xaml/model"
      xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
    
      <Control Name="DetailControl"
        SupportedContentItemKind="Details"
        DesignerImageResource="DetailControlExtension.DetailControl::ControlImage">
        <Control.Attributes>
          <DisplayName Value="$(DetailControl_DisplayName)" />
        </Control.Attributes>
        <Control.Properties>
          <!-- 
            Define 'DisplayProperty' to allow the developer to select which entity field should be shown inside the detail control. 
          -->
          <ControlProperty Name="DisplayProperty"
                           PropertyType=":String"
                           CategoryName="Appearance"
                           EditorVisibility="PropertySheet">
            <ControlProperty.Attributes>
              <!-- Reference localized strings in ModuleResource.resx -->
              <DisplayName Value="$(DisplayProperty_DisplayName)" />
              <Description Value="$(DisplayProperty_Description)" />
            </ControlProperty.Attributes>
    
            <!-- Define the default value of this property to be an empty string. -->
            <ControlProperty.DefaultValueSource>
              <ScreenExpressionTree>
                <!-- Only a constant expression is supported here. -->
                <ConstantExpression ResultType=":String" Value=""/>
              </ScreenExpressionTree>
            </ControlProperty.DefaultValueSource>
    
          </ControlProperty>
        </Control.Properties>
             </Control>
    </ModelFragment>
    

Add Resource Strings

The ModuleResources.resx file in the DetailControlExtension.Common project contains resources that are used by the control. You can add string resources for the text that will be displayed for your control in the screen designer, in this case, the three strings that you defined in the .lsml file.

To add resource strings

  1. In Solution Explorer, expand the Resources node in the DetailControlExtension.Common project, and then open the ModuleResources.resx file.

  2. Add the following values to the ModuleResources.resx file.

    Name

    Value

    Comments

    DetailControl_DisplayName

    My Detail Control

    The control’s display name in the screen designer.

    DisplayProperty_Description

    The property shown in the application.

    Short description string for the property in the screen designer.

    DisplayProperty_DisplayName

    Display Property

    Property name in the screen designer.

Implement the Detail Control

The DetailControl.xaml and DetailControl.xaml.vb or DetailControl.xaml.cs files in the Presentation, Controls folder of the DetailControlExtension.Client project contain the implementation for the control. The template creates a default implementation, which you will want to replace with your own code.

Define a Dependency Property

A dependency property lets you later switch the control from a user control to a Silverlight custom control with a similar implementation. This would allow the control to be restyled via a theme extension, and is the preferred method for creating a control extension. The dependency property will be used by the developer to bind to an entity property at design time, defining what will be displayed by the control.

To define a dependency property

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Client project, open the DetailControl.xaml.vb or DetailControl.xaml.cs file.

  2. Add the following Imports statements to the DetailControl.xaml.vb file, or using statements to the DetailControl.xaml.cs file.

    Imports Microsoft.LightSwitch.Details
    Imports Microsoft.LightSwitch.Model
    Imports System.Windows.Data
    Imports System.Diagnostics
    
    using Microsoft.LightSwitch.Details;
    using Microsoft.LightSwitch.Model;
    using System.Windows.Data;
    using System.Diagnostics;
    
  3. Add a EntityProperty property inside the DetailControl class, as follows.

    '   The EntityProperty is bound to the data property that will be shown in the TextBox.  
    ' This allows you to use DataBinding in the control UI.        
            Public Property EntityProperty As IProperty
                Get
                    Return MyBase.GetValue(DetailControl.EntityPropertyProperty)
                End Get
                Set(value As IProperty)
                    MyBase.SetValue(DetailControl.EntityPropertyProperty, value)
                End Set
            End Property
    
            Public Shared ReadOnly EntityPropertyProperty As DependencyProperty =
                DependencyProperty.Register("EntityProperty", GetType(IProperty), GetType(DetailControl), New PropertyMetadata(Nothing))
    
    /// <summary>
            ///   The EntityProperty is bound to the data property that will be shown in the TextBox.  
            ///   This allows you to use DataBinding in the control UI.
            /// </summary>
            public IProperty EntityProperty
            {
                get { return (IProperty)GetValue(EntityPropertyProperty); }
                set { SetValue(EntityPropertyProperty, value); }
            }
    
            public static readonly DependencyProperty EntityPropertyProperty =
                DependencyProperty.Register("EntityProperty", typeof(IProperty), typeof(DetailControl), new PropertyMetadata(null));
    

Create the User Interface

Next, create the user interface for the control in the DetailControl.xaml file. The code will define a DetailRoot element and set DataContext to that DetailRoot. It will also define a TextBoxcontrol named DetailTextBox and set its Text and ReadOnly bindings to the EntityProperty dependency property that you created earlier.

To create the control UI

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Client project, open the DetailControl.xaml file.

  2. Replace the existing XAML code with the following code.

    <UserControl x:Class="DetailControlExtension.Presentation.Controls.DetailControl"
                 xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
                 x:Name="DetailRoot"
                 HorizontalAlignment="Stretch"
                 >
        <!-- 
            The DataContext of the control is a ContentItem.  However, you defined several DependencyProperties inside your control to expose necessary data to be shown on the UI.
            Here, set the DataContext of the internal implementation to your control itself, so you can use short DataBindings inside.
        -->
        <Grid DataContext="{Binding ElementName=DetailRoot}" HorizontalAlignment="Stretch">
            <!-- 
            That is the TextBox you show the property value.  The EntityProperty is a dependency property that will bind to the property to be shown on the UI.
            Bind its Value and IsReadOnly properties to the text box.
            -->
            <TextBox Name="DetailTextBox"
                     HorizontalAlignment="Stretch"
                     VerticalAlignment="Stretch"
                     Text="{Binding EntityProperty.Value, Mode=TwoWay}" 
                     IsReadOnly="{Binding EntityProperty.IsReadOnly}" />
        </Grid>
    </UserControl>
    

Data Bind the EntityProperty Property

Next, you will add code to get the value for the EntityProperty property. The value is calculated using the current entity and the DisplayProperty control property. The current entity is loaded in the LightSwitch runtime application, and the DisplayProperty value is set by the application developer in the screen designer or in code. To get these two values and monitor changes correctly, you must add two more dependency properties, DisplayPropertyName and ContentItem, to the DetailControl class.

To bind the property

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Client project, open the DetailControl.xaml.vb or DetailControl.xaml.cs file.

  2. Add the following code for the dependency properties after the EntityProperty code block:

    ''' <summary>
            ''' DisplayPropertyName is the name of the property to be shown in the control.  When it is empty, you will use the summary property of the entity.
            ''' </summary>
            Public Property DisplayPropertyName As String
                Get
                    Return MyBase.GetValue(DetailControl.DisplayPropertyNameProperty)
                End Get
                Set(value As String)
                    MyBase.SetValue(DetailControl.DisplayPropertyNameProperty, value)
                End Set
            End Property
    
            Public Shared ReadOnly DisplayPropertyNameProperty As DependencyProperty =
                DependencyProperty.Register("DisplayPropertyName", GetType(String), GetType(DetailControl), New PropertyMetadata(AddressOf DetailControl.OnDisplayPropertyNameChanged))
    
            Private Shared Sub OnDisplayPropertyNameChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
                ' When the DisplayPropertyName is changed, reset the internal data binding.
                CType(d, DetailControl).SetContentDataBinding()
            End Sub
    
            ''' <summary>
            ''' ContentItem property is bound to the IContentItem inside the LightSwitch screen layout tree. This is created to monitor if the DataContext of the control is changed.
            ''' </summary>
            Public Property ContentItem As IContentItem
                Get
                    Return MyBase.GetValue(DetailControl.ContentItemProperty)
                End Get
                Set(value As IContentItem)
                    MyBase.SetValue(DetailControl.ContentItemProperty, value)
                End Set
            End Property
    
            Public Shared ReadOnly ContentItemProperty As DependencyProperty =
                DependencyProperty.Register("ContentItem", GetType(IContentItem), GetType(DetailControl), New PropertyMetadata(AddressOf DetailControl.OnContentItemChanged))
    
            Private Shared Sub OnContentItemChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
                ' When the ContentItem is changed, reset the internal data binding.
                CType(d, DetailControl).SetContentDataBinding()
            End Sub
    
    /// <summary>
            /// DisplayPropertyName is the name of the property to be shown in the control.  When it is empty, you will use the summary property of the entity.
            /// </summary>
            public string DisplayPropertyName
            {
                get { return (string)GetValue(DisplayPropertyNameProperty); }
                set { SetValue(DisplayPropertyNameProperty, value); }
            }
    
            public static readonly DependencyProperty DisplayPropertyNameProperty =
                DependencyProperty.Register("DisplayPropertyName", typeof(string), typeof(DetailControl), new PropertyMetadata(OnDisplayPropertyNameChanged));
    
            private static void OnDisplayPropertyNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                // When the DisplayPropertyName is changed, reset the internal data binding.
                ((DetailControl)d).SetContentDataBinding();
            }
    
            /// <summary>
            /// ContentItem property is bound to the IContentItem inside the LightSwitch screen layout tree. This is created to monitor if the DataContext of the control is changed.
            /// </summary>
            public IContentItem ContentItem
            {
                get { return (IContentItem)GetValue(ContentItemProperty); }
                set { SetValue(ContentItemProperty, value); }
            }
    
            public static readonly DependencyProperty ContentItemProperty = DependencyProperty.Register("ContentItem",
                        typeof(IContentItem), typeof(DetailControl), new PropertyMetadata(OnContentItemChanged));
    
            private static void OnContentItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                // When the ContentItem is changed, reset the internal data binding.
                ((DetailControl)d).SetContentDataBinding();
            }
    

    You also have to set the binding for DisplayPropertyName and ContentItem when the ControlName is created.

  3. Add the following code inside the DetailControl constructor after the InitializeComponent call.

    ' Create two data bindings to get the value of the current ContentItem, and the DisplayProperty chosen by the user.
                '  The DisplayProperty can be updated in the development phase when the developer is using the run-time screen designer, or can be changed by screen APIs.
                '  It is necessary to handle the property change event in the extension control.  A data binding can help you monitor the change easily.
                MyBase.SetBinding(DetailControl.ContentItemProperty, New Binding())
                MyBase.SetBinding(DetailControl.DisplayPropertyNameProperty, New Binding("Properties[DetailControlExtension:DetailControl/DisplayProperty]"))
    
    // Create two data bindings to get the value of the current ContentItem, and the DisplayProperty chosen by the user.
                //  The DisplayProperty can be updated in the development phase when the developer is using the run-time screen designer, or can be changed by screen APIs.
                //  It is necessary to handle the property changed event in the extension control.  A data binding can help us to monitor the change easily.
                this.SetBinding(ContentItemProperty, new Binding());
                this.SetBinding(DisplayPropertyNameProperty, new Binding("Properties[DetailControlExtension:DetailControl/DisplayProperty]"));
    

    You also have to add a SetContentDataBinding function to the class.

  4. Add the following code to the DetailControl class after the OnContentItemChanged block.

    Private Sub SetContentDataBinding()
                Me.ClearValue(EntityPropertyProperty)
    
                If ContentItem IsNot Nothing Then
                    ' A detail control can only be bound to an entity object.  You can get the type from the ContentItem.
                    Dim entityType As IEntityType = TryCast(ContentItem.ResultingDataType, IEntityType)
                    Debug.Assert(entityType IsNot Nothing, "Detail Control can only bind to an entity type.")
    
                    If entityType IsNot Nothing Then
                        Dim displayPropertyName As String = Me.DisplayPropertyName
    
    
                        Dim entityProperty As IEntityPropertyDefinition = entityType.Properties.FirstOrDefault(Function(p) [String].Equals(p.Name, displayPropertyName, StringComparison.OrdinalIgnoreCase))
                        If String.IsNullOrEmpty(displayPropertyName) Then
                            entityProperty = entityType.Properties.FirstOrDefault(Function(p) String.Equals(p.Name, displayPropertyName, StringComparison.OrdinalIgnoreCase))
                        End If
    
                        If entityProperty IsNot Nothing Then
                            ' Set data binding to the entity property.
                            ' Because the DataContext of the current control is always a content item, the binding path starts from a ContentItem.  ContentItem.Value should be the entity object.
                            '  Entity.Details.Properties.[PropertyName] allows you to get the object representing a data property.  A short version, "Value.[PropertyName]", can be used to access the
                            '  value directly.  But using the data property object, you can access additional status, like whether it is readonly, and whether it has a validation error.
    
                            Me.SetBinding(EntityPropertyProperty, New Binding([String].Format(System.Globalization.CultureInfo.InvariantCulture, "Value.Details.Properties.{0}", entityProperty.Name)))
                        End If
                    End If
                End If
            End Sub
    
    private void SetContentDataBinding()
            {
                this.ClearValue(EntityPropertyProperty);
    
                if (ContentItem != null)
                {
                    // A detail control can only be bound to an entity object.  We can get the type from the ContentItem.
                    IEntityType entityType = ContentItem.ResultingDataType as IEntityType;
                    Debug.Assert(entityType != null, "Detail Control can only bind to an entity type.");
    
                    if (entityType != null)
                    {
                        string displayPropertyName = this.DisplayPropertyName;
    
                        IEntityPropertyDefinition entityProperty =
    
                                entityType.Properties.FirstOrDefault(p => String.Equals(p.Name, displayPropertyName, StringComparison.OrdinalIgnoreCase));
                         if (string.IsNullOrEmpty(displayPropertyName)) {
    entityProperty = entityType.Properties.FirstOrDefault(p => string.Equals(p.Name, displayPropertyName, StringComparison.OrdinalIgnoreCase));
    }
    
                        if (entityProperty != null)
                        {
                            // Set data binding to the entity property.
                            // Because the DataContext of the current control is always a content item, the binding path starts from a ContentItem.  ContentItem.Value should be the entity object.
                            //  Entity.Details.Properties.[PropertyName] allows us to get the object representing a data property.  A short version "Value.[PropertyName]" can be used to access the
                            //  value directly.  But using the data property object, we can access additional status, like whether it is readonly, and whether it has a validation error.
                            this.SetBinding(EntityPropertyProperty, new Binding(String.Format(System.Globalization.CultureInfo.InvariantCulture, "Value.Details.Properties.{0}", entityProperty.Name)));
    
                        }
                    }
                }
            }
    

    The implementation of the detail control is now complete, and you can test it in LightSwitch.

Test the Detail Control

You can test the detail control in an experimental instance of Visual Studio. If you have not already tested another LightSwitch extensibility project, you have to enable the experimental instance first.

To enable an experimental instance

  1. In Solution Explorer, select the DetailControlExtension.Vsix project.

  2. On the menu bar, choose Project, DetailControlExtension.Vsix Properties.

  3. On the Debug tab, under Start Action, choose Start external program.

  4. Enter the path of the Visual Studio executable, devenv.exe.

    By default on a 32-bit system, the path is C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe; on a 64-bit system, it is C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe.

  5. In the Command line arguments field, type /rootsuffix Exp as the command-line argument.

    Note

    All subsequent LightSwitch extensibility projects will also use this setting, by default.

To test the detail control

  1. On the menu bar, choose Debug, Start Debugging. An experimental instance of Visual Studio opens.

  2. In the experimental instance, on the menu bar, choose File, New Project.

  3. In the New Project dialog box, select the LightSwitch node, and then select LightSwitch Application (Visual Basic) or LightSwitch Application (C#).

  4. In the Name field, type DetailControlTest, and then choose the OK button to create a test project.

  5. On the menu bar, choose Project, DetailControlTest Properties.

  6. In the Project Designer, on the Extensions tab, select the DetailControlExtension check box.

  7. Create a basic LightSwitch application that has a table that contains several String fields, or connect to an external data source.

  8. Add a List and Details screen, and then in the screen designer, change the Summary control below the first Rows Layout element to My Detail Control.

  9. In the Properties window, select the Display Property property, and then enter the name of a field in the table.

  10. On the menu bar, choose Debug, Start Debugging. Observe the behavior of the My Detail Control control in the application and verify that the data for the field is properly displayed.

Enhance the Control to Use the Summary Property

The current implementation of the control requires the application developer to set the DisplayProperty correctly. Otherwise, nothing will be displayed. To make the control easier to use, you can remove this requirement. If the application developer does not supply a property, you should supply the Summary property of the entity type automatically.

To do this, create a GetSummaryMethod function, add several utility methods, and add code to calculate the default Summary property.

To create a GetSummaryMethod function

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Client project, open the DetailControl.xaml.vb or DetailControl.xaml.cs file.

  2. Add the following code to the DetailControl class.

     ''' <summary>
            ''' Calculate the summary property of an entity type.
            ''' </summary>
            ''' <param name="entityType">The entity type</param>
            ''' <returns>The summary property of the entity type</returns>
            Private Shared Function GetSummaryProperty(entityType As IEntityType) As IEntityPropertyDefinition
    
                ' We should always use the summary property assigned by the user first.
                Dim attribute As ISummaryPropertyAttribute = entityType.Attributes.OfType(Of ISummaryPropertyAttribute)().FirstOrDefault()
    
                If attribute IsNot Nothing AndAlso attribute.Property IsNot Nothing Then
                    Return attribute.Property
                End If
    
                Return DetailControl.GetDefaultSummaryProperty(entityType)
    
            End Function
    
            ''' <summary>
            ''' Calculate the default summary property of an entity type.
            ''' </summary>
            ''' <param name="entityType">The entity type</param>
            ''' <returns>The default summary property of the entity type</returns>
            Private Shared Function GetDefaultSummaryProperty(entityType As IEntityType) As IEntityPropertyDefinition
    
                ' Only simple type properties can be used.  You filtered out navigation properties...
                Dim simpleTypeProperties As IEnumerable(Of IEntityPropertyDefinition) = entityType.Properties.Where(Function(p) TypeOf p.PropertyType Is ISimpleType)
    
                ' If there is any string property, you pick up the first one as the default summary property.  Otherwise, you get the first property that can be represented as a string.
                '  ModelUtilities is a utility class you add to the Common project, so it can be shared between several projects.  A project-to-project reference needs be added to make that work.
                Dim defaultSummaryProperty As IEntityPropertyDefinition =
                    simpleTypeProperties.FirstOrDefault(Function(p) ModelUtilities.GetUnderlyingSystemType(p.PropertyType) Is GetType(String))
    
                If defaultSummaryProperty Is Nothing Then
                    simpleTypeProperties.FirstOrDefault(Function(p) ModelUtilities.IsTextProperty(p))
                End If
    
                Return defaultSummaryProperty
    
            End Function
    
    /// <summary>
            /// Calculate the summary property of an entity type.
            /// </summary>
            /// <param name="entityType">The entity type</param>
            /// <returns>The summary property of the entity type</returns>
            private static IEntityPropertyDefinition GetSummaryProperty(IEntityType entityType)
            {
                // Always use the summary property assigned by the user first.
                ISummaryPropertyAttribute attribute =
                    entityType.Attributes.OfType<ISummaryPropertyAttribute>().FirstOrDefault();
    
                if (attribute != null && attribute.Property != null)
                {
                    return attribute.Property;
                }
    
                // If the summary property is not defined, calculate the default one.
                return GetDefaultSummaryProperty(entityType);
            }
    
            /// <summary>
            /// Calculate the default summary property of an entity type.
            /// </summary>
            /// <param name="entityType">The entity type</param>
            /// <returns>The default summary property of the entity type</returns>
            private static IEntityPropertyDefinition GetDefaultSummaryProperty(IEntityType entityType)
            {
                // Only simple type properties can be used.  You filtered out navigation properties...
                IEnumerable<IEntityPropertyDefinition> simpleTypeProperties = entityType.Properties.Where(p => p.PropertyType is ISimpleType);
    
                // If there is any string property, pick up the first one as the default summary property.  Otherwise, you get the first property that can be represented as a string.
                //  ModelUtilities is a utility class you add to the Common project, so it can be shared between several projects.  A project-to-project reference needs to be added to make that work.
                IEntityPropertyDefinition defaultSummaryProperty =
                    simpleTypeProperties.FirstOrDefault(p => ModelUtilities.GetUnderlyingSystemType((ISimpleType)p.PropertyType) == typeof(string)) ??
                    simpleTypeProperties.FirstOrDefault(p => ModelUtilities.IsTextProperty(p));
    
                return defaultSummaryProperty;
            }
    

    In this code, the Summary property definition is extracted from the application metadata if the user defines it in the application. You are still missing the logic to calculate the default summary property if it is not set explicitly by the application developer. You will add this logic next.

  3. Locate the SetContentDataBinding() method in the DetailControl class. Replace the If String.IsNullOrEmpty (Visual Basic) or if (string.IsNullOrEmpty (C#) block with the following:

    If String.IsNullOrEmpty(displayPropertyName) Then
                            ' When the DisplayProperty is not set, get the summary property.
                            entityProperty = DetailControl.GetSummaryProperty(entityType)
                        Else
                            ' Otherwise, you get the entity property.
                            entityProperty = entityType.Properties.FirstOrDefault(Function(p) String.Equals(p.Name, displayPropertyName, StringComparison.OrdinalIgnoreCase))
                        End If
    
    if (String.IsNullOrEmpty(displayPropertyName))
                        {
                            // When the DisplayProperty is not set, get the summary property.
                            entityProperty = GetSummaryProperty(entityType);
                        }
                        else
                        {
                            // Otherwise, you get the entity property.
                            entityProperty = entityType.Properties.FirstOrDefault(p => String.Equals(p.Name, displayPropertyName, StringComparison.OrdinalIgnoreCase));
                        }
    

    Next, you will add some utility methods that can be used in both the control and also in the designer extension you will add later. To share the code between different projects, add it to the Common project.

To add utility methods

  1. In Solution Explorer, select the DetailControlExtension.Common project.

  2. On the menu bar, choose Project, Add Class.

  3. In the Add New Item dialog box, select Class.

  4. In the Name field, type ModelUtilities as the name for your class file, and then choose the OK button.

  5. Replace the contents of the class file with the following.

    Imports System
    Imports System.Collections.Generic
    Imports System.Diagnostics
    Imports System.Linq
    Imports Microsoft.LightSwitch.Model
    
    ''' <summary>
    ''' ModelUtilities contains a few utility functions that are used in the Client, Client.Design, and Design projects.  The target projects must reference the Common project where the utility class is located.
    ''' </summary>
    Public Module ModelUtilities
    
        ''' <summary>
        ''' GetUnderlyingSystemType is a helper function to extract which CLR type is used to represent data of a LightSwitch simple type.
        ''' </summary>
        ''' <param name="dataType">LightSwitch DataType</param>
        ''' <returns>CLR type used to reprsent the data</returns>
        Public Function GetUnderlyingSystemType(dataType As ISimpleType) As Type
    
            If dataType Is Nothing Then
                Throw New ArgumentNullException("dataType")
            End If
    
            While dataType IsNot Nothing
                If TypeOf dataType Is IPrimitiveType Then
                    ' Primitive types are foundation LightSwitch data types like: String/Int32/Decimal/Date/...
                    '  This is a set that cannot be extended by a third party.
                    Return DirectCast(dataType, IPrimitiveType).ClrType
                ElseIf TypeOf dataType Is INullableType Then
                    ' NullableType represents a Nullable version of any primitive or semantic type.
                    dataType = DirectCast(dataType, INullableType).UnderlyingType
                ElseIf TypeOf dataType Is ISemanticType Then
                    ' Semantic types are types extending primitive types and adding additional semantic information like data format or validation logic.
                    dataType = DirectCast(dataType, ISemanticType).UnderlyingType
                Else
                    ' This should never happen.  A simple type in LightSwitch is either a semantic type, a nullable type or a primitive type.
                    dataType = Nothing
                End If
            End While
    
            Debug.Assert(False, "We expect all semantic types in the LightSwitch are built on the top of a primitive type.")
            Return Nothing
    
        End Function
    
        ''' <summary>
        ''' IsTextProperty returns true if a property can be represented by a string.  
        ''' In LightSwitch, a non-binary simple type property cannot be represented by a string.
        ''' </summary>
        ''' <param name="propertyDefinition">The definition of a property in the LightSwitch metadata</param>
        ''' <returns>true if the property can be represented as a string</returns>
        Public Function IsTextProperty(propertyDefinition As IPropertyDefinition) As Boolean
    
            If propertyDefinition Is Nothing Then
                Throw New ArgumentException("propertyDefinition")
            End If
    
            Dim dataType As ISimpleType = TryCast(propertyDefinition.PropertyType, ISimpleType)
            If dataType IsNot Nothing Then
                Dim clrType As Type = GetUnderlyingSystemType(dataType)
                Return clrType IsNot Nothing AndAlso clrType IsNot GetType(Byte())
            End If
            Return False
    
        End Function
    
    End Module
    
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    
    using Microsoft.LightSwitch.Model;
    
    namespace DetailControlExtension
    {
        /// <summary>
        /// ModelUtilities contains a few utility functions that are used in Client, Client.Design, and Design project.  The target project must reference the Common project, where the utility class is added.
        /// </summary>
        public static class ModelUtilities
        {
            /// <summary>
            /// GetUnderlyingSystemType is a helper function to extract which CLR type is used to represent data of a LightSwitch simple type.
            /// </summary>
            /// <param name="dataType">LightSwitch DataType</param>
            /// <returns>CLR type used to reprsent the data</returns>
            public static Type GetUnderlyingSystemType(ISimpleType dataType)
            {
                if (dataType == null)
                {
                    throw new ArgumentNullException("dataType");
                }
    
                while (dataType != null)
                {
                    if (dataType is IPrimitiveType)
                    {
                        // Primitive types are foundation LightSwitch data types like: String/Int32/Decimal/Date/...
                        //  This is a set that cannot be extended by a third party.
                        return ((IPrimitiveType)dataType).ClrType;
                    }
                    else if (dataType is INullableType)
                    {
                        // NullableType represents a Nullable version of any primitive or semantic type.
                        dataType = ((INullableType)dataType).UnderlyingType;
                    }
                    else if (dataType is ISemanticType)
                    {
                        // Semantic types are types extending primitive types and adding additional semantic information like data format or validation logic.
                        dataType = ((ISemanticType)dataType).UnderlyingType;
                    }
                    else
                    {
                        // This should never happen.  A simple type in LightSwitch is either a semantic type, a nullable type or a primitive type.
                        dataType = null;
                    }
                }
    
                Debug.Assert(false, "We expect all semantic types in the LightSwitch are built on the top of a primitive type.");
                return null;
            }
    
            /// <summary>
            /// IsTextProperty returns true if a property can be represented by a string.  
            /// In LightSwitch, a non-binary simple type property cannot be represented by a string.
            /// </summary>
            /// <param name="propertyDefinition">The definition of a property in the LightSwitch metadata</param>
            /// <returns>true if the property can be represented as a string</returns>
            public static bool IsTextProperty(IPropertyDefinition propertyDefinition)
            {
                if (propertyDefinition == null)
                {
                    throw new ArgumentNullException("propertyDefinition");
                }
    
                ISimpleType dataType = propertyDefinition.PropertyType as ISimpleType;
                if (dataType != null)
                {
                    Type clrType = GetUnderlyingSystemType(dataType);
                    return clrType != null && clrType != typeof(Byte[]);
                }
                return false;
            }
        }
    }
    

    Next, add a reference to the Common project from the Client project. This will enable you to use the utility functions defined in the Common project from the Client project.

To add a reference

  1. In Solution Explorer, open the shortcut menu for the DetailControl.Client project, and then choose Add Reference.

  2. In the Add Reference dialog box, select the Projects tab and add a reference to the DetailControl.Common project.

At this point, you can test the control again and observe the changes in behavior. Verify that the summary property is displayed if no value is specified for the DisplayProperty.

The remainder of this walkthrough covers techniques for adding optional capabilities to your control.

Make the Control Programmable

All of the built-in LightSwitch controls are programmable. To enable developers to access the control in their application code, you will want to implement the IContentVisual interface for your control.

To implement the IContentVisual interface

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Client project, open the DetailControl.xaml.vb or DetailControl.xaml.cs file.

  2. Update the class definition to implement IContentVisual.

    Partial Public Class DetailControl
            Inherits UserControl
            Implements IContentVisual
    
    public partial class DetailControl : UserControl, IContentVisual
    
  3. Add the implementation to the class.

    Public ReadOnly Property Control As Object Implements Microsoft.LightSwitch.Presentation.IContentVisual.Control
                ‘Implement the IContentVisual to allow the application developer to access the TextBox control by using IContentItemProxy.SetBinding/ControlAvailable APIs.
                Get
                    Return Me.DetailTextBox
                End Get
            End Property
    
            Public Sub Show() Implements Microsoft.LightSwitch.Presentation.IContentVisual.Show
    
            End Sub
    
    // Implement the IContentVisual to allow the application developer to access the TextBox control by using IContentItemProxy.SetBinding/ControlAvailable APIs.
            object IContentVisual.Control
            {
                get { return this.DetailTextBox; }
            }
    
            void IContentVisual.Show()
            {
            }
    

Create a Custom Editor for the DisplayProperty

So far your detail control requires the application developer to enter a valid entity field name in the Display Property text box in the Properties window. A better experience would be to provide a drop-down list with all valid choices. You can do so by creating a custom property editor.

For control extensions, two property editors are required; the LightSwitch screen designer and the run-time screen editor are built on different platforms. Working in the screen designer requires a Windows Presentation Foundation (WPF) based editor. Working in customization mode requires a Silverlight based editor.

Create a WPF Property Editor

A WPF-based property editor provides access to properties from the screen designer. When you create an extension project by using the LightSwitch Extension Library template, a Design project is added to the solution. This project generates an assembly based on the full .NET Framework, and this is where you will create a WPF-based property editor.

The first step is to add references to the LightSwitch Designer namespace and to the Common project in your solution.

To add references

  1. In Solution Explorer, open the shortcut menu for the DetailControl.Design project, and then choose Add Reference.

  2. In the Add Reference dialog box, add a reference to Microsoft.LightSwitch.Design.Designer.dll.

    You can find the assembly in the PrivateAssembly folder under the Visual Studio IDE folder.

  3. Open the shortcut menu for the DetailControl.Design node, and then choose Add Existing Item.

  4. Select the Common project in your solution, and then select the ModelUtilities file.

  5. Expand the drop-down list on the Add button, and then choose Add As Link.

The next step is to create the WPF control that will be used to edit the property.

To create the WPF control

  1. In Solution Explorer, open the shortcut menu for the DetailControl.Design project, and then choose Add New Folder. Name the new folder Editors.

  2. In the Editors folder, open the shortcut menu and choose Add New Item.

  3. In the Add New Item dialog box, expand the WPF node, and choose User Control (WPF).

  4. In the Name field, type EntityPropertyPicker, and then click Add.

    A EntityPropertyPicker.xaml file and a EntityPropertyPicker.xaml.vb or EntityPropertyPicker.xaml.cs file will be added to the Editors folder.

  5. In the Editors folder, open the shortcut menu and choose Add New Item.

  6. In the Add New Item dialog box, expand the Code node and choose Class.

  7. In the Name field, type EntityPropertyPickerEditor and then click Add.

  8. Open the EntityPropertyPickerEditor class file and replace the contents with the following code:

    Imports System.ComponentModel.Composition
    Imports System.Windows
    Imports System.Windows.Markup
    
    Imports Microsoft.LightSwitch.Designers.PropertyPages
    Imports Microsoft.LightSwitch.Designers.PropertyPages.UI
    
    Namespace DetailControlExtension.Editors
    
        ''' <summary>
        ''' EntityPropertyPickerProvider is a component to allow LightSwitch designers in Visual Studio to create a property value editor.
        ''' The name of the editor is specified in a PropertyValueEditorName attribute.  When it is needed, a designer will use the EditorTemplate to create
        ''' a WPF control, which can be hosted inside a property sheet window.
        ''' The DataContext of this control will be an IBindablePropertyEntry object.  Through the DataContext, the control can update the property value.
        ''' </summary>
        <Export(GetType(IPropertyValueEditorProvider))>
        <PropertyValueEditorName("DetailControlExtension:EntityPropertyPicker")>
        <PropertyValueEditorType("System.String")>
        Public Class EntityPropertyPickerEditor
            Implements IPropertyValueEditorProvider
    
            Public Function GetEditor(entry As Microsoft.LightSwitch.Designers.PropertyPages.IPropertyEntry) As Microsoft.LightSwitch.Designers.PropertyPages.UI.IPropertyValueEditor Implements Microsoft.LightSwitch.Designers.PropertyPages.UI.IPropertyValueEditorProvider.GetEditor
                Return New Editor()
            End Function
    
            Private Class Editor
                Implements IPropertyValueEditor
    
                Public ReadOnly Property Context As Object Implements Microsoft.LightSwitch.Designers.PropertyPages.UI.IPropertyValueEditor.Context
                    Get
                        ' A design-time editor allows an additional Context object, which is exposed through IBindablePropertyEntry.EditorContext.  This allows the editor to have additional status.
                        ' However, the run-time designer does not support it.  It is not used in this sample.
                        Return Nothing
                    End Get
                End Property
    
                ' The DataTemplate is used by the screen designer to create the UI control on the property sheet.
                Public Function GetEditorTemplate(entry As Microsoft.LightSwitch.Designers.PropertyPages.IPropertyEntry) As System.Windows.DataTemplate Implements Microsoft.LightSwitch.Designers.PropertyPages.UI.IPropertyValueEditor.GetEditorTemplate
                    Return XamlReader.Parse(EntityPropertyPickerEditor.ControlTemplate)
                End Function
    
            End Class
    
    #Region "Constants"
    
            Private Const ControlTemplate As String =
                "<DataTemplate" +
                " https://schemas.microsoft.com/winfx/2006/xaml/presentation""" +
                " xmlns:x=""https://schemas.microsoft.com/winfx/2006/xaml""" +
                " xmlns:editors=""clr-namespace:DetailControlExtension.Editors;assembly=DetailControlExtension.Design"">" +
                "   <editors:EntityPropertyPicker/>" +
                "</DataTemplate>"
    
    #End Region
    
        End Class
    
    End Namespace
    
    using System.ComponentModel.Composition;
    using System.Windows;
    using System.Windows.Markup;
    
    using Microsoft.LightSwitch.Designers.PropertyPages;
    using Microsoft.LightSwitch.Designers.PropertyPages.UI;
    
    namespace DetailControlExtension.Editors
    {
        /// <summary>
        /// EntityPropertyPickerProvider is a component to allow LightSwitch designers in Visual Studio to create a property value editor.
    /// The name of the editor is specified in a PropertyValueEditorName attribute.  When it is needed, a designer will use the
        ///  EditorTemplate to create a WPF control, which can be hosted inside a property sheet window.
        /// The DataContext of this control will be an IBindablePropertyEntry object.  Through the DataContext, the control can update the property value.
        /// </summary>
        [Export(typeof(IPropertyValueEditorProvider))]
        [PropertyValueEditorName("DetailControlExtension:EntityPropertyPicker")]
        [PropertyValueEditorType("System.String")]
        public class EntityPropertyPickerProvider
            : IPropertyValueEditorProvider
        {
            public IPropertyValueEditor GetEditor(IPropertyEntry entry)
            {
                return new Editor();
            }
    
            private class Editor : IPropertyValueEditor
            {
                public object Context
                {
                    get
                    {
                        // A design-time editor allows an additional Context object, which is exposed through IBindablePropertyEntry.EditorContext.  This allows the editor to have additional status.
                        // However, the run-time designer does not support it.  It is not used in this sample.
                        return null;
                    }
                }
    
                // The DataTemplate is used by the screen designer to create the UI control on the property sheet.
                public DataTemplate GetEditorTemplate(IPropertyEntry entry)
                {
                    return XamlReader.Parse(ControlTemplate) as DataTemplate;
                }
            }
    
            #region Constants
    
            private const string ControlTemplate =
                "<DataTemplate" +
                " xmlns=\"https://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
                " xmlns:x=\"https://schemas.microsoft.com/winfx/2006/xaml\"" +
                " xmlns:editors=\"clr-namespace:DetailControlExtension.Editors;assembly=DetailControlExtension.Design\">" +
                "   <editors:EntityPropertyPicker/>" +
                "</DataTemplate>";
    
            #endregion
        }
    }
    

    This code implements a component that can be loaded by a LightSwitch designer based on its name inside a PropertyValueEditorName attribute. When a LightSwitch designer displays a property marked to use an editor with the same name, it will create a WPF control with the DataTemplate and host it in the property sheet. The DataContext of the control will be set to an IBindablePropertyEntry object, where the control can get or set the value of the property.

    The next step is to change the control property metadata in the .lsml file for the extension.

  9. In the Common project, open the DetailControl.lsml file.

  10. Add the UIEditorID property to the ControlProperty element, as follows.

    <ControlProperty Name="DisplayProperty"
                           PropertyType=":String"
                           CategoryName="Appearance"
                           UIEditorId="DetailControlExtension:EntityPropertyPicker"
                           EditorVisibility="PropertySheet">
    
  11. In the Design project, open the EntityPropertyPicker.xaml file, and replace the default code with the following code to implement the control.

    <UserControl x:Class="DetailControlExtension.Editors.EntityPropertyPicker"
                 xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:d="https://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 >
        <!-- 
        DesignTimeFontSize and DesignTimeFontFamily are design-time public resource items.  
        Use them to ensure that you use the same font inside different controls on the property sheet.
        -->
        <Grid TextBlock.FontSize="{DynamicResource DesignTimeFontSize}"
              TextBlock.FontFamily="{DynamicResource DesignTimeFontFamily}"
    >
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
    
            <!-- 
                That is the label to show the property name.  
                The DataContext of this control is an IBindablePropertyEntry object. Use its Entry property to get the IPropertyEntry.
            -->
            <TextBlock x:Name="ComboBoxLabel"
                       Text="{Binding Entry.DisplayName, Mode=OneWay}"
                       TextWrapping="WrapWithOverflow"
                       ToolTip="{Binding Entry.Description, Mode=OneWay}"
                       Margin="0,0,0,2"/>
    
            <!-- 
                This is the ComboBox to pick up the value.  The DataContext of this control is an IBindablePropertyEntry object. Use its Entry property to get the IPropertyEntry.
                Use the 'GetTextPropertiesConverter' to collect all valid choices.  The PropertyValue.Value is the value of the DisplayProperty of the control.
            -->
            <ComboBox x:Name="ComboBox" 
                      Grid.Row ="1"
                      SelectedItem="{Binding Entry.PropertyValue.Value}"
                      >
            </ComboBox>
        </Grid>
    
    </UserControl>
    

    The XAML code adds a Label to show the property name, and a ComboBox to show the current value. Next, you will add some code to get the list for the ComboBox. To do so, you must know the exact entity type that is bound to the control.

    In the screen designer, the context of the property is exposed by IPropertyValue.ModelItem. For a control property, ModelItem is always the instance of IContentItemDefinition where the control is being used. There, a single property, IContentItemDefinition.DataType, defines which entity type is bound to the control. For the ComboBox, a list of the names of all properties in that entity type is needed. This list can be generated by creating a converter.

  12. Close and save the EntityPropertyPicker.xaml file.

Next, define the Editors namespace and add code to initialize the editor in the code-behind file.

To add code

  1. Expand the EntityPropertyPicker.xaml node, open the EntityPropertyPicker.xaml.vb or EntityPropertyPicker.xaml.cs file, and replace the contents with the following.

    Imports System.Windows.Controls
    
    Namespace Editors
    
        Public Class EntityPropertyPicker
            Inherits UserControl
    
            Public Sub New()
                InitializeComponent()
            End Sub
    
    
    
        End Class
    
    End Namespace
    
    using System.Windows.Controls;
    
    namespace DetailControlExtension.Editors
    {
         public partial class EntityPropertyPicker : UserControl
        {
            public EntityPropertyPicker()
            {
                InitializeComponent();
            }
        }
    }
    
  2. Close and save the EntityPropertyPicker.xaml.vb or EntityPropertyPicker.xaml.cs file.

Next, add another utility method to get a collection of entity properties.

To add a utility method

  1. In the Common project, open the ModelUtilities class file.

  2. Add a new method to the class.

    ''' <summary>
        ''' Get all text presentable properties of a data type.
        ''' </summary>
        ''' <param name="dataType">A data type metadata in the LightSwitch</param>
        ''' <returns>A collection of properties matching the condition.</returns>
        Public Function GetAllTextProperties(dataType As IDataType) As IEnumerable(Of IPropertyDefinition)
    
            If dataType IsNot Nothing Then
                Return dataType.Properties _
                    .Where(Function(p) ModelUtilities.IsTextProperty(p)) _
                    .Cast(Of IPropertyDefinition)()
            End If
    
            Return Enumerable.Empty(Of IPropertyDefinition)()
    
        End Function
    
    /// <summary>
            /// Get all text presentable properties of a data type.
            /// </summary>
            /// <param name="dataType">A data type metadata in the LightSwitch</param>
            /// <returns>A collection of properties matching the condition.</returns>
            public static IEnumerable<IPropertyDefinition> GetAllTextProperties(IDataType dataType)
            {
                if (dataType != null)
                {
                    return dataType.Properties
                                .Where(property => ModelUtilities.IsTextProperty(property))
                                .Cast<IPropertyDefinition>();
                }
    
                return Enumerable.Empty<IPropertyDefinition>();
            }
    

    This method retrieves a collection of properties that can be represented as a string inside a data type, including an entity type.

Next, create a converter that will use the GetAllTextProperties method to get the names of all properties in the entity so that they can be displayed in the ComboBox.

To create the GetTextPropertiesConverter

  1. In the Editors folder of the Design project, open the shortcut menu and choose Add New Item.

  2. In the Add New Item dialog box, expand the Code node, and choose Class.

  3. In the Name field, type GetTextPropertiesConverter, and then click Add.

  4. Open the GetTextPropertiesConverter class file and add the following code.

    Imports System
    Imports System.Collections.Generic
    Imports System.Windows.Data
    
    Imports Microsoft.LightSwitch.Model
    
    Namespace Editors
    
        ''' <summary>
        ''' GetTextPropertiesConverter gets a collection of names of properties of the entity type bound to a ContentItem. 
        ''' The resulting collection is used inside a ComboBox drop-down. An empty string is always added to the collection as none of the properties has been chosen.
        ''' </summary>
    
    Public Class GetTextPropertiesConverter
            Implements IValueConverter
    
            Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
    
                Dim textProperties As List(Of String) = New List(Of String)()
    
                textProperties.Add(String.Empty)
    
                Dim contentItemDefinition As IContentItemDefinition = TryCast(value, IContentItemDefinition)
                If contentItemDefinition IsNot Nothing Then
                    Dim entityType As IEntityType = TryCast(contentItemDefinition.DataType, IEntityType)
                    If entityType IsNot Nothing Then
                        For Each p As IPropertyDefinition In ModelUtilities.GetAllTextProperties(entityType)
                            textProperties.Add(p.Name)
                        Next
                    End If
                End If
                Return textProperties
    
            End Function
    
            Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
                Throw New NotSupportedException()
            End Function
    
        End Class
    
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Windows.Data;
    
    using Microsoft.LightSwitch.Model;
    
    namespace DetailControlExtension.Editors
    {
        /// <summary>
        /// GetTextPropertiesConverter gets a collection of names of properties of the entity type bound to a ContentItem. 
        /// The resulting collection is used inside a ComboBox drop-down. An empty string is always added to the collection as none of the properties has been chosen.
        /// </summary>
    #if !SILVERLIGHT
        [ValueConversion(typeof(IContentItemDefinition), typeof(IEnumerable<string>))]
    #endif
        public class GetTextPropertiesConverter
            : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                List<string> textProperties = new List<string>();
    
                textProperties.Add(String.Empty);
    
                IContentItemDefinition contentItemDefinition = (IContentItemDefinition)value;
                if (contentItemDefinition != null)
                {
                    IEntityType entityType = contentItemDefinition.DataType as IEntityType;
                    if (entityType != null)
                    {
                        foreach (IPropertyDefinition property in ModelUtilities.GetAllTextProperties(entityType))
                        {
                            textProperties.Add(property.Name);
                        }
                    }
                }
                return textProperties;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    }
    

    This converter takes the IContentItemDefinition, which can be accessed from IPropertyValue, and returns a collection of valid property names plus an empty string, which represents the default value of the property, meaning nothing has been chosen.

  5. In the Editors folder, open the EntityPropertyPicker.xaml file.

  6. Add the following namespace mapping to the control.

    xmlns:e="clr-namespace:DetailControlExtension.Editors"
    
  7. Add a resource to create the new converter.

    <UserControl.Resources>
            <e:GetTextPropertiesConverter x:Key="GetTextPropertiesConverter" />
        </UserControl.Resources>
    

    Note

    At this point you might notice an error in the designer. This error will be resolved when you build the Client project.

  8. Update the ComboBox element with the new Converter by setting the ItemsSource property:

    <ComboBox x:Name="ComboBox" 
                      Grid.Row ="1"
                      SelectedItem="{Binding Entry.PropertyValue.Value}"
                      ItemsSource="{Binding Entry.PropertyValue.ModelItem, Mode=OneWay, Converter={StaticResource GetTextPropertiesConverter}}"
                      >
            </ComboBox>
    

Next, create another converter to format the property name.

To create a GetTextPropertiesConverter

  1. In the Editors folder of the Design project, open the shortcut menu and choose Add New Item.

  2. In the Add New Item dialog box, expand the Code node, and choose Class.

  3. In the Name field, type DisplayNameConverter, and then click Add.

  4. Open the DisplayNameConverter class file and add the following code.

    Imports System
    Imports System.Globalization
    Imports System.Windows.Data
    
    Namespace Editors
    
        ''' <summary>
        ''' DisplayNameConverter is a converter to append ':' to the property name.  The result is used inside the label to edit the property.
        ''' It is important not to append ':' directly inside the property name.
        ''' </summary>
    
    Public Class DisplayNameConverter
            Implements IValueConverter
    
            Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
    
                If value IsNot Nothing Then
                    Return String.Format(CultureInfo.CurrentCulture, "{0}:", value)
                End If
                Return String.Empty
    
            End Function
    
            Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
                Throw New NotSupportedException()
            End Function
    
        End Class
    
    End Namespace
    
    using System;
    using System.Windows.Data;
    
    namespace DetailControlExtension.Editors
    {
    
        /// <summary>
        /// DisplayNameConverter is a converter to append ':' to the property name.  The result is used inside the label to edit the property.
        /// It is important not to append ':' directly inside the property name.
        /// </summary>
    #if !SILVERLIGHT
        [ValueConversion(typeof(string), typeof(string))]
    #endif
        public class DisplayNameConverter
            : IValueConverter
        {
    
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value != null)
                {
                    return String.Format(System.Globalization.CultureInfo.CurrentCulture, "{0}:", value);
                }
                return String.Empty;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    }
    

    This code formats the String with the “:” notation, which is a shortcut for "Microsoft.LightSwitch:".

  5. In the Editors folder, open the EntityPropertyPicker.xaml file.

  6. Add a resource to create the new converter.

    <UserControl.Resources>
            <e:GetTextPropertiesConverter x:Key="GetTextPropertiesConverter" />
            <e:DisplayNameConverter x:Key="DisplayNameConverter" />
        </UserControl.Resources>
    
  7. On the menu bar, choose Build, DetailControlExtension.Design.

  8. Add a converter block to the TextBlock element.

    <TextBlock x:Name="ComboBoxLabel"
                       Text="{Binding Entry.DisplayName, Mode=OneWay, Converter={StaticResource DisplayNameConverter}}"
                       TextWrapping="WrapWithOverflow"
                       ToolTip="{Binding Entry.Description, Mode=OneWay}"
                       Margin="0,0,0,2"/>
    

Next, add another converter to display user-friendly default text in the ComboBox when no entity field is selected.

To create a PropertyNameToDisplayNameConverter

  1. In the Resources folder of the Design project, open the shortcut menu and choose Add New Item.

  2. In the Add New Item dialog box, expand the General node, and choose Resources file.

  3. In the Name field, type DesignResources, and then click Add.

  4. In the DesignResources.resx file, add a string named “DefaultProperty_DisplayName”, and assign it a value of <Summary>.

    This resource string will be used by the converter when no value has been specified by the application developer.

  5. In the Editors folder of the Design project, open the shortcut menu and choose Add New Item.

  6. In the Add New Item dialog box, expand the Code node and choose Class.

  7. In the Name field, type PropertyNameToDisplayNameConverter, and then click Add.

  8. In the PropertyNameToDisplayNameConverter class file, add the following code.

    Imports System
    Imports System.Windows.Data
    
    Imports DetailControlExtension.My.Resources
    
    Namespace Editors
    
        ''' <summary>
        ''' PropertyNameToDisplayNameConverter is used to show a user-friendly name when no property has been chosen by the user.
        ''' </summary>
    
    Public Class PropertyNameToDisplayNameConverter
            Implements IValueConverter
    
            Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
    
                If TypeOf value Is String AndAlso String.IsNullOrEmpty(DirectCast(value, String)) Then
                    Return DesignResources.DefaultProperty_DisplayName
                End If
                Return value
    
            End Function
    
            Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
                Throw New NotSupportedException()
            End Function
    
        End Class
    
    End Namespace
    
    using System;
    using System.Windows.Data;
    
    using DetailControlExtension.Resources;
    
    namespace DetailControlExtension.Editors
    {
        /// <summary>
        /// PropertyNameToDisplayNameConverter is used to show a user-friendly name when no property has been chosen by the user.
        /// </summary>
    #if !SILVERLIGHT
        [ValueConversion(typeof(string), typeof(string))]
    #endif
        public class PropertyNameToDisplayNameConverter
            : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value is string && String.IsNullOrEmpty((string)value))
                {
                    return DesignResources.DefaultProperty_DisplayName;
                }
                return value;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    }
    

    This code checks to see whether the value of the ComboBox is a null String, and if it is, it replaces it with the DefaultProperty_DisplayName resource string.

  9. In the Editors folder, open the EntityPropertyPicker.xaml file.

  10. Add a resource to create the new converter, as follows.

    <UserControl.Resources>
            <e:GetTextPropertiesConverter x:Key="GetTextPropertiesConverter" />
            <e:DisplayNameConverter x:Key="DisplayNameConverter" />
            <e:PropertyNameToDisplayNameConverter x:Key="PropertyNameToDisplayNameConverter" />    
    </UserControl.Resources>
    
  11. Add an ItemTemplate block inside the ComboBox element, as follows.

    <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <!-- The 'PropertyNameToDisplayNameConverter' is used to convert the empty string to a developer friendly string. -->
                        <TextBlock Text="{Binding Converter={StaticResource PropertyNameToDisplayNameConverter}}" />
                    </DataTemplate>
                </ComboBox.ItemTemplate>
    

The property editor is now finished. You can test it in an experimental instance of Visual Studio and observe that the property editor displays a list of entity fields.

Create a Silverlight Property Editor

If you try to set the DisplayProperty in the run-time screen designer, you will notice that the property sheet still displays a TextBox for the property. So far you have only created a WPF property editor. The run-time screen designer is based on Silverlight. Therefore, it does not recognize the WPF-based editor; you will have to create a Silverlight equivalent.

The DetailControlExtension.Client.Design project in the solution is where you will write code for the run-time screen designer. You can share some of the code that you added to the DetailControlExtension.Design project; the implementation of the Silverlight editor resembles that of the WPF editor.

The first step is to add a DefaultProperty_DisplayName resource, just as you did for the WPF editor.

To add a resource

  1. In the Resources folder of the DetailControlExtension.Client.Design project, open the shortcut menu and choose Add New Item.

  2. In the Add New Item dialog box, expand the General node, and choose Resources file.

  3. In the Name field, type DesignResources, and then click Add.

  4. In the DesignResources.resx file, add a string named “DefaultProperty_DisplayName”, and assign it a value of <Summary>.

    This resource string will be used by the converter when no value has been specified by the application developer.

Next, link to the three converters and the utility file that you created earlier, sharing the code into the DetailControlExtension.Client.Design project.

To share the converter code

  1. In Solution Explorer, open the shortcut menu for the DetailControl.Client.Design project, and then choose Add New Folder. Name the new folder Editors.

  2. Open the shortcut menu for the DetailControl.Client.Design node, and then choose Add Existing Item.

  3. Select the Common project in your solution, and then select the ModelUtilities file.

  4. Expand the drop-down list on the Add button, and then choose Add As Link

  5. Select the Editors node, open the shortcut menu, and then choose Add Existing Item.

  6. In the Add Existing Item dialog box, locate the Editors folder in the DetailControl.Client.Design project and then select the DisplayNameConverter, GetTextPropertiesConverter, and PropertyNameToDisplayNameConverter files.

  7. Expand the drop-down list on the Add button, and then choose Add As Link.

Next, create a Silverlight version of the property editor. The implementation of the control resembles the WPF version. The difference is that the DataContext of this control is IPropertyEntry. Therefore, the binding paths are slightly different.

To create the Silverlight control

  1. In Solution Explorer, open the shortcut menu for the Editors node in the DetailControl.Client.Design project, and then choose Add New Item.

  2. In the Add New Item dialog box, expand the Silverlight node, and choose Silverlight User Control.

  3. In the Name field, type ClientEntityPropertyPicker, and then click Add.

    An EntityPropertyPicker.xaml file and an EntityPropertyPicker.xaml.vb or EntityPropertyPicker.xaml.cs file will be added to the Editors folder.

  4. Replace the existing XAML with the following implementation for the control.

    <UserControl x:Class="DetailControlExtension.Editors.ClientEntityPropertyPicker"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:e="clr-namespace:DetailControlExtension.Editors;assembly=DetailControlExtension.Client.Design"
        mc:Ignorable="d"
        >
        <UserControl.Resources>
            <e:PropertyNameToDisplayNameConverter x:Key="PropertyNameToDisplayNameConverter" />
            <e:DisplayNameConverter x:Key="DisplayNameConverter" />
            <e:GetTextPropertiesConverter x:Key="GetTextPropertiesConverter" />
        </UserControl.Resources>
    
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
    
            <!-- That is the label to show the property name.  Use a converter to show a ':' behind the name. -->
            <TextBlock x:Name="EditorLabel" 
                       Text="{Binding Path=DisplayName, Converter={StaticResource DisplayNameConverter}}"/>
    
            <!-- 
                This is the ComboBox to pick up the value.  The DataContext of this control is an IPropertyEntry object.
                Use the 'GetTextPropertiesConverter' to collect all valid choices.  The PropertyValue.Value is the value of the DisplayProperty of the control.
                In SilverLight, ItemsSource needs to be set before SelectedItem to ensure that the control will work correctly.
            -->
            <ComboBox Margin="0,1,0,0" Grid.Row="1"
                      ItemsSource="{Binding Path=PropertyValue.ModelItem, Converter={StaticResource GetTextPropertiesConverter}}"
                      SelectedItem="{Binding Path=PropertyValue.Value, Mode=TwoWay}"
                      AutomationProperties.LabeledBy="{Binding ElementName=EditorLabel}"
                      HorizontalAlignment="Stretch"
                      >
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <!-- The 'PropertyNameToDisplayNameConverter' is used to convert the empty string to a developer friendly string. -->
                        <TextBlock Text="{Binding Converter={StaticResource PropertyNameToDisplayNameConverter}}" />
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
    
        </Grid>
    
    </UserControl>
    
  5. Close and save the ClientEntityPropertyPicker.xaml file.

  6. In the Editors folder, open the shortcut menu and choose Add New Item.

  7. In the Add New Item dialog box, expand the Code node, and choose Class.

  8. In the Name field, type ClientEntityPropertyPickerEditor and then click Add.

  9. Add the following code to the class.

    Imports System.ComponentModel.Composition
    Imports System.Windows
    Imports System.Windows.Markup
    
    Imports Microsoft.LightSwitch.Designers.PropertyPages
    Imports Microsoft.LightSwitch.RuntimeEdit
    
    Namespace DetailControlExtension.Editors
    
        ''' <summary>
        ''' EntityPropertyPickerProvider is a component to allow LightSwitch designers in Visual Studio to create a property value editor.
        ''' The name of the editor is specified in a PropertyValueEditorName attribute.  When it is needed, a designer will use the EditorTemplate to create
        ''' a WPF control, which can be hosted inside a property sheet window.
        ''' The DataContext of this control will be an IBindablePropertyEntry object.  Through the DataContext, the control can update the property value.
        ''' </summary>
        <Export(GetType(IPropertyValueEditorProvider))>
        <PropertyValueEditorName("DetailControlExtension:EntityPropertyPicker")>
        <PropertyValueEditorType("System.String")>
        Public Class ClientEntityPropertyPickerEditor
            Implements IPropertyValueEditorProvider
    
            Public Function GetEditor(entry As Microsoft.LightSwitch.Designers.PropertyPages.IPropertyEntry) As Microsoft.LightSwitch.RuntimeEdit.IPropertyValueEditor Implements Microsoft.LightSwitch.RuntimeEdit.IPropertyValueEditorProvider.GetEditor
                Return New Editor()
            End Function
    
            Private Class Editor
                Implements IPropertyValueEditor
    
                ' The DataTemplate is used by the screen designer to create the UI control on the property sheet.
                Public Function GetEditorTemplate(entry As Microsoft.LightSwitch.Designers.PropertyPages.IPropertyEntry) As System.Windows.DataTemplate Implements Microsoft.LightSwitch.RuntimeEdit.IPropertyValueEditor.GetEditorTemplate
                    Return XamlReader.Load(ClientEntityPropertyPickerEditor.ControlTemplate)
                End Function
    
            End Class
    
    
            Private Const ControlTemplate As String =
                "<DataTemplate" +
                " https://schemas.microsoft.com/winfx/2006/xaml/presentation""" +
                " xmlns:x=""https://schemas.microsoft.com/winfx/2006/xaml""" +
                " xmlns:editors=""clr-namespace:DetailControlExtension.Editors;assembly=DetailControlExtension.Client.Design"">" +
                "   <editors:ClientEntityPropertyPicker/>" +
                "</DataTemplate>"
    
        End Class
    End Namespace
    
    using System.ComponentModel.Composition;
    using System.Windows;
    using System.Windows.Markup;
    using Microsoft.LightSwitch.Designers.PropertyPages;
    using Microsoft.LightSwitch.RuntimeEdit;
    
    namespace DetailControlExtension.Editors
    {
        /// <summary>
        /// ClientEntityPropertyPickerEditorProvider is a component that is used by the run-time screen editor to load the property value editor.
        /// The EditorName must match the UIEditor chosen inside the .lsml metadata of the control property.
        /// </summary>
        [Export(typeof(IPropertyValueEditorProvider))]
        [PropertyValueEditorName("DetailControlExtension:EntityPropertyPicker")]
        [PropertyValueEditorType("System.String")]
        public class ClientEntityPropertyPickerEditorProvider
            : IPropertyValueEditorProvider
        {
            public IPropertyValueEditor GetEditor(IPropertyEntry entry)
            {
                return new Editor();
            }
    
            private class Editor : IPropertyValueEditor
            {
                /// <summary>
                /// The DataTemplate is used by the run-time screen editor to create the UI control on its property sheet.
                /// </summary>
                /// <param name="entry"></param>
                /// <returns></returns>
                public DataTemplate GetEditorTemplate(IPropertyEntry entry)
                {
                    return (DataTemplate)XamlReader.Load(ControlTemplate);
                }
            }
    
            #region Constants
    
            private const string ControlTemplate =
                "<DataTemplate" +
                " xmlns=\"https://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
                " xmlns:x=\"https://schemas.microsoft.com/winfx/2006/xaml\"" +
                " xmlns:editors=\"clr-namespace:DetailControlExtension.Editors;assembly=DetailControlExtension.Client.Design\">" +
                "   <editors:ClientEntityPropertyPicker/>" +
                "</DataTemplate>";
    
            #endregion
        }
    }
    

    Again, this code resembles the code you added in the WPF project.

  10. Expand the ClientEntityPropertyPicker.xaml node, open the ClientEntityPropertyPicker.xaml.vb or ClientEntityPropertyPicker.xaml.cs file, and replace the contents with the following.

    Namespace Editors
    
        ''' <summary>
        ''' The ClientEntityPropertyPicker is used by the run-time screen designer to edit the DisplayProperty value.
        ''' </summary>
        Partial Public Class ClientEntityPropertyPicker
            Inherits UserControl
    
            Public Sub New()
                InitializeComponent()
            End Sub
    
        End Class
    
    End Namespace
    
    using System.Windows.Controls;
    
    namespace DetailControlExtension.Editors
    {
        /// <summary>
        /// The ClientEntityPropertyPicker is used by the run-time screen designer to edit the DisplayProperty value.
        /// </summary>
        public partial class ClientEntityPropertyPicker : UserControl
        {
            public ClientEntityPropertyPicker()
            {
                InitializeComponent();
            }
        }
    }
    
  11. Close and save the ClientEntityPropertyPicker.xaml.vb or ClientEntityPropertyPicker.xaml.cs file.

This completes the property editor. You can now test the control and observe the updated behavior in the run-time screen designer.

Handle IsComputed Status in the Control

When the DisplayProperty property of a control is a computed field, the control will display the old value of the property before the property is calculated. This is not the expected pattern for a LightSwitch control. LightSwitch controls typically indicate that a computed value is still being computed so that the end user does not make a decision based on an old value.

For value controls, this is usually handled directly by ContentItemPresenter hosting the control. Since there are multiple binding paths in a detail control, you have to handle the status by writing some code. To support this, you first have to add a new dependency property to the DetailControl.xaml.vb or DetailControl.xaml.cs file in the Client project.

To add a dependency property

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Client project, open the DetailControl.xaml.vb or DetailControl.xaml.cs file.

  2. Add the following code to the DetailControl class:

    ''' <summary>
            ''' The IsComputed property is bound to the IsComputed status of a computed property.  For other properties, it returns the default value, which is true.
            ''' </summary>
            Public Property IsComputed As Boolean
                Get
                    Return MyBase.GetValue(DetailControl.IsComputedProperty)
                End Get
                Set(value As Boolean)
                    MyBase.SetValue(DetailControl.IsComputedProperty, value)
                End Set
            End Property
    
            Public Shared ReadOnly IsComputedProperty As DependencyProperty =
                        DependencyProperty.Register("IsComputed", GetType(Boolean), GetType(DetailControl), New PropertyMetadata(True))
    
    /// <summary>
            /// The IsComputed property is bound to the IsComputed status of a computed property.  For other properties, it returns the default value, which is true.
            /// </summary>
            public bool IsComputed
            {
                get { return (bool)GetValue(IsComputedProperty); }
                set { SetValue(IsComputedProperty, value); }
            }
    
            public static readonly DependencyProperty IsComputedProperty =
                DependencyProperty.Register("IsComputed", typeof(bool), typeof(DetailControl), new PropertyMetadata(true));
    

    Next, add some code to the SetContentDataBinding method to bind the property.

  3. Add the following code to reset the binding at the beginning of the SetContentDataBinding()method.

    MyBase.ClearValue(DetailControl.IsComputedProperty)
    
    this.ClearValue(IsComputedProperty);
    
  4. Next add the following code inside the body of the method just after the line that sets the EntityPropertyProperty.

    ' Set data binding to the IsComputed status of a computed property.  IsComputed is a property of a computed property object.
                            MyBase.SetBinding(DetailControl.IsComputedProperty, New Binding(String.Format(System.Globalization.CultureInfo.InvariantCulture, "Value.Details.Properties.{0}.IsComputed", entityProperty.Name)))
    
    // Set data binding to the IsComputed status of a computed property.  IsComputed is a property of a computed property object.
    this.SetBinding(IsComputedProperty, new Binding(String.Format(System.Globalization.CultureInfo.InvariantCulture, "Value.Details.Properties.{0}.IsComputed", entityProperty.Name)));
    

Next, add a converter to help create the necessary UI.

To add a converter

  1. In Solution Explorer, open the shortcut menu for the Presentation/Controls folder in the DetailControl.Client project, and then choose Add New Item.

  2. In the Add New Item dialog box, expand the Code node, and choose Class.

  3. In the Name field, type IsComputingVisibilityConverter, and then click Add.

  4. Add the following code to the IsComputingVisibilityConverter class.

    Imports System
    Imports System.Windows
    Imports System.Windows.Data
    
    Namespace DetailControlExtension.Presentation.Controls
    
        ''' <summary>
        ''' IsComputingVisibilityConverter is a value converter to turn on the IsComputing symbol when a computed property is being calculated.
        ''' </summary>
        Public Class IsComputingVisibilityConverter
            Implements IValueConverter
    
            Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
                Dim isComputed = True
                If TypeOf value Is Boolean Then
                    isComputed = DirectCast(value, Boolean)
                End If
                Return If(isComputed, Visibility.Collapsed, Visibility.Visible)
            End Function
    
            Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
                Throw New NotSupportedException()
            End Function
    
        End Class
    
    End Namespace
    
    using System;
    using System.Windows;
    using System.Windows.Data;
    
    namespace DetailControlExtension.Presentation.Controls
    {
        /// <summary>
               /// IsComputingVisibilityConverter is a value converter to turn on the IsComputing sign when a computed property is being calculated.
        /// </summary>
        public class IsComputingVisibilityConverter
    
            : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                bool isComputed = true; 
                if (value is bool) 
                {
                    isComputed = (bool)value; 
                }
                return isComputed ? Visibility.Collapsed : Visibility.Visible;
    
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    }
    

Next, define the user interface in the DetailControl.xaml file.

To define the user interface

  1. In Solution Explorer, open the DetailControl.xaml file in the Client project.

  2. Add a namespace mapping, as follows.

    xmlns:ctl="clr-namespace:DetailControlExtension.Presentation.Controls;assembly=DetailControlExtension.Client"
    
  3. Add a resource dictionary element, as follows.

    <UserControl.Resources>
            <ResourceDictionary>
                <ctl:IsComputingVisibilityConverter x:Key="IsComputingVisibilityConverter" />
            </ResourceDictionary>
        </UserControl.Resources>
    
  4. Add a new element inside the ControlName.xaml file to show a “-” symbol when a bound computed property is being computed. This should be added inside the Grid element, after the TextBox element.

    <!-- 
            This is a sample to show an indicator to indicate a computed property is still being computed.  This overlaps the TextBox to prevent the user from making a decision based on an expired value.
            -->
            <Border BorderBrush="{StaticResource TextBoxBorderBrush}" 
                    Background="{StaticResource TextBoxBackgroundBrush}" 
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Visibility="{Binding IsComputed, Converter={StaticResource IsComputingVisibilityConverter}}">
                <TextBlock Foreground="{StaticResource TextBoxTextBrush}" Text="-" VerticalAlignment="Center" Margin="3,0" />
            </Border>
    

    While the property is being computed, the control will obtain the value from the DataBinding and set the visibility of the extra layer to Visible. That layer will overlay the original TextBox and show the computing state.

Prevent an Item from Being Deleted

When a detail control is placed inside a list, the delete key is handled by the list control. In most cases, you will not want the contents of the detail control to be deleted. To prevent this deletion, you want your control to handle the KeyUp event.

To handle the KeyUp event

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Client project, open the DetailControl.xaml file.

  2. Add the following code to the TextBox element, after the IsReadOnly line.

    KeyUp="DetailTextBox_KeyUp"
    
  3. Open the DetailControl.xaml.vb or DetailControl.xaml.cs file in the Controls folder of the DetailControlExtension.Client project.

  4. Add the following event handler.

    Private Sub DetailTextBox_KeyUp(sender As Object, e As KeyEventArgs) Handles DetailTextBox.KeyUp
    
                ' Handle the KeyUp message for the Delete key inside our TextBox, so it won't go to the list, and delete the current data.
                If e.Key = Key.Delete AndAlso Not Me.DetailTextBox.IsReadOnly Then
                    e.Handled = True
                End If
    
            End Sub
    
    private void DetailTextBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
            {
                // Handle the KeyUp message for the Delete key inside our TextBox, so it won't go to the list, and delete the current data.
                if (e.Key == System.Windows.Input.Key.Delete && !this.DetailTextBox.IsReadOnly)
                {
                    e.Handled = true;
                }
            }
    

Handle Keyboard Navigation in a DataGrid

LightSwitch supports enabling controls to behave differently when they are displayed in a DataGrid or similar container control. This may be required for performance—editable controls can be more expensive to display—and to achieve the correct tabbing and focus behavior. If a focusable control is displayed inside a DataGrid, a user will be able to set focus to both the DataGrid cell and also the underlying control when tabbing through the grid. This leads to two tab stops for each cell. It leads to additional problems when the DataGrid starts displaying cached row UI because of virtualization. The display mode of a control should therefore never be focusable or enable editing.

To prevent editing, implement a display mode version of your control which will not receive focus. You do not have to create another version of your control to do this; you only need disable the TabStop in the TextBox when your control is in display mode.

To handle keyboard navigation

  1. In Solution Explorer, in the Controls folder of the DetailControlExtension.Client project, open the DetailControl.xaml.vb or DetailControl.xaml.cs file.

  2. Add a new dependency property to the DetailControl class, as follows.

    Public Property AllowsTabStop As Boolean
                Get
                    Return MyBase.GetValue(DetailControl.AllowsTabStopProperty)
                End Get
                Set(value As Boolean)
                    MyBase.SetValue(DetailControl.AllowsTabStopProperty, value)
                End Set
            End Property
    
            ''' <summary>
            ''' The AllowsTabStop property is set to false when the control is inside a display mode cell.
            ''' </summary>
            Public Shared ReadOnly AllowsTabStopProperty As DependencyProperty =
                DependencyProperty.Register("AllowsTabStop", GetType(Boolean), GetType(DetailControl), New PropertyMetadata(True))
    
    /// <summary>
            /// The AllowsTabStop property is set to false when the control is inside a display mode cell.
            /// </summary>
            public bool AllowsTabStop
            {
                get { return (bool)GetValue(AllowsTabStopProperty); }
                set { SetValue(AllowsTabStopProperty, value); }
            }
    
            public static readonly DependencyProperty AllowsTabStopProperty =
                DependencyProperty.Register("AllowsTabStop", typeof(bool), typeof(DetailControl), new PropertyMetadata(true));
    
  3. Open the DetailControl.xaml file in the Controls folder of the DetailControlExtension.Client project.

  4. Add a binding to the TextBox element, just after the IsReadOnly line.

    IsTabStop="{Binding AllowsTabStop}"
    
  5. The next step is to create a display mode template for your control. The template should be added to the DetailControlFactory class that is generated by the control template in the DetailControl.xaml.vb or DetailControl.xaml.cs file in the Client project.

  6. Add a new private field as follows.

    Private DisplayModeDataTemplate As DataTemplate
    
    private DataTemplate displayModeDataTemplate;
    
  7. Add a new constant string for the template, as follows.

    Private Const DisplayModeControlTemplate As String = "<DataTemplate" & _
                " https://schemas.microsoft.com/winfx/2006/xaml/presentation""" & _
                " xmlns:x=""https://schemas.microsoft.com/winfx/2006/xaml""" & _
                " xmlns:ctl=""clr-namespace:DetailControlExtension.Presentation.Controls;assembly=DetailControlExtension.Client"">" & _
                "<ctl:DetailControl AllowsTabStop=""False"" />" & _
                "</DataTemplate>"
    
    private const string DisplayModeControlTemplate =
                "<DataTemplate" +
                " xmlns=\"https://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
                " xmlns:x=\"https://schemas.microsoft.com/winfx/2006/xaml\"" +
                " xmlns:ctl=\"clr-namespace:DetailControlExtension.Presentation.Controls;assembly=DetailControlExtension.Client\">" +
                "<ctl:DetailControl AllowsTabStop=\"False\" />" +
                "</DataTemplate>";
    

    You can see that the constant string resembles the regular template; the only difference is that AllowsTabStop is set to False in this case.

  8. Replace the existing GetDisplayModeDataTemplate method with the following code.

    Public Function GetDisplayModeDataTemplate(contentItem As IContentItem) As DataTemplate Implements IControlFactory.GetDisplayModeDataTemplate
    If Me.displayModeDataTemplate Is Nothing Then
    Me.displayModeDataTemplate = TryCast(XamlReader.Load(DetailControlFactory.DisplayModeControlTemplate), DataTemplate)
    End If
    Return Me.displayModeDataTemplate
    End Function
    
    public DataTemplate GetDisplayModeDataTemplate(IContentItem contentItem)
            {
                if (null == this.displayModeDataTemplate)
                {
                    this.displayModeDataTemplate = XamlReader.Load(DetailControlFactory.DisplayModeControlTemplate) as DataTemplate;
                }
                return this.displayModeDataTemplate;
            }
    

    Next, implement a new ISupportTextInput interface in the DetailControl class to make sure that the DataGrid can forward input characters to your control when it switches a display mode cell to edit mode. Without it, input may be lost before the DataGrid sets up the edit control.

  9. Add the IsupportTextInput interface to the DetailControl class definition.

    Partial Public Class DetailControl
            Inherits UserControl
            Implements IContentVisual
            Implements ISupportTextInput
    
    public partial class DetailControl : UserControl, IContentVisual, ISupportTextInput
    
  10. Add the implementation for ISupportTextInput to the DetailControl class.

    ' This method allows DataGrid to forward input to the TextBox correctly.
            Private Sub ISupportTextInput_SetText(text As String) Implements ISupportTextInput.SetText
                If Not Me.DetailTextBox.IsReadOnly Then
                    Me.DetailTextBox.Text = text
                    Me.DetailTextBox.SelectionStart = text.Length
                End If
            End Sub
    
    // This method allows DataGrid to forward input to the TextBox correctly.
            void ISupportTextInput.SetText(string text)
            {
                if (!this.DetailTextBox.IsReadOnly)
                {
                    this.DetailTextBox.Text = text;
                    this.DetailTextBox.SelectionStart = text.Length;
                }
            }
    

If you inspect the implementation of this class, you will see that it implements the regular DataTemplate and returns null in the GetDisplayModeDataTemplate method. If a display mode template is not defined, LightSwitch will always use the standard template.

Next Steps

This completes the detail control walkthrough; you should now have a fully functioning control extension that you can reuse in any LightSwitch project. This was just one example of a detail control; you might want to create a control that is significantly different in appearance or behavior. The same basic steps and principles apply to any detail control.

If you are going to distribute your control extension, there are a couple more steps to take. To make sure that the information displayed for your extension in the project designer and in Extension Manager are correct, update the properties for the VSIX package. For more information, see How to: Set VSIX Package Properties. In addition, there are several things to consider if you are going to distribute your extension publicly. For more information, see How to: Distribute a LightSwitch Extension.

See Also

Tasks

How to: Create a LightSwitch Control

How to: Distribute a LightSwitch Extension

How to: Set VSIX Package Properties

Concepts

Defining, Overriding, and Using LightSwitch Control Properties

Visual Studio LightSwitch 2011 Extensibility Toolkit