在定义 SharePoint 项目项的自定义类型并将其与 Visual Studio 中的项目模板关联后,可能还需要提供模板向导。当用户使用模板创建包含项目项的新项目时,您可以使用此向导收集用户的信息。收集的信息可用于初始化项目项。
在本演练中,您将为演练:使用项目模板创建网站栏项目项(第 1 部分)中演示的“网站栏”项目模板添加向导。当用户创建“网站栏”项目时,此向导将收集有关该网站栏的信息(如其基类型和组),并将此信息添加到新项目的 Elements.xml 文件中。
本演练将演示以下任务:
为与项目模板关联的自定义 SharePoint 项目项类型创建向导。
定义类似于 SharePoint 的内置向导的自定义向导 UI 在 Visual Studio 项目。
创建两个 SharePoint 命令,用于在向导运行时调入本地 SharePoint 网站。SharePoint 命令是一些方法,Visual Studio 扩展可使用这些方法来调用 SharePoint 服务器对象模型中的 API。有关更多信息,请参见调入 SharePoint 对象模型。
通过可替换参数使用此向导中收集的数据来初始化 SharePoint 项目文件。
在每个新“网站栏”项目实例中创建一个新的 .snk 文件。此文件用于对项目输出进行签名,以便能将 SharePoint 解决方案程序集部署到全局程序集缓存。
调试并测试向导。
![]() |
---|
可从以下位置为本演练下载包含已完成项目、代码及其他文件的示例:https://go.microsoft.com/fwlink/?LinkId=191369。 |
系统必备
若要执行本演练,必须先通过完成演练:使用项目模板创建网站栏项目项(第 1 部分)来创建 SiteColumnProjectItem 解决方案。
还需要在开发计算机上安装以下组件才能完成本演练:
Windows、SharePoint 和 Visual Studio 的支持的版本。有关更多信息,请参见开发 SharePoint 解决方案的要求。
Visual Studio SDK。本演练使用 SDK 中的**“VSIX 项目”**模板来创建 VSIX 包以部署项目项。有关更多信息,请参见扩展 Visual Studio 中的 SharePoint 工具。
了解以下概念很有用,但对于完成本演练并不是必需的:
Visual Studio 中的项目和项模板的向导。有关更多信息,请参见如何:使用向导来处理项目模板和 IWizard 接口。
SharePoint 中的网站栏。有关更多信息,请参见列。
了解向导组件
本演练中演示的向导包含几个组件。下表描述了这些组件。
组件 |
描述 |
---|---|
向导实现 |
这是一个名为 SiteColumnProjectWizard 的类,用于实现 IWizard 接口。此接口定义了 Visual Studio 在向导启动和完成时以及向导运行过程中的特定时间调用的方法。 |
向导 UI |
这是一个基于 WPF 的窗口,名为 WizardWindow。此窗口包含两个用户控件,分别名为 Page1 和 Page2。这两个用户控件表示向导的两个页面。 在本演练中,向导实现的 RunStarted 方法将显示向导 UI。 |
向导数据模型 |
这是一个名为 SiteColumnWizardModel 的中介类,它在向导 UI 和向导实现之间提供了一个层。本示例使用此类来帮助将向导实现和向导 UI 彼此隔开;此类不是所有向导的必需组件。 在本演练中,向导实现会在显示向导 UI 时将 SiteColumnWizardModel 对象传递到向导窗口。向导 UI 使用此对象的方法来保存 UI 中控件的值,并执行与验证输入网站 URL 是否有效类似的任务。在用户完成该向导后,向导实现将使用 SiteColumnWizardModel 对象确定 UI 的最终状态。 |
项目签名管理器 |
这是一个名为 ProjectSigningManager 的帮助程序类,向导实现会使用此类在每个新的项目实例中创建一个新的 key.snk 文件。 |
SharePoint 命令 |
向导实现模型可使用这两种方法在向导运行时调入本地 SharePoint 网站。由于 SharePoint 命令必须面向 .NET Framework 3.5,因此将在不同于剩余向导代码的程序集中实现这些命令。 |
创建项目
若要完成本演练,需要向在演练:使用项目模板创建网站栏项目项(第 1 部分)中创建的 SiteColumnProjectItem 解决方案添加多个项目:
一个 WPF 项目。您将实现 IWizard 接口并定义此项目中的向导 UI。
一个用于定义 SharePoint 命令的类库项目。此项目必须面向 .NET Framework 3.5。
从创建项目开始本演练。
创建 WPF 项目
在 Visual Studio,打开 SiteColumnProjectItem 解决方案。
在 解决方案资源管理器,打开 SiteColumnProjectItem 解决方案节点的快捷菜单,选择 添加,然后选择 新建项目。
说明
在 Visual Basic 项目中,解决方案节点仅当在General, Projects and Solutions, Options Dialog Box中选中“总是显示解决方案”复选框时显示。
在 添加新项目 对话框顶部,确保 .NET Framework 4.5 在 .NET Framework 的版本列表中选择。
外接 visual C# 节点或 Visual Basic 节点,然后选择 Windows 节点。
在项目模板列表中,选择 WPF 用户控件库,将项目命名为 ProjectTemplateWizard,然后选择 确定 按钮。
Visual Studio 添加 ProjectTemplateWizard 项目添加到解决方案并打开默认 UserControl1.xaml 文件。
从项目中删除 UserControl1.xaml 文件。
创建 SharePoint 命令项目
在 解决方案资源管理器,打开 SiteColumnProjectItem 解决方案节点的快捷菜单,选择 添加,然后选择 新建项目。
在 添加新项目 对话框的顶部,选择在 .NET Framework 的版本列表的 .NET Framework 3.5。
外接 visual C# 节点或 Visual Basic 节点,然后选择 Windows 节点。
选择 类库 项目模板,将项目命名为 SharePointCommands,然后选择 确定 按钮。
Visual Studio 添加 SharePointCommands 项目添加到解决方案并打开默认的 Class1 代码文件。
从项目中删除 Class1 代码文件。
配置项目
在创建向导之前,必须添加一些代码文件,而程序集引用添加到项目中。
配置向导项目
在 解决方案资源管理器,打开 ProjectTemplateWizard 项目节点的快捷菜单,然后选择 属性。
在 项目设计器,选择一个 visual C# 项目的 应用程序 选项或 Visual Basic 项目中 编译 选项。
确保目标框架设置为 .NET Framework 4.5,而不是 .NET Framework 4.5 client profile。
有关更多信息,请参见如何:面向 .NET Framework 的某个版本。
打开 ProjectTemplateWizard 项目的快捷菜单,选择 添加,然后选择 新建项。
选择 Window (WPF) 项目,将项目命名为 WizardWindow,然后选择 添加 按钮。
添加两个 用户控件 (WPF) 项目到项目,并将它们命名为 Page1 和 Page2。
添加四个代码文件添加到项目,并为它们提供以下名称:
SiteColumnProjectWizard
SiteColumnWizardModel
ProjectSigningManager
CommandIds
打开 ProjectTemplateWizard 项目节点的快捷菜单,然后选择 添加引用。
外接 程序集 节点,选择 扩展 节点,以下程序集中例旁边的复选框:
EnvDTE
Microsoft.VisualStudio.OLE.Interop
Microsoft.VisualStudio.SharePoint
Microsoft.VisualStudio.Shell.11.0
Microsoft.VisualStudio.Shell.Interop.10.0
Microsoft.VisualStudio.Shell.Interop.11.0
Microsoft.VisualStudio.TemplateWizardInterface
选择 确定 按钮将程序集添加到项目中。
在 解决方案资源管理器,在 ProjectTemplateWizard 项目的 引用 文件夹下,选择 EnvDTE。
说明
在 Visual Basic 项目中,“引用”文件夹仅当在General, Projects and Solutions, Options Dialog Box中选中“总是显示解决方案”复选框时显示。
在 属性 窗口中,更改 嵌入互操作类型 属性的值更改为 假。
如果正在开发 Visual Basic 项目中,通过使用 项目设计器,则应导入命名空间 ProjectTemplateWizard 到项目中。
有关更多信息,请参见如何:添加或移除导入的命名空间 (Visual Basic)。
配置 SharePointCommands 项目
在 解决方案资源管理器,选择 SharePointCommands 项目节点。
在菜单栏上,依次选择 项目,添加现有项。
在 添加现有项 对话框中,浏览到包含 ProjectTemplateWizard 项目的代码文件的文件夹,然后选择 CommandIds 代码文件。
在 添加 按钮旁边的下箭头,然后在出现的菜单中的 添加为链接 选项。
Visual Studio 添加代码文件添加到 SharePointCommands 项作为链接。代码文件位于 ProjectTemplateWizard 项目,但文件中的代码。SharePointCommands 项也会生成。
在 SharePointCommands 项目中,添加名为 Commands 中的其他代码文件。
选择 SharePointCommands 项目,然后,在菜单栏上,选择 项目,添加引用。
外接 程序集 节点,选择 扩展 节点,以下程序集中例旁边的复选框:
Microsoft.SharePoint
Microsoft.VisualStudio.SharePoint.Commands
选择 确定 按钮将程序集添加到项目中。
创建向导模型、签名管理器和 SharePoint 命令 ID
将代码添加到 ProjectTemplateWizard 项目中以实现示例中的以下组件:
SharePoint 命令 ID。这些字符串标识 SharePoint 命令向导使用。在本演练中,您将向 SharePointCommands 项目中添加代码以实现这些命令。
向导数据模型。
项目签名管理器。
有关这些组件的更多信息,请参见了解向导组件。
定义 SharePoint 命令 ID
在 ProjectTemplateWizard 项目中,打开 CommandIds 代码文件中,用以下代码替换该文件的整个内容。
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"; } }
创建向导模型
打开 SiteColumnWizardModel 代码文件,然后用以下代码替换该文件的整个内容。
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 } }
创建项目签名管理器
打开 ProjectSigningManager 代码文件中,用以下代码替换该文件的整个内容。
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. 中的 SharePoint 项目的内置向导。
![]() |
---|
在以下步骤中将 XAML 或代码添加到项目中后,您的项目将出现某些编译错误。在添加后面的步骤中的代码之后,这些错误将消失。 |
创建向导窗口 UI
在 ProjectTemplateWizard 项目中,打开 WizardWindow.xaml 文件的快捷菜单,然后选择 打开 在设计器中打开窗口。
在设计器的 XAML 视图中,用以下 XAML 替换当前的 XAML。XAML 定义一个 UI,该 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.11.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="< _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 创建的 windows DialogWindow 从基类派生。在添加自定义 WPF 对话框向 Visual Studio 时,我们建议您从该选件类派生自己的对话框具有一致的样式与其他 Visual Studio 对话框,并避免可能发生的模式对话框问题。有关更多信息,请参见如何:创建和管理对话框。
如果正在开发 Visual Basic 项目,请在 Window 元素的 x:Class 属性的 WizardWindow 类名称中移除 ProjectTemplateWizard 命名空间。此元素在 XAML 的第一行。完成后,第一行应类似于以下示例。
<Window x:Class="WizardWindow"
打开 WizardWindow.xaml 文件的代码隐藏文件。
替换该文件的内容,但 using 声明在文件的顶部,用下面的代码。
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
在 ProjectTemplateWizard 项目中,打开 Page1.xaml 文件的快捷菜单,然后选择 打开 在设计器中打开用户控件。
在设计器的 XAML 视图中,用以下 XAML 替换当前的 XAML。XAML 定义一个文本框可以输入在其中要用于调试的用户界面。用户界面还包括用户可以指定的选项按钮该项是否为沙盒。
<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>
如果正在开发 Visual Basic 项目,请从 UserControl 元素的 x:Class 特性中的 Page1 类名称中移除 ProjectTemplateWizard 命名空间。此命名空间位于 XAML 的第一行中。完成上述操作以后,第一行应类似于下面这样:
<UserControl x:Class="Page1"
替换 Page1.xaml 文件的内容,但 using 声明在文件的顶部,用下面的代码。
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
在 ProjectTemplateWizard 项目中,打开 Page2.xaml 文件的快捷菜单,然后选择 打开。
该用户控件即在设计器中打开。
在 XAML 视图中,用以下 XAML 替换当前 XAML。该 XAML 定义了一个 UI,此 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>
如果正在开发 Visual Basic 项目,请从 UserControl 元素的 x:Class 特性中的 Page2 类名称中移除 ProjectTemplateWizard 命名空间。此命名空间位于 XAML 的第一行中。完成上述操作以后,第一行应类似于下面这样:
<UserControl x:Class="Page2"
替换内容 Page2.xaml 文件的代码隐藏文件后,但 using 声明在文件的顶部,用下面的代码。
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 在向导启动和完成时以及向导运行过程中的特定时间调用的方法。
实现向导
在 ProjectTemplateWizard 项目中,打开 SiteColumnProjectWizard 代码文件。
将此文件的全部内容替换为以下代码。
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 命令
在 SharePointCommands 项目中,打开 Commands 代码文件。
将此文件的全部内容替换为以下代码。
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; } } }
检查点
演练进行到此时,向导的所有代码都位于项目中。生成项目以确保编译项目时不会出错。
生成项目
- 在菜单栏上,依次选择 Build,生成解决方案。
从项目模板中删除 key.snk 文件
在演练:使用项目模板创建网站栏项目项(第 1 部分)中,您创建的项目模板包含一个用于对每个“网站栏”项目实例进行签名的 key.snk 文件。不再需要此 key.snk 文件,因为向导现在会为每个项目生成一个新的 key.snk 文件。从项目模板中删除 key.snk 文件,并删除对此文件的引用。
从项目模板中删除 key.snk 文件
在 解决方案资源管理器,在 SiteColumnProjectTemplate 节点下,打开 key.snk 文件的快捷菜单,然后选择 删除。
在出现的确认对话框中,选择 确定 按钮。
在 SiteColumnProjectTemplate 节点下,打开 SiteColumnProjectTemplate.vstemplate 文件,然后从中删除以下元素。
<ProjectItem ReplaceParameters="false" TargetFileName="key.snk">key.snk</ProjectItem>
保存并关闭文件。
在 SiteColumnProjectTemplate 节点下,打开 ProjectTemplate.csproj 或 ProjectTemplate.vbproj 文件,然后从中移除以下 PropertyGroup 元素。
<PropertyGroup> <SignAssembly>true</SignAssembly> <AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile> </PropertyGroup>
删除以下 None 元素。
<None Include="key.snk" />
保存并关闭文件。
将向导与项目模板关联
现在,您已实现向导,您必须将向导与**“网站栏”**项目模板关联。为此,您必须完成以下三个过程:
用强名称对向导程序集进行签名。
获取向导程序集的公钥标记。
在**“网站栏”**项目模板的 .vstemplate 文件中添加对向导程序集的引用。
用强名称对向导程序集进行签名
在 解决方案资源管理器,打开 ProjectTemplateWizard 项目的快捷菜单,然后选择 属性。
在**“签名”选项卡上,选中“为程序集签名”**复选框。
在 选择强名称密钥文件 列表中,选择 <New...>。
在 创建强名称密钥 对话框中,键入新节点的名称密钥文件,清除 使用密码保护密钥文件 复选框,然后选择 确定 按钮。
打开 ProjectTemplateWizard 项目的快捷菜单,然后选择 Build 创建 ProjectTemplateWizard.dll 文件。
获取向导程序集的公钥标记
在 “开始”菜单,选择 所有程序,选择 Microsoft Visual Studio 2012,选择 Visual Studio 工具,然后选择 开发人员 VS2012 的命令提示。
Visual Studio 命令提示符窗口中打开。
运行以下命令,替换 PathToWizardAssembly 使用完整路径为 ProjectTemplateWizard 项目生成的 ProjectTemplateWizard.dll 程序集在开发计算机上:
sn.exe -T PathToWizardAssembly
ProjectTemplateWizard.dll 程序集的公钥标记会写入到 Visual Studio 命令提示符窗口中。
将 Visual Studio 命令提示符窗口保持打开状态。在下面的过程中,将需要公钥标记。
在 .vstemplate 文件中添加对向导程序集的引用
在**“解决方案资源管理器”中,展开“SiteColumnProjectTemplate”**项目节点,然后打开 SiteColumnProjectTemplate.vstemplate 文件。
在该文件末尾附近的 </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 模板)。
保存并关闭文件。
将可替换参数添加到项目模板的 Elements.xml 文件中
将多个可替换参数添加到 SiteColumnProjectTemplate 项目中的 Elements.xml 文件中。将在之前定义的 SiteColumnProjectWizard 类的 RunStarted 方法中初始化这些参数。当用户创建“网站栏”项目时,Visual Studio 会自动将新项目中的 Elements.xml 文件中的这些参数替换为在向导中指定的值。
可替换参数是以美元符号 ($) 字符开始和结束的标记。除了定义您自己的可替换参数外,还可以使用由 SharePoint 项目系统定义和初始化的内置参数。有关更多信息,请参见可替换参数。
向 Elements.xml 文件添加可替换参数
在 SiteColumnProjectTemplate 项目中,用以下 XML 替换 Elements.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 特性的值更改为自定义的可替换参数。
保存并关闭文件。
向 VSIX 包添加向导
若要将向导与包含“网站栏”项目模板的 VSIX 包一起部署,请将对向导项目和 SharePoint 命令项目的引用添加到 VSIX 项目中的 source.extension.vsixmanifest 文件中。
向 VSIX 包添加向导
在 解决方案资源管理器,在 SiteColumnProjectItem 项目中,打开 source.extension.vsixmanifest 文件的快捷菜单,然后选择 打开。
Visual Studio 将在清单编辑器中打开该文件。
在编辑器中 资产 选项卡中,选择 新建 按钮。
添加新资产 对话框打开。
在 类型 列表中,选择 Microsoft.VisualStudio.Assembly。
在 源 列表中,选择 当前解决方案中的项目。
在 项目 列表中,选择 ProjectTemplateWizard,然后选择 确定 按钮。
在编辑器中 资产 选项,请选择 新建 按钮。
添加新资产 对话框打开。
在 类型 列表中,输入 SharePoint.Commands.v4。
在 源 列表中,选择 当前解决方案中的项目。
在 项目 列表中,选择 SharePointCommands 项目,然后选择 确定 按钮。
在菜单栏上,依次选择 Build,生成解决方案,然后确保解决方案已生成且未发生错误。
测试向导
现在已经准备好对向导进行测试。首先,在 Visual Studio 的实验实例中开始调试 SiteColumnProjectItem 解决方案。然后,在 Visual Studio 的实验实例中测试“网站栏”项目的向导。最后,生成并运行项目以验证网站栏是否按预期方式运行。
开始调试解决方案
重新启动使用管理凭据的 Visual Studio,然后打开 SiteColumnProjectItem 解决方案。
在 ProjectTemplateWizard 项目中,打开 SiteColumnProjectWizard 代码文件,然后将断点添加到第一个代码行中 RunStarted 方法的。
在菜单栏上,依次选择 调试,异常。
在 异常 对话框中,确保清除 公共语言运行时异常 的 引发 和 用户未处理的 复选框,然后选择 确定 按钮。
开始调试通过选择 F5 键,或者在菜单栏上,选择 调试,启动调试。
Visual Studio 将扩展安装到 %UserProfile% \ AppData \ local \ Microsoft \ VisualStudio \ 11.0Exp \ extensions \ Contoso \ site Column \ 1.0 中启动 Visual Studio 的实验实例。在此 Visual Studio 实例中测试项目项。
在 Visual Studio 中测试向导
在 Visual Studio 的实验实例中,在菜单栏上,依次选择 文件,新建,项目。
外接 visual C# 节点或 Visual Basic 节点 (具体取决于项目模板支持的语言),再展开 SharePoint 节点,然后选择 2010 节点。
在项目模板列表中,选择 网站栏,将项目命名为 SiteColumnWizardTest,然后选择 确定 按钮。
验证另一个 Visual Studio 实例中的代码是否会在您之前在 RunStarted 方法中设置的断点处停止。
继续选择 F5 键调试该项目,在菜单栏上,选择 调试,继续。
在 SharePoint 自定义向导,输入要用于调试的网站的 URL,然后选择 下一步 按钮。
在**“SharePoint 自定义向导”**的第二页中,进行以下选择:
在 类型 列表中,选择 布尔值。
在 组合 列表中,选择 自定义是/否列。
在 名称 框中,输入"我的是/否列,然后选择 完成 按钮。
在 解决方案资源管理器,新项目将出现并包含一个名为 字段 1的项目项,并且,Visual Studio 将在编辑器中打开项目中的 Elements.xml 文件。
验证 Elements.xml 是否包含您在向导中指定的值。
在 SharePoint 中测试网站栏
在 Visual Studio 的实验实例中,选择 F5 键。
网站栏进行打包并部署到 SharePoint。该项目 网站 URL 属性指定站点。该浏览器将打开此网站的默认页。
说明
如果 脚本调试被禁用 出现对话框,请选择 是 按钮继续调试项目。
在 网站操作 菜单中,选择 站点设置。
在"网站设置"页,在 库下,选择 网站列 链接。
在网站的列,请验证 自定义是/否列 组包含一个名为 我的是/否列的列,然后关闭该浏览器。
清理开发计算机
测试完项目项之后,从 Visual Studio 的实验实例中删除项目模板。
清理开发计算机
在 Visual Studio 的实验实例中,在菜单栏上,依次选择 工具,扩展和更新。
扩展和更新 对话框打开。
在扩展列表中,选择 网站栏,然后选择 卸载 按钮。
在出现的对话框中,选择 是 按钮以确认您要卸载该扩展,然后选择 立即重新启动 按钮来卸载。
关闭 CustomActionProjectItem 解决方案已在中打开 Visual Studio 的实验实例和实例。
有关如何部署 Visual Studio 扩展的信息,请参见 Visual Studio 扩展部署。