逐步解說:建立詳細資料控制項擴充功能

本逐步解說示範如何建立 Visual Studio LightSwitch的 詳細資料 功能。 LightSwitch 使用詳細資料控制項以顯示實體欄位。 與可以用於藉由顯示每個排列好的實體欄位,以顯示整個實體的 Group 控制項不同。 LightSwitch Summary 控制項是詳細資料控制項的範例。

若要建立詳細控制項的擴充功能,您必須執行下列工作:

  • 建立控制項擴功能的專案.

  • 更新在 LightSwitch 中繼資料檔的控制項定義。

  • 加入資源字串。

  • 實作詳細資料控制項。

  • 針對控制項以使用摘要屬性。

  • 使控制項可程式化。

  • 建立顯示屬性的自訂編輯器。

  • 處理控制項的 IsComputed 狀態。

  • 防止項目被刪除。

  • 在 DataGrid 中處理鍵盤巡覽。。

必要條件

  • Visual Studio 2013 Professional

  • Visual Studio 2013 SDK

  • Visual Studio 2013 的 LightSwitch 擴充性工具組

建立控制項擴充功能的專案

第一步就是建立專案並加入 [控制項] 範本。

建立擴充功能專案

  1. 在 Visual Studio的功能表列,選擇 [ 檔案], 新增專案

  2. 在 [新的專案] 對話方塊中,展開 [Visual Basic] 或 [Visual C#] 節點,然後展開 [LightSwitch] 節點,選取 [擴充性] 節點,然後選取 [LightSwitch 擴充程式庫] 範本。

  3. 在 [名稱] 欄位中,輸入 DetailControlExtension 做為擴充功能程式庫的名字。 這個名稱會出現在 LightSwitch [應用程式設計工具] 的 [擴充功能] 索引標籤。

  4. 選擇 [] 按鈕以建立包含方案,其包含七個擴充功能所需的專案。

選取擴充功能類型

  1. 在 [方案總管] 中,選擇 [DetailControlExtension.Lspkg] 專案。

  2. 在功能表列中,選擇 [專案]、[加入新項目]。

  3. 在 [加入新項目] 對話方塊中,選擇 [控制項]。

  4. 在 [名稱] 欄位中,輸入 DetailControl 做為擴充功能的名字。 這個名稱會出現在 LightSwitch 螢幕設計工具上 。

  5. 選擇 [確定] 按鈕。 檔案會加入至您方案中的數個專案。

更新控制項圖示。

這兩個影像檔案名為 [DetailControl.png] 已加入至您的方案,一個在 [DetailControlExtension.Client.Design] 專案的 [ControlImages] 資料夾,和其他在 [DetailControlExtension.Design] 專案的 [ControlImages] 資料夾。 檔案中的影像會顯示為圖示。 您可以使用其他影像取代預設的控制項影像。

修改圖示影像

  1. 在 [方案總管] 中,於 [DetailControl.png] 檔案的捷徑功能表中 [DetailControlExtension.Client.Design] 專案的 [ControlImages] 資料夾,請選取 [開啟檔案]。

  2. 在 [開啟檔案] 對話方塊中,選取 [繪製],然後選擇 [] 按鈕。

  3. 在繪製中變更影像;舉例來說,在變更色彩或添加圖形後,儲存檔案並傳回 Visual Studio。

  4. 選取 [DetailControl.png] 檔案,然後在功能表列上,選擇 [編輯] 後,按下 [複製]。

  5. 選取 [DetailControlExtension.Design] 專案的 [ControlImages] 資料夾,然後在功能表列上,選擇 [編輯] 後,按下 [貼上]。 在詢問您是否要取代檔案的訊息中,請選擇 [] 按鈕。

更新在 LightSwitch 中繼資料檔的控制項定義。

定義的中繼資料控制項就會出現在 [DetailControlExtension.Common] 專案的 .lsml 檔案中。 對於控制項的詳細資料,其重要元素為 SupportedContentItemKind、 DisplayName和 DisplayProperty。

SupportedContentItemKind 指示 LightSwitch 控制的型別;對於詳細資料控制項,請將 SupportedContentItemKind設為 Details。

DisplayName 屬性會提供螢幕設計工具中顯示的名稱。 它可以定義成 String,例如:"My Detail Control",或使用標記法,則為 $(DetailControl_DisplayName),在 [ModuleResources] 的 String 資源檔。

DisplayProperty 可讓開發人員可以在設計階段指定屬性該顯示哪個實體欄位。

更新控制項的中繼資料。

  1. 在 [方案總管] 中,於 [DetailControlExtension.Common] 專案的 [Controls] 資料夾中,開啟 [DetailControl.lsml] 檔案的捷徑功能表,並選擇 [開啟檔案]。

  2. 在 [開啟檔案] 對話方塊中,選取 [XML (文字) 編輯器],然後選擇 [] 按鈕。

  3. 將 SupportedContentItemKind 項目變更為 SupportedContentItemKind="Details"。

    這會告訴 LightSwitch 控制擴充功能是一個詳細資料控制項。

  4. 將 DisplayName 項目變更為 <DisplayName Value="$(DetailControl_DisplayName)" />。

    在此情況下,控制項會在稍後步驟建立資源檔中的字串資源,取得顯示名稱。

  5. 在 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>
    

    這個程式碼會呼叫 LightSwitch 顯示在屬性工作表上的 String 屬性。 DisplayName 和 Description 屬性重新定義為資源檔的 String 資源。 預設為 DefaultValueSource 的元素呼叫 LightSwitch 顯示空的 String 。

  6. 刪除 Control.SupportedDataTypes 區塊。 因為群組控制項無法直接顯示資料,此區塊是必要的。

  7. DetailControl.lsml 檔案的完整程式碼應該會與下列範例類似。

    <?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>
    

加入資源字串

在 [DetailControlExtension.Common] 專案的 [ModuleResources.resx] 檔案包含控制項所使用的資源。 您可以將字串資源加入至螢幕設計工具上控制項顯示的文字,在此案例中,這三個字串為 .lsml 檔案下所定義。

加入資源字串

  1. 在 [方案總管] 中,展開 [DetailControlExtension.Common] 專案的 [資源] 節點,然後開啟 [ModuleResources.resx] 檔案。

  2. 將下列的值加入到 [ModuleResources.resx] 檔案中。

    名稱

    註解

    DetailControl_DisplayName

    My Detail Control

    螢幕設計工具上控制項的顯示名稱。

    DisplayProperty_Description

    在應用程式中顯示的屬性。

    在螢幕設計工具上屬性的簡短描述字串。

    DisplayProperty_DisplayName

    顯示屬性

    螢幕設計工具的屬性名稱。

實作詳細資料控制項。

[DetailControl.xaml] 和 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔就會在 [簡介] 中, 包含 [DetailControlExtension.Client] 專案中的 [控制項] 資料夾的控制項實作。 範本會建立預設的實作,而您在之後會用自己的程式碼取代此實作。

Hh290136.collapse_all(zh-tw,VS.140).gif定義相依性屬性

相依性屬性可讓您在之後將控制項,從使用者控制項,轉換為有類似實作的 Silverlight 自訂控制項。透過主題擴充套件,這可讓控制項成為 restyled,並為控制項建立的擴充功能的慣用方法。 相依性屬性會由開發人員在設計階段時,繫結至實體屬性,並由控制項定義顯示哪些屬性。

定義相依性屬性

  1. 在 [方案總管] 中,在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔。

  2. 將下列 Imports 陳述式加入至 [DetailControl.xaml.vb] 檔案或 using 陳述式加入至 [DetailControl.xaml.cs] 檔案。

    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. 將 [DetailControl] 類別中的 EntityProperty 屬性,如下所示。

    '   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));
    

Hh290136.collapse_all(zh-tw,VS.140).gif建立使用者介面

接下來,在 [DetailControl.xaml] 檔案中,為控制項建立使用者介面。 程式碼會定義一個 DetailRoot 元素,並將 DataContext 設定為 DetailRoot。 它也會定義 TextBox控制項的具名 DetailTextBox 並將它的 Text 和 ReadOnly 繫結至您先前建立的 EntityProperty 相依性屬性。

建立控制項 UI

  1. 在 [方案總管] 中,在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml] 檔案。

  2. 將現有的 XAML 程式碼更換成下列程式碼。

    <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>
    

