共用方式為


逐步解說:使用專案範本建立網站欄專案項目 (第 2 部分)

當您在 Visual Studio 中定義自訂類型的 SharePoint 專案項目,並為其建立與專案範本之間的關聯後,您也可以提供該範本專用的精靈。 您可以使用精靈,在使用者利用您的範本建立包含該專案項目的新專案時,向使用者收集資訊。 您收集到的資訊可用來初始化專案項目。

在本逐步解說中,您將會在逐步解說:使用專案範本建立網站欄專案項目 (第 1 部分)示範的網站欄專案範本中加入精靈。 當使用者建立網站欄專案時,精靈會收集網站欄的相關資訊 (例如其基底型別和群組),並將此資訊加入至新專案中的 Elements.xml 檔案。

本逐步解說將示範下列工作:

  • 建立與專案範本有關聯之自訂 SharePoint 專案項目類型的精靈。

  • 為 Visual Studio 2010 中的 SharePoint 專案定義類似於內建精靈的自訂精靈 UI。

  • 建立兩個「SharePoint 命令」(SharePoint Command),用來在精靈執行時呼叫本機 SharePoint 網站。 SharePoint 命令是 Visual Studio 擴充功能可用來在 SharePoint 伺服器物件模型中呼叫 API 的方法。 如需詳細資訊,請參閱呼叫 SharePoint 物件模型

  • 使用可取代的參數,以您在精靈中收集到的資料初始化 SharePoint 專案檔。

  • 在每個新的網站欄專案執行個體中建立新的 .snk 檔案。 這個檔案是用來簽署專案輸出,讓 SharePoint 方案組件可以部署到全域組件快取。

  • 對精靈進行偵錯和測試。

注意事項注意事項

您可以從下列位置取得本逐步解說所含之完成的專案、程式碼和其他檔案:https://go.microsoft.com/fwlink/?LinkId=191369 (英文)。

必要條件

若要執行本逐步解說,您必須先完成逐步解說:使用專案範本建立網站欄專案項目 (第 1 部分),以建立 SiteColumnProjectItem 方案。

此外,您的開發電腦上必須要有下列元件,才能完成此逐步解說:

了解下列概念有助於完成此逐步解說 (但非必要):

了解精靈元件

本逐步解說中示範的精靈包含數個元件。 下表說明這些元件。

元件

描述

精靈實作

這是名為 SiteColumnProjectWizard 的類別,其實作 IWizard 介面。 這個介面會定義 Visual Studio 在精靈啟動和完成時呼叫的方法,以及在精靈執行時的某些時候呼叫的方法。

精靈 UI

這是名為 WizardWindow 的 WPF 視窗。 這個視窗包含兩個使用者控制項,分別名為 Page1 和 Page2。 這兩個使用者控制項代表精靈的兩個頁面。

在本逐步解說中,精靈實作的 RunStarted 方法會顯示精靈 UI。

精靈資料模型

這是名為 SiteColumnWizardModel 的中繼類別,其提供精靈 UI 與精靈實作之間的階層。 此範例會使用這個類別來協助互相擷取精靈實作與精靈 UI;這個類別不是所有精靈的必要元件。

在本逐步解說中,精靈實作顯示精靈 UI 時會將 SiteColumnWizardModel 物件傳遞到精靈視窗。 精靈 UI 會使用這個物件的方法,儲存 UI 中的控制項值以及執行像是驗證輸入網站 URL 是否有效的工作。 在使用者完成精靈之後,精靈實作會使用 SiteColumnWizardModel 物件判斷 UI 的最終狀態。

專案簽署管理員

這是名為 ProjectSigningManager 的 Helper 類別,其由精靈實作用來在每個新的專案執行個體中建立新的 key.snk 檔案。

SharePoint 命令

這些是精靈資料模型用來在精靈執行時呼叫本機 SharePoint 網站的方法。 因為 SharePoint 命令必須以 .NET Framework 3.5 為目標,這些命令都是在與其餘的精靈程式碼不同的組件中實作。

建立專案

為了完成本逐步解說,您必須在逐步解說:使用專案範本建立網站欄專案項目 (第 1 部分) 建立的 SiteColumnProjectItem 方案中加入數個專案:

  • WPF 專案。 您將會在這個專案中實作 IWizard 介面並定義精靈 UI。

  • 定義 SharePoint 命令的類別庫專案。 這個專案必須以 .NET Framework 3.5 為目標。

從建立這些專案開始進行此逐步解說。

