共用方式為


自訂 [屬性] 視窗

您可以在 Visual Studio 中,自訂特定領域語言 (DSL) 中屬性視窗的外觀和行為。 在 DSL 定義中,您會在每個網域類別上定義網域屬性。 根據預設,當您在圖表或模型總管中選取類別的執行個體時,每個網域屬性都會列在屬性視窗中。 這可讓您查看和編輯網域屬性的值,即使您尚未將其對應至圖表上的圖形欄位也一樣。

名稱、描述和類別

名稱和顯示名稱。 在網域屬性的定義中,屬性的 [顯示名稱] 是出現在屬性視窗中執行階段的名稱。 相較之下,當您撰寫程式碼來更新屬性時,會使用「名稱」。 「名稱」必須是正確的 CLR 英數字元名稱,但「顯示名稱」可以包含空格。

當您在 DSL 定義中設定屬性的「名稱」時,其「顯示名稱」會自動設定為「名稱」的複本。 如果您撰寫 Pascal 大小寫的名稱,例如 「FuelGauge」,「顯示名稱」會自動包含空格:「Fuel Gauge」。 不過,您可以將「顯示名稱」明確設定為另一個值。

描述。 網域屬性的描述會出現在兩個地方:

  • 當使用者選取屬性時,在屬性視窗底部。 您可以使用該描述向使用者說明屬性所代表的內容。

  • 在產生的程式碼中。 如果您使用文件設備來擷取 API 文件,則會在 API 中顯示為此屬性的描述。

類別。 類別是 [屬性] 視窗中的標題。

公開樣式功能

圖形元素的某些動態功能可以表示或公開為網域屬性。 使用者可以更新以這種方式公開的功能,而且程式碼可以更輕鬆地更新。

以滑鼠右鍵按一下 DSL 定義中的圖形類別,指向 [新增公開],然後選擇功能。

在圖形上,您可以公開 FillColorOutlineColorTextColorOutlineDashStyleOutlineThicknessFillGradientMode 屬性。 在連接器上,您可以公開 Color,TextColorDashStyleThickness 屬性。 在圖表上,您可以公開 FillColorTextColor 屬性。

當您的 DSL 使用者選取模型中的元素時,該元素的屬性會顯示在屬性視窗中。 不過,您也可以顯示指定相關元素的屬性。 如果您已定義一組一起運作的元素,這會很有用。 例如,您可以定義主要元素和選擇性外掛程式元素。 如果主要元素對應至圖形,而另一個元素未對應,則看到其所有屬性就如同在一個元素上一樣,很有用。

此效果稱為屬性轉送,而且會在數種情況下自動發生。 在其他情況下,您可以藉由定義網域類型描述元來達成屬性轉送。

預設屬性轉送案例

當使用者選取圖形或連接器,或 Explorer 中的元素時,下列屬性會顯示在 [屬性] 視窗中:

  • 在模型元素的網域類別上定義的網域屬性,包括基底類別中定義的網域屬性。 例外狀況是您已將 Is Browsable 設定為 False 的網域屬性。

  • 透過多重性為 0..1 之關聯性連結的元素名稱。 即使您尚未定義關聯性的連接器對應,這也會提供方便的方法,讓您查看選擇性連結的元素。

  • 以元素為目標之內嵌關聯性的網域屬性。 因為內嵌關聯性通常不會明確顯示,這可讓使用者查看其屬性。

  • 在選取的圖形或連接器上定義的網域屬性。

新增屬性轉送

若要轉送屬性,您可以定義網域類型描述元。 如果您在兩個網域類別之間有網域關聯性,您可以使用網域類型描述元,將第一個類別中的網域屬性設定為第二個網域類別中網域屬性的值。 例如,如果您在 Book 網域類別與 Author 網域類別之間有關聯性,您可以使用網域類型描述元,在使用者選取 Book 時,將 Book AuthorName 屬性顯示在 [屬性] 視窗中。

注意

屬性轉送只會影響使用者編輯模型時的 [屬性] 視窗。 它不會在接收類別上定義網域屬性。 如果您想要存取 DSL 定義或程式碼其他部分的轉送網域屬性,您必須存取轉送元素。

下列程序假設您已建立 DSL。 前幾個步驟摘要說明必要條件。