Hh290136.collapse_all(zh-tw,VS.140).gif繫結至 EntityProperty 屬性的資料。

接下來,您將加入程式碼,並取得 EntityProperty 屬性的值。使用目前實體和 DisplayProperty 控制項的屬性,得到計算後的值。目前實體在 LightSwitch 執行階段應用程式載入,因此, DisplayProperty 值由應用程式開發人員在螢幕設計工具或程式碼中設定。 若要取得這兩個值並監視或變更,您必須將這兩個相依性屬性, DisplayPropertyName 和 ContentItem,加入至 DetailControl 類別。

繫結屬性:

  1. 在 [方案總管] 中,在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔。

  2. 在 EntityProperty 程式碼區塊之後,加入以下相依性屬性的程式碼:

    ''' <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();
            }
    

    建立 ControlName 時,您也必須為 [DisplayPropertyName] 和 [ContentItem] 設定繫結。

  3. 在 InitializeComponent 呼叫之後,將下列程式碼加入至 DetailControl 建構函式中:

    ' 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]"));
    

    您也必須將 [SetContentDataBinding] 函式加入至類別。

  4. 將下列程式碼加入至 DetailControl 類別,放在 OnContentItemChanged 區塊之後:

    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)));
    
                        }
                    }
                }
            }
    

    詳細資料控制項的實作現在已完成,因此,您現在可以在 LightSwitch 中測試。

Hh290136.collapse_all(zh-tw,VS.140).gif測試詳細資料控制項。

您可以在 Visual Studio的實驗執行個體中,測試詳細資料控制項。 如果您尚未測試其他 LightSwitch 擴充性專案,您必須先啟用實驗執行個體。

啟用實驗執行個體

  1. 在 [方案總管] 中,選擇 [BusinessTypeExtension.Vsix] 專案。

  2. 選擇功能表列上的 [專案]、[BusinessTypeExtension.Vsix 屬性]。

  3. 在 [偵錯] 索引標籤的 [啟動動作] 底下,選取 [啟動外部程式]。

  4. 進入 Visual Studio 可執行檔, devenv.exe 的路徑。

    在 32 位元系統上,預設路徑是 C:\Program Files\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe;在 64 位元系統上,則是 :\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe。

  5. 在 [命令列引數] 欄位中,輸入 /rootsuffix Exp。

    注意事項注意事項

    所有後續的 LightSwitch 擴充性專案也會預設使用這個設定。

若要測試詳細資料控制項

  1. 在功能表列上,選擇 [偵錯]、[開始偵錯]。 Visual Studio 的實驗執行個體隨即開啟。

  2. 在實驗個體功能列表中,選擇 [檔案]、[新增]、[專案]。

  3. 在 [新增專案] 對話方塊中,展開 [Visual Basic] 或 [Visual C#] 節點,然後選擇 [LightSwitch] 節點,再選擇 [LightSwitch 桌面應用程式] 範本。

  4. 在 [名稱] 欄位中,輸入 DetailControlTest,然後選擇 [] 按鈕以建立測試專案。

  5. 在選擇功能表列上,選擇 [專案]、[DetailControlTest Properties]。

  6. 在專案設計工具中,請在 [擴充功能] 索引標籤上,選取 [DetailControlExtension] 核取方塊。

  7. 建立一個基本的 LightSwitch 應用程式,此應用程式的資料表包含數個 String 欄位,或連接到外部資料來源。

  8. 加入 [清單和詳細資料] 畫面,然後在螢幕設計工具上,將在第一個 [資料行配置] 項目下的 [摘要] 控制項,變更至 [My Detail Control]。

  9. 在 [屬性] 視窗中,選取 Display Property 屬性,然後在資料表輸入一個欄位的名稱。

  10. 在功能表列上,選擇 [偵錯]、[開始偵錯]。 檢視 [My Detail Control] 控制項在應用程式的行為,並確認其適當地顯示資料欄位。

針對控制項使用摘要屬性。

目前控制項的實作要求應用程式開發人員正確設定 DisplayProperty 。 否則不會顯示任何東西。 若要讓控制項更容易使用,您可以移除這項要求。 如果應用程式開發人員並不提供屬性,您應該自動提供實體型別的 Summary 屬性。

為完成這項工作,請建立 GetSummaryMethod 函式,加入數個方法和程式碼,以計算預設的 Summary 屬性。

建立 GetSummaryMethod 函式

  1. 在 [方案總管] 中,在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔。

  2. 將下列程式碼加入至 DetailControl 類別。

     ''' <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;
            }
    

    在此程式碼中,如果使用者在應用程式中定義 Summary 屬性,則此定義會從應用程式的中繼資料中擷取。 如果它不是由應用程式開發人員所明確設定,則您仍然會遺漏邏輯計算所預設的摘要屬性。 在下一步,您會加入下列的邏輯。

  3. 定位 DetailControl 類別中的 SetContentDataBinding() 方法。 以下列內容取代 If String.IsNullOrEmpty (Visual Basic) 或 if (string.IsNullOrEmpty (C#) 區塊:

    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));
                        }
    

    接下來,您會將一些可用於控制項和(稍後會加入的)設計工具擴充功能的公用程式方法添加至其中。若要共用不同專案之間的程式碼,請將它加入至 [共用] 專案。