若要建立 WPF 專案

  1. 在 Visual Studio 中開啟 SiteColumnProjectItem 方案。

  2. 在 [方案總管] 中,以滑鼠右鍵按一下方案節點,指向 [加入],然後按一下 [新增專案]。

    注意事項注意事項

    在 Visual Basic 專案中,只有在已選取選項對話方塊、專案和方案、一般中的 [永遠顯示方案] 核取方塊時,才會出現方案節點。

  3. 在 [加入新的專案] 對話方塊中展開 [Visual C#] 或 [Visual Basic] 節點,然後按一下 [Windows]。

  4. 在 [加入新的專案] 對話方塊頂端的下拉式方塊中,確定已選取 [.NET Framework 4]。

  5. 選取 [WPF 使用者控制項程式庫] 專案範本。

  6. 在 [名稱] 方塊中,輸入 ProjectTemplateWizard。

  7. 按一下 [確定]。

    Visual Studio 會將 [ProjectTemplateWizard] 專案加入至方案。

  8. 從專案中刪除 UserControl1.xaml 檔案。

若要建立 SharePoint 命令專案

  1. 在 [方案總管] 中,以滑鼠右鍵按一下方案節點,指向 [加入],然後按一下 [新增專案]。

  2. 在 [加入新的專案] 對話方塊中展開 [Visual C#] 或 [Visual Basic],然後按一下 [Windows]。

  3. 按一下 [類別庫] 專案範本。

  4. 在對話方塊上方的下拉式方塊中,選取 [.NET Framework 3.5]。

  5. 在 [名稱] 方塊中,輸入 SharePointCommands。

  6. 按一下 [確定]。

    Visual Studio 會將 SharePointCommands 專案加入至方案,並且開啟預設的 Class1 程式碼檔。

  7. 從專案刪除 Class1 程式碼檔。

設定專案

在建立精靈之前,您必須先將一些程式碼檔案和組件參考加入至專案。

若要設定精靈專案

  1. 在 [方案總管] 中,以滑鼠右鍵按一下 [ProjectTemplateWizard] 專案節點,然後按一下 [屬性]。

  2. 在 [專案設計工具] 中,將目標 Framework 項目從 .NET Framework 4 Client Profile 變更為 .NET Framework 4。 您可以分別針對 Visual C# 專案和 Visual Basic 專案,在 [應用程式] 索引標籤和 [編譯] 索引標籤上執行這個動作。 如需詳細資訊,請參閱 HOW TO:以特定的 .NET Framework 版本或設定檔為目標

    注意事項注意事項

    根據預設,建立以 .NET Framework 4 為目標的新專案時,這個專案會以用戶端設定檔為目標。 本逐步解說需要完整的 .NET Framework 4。

  3. 在 ProjectTemplateWizard 專案中,將新的 [視窗 (WPF)] 項目加入至專案。 將項目命名為 WizardWindow。

  4. 將兩個新的 [使用者控制項 (WPF)] 項目加入至專案。 分別將這兩個項目命名為 Page1 和 Page2。

  5. 加入四個程式碼檔案,並分別命名如下:

    • SiteColumnProjectWizard

    • SiteColumnWizardModel

    • ProjectSigningManager

    • CommandIds

  6. 在 [專案] 功能表上,按一下 [加入參考]。

  7. 在 [.NET] 索引標籤上,按住 CTRL,然後按一下下列組件,再按一下 [確定]:

    • EnvDTE

    • Microsoft.VisualStudio.OLE.Interop

    • Microsoft.VisualStudio.SharePoint

    • Microsoft.VisualStudio.Shell.10.0

    • Microsoft.VisualStudio.Shell.Interop.10.0

    • Microsoft.VisualStudio.TemplateWizardInterface

  8. 在 [方案總管] 中,按一下 ProjectTemplateWizard 專案的 [參考] 資料夾底下的 [EnvDTE]。

    注意事項注意事項

    在 Visual Basic 專案中,您必須按一下 [方案總管] 中的 [顯示所有檔案] 按鈕,才能查看 [參考] 資料夾。

  9. 在 [屬性] 視窗中,將 [內嵌 Interop 型別] 屬性變更為 [False]。

  10. 如果開發 Visual Basic 專案,請使用 [專案設計工具] 將 ProjectTemplateWizard 命名空間匯入專案中。 如需詳細資訊,請參閱 HOW TO:加入或移除匯入的命名空間 (Visual Basic)

若要設定 SharePointCommands 專案

  1. 在 [SharePointCommands] 專案中,加入名為 Commands 的程式碼檔。

  2. 在 [方案總管] 中,按一下 [SharePointCommands] 專案節點。

  3. 在 [專案] 功能表中選取 [加入現有項目]。

  4. 在 [加入現有項目] 對話方塊中,瀏覽至包含 ProjectTemplateWizard 專案程式碼檔案的資料夾。

  5. 選取 CommandIds 程式碼檔案。

  6. 按一下 [加入] 按鈕的下拉式功能表,然後選取 [加入做為連結]。

    Visual Studio 會將程式碼檔案加入至 SharePointCommands 專案做為連結。 這表示,程式碼檔案位於 ProjectTemplateWizard 專案中,但是檔案中的程式碼也會在 SharePointCommands 專案中編譯。

  7. 在 [專案] 功能表上,按一下 [加入參考]。

  8. 在 [.NET] 索引標籤上,按 CTRL,然後選取下列組件,再按一下 [確定]:

    • Microsoft.SharePoint

    • Microsoft.VisualStudio.SharePoint.Commands

建立精靈模型、簽署管理員和 SharePoint 命令 ID

將程式碼加入至 ProjectTemplateWizard 專案,以便在範例中實作下列元件:

  • SharePoint 命令 ID。 這些是精靈用來識別 SharePoint 命令的字串。 稍後在本逐步解說中,您將在 SharePointCommands 專案中加入程式碼來實作命令。

  • 精靈資料模型。

  • 專案簽署管理員。

如需這些元件的詳細資訊,請參閱了解精靈元件。

若要定義 SharePoint 命令 ID

  1. 在 ProjectTemplateWizard 專案中,開啟 CommandIds 程式碼檔案。

  2. 以下列程式碼取代這個檔案的整個內容。

    Namespace Contoso.SharePoint.Commands
        Public Class CommandIds
            Public Const GetFieldTypes As String = "Contoso.Commands.GetFieldTypes"
            Public Const ValidateSite As String = "Contoso.Commands.ValidateSite"
        End Class
    End Namespace
    
    namespace Contoso.SharePoint.Commands
    {
        public static class CommandIds
        {
            public const string GetFieldTypes = "Contoso.Commands.GetFieldTypes";
            public const string ValidateSite = "Contoso.Commands.ValidateSite";
        }
    }
    

若要建立精靈模型

  1. 開啟 SiteColumnWizardModel 程式碼檔案。

  2. 以下列程式碼取代這個檔案的整個內容。

    Imports EnvDTE
    Imports Microsoft.VisualStudio.SharePoint
    Imports Microsoft.VisualStudio
    Imports Microsoft.VisualStudio.Shell
    Imports Microsoft.VisualStudio.Shell.Interop
    Imports IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider
    
    Public Class SiteColumnWizardModel
        Private dteObject As DTE
        Private projectServiceValue As ISharePointProjectService
        Private validatedUrls As New List(Of String)
    
        Friend Sub New(ByVal dteObject As DTE, ByVal requiresFarmPriveleges As Boolean)
            Me.dteObject = dteObject
    
            ' Initialize default values for wizard choices.
            IsSandboxed = Not requiresFarmPriveleges
            IsSecondPagePopulated = False
            FieldType = "Text"
            FieldGroup = "Custom Columns"
            FieldName = "My Custom Column"
            CurrentSiteUrl = GetLocalHostUrl()
        End Sub
    
    #Region "Helper methods used by the wizard UI"
    
        ' Specifies whether the current site URL is valid. Uses the ValidateSite SharePoint command to do this.
        Friend Function ValidateCurrentUrl(ByVal errorMessage As String) As Boolean
            Dim isValid As Boolean = False
            errorMessage = String.Empty
    
            If validatedUrls.Contains(CurrentSiteUrl) Then
                isValid = True
            Else
                Dim uriToValidate As Uri = New Uri(CurrentSiteUrl, UriKind.Absolute)
                Dim vsThreadedWaitDialog As IVsThreadedWaitDialog2 = Nothing
    
                Try
                    vsThreadedWaitDialog = ShowProgressDialog("Connect to SharePoint",
                        "Connecting to SharePoint site " + CurrentSiteUrl)
                    isValid = Me.ProjectService.SharePointConnection.ExecuteCommand(Of Uri, Boolean)(
                        Contoso.SharePoint.Commands.CommandIds.ValidateSite, uriToValidate)
                Catch ex As Exception
                    errorMessage = "An error occurred while validating the site. " + ex.Message
                Finally
                    If isValid Then
                        validatedUrls.Add(CurrentSiteUrl)
                    End If
                    If vsThreadedWaitDialog IsNot Nothing Then
                        CloseProgressDialog(vsThreadedWaitDialog)
                    End If
                End Try
            End If
            Return isValid
        End Function
    
        ' Gets the available field types from the SharePoint site. Uses the GetFieldTypes SharePoint command to do this.
        Friend Function GetFieldTypes() As ArrayList
            ' If we have not yet validated this site, do it now.
            Dim errorMessage As String = String.Empty
            If Not ValidateCurrentUrl(errorMessage) Then
                MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                    CurrentSiteUrl, errorMessage), "SharePoint Connection Error")
                Return Nothing
            End If
    
            ' Site is valid, so go ahead and get the available field types.
            Dim siteUri As Uri = New Uri(CurrentSiteUrl, UriKind.Absolute)
            Dim vsThreadedWaitDialog As IVsThreadedWaitDialog2 = ShowProgressDialog(
                "Connect to SharePoint", "Connecting to SharePoint site " + CurrentSiteUrl)
            Dim fieldTypesArray As String() = Me.ProjectService.SharePointConnection.ExecuteCommand(Of Uri, String())(
                Contoso.SharePoint.Commands.CommandIds.GetFieldTypes, siteUri)
    
            If vsThreadedWaitDialog IsNot Nothing Then
                CloseProgressDialog(vsThreadedWaitDialog)
            End If
    
            Return New ArrayList(fieldTypesArray)
        End Function
    
        ' Returns the default column group names in SharePoint.
        Friend Function GetFieldGroups() As List(Of String)
            Dim groups As List(Of String) = New List(Of String)()
            groups.Add("Base Columns")
            groups.Add("Core Contact and Calendar Columns")
            groups.Add("Core Document Columns")
            groups.Add("Core Task and Issue Columns")
            groups.Add("Extended Columns")
            Return groups
        End Function
    #End Region
    
    #Region "Properties shared by the wizard implementation and the wizard UI"
    
        Friend ReadOnly Property ProjectService As ISharePointProjectService
            Get
                If projectServiceValue Is Nothing Then
                    projectServiceValue = GetProjectService()
                End If
                Return projectServiceValue
            End Get
        End Property
    
        Friend Property IsSecondPagePopulated As Boolean
        Friend Property IsSandboxed As Boolean
        Friend Property FieldType As String
        Friend Property FieldGroup As String
        Friend Property FieldName As String
        Friend Property CurrentSiteUrl As String
    #End Region
    
    #Region "Private methods"
    
        Private Function GetLocalHostUrl() As String
            Const HttpScheme As String = "http"
            Dim builder As UriBuilder = New UriBuilder(HttpScheme, Environment.MachineName.ToLowerInvariant())
            Return builder.ToString()
        End Function
    
        Private Function GetProjectService() As ISharePointProjectService
            Dim serviceProvider As ServiceProvider = New ServiceProvider(CType(dteObject, IOleServiceProvider))
            Return CType(serviceProvider.GetService(GetType(ISharePointProjectService)), ISharePointProjectService)
        End Function
    
        Private Function ShowProgressDialog(ByVal caption As String, ByVal message As String) As IVsThreadedWaitDialog2
            Dim oleServiceProvider As IOleServiceProvider = CType(dteObject, IOleServiceProvider)
            Dim dialogFactory As IVsThreadedWaitDialogFactory = CType(New ServiceProvider(oleServiceProvider).GetService(
                GetType(SVsThreadedWaitDialogFactory)), IVsThreadedWaitDialogFactory)
    
            If dialogFactory Is Nothing Then
                Throw New InvalidOperationException("The IVsThreadedWaitDialogFactory object could not be retrieved.")
            End If
    
            Dim vsThreadedWaitDialog As IVsThreadedWaitDialog2 = Nothing
            ErrorHandler.ThrowOnFailure(dialogFactory.CreateInstance(vsThreadedWaitDialog))
            ErrorHandler.ThrowOnFailure(vsThreadedWaitDialog.StartWaitDialog(caption, message,
                Nothing, Nothing, String.Empty, 0, False, True))
            Return vsThreadedWaitDialog
        End Function
    
        Private Sub CloseProgressDialog(ByVal vsThreadedWaitDialog As IVsThreadedWaitDialog2)
            If vsThreadedWaitDialog Is Nothing Then
                Throw New ArgumentNullException("vsThreadedWaitDialog")
            End If
            Dim canceled As Integer
            ErrorHandler.ThrowOnFailure(vsThreadedWaitDialog.EndWaitDialog(canceled))
        End Sub
    #End Region
    End Class
    
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Windows;
    using EnvDTE;
    using Microsoft.VisualStudio.SharePoint;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Shell.Interop;
    using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
    
    namespace ProjectTemplateWizard
    {
        internal class SiteColumnWizardModel
        {
            private DTE dteObject;
            private ISharePointProjectService projectServiceValue;
            private List<string> validatedUrls = new List<string>();
    
            internal SiteColumnWizardModel(DTE dteObject, bool requiresFarmPriveleges)
            {
                this.dteObject = dteObject;
    
                // Initialize default values for wizard choices.
                IsSandboxed = !requiresFarmPriveleges;
                IsSecondPagePopulated = false;
                FieldType = "Text";
                FieldGroup = "Custom Columns";
                FieldName = "My Custom Column";
                CurrentSiteUrl = GetLocalHostUrl();
            }
    
            #region Helper methods used by the wizard UI
    
            // Specifies whether the current site URL is valid. Uses the ValidateSite SharePoint command to do this.
            internal bool ValidateCurrentUrl(out string errorMessage)
            {
                bool isValid = false;
                errorMessage = String.Empty;
    
                if (validatedUrls.Contains(CurrentSiteUrl))
                {
                    isValid = true;
                }
                else
                {
                    Uri uriToValidate = new Uri(CurrentSiteUrl, UriKind.Absolute);
                    IVsThreadedWaitDialog2 vsThreadedWaitDialog = null;
    
                    try
                    {
                        vsThreadedWaitDialog = ShowProgressDialog("Connect to SharePoint",
                            "Connecting to SharePoint site " + CurrentSiteUrl);
                        isValid = this.ProjectService.SharePointConnection.ExecuteCommand<Uri, bool>(
                            Contoso.SharePoint.Commands.CommandIds.ValidateSite, uriToValidate);
                    }
                    catch (Exception ex)
                    {
                        errorMessage = "An error occurred while validating the site. " + ex.Message;
                    }
                    finally
                    {
                        if (isValid)
                        {
                            validatedUrls.Add(CurrentSiteUrl);
                        }
    
                        if (vsThreadedWaitDialog != null)
                        {
                            CloseProgressDialog(vsThreadedWaitDialog);
                        }
                    }
                }
    
                return isValid;
            }
    
            // Gets the available field types from the SharePoint site. Uses the GetFieldTypes SharePoint command to do this.
            internal ArrayList GetFieldTypes()
            {
                // If we have not yet validated this site, do it now.
                string errorMessage;
                if (!ValidateCurrentUrl(out errorMessage))
                {
                    MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                        CurrentSiteUrl, errorMessage), "SharePoint Connection Error");
                    return null;
                }
    
                // Site is valid, so go ahead and get the available field types.
                Uri siteUri = new Uri(CurrentSiteUrl, UriKind.Absolute);
                IVsThreadedWaitDialog2 vsThreadedWaitDialog = ShowProgressDialog(
                    "Connect to SharePoint", "Connecting to SharePoint site " + CurrentSiteUrl);
                string[] fieldTypesArray = this.ProjectService.SharePointConnection.ExecuteCommand<Uri, string[]>(
                        Contoso.SharePoint.Commands.CommandIds.GetFieldTypes, siteUri);
    
                if (vsThreadedWaitDialog != null)
                {
                    CloseProgressDialog(vsThreadedWaitDialog);
                }
    
                return new ArrayList(fieldTypesArray);
            }
    
            // Returns the default column group names in SharePoint.
            internal List<string> GetFieldGroups()
            {
                List<string> groups = new List<string>();
                groups.Add("Base Columns");
                groups.Add("Core Contact and Calendar Columns");
                groups.Add("Core Document Columns");
                groups.Add("Core Task and Issue Columns");
                groups.Add("Extended Columns");
                return groups;
            }
    
            #endregion
    
            #region Properties shared by the wizard implementation and the wizard UI
    
            internal ISharePointProjectService ProjectService
            {
                get
                {
                    if (projectServiceValue == null)
                    {
                        projectServiceValue = GetProjectService();
                    }
                    return projectServiceValue;
                }
            }
    
            internal bool IsSecondPagePopulated { get; set; }
            internal bool IsSandboxed { get; set; }
            internal string FieldType { get; set; }
            internal string FieldGroup { get; set; }
            internal string FieldName { get; set; }
            internal string CurrentSiteUrl { get; set; }
    
            #endregion
    
            #region Private methods
    
            private string GetLocalHostUrl()
            {
                const string HttpScheme = "http";
                UriBuilder builder = new UriBuilder(HttpScheme, Environment.MachineName.ToLowerInvariant());
                return builder.ToString();
            }
    
            private ISharePointProjectService GetProjectService()
            {
                ServiceProvider serviceProvider = new ServiceProvider(dteObject as IOleServiceProvider);
                return serviceProvider.GetService(typeof(ISharePointProjectService)) as ISharePointProjectService;
            }
    
            private IVsThreadedWaitDialog2 ShowProgressDialog(string caption, string message)
            {
                IOleServiceProvider oleServiceProvider = dteObject as IOleServiceProvider;
                IVsThreadedWaitDialogFactory dialogFactory = new ServiceProvider(oleServiceProvider).GetService(
                    typeof(SVsThreadedWaitDialogFactory)) as IVsThreadedWaitDialogFactory;
    
                if (dialogFactory == null)
                {
                    throw new InvalidOperationException("The IVsThreadedWaitDialogFactory object could not be retrieved.");
                }
    
                IVsThreadedWaitDialog2 vsThreadedWaitDialog = null;
                ErrorHandler.ThrowOnFailure(dialogFactory.CreateInstance(out vsThreadedWaitDialog));
                ErrorHandler.ThrowOnFailure(vsThreadedWaitDialog.StartWaitDialog(caption, message,
                     null, null, String.Empty, 0, false, true));
                return vsThreadedWaitDialog;
            }
    
            private void CloseProgressDialog(IVsThreadedWaitDialog2 vsThreadedWaitDialog)
            {
                if (vsThreadedWaitDialog == null)
                {
                    throw new ArgumentNullException("vsThreadedWaitDialog");
                }
    
                int canceled;
                ErrorHandler.ThrowOnFailure(vsThreadedWaitDialog.EndWaitDialog(out canceled));
            }
    
            #endregion
        }
    }
    