從另一個元素轉送屬性

  1. 建立包含至少兩個類別的特定領域語言工具解決方案,在此範例中稱為 BookAuthorBookAuthor 之間應該有某種關聯性。

    來源角色的多重性 (Book 端的角色) 應該是 0..1 或 1..1,因此每個 Book 都有一個 Author

  2. DSL Explorer 中,以滑鼠右鍵按一下 [Book] 網域類別,然後按一下 [新增 DomainTypeDescriptor]

    名稱為 [自訂屬性描述元的路徑] 的節點會出現在 [自訂類型描述元] 節點下方。

  3. 以滑鼠右鍵按一下 [自訂類型描述元] 節點,然後按一下 [新增 PropertyPath]

    新的屬性路徑會出現在 [自訂屬性描述元的路徑] 節點下。

  4. 選取新的屬性路徑,然後在 [屬性] 視窗中,將 [屬性路徑] 設定為適當模型元素的路徑。

    您可以按一下該屬性右邊的向下箭號,以編輯樹狀檢視中的路徑。 如需網域路徑的詳細資訊,請參閱網域路徑語法。 當您編輯它時,路徑應該類似於 BookReferencesAuthor.Author/!Author

  5. 將 [屬性] 設定為 AuthorName 網域屬性。

  6. 將 [顯示名稱] 設定為作者名稱

  7. 轉換所有範本,建置並執行 DSL。

  8. 在模型圖表中,建立書籍、作者,並使用參考關聯性連結它們。 選取書籍元素,並在 [屬性] 視窗中,除了書籍屬性以外,您應該看到作者名稱。 變更連結作者的名稱,或將書籍連結至不同的作者,並觀察書籍的作者名稱變更。

自訂屬性編輯器

屬性視窗會為每個網域屬性的類型提供適當的預設編輯體驗。 例如,針對列舉類型,使用者會看到下拉式清單,而針對數值屬性,使用者可以輸入數字。 這只適用於內建類型。 如果您指定外部類型,使用者將可以看到屬性的值,但無法編輯它。

不過,您可以指定下列編輯器和類型:

  1. 另一個搭配標準類型使用的編輯器。 例如,您可以指定字串屬性的檔案路徑編輯器。

  2. 網域屬性的外部類型,及其編輯器。

  3. .NET 編輯器,例如檔案路徑編輯器,或者您可以建立自己的自訂屬性編輯器。

    外部類型與字串等類型的轉換,其具有預設編輯器。

    在 DSL 中,外部類型是任何非簡單類型 (例如布林值或 Int32) 或 String 的類型。

定義具有外部類型的網域屬性

  1. 在 [方案總管] 中,在 Dsl 專案中,新增包含外部類型的組件 (DLL) 參考。

    組件可以是 .NET 元件,或您提供的組件。

  2. 除非您已經這麼做,否則請將類型新增至 [網域類型] 清單。

    1. 開啟 DslDefinition.dsl,然後在 [DSL 總管] 中,以滑鼠右鍵按一下根節點,然後按一下 [加入新的外部類型]

      新的項目會出現在 [網域類型] 節點下。

      警告

      功能表項目位於 DSL 根節點上,而非 [網域類型] 節點上。

    2. 在 [屬性] 視窗中,設定新類型的名稱和命名空間。

  3. 以一般方式將網域屬性新增至網域類別。

    在 [屬性] 視窗中,從 [類型] 欄位中的下拉式清單中選取外部類型。

    在這個階段,使用者可以檢視屬性的值,但無法加以編輯。 顯示的值是從 ToString() 函式取得。 您可以撰寫程式碼來設定屬性的值,例如在命令或規則中。

設定屬性編輯器

將 CLR 屬性新增至網域屬性,格式如下:

[System.ComponentModel.Editor (
   typeof(AnEditor),
   typeof(System.Drawing.Design.UITypeEditor))]

您可以使用 [屬性] 視窗中的 [自訂屬性] 項目,在屬性 (property) 上設定屬性 (attribute)。

AnEditor 的類型必須衍生自第二個參數中指定的類型。 第二個參數應該是 UITypeEditorComponentEditor。 如需詳細資訊,請參閱EditorAttribute

您可以指定自己的編輯器或 .NET 編輯器,例如 FileNameEditorImageEditor。 例如,使用下列程序,讓使用者可以在其中輸入檔案名稱的屬性。

定義檔案名稱網域屬性

  1. 使用您的 DSL 定義將網域屬性新增至網域類別。

  2. 選取新屬性。 在 [屬性] 視窗中的 [自訂屬性] 欄位中,輸入下列屬性。 若要輸入此屬性,請按一下省略符號 [...],然後分別輸入屬性名稱和參數:

    [System.ComponentModel.Editor (
       typeof(System.Windows.Forms.Design.FileNameEditor)
       , typeof(System.Drawing.Design.UITypeEditor))]
    
    
  3. 將網域屬性的類別保留為字串的預設設定。

  4. 若要測試編輯器,請確認使用者可以開啟檔案名稱編輯器來編輯您的網域屬性。

    1. 按 CTRL+F5 或 F5。 在偵錯解決方案中,開啟測試檔案。 建立網域類別的元素,並加以選取。

    2. 在 [屬性] 視窗中,選取網域屬性。 值欄位會顯示省略符號 [...]

    3. 按一下省略符號。 檔案對話方塊隨即出現。 選取檔案,並關閉對話方塊。 檔案路徑現在是網域屬性的值。