加入公用程式方法

  1. 在 [方案總管] 中,選擇 [DetailControlExtension.Common] 專案。

  2. 在功能表列上,選擇 [專案]、[新增類別]。

  3. 在 [加入新項目] 對話方塊中選擇 [類別]。

  4. 在 [名稱] 欄位中,輸入 ModelUtilities 做為名稱。您的類別檔案,然後選擇 [] 按鈕。

  5. 以下列程式碼取代類別檔案的內容。

    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;
            }
        }
    }
    

    接著,從 [用戶端] 專案將參考加入至 [共用] 專案。 [用戶端] 專案的 [共用] 專案可讓您使用定義的公用程式函式。

若要加入參考

  1. 在 [方案總管] 中,開啟 [DetailControl.Client] 專案的捷徑功能表,然後選擇 [加入參考]。

  2. 在 [加入參考] 對話方塊中,選取 [專案] 索引標籤,並將參考加入至 [DetailControl.Common] 專案。

此時,您可以再次測試控制項並觀察行為的變化。 如果未指定 DisplayProperty中的值,則驗證顯示的摘要屬性。

這個逐步解說其餘部分,包括將任意功能加入至控制項的技術。

使控制項可程式化。

LightSwitch 中所有內建的控制項皆可程式化。 為了讓開發人員能夠存取應用程式碼中的控制項,您會為您的控制項實作 IContentVisual 介面。