若要建立專案簽署管理員

  1. 開啟 ProjectSigningManager 程式碼檔案。

  2. 以下列程式碼取代這個檔案的整個內容。

    Imports EnvDTE
    Imports System
    Imports System.IO
    Imports System.Runtime.InteropServices
    
    Friend Class ProjectSigningManager
        Private Const KEY_FILENAME As String = "key.snk"
        Private keyBuffer As Byte()
        Private strongNameGenerated As Boolean = False
    
    #Region "Methods used by the project wizard"
    
        Friend Sub GenerateKeyFile()
            If Not strongNameGenerated Then
                keyBuffer = CreateNewKeyPair()
                strongNameGenerated = True
            End If
        End Sub
    
        Friend Sub AddKeyFile(ByVal project As Project)
            If strongNameGenerated Then
                AddKeyFileToProject(project)
            End If
        End Sub
    #End Region
    
    #Region "Private members"
    
        Private Function CreateNewKeyPair() As Byte()
            Dim buffer As IntPtr = IntPtr.Zero
            Dim bufferSize As UInteger
            Dim keyBuffer As Byte()
    
            Try
                If 0 = NativeMethods.StrongNameKeyGen(IntPtr.Zero, 0, buffer, bufferSize) Then
                    Marshal.ThrowExceptionForHR(NativeMethods.StrongNameErrorInfo())
                End If
                If buffer = IntPtr.Zero Then
                    Throw New InvalidOperationException("Cannot generate the strong name key.")
                End If
    
                ' Copy generated key to managed memory.
                keyBuffer = New Byte(bufferSize) {}
                Marshal.Copy(buffer, keyBuffer, 0, CInt(bufferSize))
            Finally
                ' Free native resources.
                NativeMethods.StrongNameFreeBuffer(buffer)
            End Try
            Return keyBuffer
        End Function
    
        Private Sub AddKeyFileToProject(ByVal project As Project)
    
            ' Save the key to a file.
            If keyBuffer IsNot Nothing Then
                Try
                    Dim destinationDirectory As String = Path.GetDirectoryName(project.FullName)
                    Dim keySavePath As String = Path.Combine(destinationDirectory, KEY_FILENAME)
    
                    File.WriteAllBytes(keySavePath, keyBuffer)
                    project.ProjectItems.AddFromFile(keySavePath)
    
                    ' Add properties in the project to use the key for signing.
                    Dim projProps As EnvDTE.Properties = project.Properties
                    projProps.Item("SignAssembly").Value = True
                    projProps.Item("AssemblyOriginatorKeyFile").Value = KEY_FILENAME
                Catch e As Exception
                    Throw New Exception("Cannot add the strong name key to the project. " & e.Message, e)
                End Try
            End If
        End Sub
    
        Private Class NativeMethods
            <DllImport("mscoree.dll")>
            Friend Shared Function StrongNameFreeBuffer(ByVal pbMemory As IntPtr) As Integer
            End Function
    
            <DllImport("mscoree.dll", CharSet:=CharSet.Unicode, ExactSpelling:=True)>
            Friend Shared Function StrongNameKeyGen(ByVal wszKeyContainer As IntPtr, ByVal dwFlags As UInteger, _
                ByRef KeyBlob As IntPtr, ByRef KeyBlobSize As UInteger) As Integer
            End Function
    
            <DllImport("mscoree.dll", CharSet:=CharSet.Unicode)>
            Friend Shared Function StrongNameErrorInfo() As Integer
            End Function
        End Class
    #End Region
    End Class
    
    using EnvDTE;
    using System;
    using System.IO;
    using System.Runtime.InteropServices;
    
    namespace ProjectTemplateWizard
    {
        internal class ProjectSigningManager
        {
            private const string KEY_FILENAME = "key.snk";
            private byte[] keyBuffer;
            private bool strongNameGenerated = false;
    
            #region Methods used by the project wizard
    
            internal void GenerateKeyFile()
            {
                if (!strongNameGenerated)
                {
                    keyBuffer = CreateNewKeyPair();
                    strongNameGenerated = true;
                }
            }
    
            internal void AddKeyFile(Project project)
            {
                if (strongNameGenerated)
                {
                    AddKeyFileToProject(project);
                }
            }
    
            #endregion
    
            #region Private members
    
            private byte[] CreateNewKeyPair()
            {
                IntPtr buffer = IntPtr.Zero;
                uint bufferSize;
                byte[] keyBuffer;
    
                try
                {
                    if (0 == NativeMethods.StrongNameKeyGen(IntPtr.Zero, 0, out buffer, out bufferSize))
                    {
                        Marshal.ThrowExceptionForHR(NativeMethods.StrongNameErrorInfo());
                    }
    
                    if (buffer == IntPtr.Zero)
                    {
                        throw new InvalidOperationException("Cannot generate the strong name key.");
                    }
    
                    // Copy generated key to managed memory.
                    keyBuffer = new byte[bufferSize];
                    Marshal.Copy(buffer, keyBuffer, 0, (int)bufferSize);
                }
                finally
                {
                    // Free native resources.
                    NativeMethods.StrongNameFreeBuffer(buffer);
                }
    
                return keyBuffer;
            }
    
            private void AddKeyFileToProject(Project project)
            {
                // Save the key to a file.
                if (keyBuffer != null)
                {
                    try
                    {
                        string destinationDirectory = Path.GetDirectoryName(project.FullName);
                        string keySavePath = Path.Combine(destinationDirectory, KEY_FILENAME);
    
                        File.WriteAllBytes(keySavePath, keyBuffer);
                        project.ProjectItems.AddFromFile(keySavePath);
    
                        // Add properties in the project to use the key for signing.
                        EnvDTE.Properties projProps = project.Properties;
                        projProps.Item("SignAssembly").Value = true;
                        projProps.Item("AssemblyOriginatorKeyFile").Value = KEY_FILENAME;
                    }
                    catch (Exception e)
                    {
                        throw new Exception("Cannot add the strong name key to the project. " + e.Message, e);
                    }
                }
            }
    
            private static class NativeMethods
            {
                [DllImport("mscoree.dll")]
                internal extern static int StrongNameFreeBuffer(IntPtr pbMemory);
    
                [DllImport("mscoree.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
                internal static extern int StrongNameKeyGen(IntPtr wszKeyContainer, uint dwFlags, out IntPtr KeyBlob, 
                    out uint KeyBlobSize);
    
                [DllImport("mscoree.dll", CharSet = CharSet.Unicode)]
                internal static extern int StrongNameErrorInfo();
            }
    
            #endregion
        }
    }
    

建立精靈 UI

加入 XAML 以定義精靈視窗的 UI 和兩個為精靈頁面提供 UI 的使用者控制項,並加入程式碼以定義視窗和使用者控制項的行為。 所建立的精靈類似於 Visual Studio 2010 中 SharePoint 專案的內建精靈。

注意事項注意事項

在下列步驟中,於專案中加入 XAML 或程式碼之後,將會出現一些編譯錯誤。 在後續步驟中加入程式碼時,這些錯誤將不存在。

若要建立精靈視窗 UI

  1. 在 ProjectTemplateWizard 專案中,按兩下 WizardWindow.xaml 檔案將視窗在設計工具中開啟。

  2. 在設計工具的 XAML 檢視中,以下列 XAML 取代目前的 XAML。 這個 XAML 定義的 UI 包含標題、內含精靈頁面的 Grid 和位於視窗底部的導覽按鈕。

    <ui:DialogWindow x:Class="ProjectTemplateWizard.WizardWindow"
                     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
                     xmlns:ui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.10.0"        
                     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"                                
                     Title="SharePoint Customization Wizard" Height="500" Width="700" ResizeMode="NoResize" 
                     Loaded="Window_Loaded" TextOptions.TextFormattingMode="Display">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="75*" />
                <RowDefinition Height="364*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="60*" />
            </Grid.RowDefinitions>
            <Grid Grid.Row="0" Name="headingGrid" Background="White">
                <Label Height="28" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="18,0,0,0" 
                       Name="headingLabel" FontWeight="ExtraBold" />
            </Grid>
            <Grid Grid.Row="1" Name="pageGrid" />
            <Rectangle Grid.Row="2" Name="separatorRectangle" Fill="White"  />
            <StackPanel Grid.Row="3" Name="navigationPanel" Orientation="Horizontal">
                <Button Content="&lt; _Previous" Margin="300,0,0,0"  Height="25" Name="previousButton" Width="85" 
                        IsEnabled="False" Click="previousButton_Click" />
                <Button Content="_Next >" Margin="10,0,0,0" Height="25" Name="nextButton" Width="85" Click="nextButton_Click" 
                        IsDefault="True" />
                <Button Content="_Finish" Margin="10,0,0,0" Height="25" Name="finishButton" Width="85" 
                        Click="finishButton_Click" />
                <Button Content="Cancel" Margin="10,0,0,0" Height="25" Name="cancelButton" Width="85" 
                        IsCancel="True" />
            </StackPanel>
        </Grid>
    </ui:DialogWindow>
    
    注意事項注意事項

    這個 XAML 中建立的視窗衍生自 DialogWindow 基底類別。 在 Visual Studio 中加入自訂 WPF 對話方塊時,建議您從這個類別衍生對話方塊,使其樣式與其他 Visual Studio 對話方塊一致,以避免可能發生的強制回應對話方塊的問題。 如需詳細資訊,請參閱 How to: Create and Manage Dialog Boxes

  3. 如果您正在開發 Visual Basic 專案,請在 Window 項目的 x:Class 屬性中,將 ProjectTemplateWizard 命名空間從 WizardWindow 類別名稱移除。 這位於 XAML 的第一行。 當您完成之後,第一行應如下所示。

    <Window x:Class="WizardWindow"
    
  4. 開啟 WizardWindow.xaml 檔案的程式碼後置檔案。

  5. 以下列程式碼取代這個檔案的整個內容。

    Public Class WizardWindow
        Private firstPage As Page1
        Private secondPage As Page2
        Private Const firstPageLabel As String = "Specify the site and security level for debugging"
        Private Const secondPageLabel As String = "Configure the site column"
    
        Friend Sub New(ByVal presentationModel As SiteColumnWizardModel)
            InitializeComponent()
            Me.PresentationModel = presentationModel
            firstPage = New Page1(Me)
            secondPage = New Page2(Me)
            secondPage.Visibility = Visibility.Hidden
        End Sub
    
        Friend Property PresentationModel As SiteColumnWizardModel
    
        Private Sub Window_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            headingLabel.Content = firstPageLabel
            pageGrid.Children.Add(firstPage)
            pageGrid.Children.Add(secondPage)
        End Sub
    
        Private Sub nextButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
    
            ' Initialize the second wizard page if this is the first time 
            ' it has been shown with the current site URL.
            If Not PresentationModel.IsSecondPagePopulated Then
    
                If Not ValidateUrl() Then
                    Return
                End If
    
                ' Refresh the UI in the second page.
                secondPage.ClearControls()
                secondPage.PopulateSiteColumnOptions()
    
                ' Do not do this work again until the user changes the site URL.
                PresentationModel.IsSecondPagePopulated = True
            End If
    
            ' Display the second wizard page and update related controls.
            firstPage.Visibility = Visibility.Hidden
            secondPage.Visibility = Visibility.Visible
            previousButton.IsEnabled = True
            nextButton.IsEnabled = False
            nextButton.IsDefault = False
            finishButton.IsDefault = True
            headingLabel.Content = secondPageLabel
        End Sub
    
        ' Display the first wizard page again and update related controls.
        Private Sub previousButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            secondPage.Visibility = Visibility.Hidden
            firstPage.Visibility = Visibility.Visible
            previousButton.IsEnabled = False
            finishButton.IsDefault = False
            nextButton.IsEnabled = True
            nextButton.IsDefault = True
            headingLabel.Content = firstPageLabel
        End Sub
    
        Private Sub finishButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If ValidateUrl() Then
                DialogResult = True
                Close()
            End If
        End Sub
    
        Private Function ValidateUrl() As Boolean
            Dim errorMessage As String = String.Empty
            If Not PresentationModel.ValidateCurrentUrl(errorMessage) Then
                MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                    PresentationModel.CurrentSiteUrl, errorMessage),
                    "SharePoint Connection Error")
                Return False
            End If
            Return True
        End Function
    End Class
    
    using System;
    using System.Windows;
    using Microsoft.VisualStudio.PlatformUI;
    
    namespace ProjectTemplateWizard
    {
        public partial class WizardWindow : DialogWindow
        {
            private Page1 firstPage;
            private Page2 secondPage;
            private const string firstPageLabel = "Specify the site and security level for debugging";
            private const string secondPageLabel = "Configure the site column";
    
            internal WizardWindow(SiteColumnWizardModel presentationModel)
            {
                InitializeComponent();
                this.PresentationModel = presentationModel;
                firstPage = new Page1(this);
                secondPage = new Page2(this);
                secondPage.Visibility = Visibility.Hidden;
            }
    
            internal SiteColumnWizardModel PresentationModel { get; set; }
    
            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                headingLabel.Content = firstPageLabel;
                pageGrid.Children.Add(firstPage);
                pageGrid.Children.Add(secondPage);
            }
    
            private void nextButton_Click(object sender, RoutedEventArgs e)
            {
                // Initialize the second wizard page if this is the first time 
                // it has been shown with the current site URL.
                if (!PresentationModel.IsSecondPagePopulated)
                {
                    if (!ValidateUrl())
                    {
                        return;
                    }
    
                    // Refresh the UI in the second page.
                    secondPage.ClearControls();
                    secondPage.PopulateSiteColumnOptions();
    
                    // Do not do this work again until the user changes the site URL.
                    PresentationModel.IsSecondPagePopulated = true;
                }
    
                // Display the second wizard page and update related controls.
                firstPage.Visibility = Visibility.Hidden;
                secondPage.Visibility = Visibility.Visible;
                previousButton.IsEnabled = true;
                nextButton.IsEnabled = false;
                finishButton.IsDefault = true;
                headingLabel.Content = secondPageLabel;
            }
    
            // Display the first wizard page again and update related controls.
            private void previousButton_Click(object sender, RoutedEventArgs e)
            {
                secondPage.Visibility = Visibility.Hidden;
                firstPage.Visibility = Visibility.Visible;
                previousButton.IsEnabled = false;
                finishButton.IsDefault = false;
                nextButton.IsEnabled = true;
                nextButton.IsDefault = true;
                headingLabel.Content = firstPageLabel;
            }
    
            private void finishButton_Click(object sender, RoutedEventArgs e)
            {
                if (ValidateUrl())
                {
                    DialogResult = true;
                    Close();
                }
            }
    
            private bool ValidateUrl()
            {
                string errorMessage;
                if (!PresentationModel.ValidateCurrentUrl(out errorMessage))
                {
                    MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                        PresentationModel.CurrentSiteUrl, errorMessage),
                        "SharePoint Connection Error");
                    return false;
                }
                return true;
            }
        }
    }
    