定義您自己的屬性編輯器

您可以定義自己的編輯器。 您可以這麼做,讓使用者編輯您已定義的類型,或以特殊方式編輯標準類型。 例如,您可以允許使用者輸入代表公式的字串。

您可以撰寫衍生自 UITypeEditor 的類別來定義編輯器。 您的類別必須覆寫:

  • EditValue,以與使用者互動,並更新屬性值。

  • GetEditStyle,以指定您的編輯器是否會開啟對話方塊或提供下拉式功能表。

您也可以提供將顯示在屬性方格中屬性值的圖形表示。 若要這樣做,請覆寫 GetPaintValueSupportedPaintValue。 如需詳細資訊,請參閱UITypeEditor

注意

Dsl 專案的個別程式碼檔案中,新增程式碼。

例如:

internal class TextFileNameEditor : System.Windows.Forms.Design.FileNameEditor
{
  protected override void InitializeDialog(System.Windows.Forms.OpenFileDialog openFileDialog)
  {
    base.InitializeDialog(openFileDialog);
    openFileDialog.Filter = "Text files(*.txt)|*.txt|All files (*.*)|*.*";
    openFileDialog.Title = "Select a text file";
  }
}

若要使用此編輯器,請將網域屬性的自訂屬性設定為:

[System.ComponentModel.Editor (
   typeof(MyNamespace.TextFileNameEditor)
   , typeof(System.Drawing.Design.UITypeEditor))]

如需詳細資訊,請參閱UITypeEditor

提供值的下拉式清單

您可以為使用者提供值的下拉式清單以供選擇。

注意

這項技術提供可在執行階段變更的值清單。 如果您想要提供不會變更的清單,請考慮改用列舉類型作為網域屬性的類型。

若要定義標準值清單,可在網域屬性中新增 CLR 屬性,其格式如下:

[System.ComponentModel.TypeConverter
(typeof(MyTypeConverter))]

定義衍生自 TypeConverter 的類別。 在 Dsl 專案的個別檔案中,新增程式碼。 例如:

/// <summary>
/// Type converter that provides a list of values
/// to be displayed in the property grid.
/// </summary>
/// <remarks>This type converter returns a list
/// of the names of all "ExampleElements" in the
/// current store.</remarks>
public class MyTypeConverter : System.ComponentModel.TypeConverter
{
  /// <summary>
  /// Return true to indicate that we return a list of values to choose from
  /// </summary>
  /// <param name="context"></param>
  public override bool GetStandardValuesSupported
    (System.ComponentModel.ITypeDescriptorContext context)
  {
    return true;
  }

  /// <summary>
  /// Returns true to indicate that the user has
  /// to select a value from the list
  /// </summary>
  /// <param name="context"></param>
  /// <returns>If we returned false, the user would
  /// be able to either select a value from
  /// the list or type in a value that is not in the list.</returns>
  public override bool GetStandardValuesExclusive
      (System.ComponentModel.ITypeDescriptorContext context)
  {
    return true;
  }

  /// <summary>
  /// Return a list of the values to display in the grid
  /// </summary>
  /// <param name="context"></param>
  /// <returns>A list of values the user can choose from</returns>
  public override StandardValuesCollection GetStandardValues
      (System.ComponentModel.ITypeDescriptorContext context)
  {
    // Try to get a store from the current context
    // "context.Instance"  returns the element(s) that
    // are currently selected i.e. whose values are being
    // shown in the property grid.
    // Note that the user could have selected multiple objects,
    // in which case context.Instance will be an array.
    Store store = GetStore(context.Instance);

    List<string> values = new List<string>();

    if (store != null)
    {
      values.AddRange(store.ElementDirectory
        .FindElements<ExampleElement>()
        .Select<ExampleElement, string>(e =>
      {
        return e.Name;
      }));
    }
    return new StandardValuesCollection(values);
  }

  /// <summary>
  /// Attempts to get to a store from the currently selected object(s)
  /// in the property grid.
  /// </summary>
  private Store GetStore(object gridSelection)
  {
    // We assume that "instance" will either be a single model element, or
    // an array of model elements (if multiple items are selected).

    ModelElement currentElement = null;

    object[] objects = gridSelection as object[];
    if (objects != null && objects.Length > 0)
    {
      currentElement = objects[0] as ModelElement;
    }
    else
    {
        currentElement = gridSelection as ModelElement;
    }

    return (currentElement == null) ? null : currentElement.Store;
  }

}