實作 IContentVisual 介面

  1. 在 [方案總管] 中,在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔。

  2. 更新類別定義並實作 IContentVisual。

    Partial Public Class DetailControl
            Inherits UserControl
            Implements IContentVisual
    
    public partial class DetailControl : UserControl, IContentVisual
    
  3. 加入實作至類別中。

    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()
            {
            }
    

建立 DisplayProperty 的自訂編輯器。

到目前為止,您的詳細資料控制項需要應用程式開發人員,在 [顯示屬性] 文字方塊內的 [屬性] 視窗中,輸入有效實體欄位名稱。 更好的經驗是以任何有效的選項方式提供下拉式清單。 您可以以此方式建立自訂屬性編輯器。

對於控制項擴充功能,需要兩個屬性編輯器;在不同平台上建置 LightSwitch 螢幕設計工具和執行階段螢幕編輯器。 在螢幕設計工具上工作需要 Windows Presentation Foundation (WPF) 編輯器。 在自訂模式工作則需要 Silverlight 架構編輯器。

Hh290136.collapse_all(zh-tw,VS.140).gif建立 WPF 屬性編輯器。

WPF 屬性編輯器對螢幕設計工具提供存取屬性的權限。 使用 LightSwitch 擴充程式庫範本建立擴充功能專案時, [設計] 專案會加入至方案中。 這個專案會產生完整的 .NET Framework組件,因此,這是您將建立 WPF 屬性編輯器的地方。

第一個步驟是加入參考至 LightSwitch Designer 命名空間以及方案的 [共用] 專案中。

若要加入參考

  1. 在 [方案總管] 中,開啟 [DetailControl.Design] 專案的捷徑功能表,然後選擇 [加入參考]。

  2. 在 [加入參考]對話方塊中,加入參考至 [Microsoft.LightSwitch.Design.Designer.dll] 。

    您可以在 Visual Studio [IDE] 資料夾底下找到 [PrivateAssembly] 資料夾中的組件。

  3. 開啟 [DetailControl.Design] 節點的捷徑功能表,選擇 [加入現有項目]。

  4. 選擇方案中的 [共用] 專案,然後選取 [ModelUtilities] 檔案。

  5. 展開 [加入] 按鈕的下拉式清單,然後選取 [加入為連結]。

下一步是建立用於編輯屬性的 WPF 控制項。

建立 WPF 控制項。

  1. 在 [方案總管],開啟 DetailControl.Design 專案的捷徑功能表,選取 [加入資料夾]。 將新的資料夾命名為 Editors。

  2. 在 [Editors] 資料夾中,開啟捷徑功能表並選擇 [加入新項目]。

  3. 從 [加入新項目] 對話方塊中,展開 WPF 節點,並選擇 [User Control (WPF)]。

  4. 在 [名稱] 欄位中輸入 EntityPropertyPicker,然後按一下 [加入]。

    [EntityPropertyPicker.xaml] 檔案和 [EntityPropertyPicker.xaml.vb] 或 [EntityPropertyPicker.xaml.cs] 檔,會加入至 [Editors] 資料夾。

  5. 在 [Editors] 資料夾中,開啟捷徑功能表並選擇 [加入新項目]。

  6. 在 [加入新項目。] 對話方塊中,展開 [程式碼] 節點並選取 [類別]。

  7. 在 [名稱] 欄位中輸入 EntityPropertyPickerEditor,然後按一下 [加入]。

  8. 開啟 EntityPropertyPickerEditor 類別檔案,並將內容取為為下列程式碼:

    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
        }
    }
    

    這個程式碼會實作一個元件,由根據其在 PropertyValueEditorName 屬性中命名的 LightSwitch 設計工具所載入。 當 LightSwitch 設計工具顯示標記為用於相同名稱編輯器的屬性時,會與 DataTemplate 建立 WPF 控制項,並將它裝載在屬性工作表。控制項的 DataContext 會設定為 IBindablePropertyEntry 物件,讓控制項可以取得或設定屬性的值。

    下一步是變更 .lsml 檔案的控制項屬性中繼資料副檔名。

  9. 在 [共用] 專案中,開啟 [DetailControl.lsml] 檔案。

  10. 將 UIEditorID 屬性設定為 ControlProperty 項目,如下所示。

    <ControlProperty Name="DisplayProperty"
                           PropertyType=":String"
                           CategoryName="Appearance"
                           UIEditorId="DetailControlExtension:EntityPropertyPicker"
                           EditorVisibility="PropertySheet">
    
  11. 在 [設計] 專案中,開啟 [EntityPropertyPicker.xaml] 檔案,然後以下列程式碼取代預設的程式碼實作控制項。

    <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>
    

    XAML 程式碼會加入 Label 並顯示屬性名稱,以及 ComboBox 來顯示目前的值。接下來,您會加入一些程式碼以取得 ComboBox的清單。 為完成此工作,您必須知道繫結至控制項的正確的實體類型。

    在螢幕設計工具中,屬性的內容是由 IPropertyValue.ModelItem所公開。 針對控制項屬性, ModelItem 永遠都是使用 IContentItemDefinition 控制項的執行個體。 所以,單一屬性,其為 IContentItemDefinition.DataType,定義何種實體類型繫結至控制項。 對於 ComboBox 會需要在該實體類型所有屬性名稱的清單。 這份清單可以藉由建立轉換器所產生。

  12. 關閉 [EntityPropertyPicker.xaml ] 並儲存檔案。