若要建立第一個精靈頁面 UI

  1. 按兩下 Page1.xaml 檔案將使用者控制項在設計工具中開啟。

  2. 在設計工具的 XAML 檢視中,以下列 XAML 取代目前的 XAML。 這個 XAML 定義的 UI 包含使用者可輸入偵錯用的本機網站 URL 的文字方塊,以及用於指定是否將專案沙箱化的選項按鈕。

    <UserControl x:Class="ProjectTemplateWizard.Page1"
                 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" d:DesignHeight="364" d:DesignWidth="700" Loaded="UserControl_Loaded">
        <Grid Height="364" HorizontalAlignment="Left" Name="page1Grid" Width="700">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20*" />
                <ColumnDefinition Width="548*" />
                <ColumnDefinition Width="132*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <TextBox Grid.Row="1" Grid.Column="1" Margin="5,0,1,0" Height="23" Name="siteUrlTextBox" 
                     TextChanged="siteUrlTextBox_TextChanged" />
            <Label Grid.Row="0" Grid.Column="1" Margin="0,20,0,0" Name="siteLabel" FontWeight="Bold" 
                   Target="{Binding ElementName=siteUrlTextBox}" 
                   Content="What local _site do you want to use for debugging?" />
            <Button Grid.Row="1" Grid.Column="2" Content="_Validate" Height="25" Name="validateButton" 
                    Width="88" Click="validateButton_Click" HorizontalAlignment="Left" 
                    Margin="5,0,0,0" VerticalAlignment="Top" />
            <Label Grid.Row="2" Grid.Column="1" Margin="0,10,0,0" Content="What is the trust level for this SharePoint solution?" 
                   Name="trustLabel" FontWeight="Bold" />
            <StackPanel Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Vertical">
                <RadioButton Content="Deploy as a sand_boxed solution" Margin="5,0,0,0" Name="sandboxedSolutionRadioButton" 
                             FontWeight="Bold" Checked="sandboxedSolutionRadioButton_Checked" />
                <TextBlock TextWrapping="WrapWithOverflow" Margin="20,7,50,0">Clicking this option causes the solution to be 
                           deployed as a Sandboxed solution. Sandboxed solutions can be deployed by the site collection owner 
                           and are run in a secure, monitored process that has limited resource access.</TextBlock>
                <RadioButton Content="Deploy as a _farm solution" Margin="5,7,0,0" Name="farmSolutionRadioButton" FontWeight="Bold" 
                             Checked="farmSolutionRadioButton_Checked" />
                <TextBlock TextWrapping="WrapWithOverflow" Margin="20,7,50,0">Clicking this option means that users must have 
                           SharePoint administrator privileges to run or deploy the solution.</TextBlock>
            </StackPanel>
        </Grid>
    </UserControl>
    
  3. 如果您正在開發 Visual Basic 專案,請在 UserControl 項目的 x:Class 屬性中,將 ProjectTemplateWizard 命名空間從 Page1 類別名稱移除。 這位於 XAML 的第一行。 當您完成之後,第一行應如下所示。

    <UserControl x:Class="Page1"
    
  4. 開啟 Page1.xaml 檔案的程式碼後置檔案。

  5. 以下列程式碼取代這個檔案的整個內容。

    Public Class Page1
        Private mainWindow As WizardWindow
    
        Friend Sub New(ByVal mainWindow As WizardWindow)
            Me.mainWindow = mainWindow
            InitializeComponent()
        End Sub
    
        Private Sub UserControl_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If (mainWindow.PresentationModel.IsSandboxed) Then
                sandboxedSolutionRadioButton.IsChecked = True
            Else
                sandboxedSolutionRadioButton.IsEnabled = False
                farmSolutionRadioButton.IsChecked = True
            End If
            siteUrlTextBox.Text = mainWindow.PresentationModel.CurrentSiteUrl
        End Sub
    
        ' Validate that the URL exists on the development computer.
        Private Sub validateButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim errorMessage As String = String.Empty
            validateButton.IsEnabled = False
    
            If Not mainWindow.PresentationModel.ValidateCurrentUrl(errorMessage) Then
                MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                    mainWindow.PresentationModel.CurrentSiteUrl, errorMessage),
                    "SharePoint Connection Error")
            Else
                MessageBox.Show("Successfully connected to SharePoint site " +
                    mainWindow.PresentationModel.CurrentSiteUrl, "Connection Successful")
            End If
            validateButton.IsEnabled = True
        End Sub
    
        ' Prevent users from finishing the wizard if the URL is not formatted correctly.
        Private Sub siteUrlTextBox_TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
            Dim url As String = EnsureTrailingSlash(siteUrlTextBox.Text)
    
            ' Perform some basic error-checking on the URL here.
            If url.Length > 0 AndAlso Uri.IsWellFormedUriString(Uri.EscapeUriString(url), UriKind.Absolute) Then
    
                mainWindow.finishButton.IsEnabled = True
                mainWindow.nextButton.IsEnabled = True
                validateButton.IsEnabled = True
                mainWindow.PresentationModel.CurrentSiteUrl = url
                mainWindow.PresentationModel.IsSecondPagePopulated = False
            Else
                mainWindow.finishButton.IsEnabled = False
                mainWindow.nextButton.IsEnabled = False
                validateButton.IsEnabled = False
            End If
        End Sub
    
        Private Sub sandboxedSolutionRadioButton_Checked(ByVal sender As Object, ByVal e As RoutedEventArgs)
            mainWindow.PresentationModel.IsSandboxed = CBool(sandboxedSolutionRadioButton.IsChecked)
        End Sub
    
        Private Sub farmSolutionRadioButton_Checked(ByVal sender As Object, ByVal e As RoutedEventArgs)
            mainWindow.PresentationModel.IsSandboxed = CBool(sandboxedSolutionRadioButton.IsChecked)
        End Sub
    
        Private Function EnsureTrailingSlash(ByVal url As String)
            If Not String.IsNullOrEmpty(url) AndAlso url(url.Length - 1) <> "/" Then
                url += "/"
            End If
            Return url
        End Function
    End Class
    
    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace ProjectTemplateWizard
    {
        public partial class Page1 : UserControl
        {
            private WizardWindow mainWindow;
    
            internal Page1(WizardWindow mainWindow)
            {
                this.mainWindow = mainWindow;
                InitializeComponent();
            }
    
            private void UserControl_Loaded(object sender, RoutedEventArgs e)
            {
                if (mainWindow.PresentationModel.IsSandboxed)
                {
                    sandboxedSolutionRadioButton.IsChecked = true;
                }
                else
                {
                    sandboxedSolutionRadioButton.IsEnabled = false;
                    farmSolutionRadioButton.IsChecked = true;
                }
    
                siteUrlTextBox.Text = mainWindow.PresentationModel.CurrentSiteUrl;
            }
    
            // Validate that the URL exists on the development computer.
            private void validateButton_Click(object sender, RoutedEventArgs e)
            {
                string errorMessage;
                validateButton.IsEnabled = false;
    
                if (!mainWindow.PresentationModel.ValidateCurrentUrl(out errorMessage))
                {
                    MessageBox.Show(String.Format("Cannot connect to the SharePoint site: {0}. {1}",
                        mainWindow.PresentationModel.CurrentSiteUrl, errorMessage),
                        "SharePoint Connection Error");
                }
                else
                {
                    MessageBox.Show("Successfully connected to SharePoint site " +
                        mainWindow.PresentationModel.CurrentSiteUrl, "Connection Successful");
                }
    
                validateButton.IsEnabled = true;
            }
    
            // Prevent users from finishing the wizard if the URL is not formatted correctly.
            private void siteUrlTextBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                string url = EnsureTrailingSlash(siteUrlTextBox.Text);
    
                // Perform some basic error-checking on the URL here.
                if ((url.Length > 0) && (Uri.IsWellFormedUriString(Uri.EscapeUriString(url), UriKind.Absolute)))
                {
                    mainWindow.finishButton.IsEnabled = true;
                    mainWindow.nextButton.IsEnabled = true;
                    validateButton.IsEnabled = true;
                    mainWindow.PresentationModel.CurrentSiteUrl = url;
                    mainWindow.PresentationModel.IsSecondPagePopulated = false;
                }
                else
                {
                    mainWindow.finishButton.IsEnabled = false;
                    mainWindow.nextButton.IsEnabled = false;
                    validateButton.IsEnabled = false;
                }
            }
    
            private void sandboxedSolutionRadioButton_Checked(object sender, RoutedEventArgs e)
            {
                mainWindow.PresentationModel.IsSandboxed = (bool)sandboxedSolutionRadioButton.IsChecked;
            }
    
            private void farmSolutionRadioButton_Checked(object sender, RoutedEventArgs e)
            {
                mainWindow.PresentationModel.IsSandboxed = (bool)sandboxedSolutionRadioButton.IsChecked;
            }
    
            private string EnsureTrailingSlash(string url)
            {
                if (!String.IsNullOrEmpty(url)
                    && url[url.Length - 1] != '/')
                {
                    url += '/';
                }
                return url;
            }
        }
    }
    

