自定义 SharePoint 列表窗体
自定义 SharePoint 列表使用的窗体。
引言
Microsoft SharePoint 本身拥有一套非常丰富的功能,但有时为了满足项目的需求,您可能需要更多,或者需要一些稍有不同的功能。本文将讨论展示 SharePoint 列表窗体 UI 的各种方法,例如在添加列表项或查看和编辑列表项时使用的“新建”、“编辑”和“显示”窗体。
背景
网上有大量关于如何使用 InfoPath 或 SharePoint Designer 自定义 SharePoint 列表窗体的内容。虽然这些方法可以满足某些要求,但肯定不能涵盖所有情况。InfoPath 快捷方便,在 SharePoint 中得到了广泛应用,尤其是在工作流窗体方面。然而,您不能使用 JavaScript 或代码隐藏进行处理,也不能使用 CSS 来设置窗体样式。尽管 SharePoint Designer 允许您应用 CSS 和使用 JavaScript,因为窗体是站点页面,所以您不能使用代码隐藏或嵌入代码。此外,SharePoint Designer 可能并非人人可用,而且它也不能生成可重用解决方案。
本文中的示例是使用 Visual Studio 2010 为 SharePoint Server 2010 Foundation 及以上版本创建的。本文不讨论创建 ContentType
或 SharePoint 列表的深入细节。假定读者具备 SharePoint 管理经验,例如创建自定义列表,以及 ASP.NET 开发经验。
列表和 ContentTypes
在 SharePoint 中,数据存储在列表(List)中,这些列表关联有一个或多个 ContentType
。这为组织和存储数据提供了极大的灵活性。在本文中,我将创建一个 ContentType
来演示我将要讨论的功能。
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Parent ContentType: Item (0x01) -->
<ContentType ID="0x0100b10e34ab90214c4fa9efbb7653c208d8"
Name="CP Project Base"
Group="Code Project"
Description="Demo contenttype for CodeProject article"
Inherits="TRUE"
Version="0">
<FieldRefs>
<!-- Custom fields -->
<FieldRef ID="{a9f5f963-cd43-437f-9832-fa340e50334a}"
Name="ProjectName" DisplayName="Name"/>
<FieldRef ID="{c55b0ed1-c874-491e-914d-621abd9066f6}"
Name="ProjectDescription" DisplayName="Description"/>
<!-- Builtin fields -->
<FieldRef ID="{64CD368D-2F95-4BFC-A1F9-8D4324ECB007}"
Name="StartDate" DisplayName="Start"/>
<FieldRef ID="{8A121252-85A9-443D-8217-A1B57020FADF}"
Name="_EndDate" DisplayName="End"/>
</FieldRefs>
</ContentType>
</Elements>
如您所见,这里没有什么突破性的东西。该 ContentType
使用两个自定义站点列和两个内置站点列,并将作为我演示 SharePoint 列表窗体相关功能的基础。
SharePoint 列表窗体
创建自定义列表并将 _CP Project Base_ ContentType
与之关联后,单击“添加新项”链接,您应该会看到类似这样的对话框。
这是 SharePoint 为所有列表(本文不涉及的文档库除外)显示的默认窗体。此窗体是如何显示的?SharePoint 如何知道显示什么?
列表定义架构
所有 SharePoint 列表都有一个架构文件,该文件定义了由此创建的任何列表的内容和行为。
<?xml version="1.0" encoding="utf-8"?>
<List xmlns:ows="Microsoft SharePoint"
Title="CPProject Base List Def"
FolderCreation="FALSE"
Direction="$Resources:Direction;"
Url="Lists/SharePointListForms-CPProjectListDef"
BaseType="0"
xmlns="http://schemas.microsoft.com/sharepoint/">
<MetaData>
<ContentTypes>
<ContentType ID="0x0100b10e34ab90214c4fa9efbb7653c208d8"
Name="CP Project"
Group="Code Project"
Description="Demo contenttype for CodeProject article"
Inherits="TRUE"
Version="0">
<FieldRefs>
<FieldRef ID="{a9f5f963-cd43-437f-9832-fa340e50334a}"
Name="ProjectName" DisplayName="Name" />
<FieldRef ID="{c55b0ed1-c874-491e-914d-621abd9066f6}"
Name="ProjectDescription" DisplayName="Description" />
<FieldRef ID="{64CD368D-2F95-4BFC-A1F9-8D4324ECB007}"
Name="StartDate" DisplayName="Start" />
<FieldRef ID="{8A121252-85A9-443D-8217-A1B57020FADF}"
Name="_EndDate" DisplayName="End" />
</FieldRefs>
</ContentType>
</ContentTypes>
<Fields>
<Field ID="{a9f5f963-cd43-437f-9832-fa340e50334a}" Name="ProjectName"
DisplayName="Name" Group="Code Project" Type="Text" />
<Field ID="{c55b0ed1-c874-491e-914d-621abd9066f6}" Name="ProjectDescription"
DisplayName="Description" Group="Code Project" Type="Note" />
<Field ID="{8A121252-85A9-443d-8217-A1B57020FADF}" Name="_EndDate"
Group="$Resources:core,Base_Columns;" Type="DateTime"
DisplayName="$Resources:core,End_Date;"
Format="DateTime"
SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
StaticName="_EndDate">
<Default>[today]</Default>
</Field>
<Field ID="{64cd368d-2f95-4bfc-a1f9-8d4324ecb007}" Name="StartDate"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="StartDate"
Group="$Resources:core,Base_Columns;"
Type="DateTime" Format="DateOnly"
DisplayName="$Resources:core,Start_Date;">
<Default>[today]</Default>
</Field>
</Fields>
<Views>Removed for brevity</Views>
<Forms>
<Form Type="DisplayForm" Url="DispForm.aspx"
SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="EditForm" Url="EditForm.aspx"
SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="NewForm" Url="NewForm.aspx"
SetupPath="pages\form.aspx" WebPartZoneID="Main" />
</Forms>
</MetaData>
</List>
从这个示例中可以看出,ContentType
类型及其包含的字段与从该模板创建的列表实例可用的任何视图一起被定义。为了简洁起见,此处已删除 Views
元素,但完整版本可在本文附带的源代码下载中找到。您应该对这一切都很熟悉,我在这里不再赘述这些元素。
在本讨论中,我们关心的是 Forms
元素。
<Forms>
<Form Type="DisplayForm" Url="DispForm.aspx"
SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="EditForm" Url="EditForm.aspx"
SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="NewForm" Url="NewForm.aspx"
SetupPath="pages\form.aspx" WebPartZoneID="Main" />
</Forms>
这是定义用于显示、编辑和创建新列表项的默认窗体的地方。
Type
属性描述了窗体的类型:新建 (New)、编辑 (Edit) 或显示 (Display)。Url
属性是将被提供的窗体的路径,相对于创建的列表实例。例如:http://mysite/Lists/DemoList/NewForm.aspx。SetupPath
属性是用于提供Url
窗体的窗体路径。此站点页面位于 _<SharePoint Root>\14\TEMPLATE\Pages_。WebPartZoneID
属性与RenderingTemplates
中定义的WebPartZones
相关,我将在稍后讨论。
使用 ContentType 控制窗体显示
控制列表窗体显示内容的简单方法之一是使用 ContentType
定义。
假设,在使用演示 ContentType
时,用户在创建新项时无需填写 EndDate
字段,或者不希望他们填写。也许这是在其他地方使用其他业务逻辑计算得出的,或者响应列表事件或工作流。如您之前所见,该字段默认显示,但可以使用 ShowInXXXForm
属性进行更改。
要防止该字段出现在新项窗体中,我将添加 ShowInNewForm
属性并将其值设置为 FALSE
。存在类似命名的属性,分别对应于编辑窗体和显示窗体:ShowInEditForm
和 ShowInDisplayForm
。
<FieldRef ID="{8A121252-85A9-443D-8217-A1B57020FADF}"
Name="_EndDate" DisplayName="End" ShowInNewForm="FALSE"/>
再次,在从修改后的 ContentType
的列表定义创建列表实例后,您将看到在添加新项时 EndDate
不显示。但是,它在项显示和编辑页面上是可用的。
默认窗体构造
如上所述,列表实例使用的窗体是从 SetupPath
属性中定义的页面 _Pages\fom.aspx_ 提供的。在此页面中,与所有 SharePoint 页面一样,定义了许多内容服务器控件。对于此讨论,我们关注 PlaceHolderMain
,如下所示:
<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
<SharePoint:UIVersionedContent UIVersion="4" runat="server">
<ContentTemplate>
<div style="padding-left:5px">
</ContentTemplate>
</SharePoint:UIVersionedContent>
<table cellpadding="0" cellspacing="0"
id="onetIDListForm" style="width:100%">
<tr>
<td>
<WebPartPages:WebPartZone runat="server"
FrameType="None" ID="Main" Title="loc:Main" />
<img src="https://codeproject.org.cn/_layouts/images/blank.gif"
width='590' height='1' alt="" />
</td>
</tr>
</table>
<SharePoint:UIVersionedContent UIVersion="4" runat="server">
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Parent ContentType: Item (0x01) --><ContentType
ID="0x0100a5d5cfc1e7ca4947bfa173184c15334c"
Name="StateLookup"
Group="LookupTest"
Description="My Content Type"
Inherits="TRUE"
Version="0"><FieldRefs><FieldRef
ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}"
Name="Title" Required="TRUE" Hidden="TRUE"
ShowInNewForm="TRUE" ShowInEditForm="TRUE"
ReadOnly="FALSE" PITarget="" PrimaryPITarget=""
PIAttribute="" PrimaryPIAttribute=""
Aggregation="" Node="" /></FieldRefs></ContentType></Elements>
<ContentTemplate>
</div>
</ContentTemplate>
</SharePoint:UIVersionedContent>
</asp:Content>
我们可以进一步缩小讨论范围,只关注 WebPartZone
ID 为“Main”的那个。这对应于我之前提到的 WebPartZoneId
属性,并且是列表项字段将要渲染的地方。SharePoint 在此 WebPartZone
中插入一个 ListFormWebPart
,它将使用与可选 Template
属性匹配的 RenderingTemplate
来渲染列表项的内容。如果未定义,则默认为 ListForm
。可以通过将 UseDefaultListFormWebPart
属性设置为 false
并在列表定义架构的 AllUsersWebPart
元素中提供 WebPart 来覆盖默认 ListFormWebPart
的使用。
<Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx"
WebPartZoneID="Main" UseDefaultListFormWebPart="False">
<WebParts>
<AllUsersWebPart WebPartZoneID="Main" WebPartOrder="1">
<![CDATA[
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2"
xmlns:iwp="http://schemas.microsoft.com/WebPart/v2/Image">
<Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c</Assembly>
<TypeName>Microsoft.SharePoint.WebPartPages.ImageWebPart</TypeName>
<FrameType>None</FrameType>
<Title>My Custom Image</Title>
<iwp:ImageLink>/_layouts/images/homepage.gif</iwp:ImageLink>
</WebPart>]]>
</AllUsersWebPart>
<AllUsersWebPart WebPartZoneID="Main" WebPartOrder="2">
<![CDATA[
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2">
<Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c</Assembly>
<TypeName>Microsoft.SharePoint.WebPartPages.ListFormWebPart</TypeName>
<PageType>PAGE_NEWFORM</PageType>
</WebPart>]]>
</AllUsersWebPart>
</WebParts>
</Form>
尽管未使用默认的 ListFormWebPart
,但您仍必须包含一个,否则在从此定义创建列表时会发生错误。
为 URL _Lists/DemoList3/NewForm.aspx_ 定义了一个窗体,但未在 AllUsersWebPart
XML 元素中检测到有效的 WebPart 类型。请确保在类型为 NewForm
的 AllUsersWebPart
元素的 CDATA
元素中至少定义一个 WebPart,或者将 Form
元素的 UseDefaultListFormWebPart
XML 属性设置为 True
。
诚然,这并不是一个非常有用的实现,但它至少展示了如何在列表窗体中包含额外的 WebPart。要获得对显示的更多控制,您需要创建一个自定义 RenderingTemplate
。
RenderingTemplates
由于 SharePoint 是为支持大量自定义而构建的,因此创建每个情况的窗体或 WebPart 都是不可能的。为了克服这一点,ListFormWebPart
是一个模板控件,意味着它接受一个 ITemplate
派生控件,该控件用于根据正在显示的列表类型及其模式(显示、编辑或新建)来按需渲染 UI。
protected override void CreateChildControls()
{
if (!this.hideWebPart)
{
if (base.PageComponent != null)
{
this.ItemContext.CurrentPageComponent = base.PageComponent;
}
if (((SPContext.Current != null) && SPContext.Current.IsDesignTime) ||
(this.UseLegacyForm() || (this.Template == null)))
{
base.CreateChildControls();
}
else
{
this.Controls.Clear();
TemplateContainer container = new TemplateContainer {
ControlMode = PageTypeToControlMode(this.pageType),
ItemContext = this.ItemContext
};
this.Template.InstantiateIn(container);
this.Controls.Add(container);
}
}
}
[WebPartStorage(Storage.None), DefaultValue((string) null),
Browsable(false), TemplateContainer(typeof(TemplateContainer))]
public ITemplate Template
{
get
{
if ((this.template == null) && (this.TemplateName != null))
{
this.template = SPControlTemplateManager.GetTemplateByName(this.TemplateName);
}
return this.template;
}
set
{
this.template = value;
}
}
ListViewWewPart
中 UI 使用的模板包含在 RenderingTemplates
中,这些模板可以在 _<SharePoint Root>\14\TEMPLATE\CONTROLTEMPLATES\DefautlTemplates.ascx_ 中找到。此用户控件包含 145 个 RenderingTemplate
,用于显示字段(如 NumberField
和 CurrencyField
)以及窗体 UI(如 SurveyForm
或 WkiEditForm
)。对于本讨论,我们感兴趣的是 ID 为 ListForm
的 RenderingTemplate
。
<SharePoint:RenderingTemplate id="ListForm" runat="server">
<Template>
<span id='part1'>
<SharePoint:InformationBar ID="InformationBar1" runat="server"/>
<div id="listFormToolBarTop">
<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop"
RightButtonSeparator=" " runat="server">
<Template_RightButtons>
<SharePoint:NextPageButton runat="server"/>
<SharePoint:SaveButton runat="server"/>
<SharePoint:GoBackButton runat="server"/>
</Template_RightButtons>
</wssuc:ToolBar>
</div>
<SharePoint:FormToolBar ID="FormToolBar1" runat="server"/>
<SharePoint:ItemValidationFailedMessage
ID="ItemValidationFailedMessage1" runat="server"/>
<table class="ms-formtable" style="margin-top: 8px;" border="0"
cellpadding="0" cellspacing="0" width="100%">
<SharePoint:ChangeContentType ID="ChangeContentType1" runat="server"/>
<SharePoint:FolderFormFields ID="FolderFormFields1" runat="server"/>
<SharePoint:ListFieldIterator ID="ListFieldIterator1" runat="server"/>
<SharePoint:ApprovalStatus ID="ApprovalStatus1" runat="server"/>
<SharePoint:FormComponent ID="FormComponent1"
TemplateName="AttachmentRows" runat="server"/>
</table>
<table cellpadding="0" cellspacing="0"
width="100%"><tr><td class="ms-formline">
<img src="/_layouts/images/blank.gif" width='1' height='1'
alt="" /></td></tr></table>
<table cellpadding="0" cellspacing="0" width="100%"
style="padding-top: 7px"><tr><td width="100%">
<SharePoint:ItemHiddenVersion ID="ItemHiddenVersion1" runat="server"/>
<SharePoint:ParentInformationField
ID="ParentInformationField1" runat="server"/>
<SharePoint:InitContentType ID="InitContentType1" runat="server"/>
<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl"
RightButtonSeparator=" " runat="server">
<Template_Buttons>
<SharePoint:CreatedModifiedInfo runat="server"/>
</Template_Buttons>
<Template_RightButtons>
<SharePoint:SaveButton runat="server"/>
<SharePoint:GoBackButton runat="server"/>
</Template_RightButtons>
</wssuc:ToolBar>
</td></tr></table>
</span>
<SharePoint:AttachmentUpload ID="AttachmentUpload1" runat="server"/>
</Template>
</SharePoint:RenderingTemplate>
正如您所见,在此模板中,有许多不同的控件,如 FormToolBar
和 InformationBar
,SharePoint 使用它们来显示我们在列表窗体中看到的 UI 组件。特别值得关注的是 ListFieldIterator
。这是实际显示字段(带有标签和编辑或显示控件)的控件,其中许多也使用 RenderTemplates
。
要创建和使用自定义 RenderingTemplate
,我将首先从 _DefaultTeamplates.ascx_ 复制 ListForm
模板,并将其放在项目中的新用户控件中。在此需要注意的一点是,通过 Visual Studio 2010 添加用户控件时,它会在映射的 _CONTROLTEMPLATES_ 文件夹下为新文件创建一个文件夹。但是,这对于 RenderingTemplate
不起作用。SPControlTemplateManager.GetTemplateByName
方法只查找 _CONTROLTEMPLATES_ 文件夹,而不查找子文件夹,因此您需要确保将此文件移出子文件夹。
要使用此模板,您必须按如下方式修改 Contenttype
_Element.xml_ 文件中的 ContentType
元素。
<XmlDocuments>
<XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
<FormTemplates xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
<New>CPListForm</New>
</FormTemplates>
</XmlDocument>
</XmlDocuments>
New
元素中指定的值必须与 RenderingTemplate
中的 ID
属性匹配。
在使用自定义 RenderingTemplate
时,可能会出现两个重大且未充分记录的问题。首先,您可能会注意到使用的是默认 RenderingTemplate
而不是您创建的自定义模板。这是因为 ContentType
上的 Inherits
属性。出于一个尚未解释的原因,它必须被移除或设置为 false,以便使用自定义 RenderingTemplate
。
Inherits="FALSE"
第二个问题出现在使用 DefaultTemplates.ascx
中的默认 RenderingTemplate
作为基类时。如上所示,它包含两个 wssuc:ToolBar
控件的实例。
<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop"
RightButtonSeparator=" " runat="server">
<Template_RightButtons>
<SharePoint:NextPageButton runat="server"/>
<SharePoint:SaveButton runat="server"/>
<SharePoint:GoBackButton runat="server"/>
</Template_RightButtons>
</wssuc:ToolBar>
当您创建列表并尝试显示新项窗体时,可能会惊讶地发现没有任何内容被渲染,如下图所示。没有错误消息,没有异常,只有一个空的窗体。
问题在于,当通过 Visual Studio 创建新用户控件时,它不包含 Toolbar 的 Register
标签。
<%@ Register TagPrefix="wssuc" TagName="ToolBar" src="~/_controltemplates/ToolBar.ascx" %>
添加此标签后,窗体将按预期使用自定义 RenderingTemplate
进行渲染。
自定义列表窗体
自定义列表窗体的另一种方法是使用自定义应用程序页面。这为您提供了对 UI 的完全控制,并且您必须实现所有功能,包括所需的任何工具栏、按钮或字段。
使用自定义列表窗体与上面描述的 RenderingTemplate
方法类似,只是您使用 FormUrls
元素而不是 FormTemplates
元素。New
元素的值是将被使用的应用程序页面的路径。在此示例中,我将页面放置在站点中使用的任何其他应用程序页面一样,放在 _LAYOUTS_ 文件夹下,并放在功能子文件夹中。
<XmlDocuments>
<XmlDocument
NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
<FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
<New >_layouts/SharePointListForms/CPNewItem.aspx</New>
</FormUrls>
</XmlDocument>
</XmlDocuments>
与上一个示例一样,只有当 ContentType
使用 Inherits=FALSE
时,才会使用应用程序页面。
如您所见,窗体是一个空白的画布,没有工具栏或其他元素,由开发人员负责构建页面并提供必要的功能。
结论
在本文中,我回顾了显示 SharePoint 列表窗体 UI 的各种方法,从简单地隐藏字段到使用应用程序页面进行完全自定义。与 SharePoint 的大多数情况一样,有很多方法可以完成同一件事,选择哪种方法最适合特定情况取决于知识和经验。我希望本文能为两者都做出贡献。
关注点
使用 RenderingTemplate
或应用程序页面时,要记住的一个主要点是 ContentType
必须使用 Inherits=false
。
历史
- 2011/7/9:初始发布。
- 2011/7/11:更新了源代码。