然後定義 Editors 命名空間,並加入程式碼以初始化在程式碼後置檔案中的編輯器。

若要加入程式碼

  1. 展開 [EntityPropertyPicker.xaml ] 節點,開啟 [EntityPropertyPicker.xaml.vb] 或 [EntityPropertyPicker.xaml.cs] 檔案,並以下列內容取代之。

    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. 關閉 [EntityPropertyPicker.xaml.vb] 或 [EntityPropertyPicker.xaml.cs] ,並儲存檔案。

接下來,加入其他公用程式方法以取得實體屬性的集合。

加入公用程式方法

  1. 在 [共用] 專案中,開啟 [ModelUtilities] 類別檔案。

  2. 將新方法加入至類別中。

    ''' <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>();
            }
    

    這個方法會擷取可以表示為,資料型別內包含實體類型的字串屬性的集合。

接下來,建立用於實體中的 GetAllTextProperties 方法,以取得的所有屬性名稱的型別轉換子,以便在下拉式方塊中顯示。

建立 GetTextPropertiesConverter

  1. 在 [設計] 專案的 [Editors] 資料夾中,開啟捷徑功能表並選擇 [加入新項目。]。

  2. 在 [加入新項目。] 對話方塊中,展開 [程式碼] 節點,並選取 [類別]。

  3. 在 [名稱] 欄位中,輸入 GetTextPropertiesConverter,然後按一下 [加入]。

  4. 開啟 GetTextPropertiesConverter 類別檔案,並加入下列程式碼。

    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();
            }
        }
    }
    

    此轉換子使用可以從 [IPropertyValue] 存取的 IContentItemDefinition,並傳回有效的屬性名稱,而空字串集合代表屬性的預設值,表示沒有選取。

  5. 在 [Editors] 資料夾中,開啟 [EntityPropertyPicker.xaml] 檔案。

  6. 加入下列命名空間應至控制項。

    xmlns:e="clr-namespace:DetailControlExtension.Editors"
    
  7. 加入資源以建立新的轉換子

    <UserControl.Resources>
            <e:GetTextPropertiesConverter x:Key="GetTextPropertiesConverter" />
        </UserControl.Resources>
    
    注意事項注意事項

    此時您可能會注意到設計工具的錯誤。當您建立 [用戶端] 專案時,此錯誤會被重新解析。

  8. 藉由將 ItemsSource 屬性更新為新的 Converter 的 ComboBox 項目:

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

接下來,建立另一個轉換子並設定屬性名稱。

建立 GetTextPropertiesConverter

  1. 在 [設計] 專案的 [Editors] 資料夾中,開啟捷徑功能表並選擇 [加入新項目。]。

  2. 在 [加入新項目。] 對話方塊中,展開 [程式碼] 節點,並選取 [類別]。

  3. 在 [名稱] 欄位中輸入 DisplayNameConverter,然後按一下 [加入]。

  4. 開啟 DisplayNameConverter 類別檔案,並加入下列程式碼。

    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();
            }
        }
    }
    

    這個程式碼格式的字串「:」標記法,即為「Microsoft.LightSwitch:」的捷徑。

  5. 在 [Editors] 資料夾中,開啟 [EntityPropertyPicker.xaml] 檔案。

  6. 加入資源以建立新的轉換子

    <UserControl.Resources>
            <e:GetTextPropertiesConverter x:Key="GetTextPropertiesConverter" />
            <e:DisplayNameConverter x:Key="DisplayNameConverter" />
        </UserControl.Resources>
    
  7. 在功能表列上,選擇 [建置]、[DetailControlExtension.Design]。

  8. 將轉換器區塊加入至 TextBlock 項目。

    <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"/>
    

接著,當未選取任何實體欄位時,加入其他轉換器以顯示 ComboBox 中,使用者易記的預設文字。

建立 PropertyNameToDisplayNameConverter

  1. 在 [設計] 專案的 [Resources] 資料夾中,開啟捷徑功能表並選擇 [加入新項目。]。

  2. 在 [加入新項目] 對話方塊中,展開 [概觀] 節點,然後選取 [資源檔]。

  3. 在 [名稱] 欄位中輸入 DesignResources,然後按一下 [加入]。

  4. 在 [DesignResources.resx] 檔案中,加入名為「DefaultProperty_DisplayName」的字串,並為它指派 <Summary>的值。

    如果值不是由應用程式開發人員所指定,則資源字串會由轉換器所使用。

  5. 在 [設計] 專案的 [Editors] 資料夾中,開啟捷徑功能表並選擇 [加入新項目。]。

  6. 在 [加入新項目。] 對話方塊中,展開 [程式碼] 節點並選取 [類別]。

  7. 在 [名稱] 欄位中輸入 PropertyNameToDisplayNameConverter,然後按一下 [加入]。

  8. PropertyNameToDisplayNameConverter 類別檔案中,加入下列程式碼:

    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();
            }
        }
    }
    

    這段程式碼會檢查 ComboBox 的值是否為 null String,若是,則以 DefaultProperty_DisplayName 資源字串取代之。

  9. 在 [Editors] 資料夾中,開啟 [EntityPropertyPicker.xaml] 檔案。

  10. 加入資源以建立新的轉換器,如下所示。

    <UserControl.Resources>
            <e:GetTextPropertiesConverter x:Key="GetTextPropertiesConverter" />
            <e:DisplayNameConverter x:Key="DisplayNameConverter" />
            <e:PropertyNameToDisplayNameConverter x:Key="PropertyNameToDisplayNameConverter" />    
    </UserControl.Resources>
    
  11. 將 ItemTemplate 區塊加入至 ComboBox 項目中,如下所示。

    <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>
    

屬性編輯器現在已經完成了。 您可以在 Visual Studio 的實驗執行個體中進行測試,並注意到屬性編輯器顯示的實體欄位清單。

Hh290136.collapse_all(zh-tw,VS.140).gif建立 Silverlight 屬性編輯器

如果您嘗試在執行階段螢幕設計工具中設定 DisplayProperty,您會注意到屬性工作表仍然會為屬性顯示 TextBox 。到目前為止您只建立 WPF 屬性編輯器。 執行階段螢幕設計工具是以 Silverlight 為基礎。 因此,它無法辨認 WPF 編輯器;您必須建立 Silverlight 的對等用法。

在方案中的 [DetailControlExtension.Client.Design] 專案是您為執行階段螢幕設計工具撰寫程式碼的地方。 您可以共享加入至 [DetailControlExtension.Design] 專案中的某些程式碼;Silverlight 編輯器的實作類似於 WPF 編輯器。

就像 WPF 編輯器,第一個步驟是加入 DefaultProperty_DisplayName 資源。

加入資源

  1. 在 [DetailControlExtension.Client.Design] 專案的 [Resources] 資料夾中,開啟捷徑功能表並選擇 [加入新項目]。

  2. 在 [加入新項目] 對話方塊中,展開 [概觀] 節點,然後選取 [資源檔]。

  3. 在 [名稱] 欄位中輸入 DesignResources,然後按一下 [加入]。

  4. 在 [DesignResources.resx] 檔案中,加入名為「DefaultProperty_DisplayName」的字串,並為它指派 <Summary>的值。

    如果值不是由應用程式開發人員所指定,則資源字串會由轉換器所使用。

接著,連結至三個轉換器和您之前建立的公用程式檔案,並將程式碼共享至 [DetailControlExtension.Client.Design] 專案。

共用轉換器程式碼

  1. 在 [方案總管],開啟 DetailControl.Client.Design 專案的捷徑功能表,選取 [加入資料夾]。 將新的資料夾命名為 Editors。

  2. 開啟 [DetailControl.Client.Design] 節點的捷徑功能表,選擇 [加入現有項目]。

  3. 選擇方案中的 [共用] 專案,然後選擇 [ModelUtilities] 檔案。

  4. 展開 [加入] 按鈕的下拉式清單,並選取 [加入為連結]。

  5. 選取 [Editors] 節點,開啟捷徑功能表,並選擇 [加入現有項目]。

  6. 在 [加入現有項目] 對話方塊中,找出 [DetailControl.Client.Design] 專案中的 [Editors] 資料夾,然後選擇 [DisplayNameConverter]、 [GetTextPropertiesConverter] 和 [PropertyNameToDisplayNameConverter] 檔案。

  7. 展開 [加入] 按鈕的下拉式清單,然後選取 [加入為連結]。

接下來,建立屬性編輯器的 Silverlight 版本。 控制項的實作類似於 WPF 的版本。 其中差異為控制項的 DataContext 是 IPropertyEntry。 因此,繫結關係的路徑稍有不同。

建立 Silverlight 控制項

  1. 在 [方案總管] 中,[DetailControl.Client.Design] 專案下,開啟 [Editors] 節點的捷徑功能表,並選擇 [加入新項目。]。

  2. 在 [加入新項目] 對話方塊中,展開 [Silverlight] 節點,並選擇 [Silverlight 使用者控制項]。

  3. 在 [名稱] 欄位中輸入 ClientEntityPropertyPicker,然後按一下 [加入]。

    [EntityPropertyPicker.xaml] 檔案和 [EntityPropertyPicker.xaml.vb] 或 [EntityPropertyPicker.xaml.cs] 檔,會加入至 [Editors] 資料夾中。

  4. 使用下列控制項的實作取代現有的 XAML。

    <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. 關閉 [ClientEntityPropertyPicker.xaml] 並儲存檔案。

  6. 在 [Editors] 資料夾中,開啟捷徑功能表並選擇 [加入新項目]。

  7. 在 [加入新項目。] 對話方塊中,展開 [程式碼] 節點,並選取 [類別]。

  8. 在 [名稱] 欄位中輸入 ClientEntityPropertyPickerEditor,然後按一下 [加入]。

  9. 將下列程式碼加入至類別中。

    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
        }
    }
    

    同樣地,這段程式碼類似您在 WPF 專案中加入的程式碼。

  10. 展開 [ClientEntityPropertyPicker.xaml ] 節點,開啟 [ClientEntityPropertyPicker.xaml.vb] 或 [ClientEntityPropertyPicker.xaml.cs] 檔案,並以下列內容取代之。

    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. 關閉 [ClientEntityPropertyPicker.xaml.vb] 或 [ClientEntityPropertyPicker.xaml.cs] ,並儲存檔案。

這步驟將完成屬性編輯器的建立。 您現在可以測試控制項,並觀察執行階段螢幕設計工具中的更新行為。

處理 Control 中的 IsComputed Status。

當控制項的 DisplayProperty 屬性為計算資料欄位,在計算屬性之前。控制項會顯示屬性的舊值。 這不是 LightSwitch 控制所要的樣式。LightSwitch 控制項通常表示計算的值仍會繼續計算,讓使用者不會根據舊值來做決定。

對於控制項,這個值通常會直接由裝載控制項的 ContentItemPresenter 處理。因為有詳細資料控制項中有多個繫結關係路徑,您必須撰寫一些程式碼以處理這些狀態。 若要支援這項作業,您必須先將新相依性屬性加入至 [用戶端] 專案的 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔。

加入相依性屬性。

  1. 在 [方案總管] 中,在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔。

  2. 將下列程式碼加入至 DetailControl 類別:

    ''' <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));
    

    接下來,加入一些程式碼至 SetContentDataBinding 方法中以繫結屬性。

  3. 將下列程式碼加入至 SetContentDataBinding()方法的開頭,並重新設定繫結關係。

    MyBase.ClearValue(DetailControl.IsComputedProperty)
    
    this.ClearValue(IsComputedProperty);
    
  4. 接著將下列方法的程式碼,加入至主體中設定 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)));
    