若要建立第二個精靈頁面 UI

  1. 按兩下 Page2.xaml 檔案將使用者控制項在設計工具中開啟。

  2. 在設計工具的 XAML 檢視中,以下列 XAML 取代目前的 XAML。 這個 XAML 定義的 UI 包含用於選擇網站欄之基底型別的下拉式清單、用於指定要在組件庫中顯示網站欄所依據之自訂或內建群組的下拉式方塊,以及用於指定網站欄名稱的文字方塊。

    <UserControl x:Class="ProjectTemplateWizard.Page2"
                 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" d:DesignHeight="364" d:DesignWidth="700" Loaded="UserControl_Loaded">
        <Grid Height="364" HorizontalAlignment="Left" Name="page2Grid" Width="700">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20*" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="450*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Label Grid.Row="0" Grid.Column="1" Margin="0,20,0,0" Content="_Type:" Name="fieldTypeLabel" 
                   FontWeight="Bold" Target="{Binding ElementName=fieldTypeComboBox}"/>
            <Label Grid.Row="1" Grid.Column="1" Margin="0,10,0,0" Content="_Group:" Name="groupLabel" 
                   FontWeight="Bold" Target="{Binding ElementName=groupComboBox}"/>
            <Label Grid.Row="2" Grid.Column="1" Margin="0,10,0,0" Content="_Name:" Name="nameLabel" 
                   FontWeight="Bold" Target="{Binding ElementName=nameTextBox}"/>
            <ComboBox Grid.Row="0" Grid.Column="2" HorizontalAlignment="Left" Margin="0,20,0,0" Height="23" 
                      Name="fieldTypeComboBox" Width="450" SelectionChanged="fieldTypeComboBox_SelectionChanged" />
            <ComboBox Grid.Row="1" Grid.Column="2" HorizontalAlignment="Left" Margin="0,10,0,0" Height="23" 
                      Name="groupComboBox" Width="450" IsEditable="True"  />
            <TextBox Grid.Row="2" Grid.Column="2" HorizontalAlignment="Left"  Margin="0,10,0,0" Height="23" 
                     Name="nameTextBox" Width="450" TextChanged="nameTextBox_TextChanged" />
        </Grid>
    </UserControl>
    
  3. 如果您正在開發 Visual Basic 專案,請在 UserControl 項目的 x:Class 屬性中,將 ProjectTemplateWizard 命名空間從 Page2 類別名稱移除。 這位於 XAML 的第一行。 當您完成之後,第一行應如下所示。

    <UserControl x:Class="Page2"
    
  4. 開啟 Page2.xaml 檔案的程式碼後置檔案。

  5. 以下列程式碼取代這個檔案的整個內容。

    Public Class Page2
        Private mainWindow As WizardWindow
        Private innerTextBoxForGroupComboBox As TextBox
    
        Friend Sub New(ByVal mainWindow As WizardWindow)
            Me.mainWindow = mainWindow
            InitializeComponent()
        End Sub
    
        Friend Sub ClearControls()
            fieldTypeComboBox.Items.Clear()
            groupComboBox.Items.Clear()
            nameTextBox.Clear()
        End Sub
    
        Friend Sub PopulateSiteColumnOptions()
            ' Add the available field type names to the combo box.
            Dim fieldTypes As System.Collections.ArrayList = mainWindow.PresentationModel.GetFieldTypes()
            If fieldTypes IsNot Nothing Then
                fieldTypes.Sort()
                For Each fieldValue As String In fieldTypes
                    fieldTypeComboBox.Items.Add(fieldValue)
                Next
                fieldTypeComboBox.SelectedIndex = 0
            End If
    
            ' Add the default group names to the combo box.
            Dim fieldGroups As List(Of String) = mainWindow.PresentationModel.GetFieldGroups()
            For Each fieldGroupValue As String In fieldGroups
                groupComboBox.Items.Add(fieldGroupValue)
            Next
            groupComboBox.SelectedIndex = 0
        End Sub
    
        Private Sub UserControl_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Handle the TextChanged event of the underlying TextBox for the ComboBox. This enables us to determine 
            ' 1) when the user selects an item in the list and 2) when they type their own custom group name. 
            ' The ComboBox.SelectionChanged event is not raised when you type in an editable ComboboBox.
            innerTextBoxForGroupComboBox = CType(groupComboBox.Template.FindName(
                "PART_EditableTextBox", groupComboBox), TextBox)
            AddHandler innerTextBoxForGroupComboBox.TextChanged, AddressOf innerTextBoxForGroupComboBox_TextChanged
        End Sub
    
        Private Sub fieldTypeComboBox_SelectionChanged(ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
            mainWindow.PresentationModel.FieldType = CStr(fieldTypeComboBox.SelectedItem)
        End Sub
    
        Private Sub innerTextBoxForGroupComboBox_TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
            mainWindow.PresentationModel.FieldGroup = groupComboBox.Text
        End Sub
    
        Private Sub nameTextBox_TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
            mainWindow.PresentationModel.FieldName = nameTextBox.Text
        End Sub
    End Class
    
    using System.Windows;
    using System.Windows.Controls;
    
    namespace ProjectTemplateWizard
    {
        public partial class Page2 : UserControl
        {
            private WizardWindow mainWindow;
            private TextBox innerTextBoxForGroupComboBox;
    
            internal Page2(WizardWindow mainWindow)
            {
                this.mainWindow = mainWindow;
                InitializeComponent();
            }
    
            internal void ClearControls()
            {
                fieldTypeComboBox.Items.Clear();
                groupComboBox.Items.Clear();
                nameTextBox.Clear();
            }
    
            internal void PopulateSiteColumnOptions()
            {
                // Add the available field type names to the combo box.
                System.Collections.ArrayList fieldTypes = mainWindow.PresentationModel.GetFieldTypes();
                if (fieldTypes != null)
                {
                    fieldTypes.Sort();
                    foreach (string fieldValue in fieldTypes)
                    {
                        fieldTypeComboBox.Items.Add(fieldValue);
                    }
    
                    fieldTypeComboBox.SelectedIndex = 0;
                }
    
                // Add the default group names to the combo box.
                System.Collections.Generic.List<string> fieldGroups = mainWindow.PresentationModel.GetFieldGroups();
                foreach (string fieldGroupValue in fieldGroups)
                {
                    groupComboBox.Items.Add(fieldGroupValue);
                }
    
                groupComboBox.SelectedIndex = 0;
            }
    
            private void UserControl_Loaded(object sender, RoutedEventArgs e)
            {
                // Handle the TextChanged event of the underlying TextBox for the ComboBox. This enables us to determine 
                // 1) when the user selects an item in the list and 2) when they type their own custom group name. 
                // The ComboBox.SelectionChanged event is not raised when you type in an editable ComboboBox.
                innerTextBoxForGroupComboBox = groupComboBox.Template.FindName(
                    "PART_EditableTextBox", groupComboBox) as TextBox;
                innerTextBoxForGroupComboBox.TextChanged += innerTextBoxForGroupComboBox_TextChanged;
            }
    
            private void fieldTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                mainWindow.PresentationModel.FieldType = (string)fieldTypeComboBox.SelectedItem;
            }
    
            void innerTextBoxForGroupComboBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                mainWindow.PresentationModel.FieldGroup = groupComboBox.Text;
            }
    
            private void nameTextBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                mainWindow.PresentationModel.FieldName = nameTextBox.Text;
            }
        }
    }
    

實作精靈

精靈的主要功能是藉由實作 IWizard 介面來定義。 這個介面會定義 Visual Studio 在精靈啟動和完成時呼叫的方法,以及在精靈執行時的某些時候呼叫的方法。

若要實作精靈

  1. 在 ProjectTemplateWizard 專案中,開啟 SiteColumnProjectWizard 程式碼檔案。

  2. 以下列程式碼取代這個檔案的整個內容。

    Imports EnvDTE
    Imports Microsoft.VisualStudio.SharePoint
    Imports Microsoft.VisualStudio.TemplateWizard
    Imports System
    Imports System.Collections.Generic
    
    Public Class SiteColumnProjectWizard
        Implements IWizard
    
        Private wizardUI As WizardWindow
        Private dteObject As DTE
        Private presentationModel As SiteColumnWizardModel
        Private signingManager As ProjectSigningManager
    
        Public Sub New()
            signingManager = New ProjectSigningManager()
        End Sub
    
        Public Sub RunStarted(ByVal automationObject As Object, ByVal replacementsDictionary As Dictionary(Of String, String), _
            ByVal runKind As WizardRunKind, ByVal customParams() As Object) Implements IWizard.RunStarted
    
            dteObject = CType(automationObject, DTE)
            presentationModel = New SiteColumnWizardModel(dteObject, False)
    
            If Not presentationModel.ProjectService.IsSharePointInstalled Then
                Dim errorString As String = "A SharePoint server is not installed on this computer. A SharePoint server " &
                    "must be installed to work with SharePoint projects."
                System.Windows.MessageBox.Show(errorString, "SharePoint Not Installed", System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Error)
                Throw New WizardCancelledException(errorString)
            End If
    
            wizardUI = New WizardWindow(presentationModel)
            Dim dialogCompleted? As Boolean = wizardUI.ShowModal()
    
            If (dialogCompleted = True) Then
                replacementsDictionary.Add("$selectedfieldtype$", presentationModel.FieldType)
                replacementsDictionary.Add("$selectedgrouptype$", presentationModel.FieldGroup)
                replacementsDictionary.Add("$fieldname$", presentationModel.FieldName)
                signingManager.GenerateKeyFile()
            Else
                Throw New WizardCancelledException()
            End If
        End Sub
    
        ' Populate the SiteUrl and IsSandboxedSolution properties in the new project, and add a new 
        ' key.snk file to the project.
        Public Sub ProjectFinishedGenerating(ByVal project As Project) _
            Implements IWizard.ProjectFinishedGenerating
            Dim sharePointProject As ISharePointProject = presentationModel.ProjectService.Convert(Of Project, ISharePointProject)(project)
            sharePointProject.SiteUrl = New Uri(presentationModel.CurrentSiteUrl, UriKind.Absolute)
            sharePointProject.IsSandboxedSolution = presentationModel.IsSandboxed
            signingManager.AddKeyFile(project)
        End Sub
    
        ' Always return true; this IWizard implementation throws a WizardCancelledException
        ' that is handled by Visual Studio if the user cancels the wizard.
        Public Function ShouldAddProjectItem(ByVal filePath As String) As Boolean _
            Implements IWizard.ShouldAddProjectItem
            Return True
        End Function
    
        ' The following IWizard methods are not used in this example.
        Public Sub BeforeOpeningFile(ByVal projectItem As ProjectItem) _
            Implements IWizard.BeforeOpeningFile
        End Sub
    
        Public Sub ProjectItemFinishedGenerating(ByVal projectItem As ProjectItem) _
            Implements IWizard.ProjectItemFinishedGenerating
        End Sub
    
        Public Sub RunFinished() Implements IWizard.RunFinished
        End Sub
    End Class
    
    using EnvDTE;
    using Microsoft.VisualStudio.SharePoint;
    using Microsoft.VisualStudio.TemplateWizard;
    using System;
    using System.Collections.Generic;
    
    namespace ProjectTemplateWizard
    {
        public class SiteColumnProjectWizard : IWizard
        {
            private WizardWindow wizardUI;
            private DTE dteObject;
            private SiteColumnWizardModel presentationModel;
            private ProjectSigningManager signingManager;
    
            public SiteColumnProjectWizard()
            {
                signingManager = new ProjectSigningManager();
            }
    
            public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, 
                WizardRunKind runKind, object[] customParams)
            {
                dteObject = automationObject as DTE;
                presentationModel = new SiteColumnWizardModel(dteObject, false);
    
                if (!presentationModel.ProjectService.IsSharePointInstalled)
                {
                    string errorString = "A SharePoint server is not installed on this computer. A SharePoint server " +
                        "must be installed to work with SharePoint projects.";
                    System.Windows.MessageBox.Show(errorString, "SharePoint Not Installed", System.Windows.MessageBoxButton.OK,
                        System.Windows.MessageBoxImage.Error);
                    throw new WizardCancelledException(errorString);
                }
    
                wizardUI = new WizardWindow(presentationModel);
                Nullable<bool> dialogCompleted = wizardUI.ShowModal();
    
                if (dialogCompleted == true)
                {
                    replacementsDictionary.Add("$selectedfieldtype$", presentationModel.FieldType);
                    replacementsDictionary.Add("$selectedgrouptype$", presentationModel.FieldGroup);
                    replacementsDictionary.Add("$fieldname$", presentationModel.FieldName);
                    signingManager.GenerateKeyFile();
                }
                else
                {
                    throw new WizardCancelledException();
                }
            }
    
            // Populate the SiteUrl and IsSandboxedSolution properties in the new project, and add a new 
            // key.snk file to the project.
            public void ProjectFinishedGenerating(Project project)
            {
                ISharePointProject sharePointProject = presentationModel.ProjectService.Convert<Project, ISharePointProject>(project);
                sharePointProject.SiteUrl = new Uri(presentationModel.CurrentSiteUrl, UriKind.Absolute);
                sharePointProject.IsSandboxedSolution = presentationModel.IsSandboxed;
                signingManager.AddKeyFile(project);
            }
    
            // Always return true; this IWizard implementation throws a WizardCancelledException
            // that is handled by Visual Studio if the user cancels the wizard.
            public bool ShouldAddProjectItem(string filePath)
            {
                return true;
            }
    
            // The following IWizard methods are not used in this example.
            public void BeforeOpeningFile(ProjectItem projectItem)
            {
            }
    
            public void ProjectItemFinishedGenerating(ProjectItem projectItem)
            {
            }
    
            public void RunFinished()
            {
            }
        }
    }
    