接下來,加入轉換器以輔助建立必要的 UI。

新增轉換器

  1. 在 [方案總管] 中,開啟 [DetailControl.Client] 專案下 [Presentation/Controls] 資料夾的捷徑功能表,並選擇 [加入新項目]。

  2. 在 [加入新項目。] 對話方塊中,展開 [程式碼] 節點,並選取 [類別]。

  3. 在 [名稱] 欄位中輸入 IsComputingVisibilityConverter,然後按一下 [加入]。

  4. 將下列程式碼加入至 IsComputingVisibilityConverter 類別。

    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();
            }
        }
    }
    

然後,定義 [DetailControl.xaml] 檔案中的使用者介面。

定義使用者介面

  1. 在 [方案總管] 中,開啟 [MySmartLayoutExtension.Client] 專案的 [DetailControl.xaml] 檔案。

  2. 新增命名空間對應,如下所示。

    xmlns:ctl="clr-namespace:DetailControlExtension.Presentation.Controls;assembly=DetailControlExtension.Client"
    
  3. 新增資源字典項目,如下所示。

    <UserControl.Resources>
            <ResourceDictionary>
                <ctl:IsComputingVisibilityConverter x:Key="IsComputingVisibilityConverter" />
            </ResourceDictionary>
        </UserControl.Resources>
    
  4. 當繫結計算的屬性正在計算時,請在 [ControlName.xaml] 檔案內,建立新項目以顯示「-」符號。 應該將此加入至 Grid 在項目中、TextBox 項目之後。

    <!-- 
            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>
    

    當屬性正在計算時,控制項會從 DataBinding 取得值,並將額外圖層的可視性設定為 Visible。該圖層會與原始的 TextBox 重疊,並顯示計算狀態。