建立 SharePoint 命令

建立兩個呼叫 SharePoint 伺服器物件模型的自訂命令。 一個命令判斷使用者在精靈中輸入的網站 URL 是否有效。 另一個命令會從指定的 SharePoint 網站取得所有欄位型別,讓使用者可以選擇要用哪一個型別做為新網站欄的基礎。

若要定義 SharePoint 命令

  1. 在 [SharePointCommands] 專案中,開啟 Commands 程式碼檔案。

  2. 以下列程式碼取代這個檔案的整個內容。

    Imports Microsoft.SharePoint
    Imports Microsoft.VisualStudio.SharePoint.Commands
    
    Namespace Contoso.SharePoint.Commands
    
        Friend Class Commands
    
            <SharePointCommand(CommandIds.ValidateSite)> _
            Private Function ValidateSite(ByVal context As ISharePointCommandContext, ByVal url As Uri) As Boolean
                Using site As SPSite = New SPSite(url.AbsoluteUri)
                    Dim webUrl As String = DetermineWebUrl(url.AbsolutePath, site.ServerRelativeUrl)
                    If webUrl IsNot Nothing Then
                        Using web As SPWeb = site.OpenWeb(webUrl, True)
                            Return web.Exists
                        End Using
                    End If
                End Using
                Return False
            End Function
    
            ' For simplicity, this command does not check to make sure the provided Uri is valid. 
            ' Use the ValidateSite command to verify that the Uri is valid first.
            <SharePointCommand(CommandIds.GetFieldTypes)> _
            Private Function GetFieldTypes(ByVal context As ISharePointCommandContext, ByVal url As Uri) As String()
                Dim columnDefinitions As List(Of String) = New List(Of String)()
                Using site As SPSite = New SPSite(url.AbsoluteUri)
                    Dim webUrl As String = DetermineWebUrl(url.AbsolutePath, site.ServerRelativeUrl)
                    Using web As SPWeb = site.OpenWeb(webUrl, True)
                        For Each columnDefinition As SPFieldTypeDefinition In web.FieldTypeDefinitionCollection
                            columnDefinitions.Add(columnDefinition.TypeName)
                        Next
                        ' SharePoint commands cannot serialize List<string>, so return an array.
                        Return columnDefinitions.ToArray()
                    End Using
                End Using
            End Function
    
            Private Function DetermineWebUrl(ByVal serverRelativeInputUrl As String, ByVal serverRelativeSiteUrl As String) As String
                ' Make sure both URLs have a trailing slash.
                serverRelativeInputUrl = EnsureTrailingSlash(serverRelativeInputUrl)
                serverRelativeSiteUrl = EnsureTrailingSlash(serverRelativeSiteUrl)
    
                Dim webUrl As String = Nothing
                Dim isSubString As Boolean = serverRelativeInputUrl.StartsWith(serverRelativeSiteUrl, StringComparison.OrdinalIgnoreCase)
    
                If isSubString Then
                    ' The Web URL cannot have escaped characters.
                    webUrl = Uri.UnescapeDataString(serverRelativeInputUrl.Substring(serverRelativeSiteUrl.Length))
                End If
                Return webUrl
            End Function
    
            Private Function EnsureTrailingSlash(ByVal url As String)
                If Not String.IsNullOrEmpty(url) AndAlso url(url.Length - 1) <> "/" Then
                    url += "/"
                End If
                Return url
            End Function
        End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using Microsoft.SharePoint;
    using Microsoft.VisualStudio.SharePoint.Commands;
    
    namespace Contoso.SharePoint.Commands
    {
        internal class Commands
        {
            [SharePointCommand(CommandIds.ValidateSite)]
            private bool ValidateSite(ISharePointCommandContext context, Uri url)
            {
                using (SPSite site = new SPSite(url.AbsoluteUri))
                {
                    string webUrl = DetermineWebUrl(url.AbsolutePath, site.ServerRelativeUrl);
                    if (webUrl != null)
                    {
                        using (SPWeb web = site.OpenWeb(webUrl, true))
                        {
                            return web.Exists;
                        }
                    }
                }
    
                return false;
            }
    
            // For simplicity, this command does not check to make sure the provided Uri is valid. 
            // Use the ValidateSite command to verify that the Uri is valid first.
            [SharePointCommand(CommandIds.GetFieldTypes)]
            private string[] GetFieldTypes(ISharePointCommandContext context, Uri url)
            {
                List<string> columnDefinitions = new List<string>();
                using (SPSite site = new SPSite(url.AbsoluteUri))
                {
                    string webUrl = DetermineWebUrl(url.AbsolutePath, site.ServerRelativeUrl);
                    using (SPWeb web = site.OpenWeb(webUrl, true))
                    {
                        foreach (SPFieldTypeDefinition columnDefinition in web.FieldTypeDefinitionCollection)
                        {
                            columnDefinitions.Add(columnDefinition.TypeName);
                        }
    
                        // SharePoint commands cannot serialize List<string>, so return an array.
                        return columnDefinitions.ToArray();
                    }
                }
            }
    
            private string DetermineWebUrl(string serverRelativeInputUrl, string serverRelativeSiteUrl)
            {
                // Make sure both URLs have a trailing slash.
                serverRelativeInputUrl = EnsureTrailingSlash(serverRelativeInputUrl);
                serverRelativeSiteUrl = EnsureTrailingSlash(serverRelativeSiteUrl);
    
                string webUrl = null;
                bool isSubString = serverRelativeInputUrl.StartsWith(serverRelativeSiteUrl, StringComparison.OrdinalIgnoreCase);
    
                if (isSubString)
                {
                    // The Web URL cannot have escaped characters.
                    webUrl = Uri.UnescapeDataString(serverRelativeInputUrl.Substring(serverRelativeSiteUrl.Length));
                }
    
                return webUrl;
            }
    
            private string EnsureTrailingSlash(string url)
            {
                if (!String.IsNullOrEmpty(url)
                    && url[url.Length - 1] != '/')
                {
                    url += '/';
                }
                return url;
            }
        }
    }
    

檢查點

在逐步解說中進行至此處時,精靈的所有程式碼都會位於專案中。 建置專案,以確定在編譯時未發生任何錯誤。

若要建置您的專案

  • 在 [建置] 功能表上,選取 [建置方案]。

從專案範本移除 key.snk 檔案

逐步解說:使用專案範本建立網站欄專案項目 (第 1 部分)中,您所建立的專案範本包含用來簽署每個網站欄專案執行個體的 key.snk 檔案。 不再需要這個 key.snk 檔案,因為精靈現在會為每個專案產生新的 key.snk 檔案。 請從專案範本移除這個 key.snk 檔案,並移除這個檔案參考。

若要從專案範本移除 key.snk 檔案

  1. 在 [方案總管] 的 [SiteColumnProjectTemplate] 節點底下,以滑鼠右鍵按一下 [key.snk] 檔案,然後按一下 [刪除]。 在要求確認的訊息方塊中,按一下 [確定]。

  2. 在 [SiteColumnProjectTemplate] 節點底下,開啟 SiteColumnProjectTemplate.vstemplate 檔案。

  3. 從這個檔案移除下列項目。

    <ProjectItem ReplaceParameters="false" TargetFileName="key.snk">key.snk</ProjectItem>
    
  4. 儲存並關閉檔案。

  5. 在 [SiteColumnProjectTemplate] 節點底下,開啟 ProjectTemplate.csproj 或 ProjectTemplate.vbproj 檔案。

  6. 移除 PropertyGroup 項目。

    <PropertyGroup>
      <SignAssembly>true</SignAssembly>
      <AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
    </PropertyGroup>
    
  7. 移除 None 項目。

    <None Include="key.snk" />
    
  8. 儲存並關閉檔案。

建立精靈與專案範本的關聯

現在您已實作精靈,接著必須建立精靈與 [網站欄] 專案範本的關聯。 若要執行此作業,必須完成三個程序:

  1. 使用強式名稱簽署精靈組件。

  2. 取得精靈組件的公開金鑰語彙基元。

  3. 在 [網站欄] 專案範本的 .vstemplate 檔案中加入精靈組件的參考。

若要使用強式名稱簽署精靈組件

  1. 在 [方案總管] 中,以滑鼠右鍵按一下 [ProjectTemplateWizard] 專案節點,然後按一下 [屬性]。

  2. 按一下 [簽署] 索引標籤。

  3. 選取 [簽署組件] 核取方塊。

  4. 在 [選擇強式名稱金鑰檔] 下拉式清單中,選取 [<新增>]。

  5. 在 [建立強式名稱金鑰] 對話方塊中,為新的金鑰檔輸入名稱並清除 [以密碼保護我的金鑰檔] 核取方塊。

  6. 按一下 [確定]。

  7. 在 [建置] 功能表上,選取 [建置方案]。

若要取得精靈組件的公開金鑰語彙基元

  1. 開啟 [Visual Studio 命令提示字元] 視窗。

  2. 執行下列命令。 將 path to wizard assembly 取代為 ProjectTemplateWizard 專案的內建 ProjectTemplateWizard.dll 組件位於您開發電腦上的完整路徑。

    sn.exe -T path to wizard assembly
    

    ProjectTemplateWizard.dll 組件的公開金鑰語彙基元會寫入至 [Visual Studio 命令提示字元] 視窗。

  3. 讓 [Visual Studio 命令提示字元] 視窗保持開啟。 在下一個程序中,您將會需要這個公開金鑰語彙基元。

若要在 .vstemplate 檔案中加入精靈組件的參考

  1. 在 [方案總管] 中,展開 [SiteColumnProjectTemplate] 專案節點並開啟 SiteColumnProjectTemplate.vstemplate 檔案。

  2. 在檔案結尾附近,於 </TemplateContent> 與 </VSTemplate> 標記之間加入 WizardExtension 項目。 將 PublicKeyToken 屬性的 your token 值取代為您在上一個程序取得的公開金鑰語彙基元。

    <WizardExtension>
      <Assembly>ProjectTemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=your token</Assembly>
      <FullClassName>ProjectTemplateWizard.SiteColumnProjectWizard</FullClassName>
    </WizardExtension>
    

    如需 WizardExtension 項目的詳細資訊,請參閱 WizardExtension 項目 (Visual Studio 範本)

  3. 儲存並關閉檔案。

將可取代的參數加入至專案範本中的 Elements.xml 檔案

將數個可取代的參數加入至 SiteColumnProjectTemplate 專案中的 Elements.xml 檔案。 這些參數會以您先前定義之 SiteColumnProjectWizard 類別中的 RunStarted 方法初始化。 當使用者建立網站欄專案時,Visual Studio 會自動在新專案中,將 Elements.xml 檔案中的這些參數取代為使用者在精靈中所指定的值。

可取代的參數是以貨幣符號 ($) 字元開頭及結尾的語彙基元。 除了定義您自己的可取代參數以外,您也可以使用 SharePoint 專案系統所定義和初始化的內建參數。 如需詳細資訊,請參閱 可置換的參數

若要將可取代的參數加入至 Elements.xml 檔案

  1. 開啟 SiteColumnProjectTemplate 專案中的 Elements.xml 檔案。

  2. 以下列 XML 取代這個檔案的內容。

    <?xml version="1.0" encoding="utf-8"?>
    <Elements xmlns="https://schemas.microsoft.com/sharepoint/">
      <Field ID="{$guid5$}" 
             Name="$fieldname$" 
             DisplayName="$fieldname$" 
             Type="$selectedfieldtype$" 
             Group="$selectedgrouptype$">
      </Field>
    </Elements>
    

    新的 XML 會將 Name、DisplayName、Type 和 Group 屬性的值變更為可取代的參數。

  3. 儲存並關閉檔案。

將精靈加入至 VSIX 套件

若要使用包含網站欄專案範本的 VSIX 套件來部署精靈,請在 VSIX 專案的 source.extension.vsixmanifest 檔案中加入精靈專案和 SharePoint 命令專案的參考。

若要將精靈加入至 VSIX 套件

  1. 在 [方案總管] 中,按兩下 SiteColumnProjectItem 專案中的 [source.extension.vsixmanifest] 檔案。

    Visual Studio 會在資訊清單編輯器中開啟檔案。

  2. 在編輯器的 [內容] 區段中,按一下 [加入內容] 按鈕。

  3. 在 [加入內容] 對話方塊的 [選取內容類型] 清單方塊中,選取 [範本精靈]。

  4. 按一下 [選取來源] 底下的 [專案] 選項按鈕,並選取其旁邊清單方塊中的 [ProjectTemplateWizard]。

  5. 按一下 [確定]。

  6. 在資訊清單編輯器中,再按一下 [加入內容] 按鈕。

  7. 在 [加入內容] 對話方塊的 [選取內容類型] 清單方塊中,選取 [自訂延伸類型]。

    注意事項注意事項

    這個值對應於 extension.vsixmanifest 檔案中的 CustomExtension 項目。 此項目會指定要加入 Visual Studio 擴充功能的自訂擴充功能。 如需詳細資訊,請參閱 CustomExtension Element (VSX Schema)

  8. 在 [類型] 文字方塊中,輸入 SharePoint.Commands.v4。

    注意事項注意事項

    這個值對應於 extension.vsixmanifest 檔案中的 CustomExtension 項目的 Type 屬性。 包含自訂 SharePoint 命令的所有自訂擴充組件都需要 Sharepoint.Commands.v4 這個值。

  9. 按一下 [選取來源] 底下的 [專案] 選項按鈕,並選取其旁邊清單方塊中的 [SharePointCommands]。

  10. 按一下 [確定]。

  11. 在 [建置] 功能表上,按一下 [建置方案]。 請確定方案編譯作業未發生錯誤。

測試精靈

您現在可以測試精靈。 首先,在 Visual Studio 的實驗執行個體中開始偵錯 SiteColumnProjectItem 方案。 接著,在 Visual Studio 的實驗執行個體中測試網站欄專案的精靈。 最後,建置並執行專案,確認網站欄功能正常。

若要開始偵錯方案

  1. 以系統管理員權限重新啟動 Visual Studio,並開啟 SiteColumnProjectItem 方案。

  2. 在 ProjectTemplateWizard 專案中開啟 SiteColumnProjectWizard 程式碼檔案,然後將中斷點加入至 RunStarted 方法內的第一行程式碼中。

  3. 在 [偵錯] 功能表上,按 [例外狀況]。

  4. 在 [例外狀況] 對話方塊中,確定 [Common Language Runtime 例外狀況] 的 [擲回] 和 [使用者未處理] 核取方塊均已清除。

  5. 按一下 [確定]。

  6. 按 F5 鍵啟動偵錯作業。

    Visual Studio 會將擴充功能安裝至 %UserProfile%\AppData\Local\Microsoft\VisualStudio\10.0Exp\Extensions\Contoso\Site Column\1.0,並啟動 Visual Studio 的實驗執行個體。 您將會在 Visual Studio 的這個執行個體中測試專案項目。

若要在 Visual Studio 中測試精靈

  1. 在 Visual Studio 的實驗執行個體中,指向 [檔案] 功能表上的 [新增],然後按一下 [專案]。

  2. 展開 [Visual C#] 或 [Visual Basic] (視專案範本所支援的語言而定),然後展開 [SharePoint] 節點,再按一下 [2010]。

  3. 在專案範本的清單中,按一下 [網站欄]。

  4. 在 [名稱] 方塊中輸入 SiteColumnWizardTest。

  5. 按一下 [確定]。

  6. 確認另一個 Visual Studio 執行個體中的程式碼在您之前於 RunStarted 方法中設定的中斷點停止。 按 F5 繼續偵錯專案。

  7. 在 [SharePoint 自訂精靈] 中,輸入要用於偵錯的網站 URL,然後按一下 [下一步]。

  8. 在 [SharePoint 自訂精靈] 的第二個頁面中進行以下選擇:

    • 針對 [型別],在下拉式清單中選取 [Boolean]。

    • 針對 [群組],在下拉式方塊中輸入 Custom Yes/No Columns

    • 針對 [名稱],在文字方塊中輸入 My Yes/No Column

  9. 按一下 [完成]。

    含有名為 [Field1] 之專案項目的新專案會出現在 [方案總管] 中,而且 Visual Studio 會將 Elements.xml 檔案在編輯器中開啟。 確認 Elements.xml 包含您在精靈中指定的值。

若要在 SharePoint 中測試網站欄

  1. 在 Visual Studio 的實驗執行個體中按 F5。 網站欄會封裝並部署至專案的 [網站 URL] 屬性指定的 SharePoint 網站。 Web 瀏覽器會開啟此網站的預設頁面。

    注意事項注意事項

    如果出現 [已停用指令碼偵錯] 對話方塊,請按一下 [] 繼續偵錯專案。

  2. 按一下 [網站動作] 功能表上的 [網站設定]。

  3. 按一下 [組件庫] 底下的 [網站欄]。

  4. 在網站欄清單中,確認有一個 [Custom Yes/No Columns] 群組,其中包含名為 [My Yes/No Column] 的欄。

  5. 關閉 Web 瀏覽器。

清理開發電腦

在您完成測試專案項目之後,請從 Visual Studio 的實驗執行個體中移除專案範本。

若要清理開發電腦

  1. 在 Visual Studio 的實驗執行個體中,按一下 [工具] 功能表上的 [擴充管理員]。

    [擴充管理員] 對話方塊隨即開啟。

  2. 在擴充功能清單中,按一下 [網站欄],然後按一下 [解除安裝]。

  3. 在所顯示的對話方塊中,按一下 [],確認您要解除安裝擴充功能。

  4. 按一下 [立即重新啟動] 完成解除安裝。

  5. 關閉 Visual Studio 的兩個執行個體 (Visual Studio 的實驗執行個體,以及其中有開啟 SiteColumnProjectItem 專案的執行個體)。

請參閱

工作

HOW TO:搭配專案範本使用精靈

參考

Visual Studio 範本結構描述參考

IWizard

其他資源

逐步解說:使用專案範本建立網站欄專案項目 (第 1 部分)

定義自訂 SharePoint 專案項目類型

為 SharePoint 專案項目建立項目範本和專案範本