防止項目被刪除。

當詳細資料控制項准於清單內時,刪除 Key 主要由清單控制項處理。 在大部分的情況下,您並不希望刪除詳細資料控制項的內容。 若要防止刪除,請使用控制項處理 KeyUp 事件。

處理 KeyUp 事件。

  1. 在 [方案總管] 中,在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml] 檔案。

  2. 將下列程式碼加入至 TextBox 項目中、IsReadOnly 行數之後。

    KeyUp="DetailTextBox_KeyUp"
    
  3. 在 [DetailControlExtension.Client] 專案中,開啟 [控制項] 資料夾中的 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔。

  4. 加入下列事件處理常式:

    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;
                }
            }
    

在 DataGrid 中處理 Keyboard Navigation。

當控制項在 DataGrid 或類似的容器控制項中表現時,LightSwitch 可讓控制項有不同的行為。 這可能需要較耗費資源的可編輯控制項顯示,並達成正確定位和焦點行為 如果可設定焦點的控制項會顯示在 DataGrid中,使用者可以將焦點設定在 DataGrid 儲存格,和透過方格索引中的基礎控制項。 這會導致兩個索引為每個儲存格定位。當 DataGrid 開始顯示快取的資料列時,因為虛擬化的緣故,會造成其他的問題。 因此控制項的顯示模式不應該為可設定焦點或可編輯。

若要防止編輯,請實作不會接收焦點控制項的顯示模式版本。 您並不需要建立控制項的其他版本進行這項作業,當控制項處於顯示模式時,您只需要在 TextBox 中,停用 TabStop。

處理鍵盤巡覽。

  1. 在 [方案總管] 中,在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔。

  2. 加入新的相依性屬性至 DetailControl 類別中,如下所示。

    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. 在 [DetailControlExtension.Client] 專案的 [控制項] 資料夾中,開啟 [DetailControl.xaml] 檔案。

  4. 在 IsReadOnly 這一行之後,新增繫結至 TextBox 項目。

    IsTabStop="{Binding AllowsTabStop}"
    
  5. 下一個步驟是建立控制項的顯示模式範本。 在 [用戶端] 專案中,範本應加入至由 [DetailControl.xaml.vb] 或 [DetailControl.xaml.cs] 檔案控制項樣板產生的 DetailControlFactory 類別中。

  6. 加入新的私用欄位,如下所示。

    Private DisplayModeDataTemplate As DataTemplate
    
    private DataTemplate displayModeDataTemplate;
    
  7. 在範本中加入新常數字串,如下所示。

    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>";
    

    您可以看到常數字串類似於一般樣板;唯一的差異在於 AllowsTabStop 在這個案例中是被設定為 False 。

  8. 以下列程式碼取代現有的 GetDisplayModeDataTemplate 方法。

    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;
            }
    

    接著,在 DetailControl 類別中。實作新的 ISupportTextInput 介面,以確保切換至顯示模式儲存格編輯模式, DataGrid 可以轉接輸入字元至控制項中。 若未如上設定,則在 [DataGrid] 設定編輯控制項之前,輸入項目可能會遺失。

  9. 將 [IsupportTextInput] 加入至介面 DetailControl 類別定義。

    Partial Public Class DetailControl
            Inherits UserControl
            Implements IContentVisual
            Implements ISupportTextInput
    
    public partial class DetailControl : UserControl, IContentVisual, ISupportTextInput
    
  10. 加入 ISupportTextInput to the DetailControl 的實作至類別中。

    ' 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;
                }
            }
    

如果核取這個類別的實作,您會看到它實作這個一般的 DataTemplate ,並傳回在 GetDisplayModeDataTemplate 方法為 null。如果未定義顯示模式範本, LightSwitch 會使用標準範本。

後續步驟

現在完成了詳細資料控制項逐步解說;您應該可以在任何的 LightSwitch 專案中,重複使用控制項的擴充功能。 這是詳細資料控制項的一個例子;您可以建立稍微不同的控制項外觀或行為。 這些基本步驟及原則適用於所有詳細資料控制項。

如果您要發佈控制項擴充功能,這裡有一些可採取的步驟。 若要確定您的擴充功能所顯示的資訊在 [專案設計工具] 和 [增益集管理員] 是正確的,您必須更新 VSIX 套件的屬性。 如需詳細資訊,請參閱如何:設定 VSIX 套件屬性。 此外,在您公開發佈您的擴充功能時,請考慮以下幾點事項。 如需詳細資訊,請參閱如何:散發 LightSwitch 擴充功能

請參閱

工作

如何:建立 LightSwitch 控制項

如何:散發 LightSwitch 擴充功能

如何:設定 VSIX 套件屬性

概念

定義、覆寫和使用 LightSwitch 控制項屬性

Visual Studio 2013 的 LightSwitch 擴充性工具組