今年一直在为MOSS 2007写自制ASP.NET程序, 为了让程序的样式更通用, 更配合MOSS自带的风格, 我花不了少时间调整样式, 建立公用的CSS和JS.
一个偶然的机会, 我从一大堆"无用的"SharePoint内置控件中找到一个可以用的控件, 就是SPGridView. SPGridView提供了MOSS的列表样式, 给我省了不少功夫. 虽然SPGridView跟MOSS列表页里用的ListViewWebPart长得一模一样, 但是ListViewWebPart根本没用到SPGridView, 所以MOSS列表页里那个漂亮的工具条你就别想了 呵呵.
SPGridView是MOSS内置控件中少数能脱离SharePoint List等内置数据源使用的控件, 它继承自System.Web.UI.WebControls.GridView, 但是使用SPGridView时必须手动把AutoGenerateColumns设成false.
1. 先创建一个ASP.NET Web Application(ASP.NET Web应用程序)项目.
该项目模板已经包含在VS2005 SP1中, 如果你不打算安装SP1, 就需要先更新KB915364, 再安装 Microsoft Visual Studio Web Application Projects.
2. 把引用中无用System.Web.Mobile等Assembly都去掉, 加入Microsoft.SharePoint (找不到的人去找块豆腐撞死好了). 再建个名为 ~masterurl 的目录, 放入一个default.master.
default.master的代码如下
1: <%@ Master Language="C#" %>
2: <html>
3: <head runat="server">
4: <asp:contentplaceholder id="PlaceHolderAdditionalPageHead" runat="server"></asp:contentplaceholder>
5: </head>
6: <body>
7: <form id="form1" runat="server">
8: <asp:ContentPlaceHolder ID="PlaceHolderPageTitleInTitleArea" runat="server">
9: </asp:ContentPlaceHolder>
10: <asp:ContentPlaceHolder ID="PlaceHolderMain" runat="server">
11: </asp:ContentPlaceHolder>
12: </form>
13: </body>
14: </html>
项目中的那个instnwnd.sql是取自Microsoft提供的SQL Server 2000 Sample Databases里的Northwind数据库的脚本.使用脚本建立Northwind数据库. 并将连接字符串加入web.config.
3. 建立数据访问类NorthwindData.cs. 后面我们将使用ObjectDataSource组件来为SPGridView提供数据.
1: using System;
2: using System.Data;
3: using System.Data.SqlClient;
4: using System.Configuration;
5:
6: namespace SPGridView_Demo
7: {
8: public class NorthwindData
9: {
10: private static string connectionString = null;
11:
12: public NorthwindData()
13: {
14: if(string.IsNullOrEmpty(connectionString))
15: {
16: connectionString = ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
17: }
18: }
19:
20: public int GetProductCount()
21: {
22: int count = 0;
23: string commandText = "SELECT COUNT(ProductId) FROM dbo.Products";
24: SqlConnection connection = new SqlConnection(connectionString);
25: SqlCommand command = new SqlCommand(commandText, connection);
26: connection.Open();
27: count = Convert.ToInt32(command.ExecuteScalar());
28: connection.Close();
29: return count;
30: }
31:
32: public DataTable GetProductList(string sortExpression)
33: {
34: DataTable dt = new DataTable();
35: string commandText = "SELECT ProductId, ProductName, p.CategoryId, p.UnitPrice, p.Discontinued, c.CategoryName FROM dbo.Products p INNER JOIN dbo.Categories c ON p.CategoryId = c.CategoryId ORDER BY {SORT}";
36:
37: if(sortExpression == null || sortExpression.Trim() == string.Empty)
38: {
39: sortExpression = "ProductId";
40: }
41:
42: commandText = commandText.Replace("{SORT}", sortExpression);
43: SqlConnection connection = new SqlConnection(connectionString);
44: SqlDataAdapter adapter = new SqlDataAdapter(commandText, connection);
45: connection.Open();
46: adapter.Fill(dt);
47: connection.Close();
48: return dt;
49: }
50:
51: public DataTable GetProductList(int startRowIndex, int maximumRows, string sortExpression)
52: {
53: DataTable dt = new DataTable();
54: string commandText = "SELECT * FROM (SELECT ProductId, ProductName, p.CategoryId, UnitPrice, p.Discontinued, c.CategoryName, ROW_NUMBER() OVER (ORDER BY {SORT}) AS RowNumber FROM dbo.Products p INNER JOIN dbo.Categories c ON p.CategoryId = c.CategoryId) a WHERE RowNumber BETWEEN @StartRowIndex + 1 AND @StartRowIndex + @MaximumRows";
55:
56: if(sortExpression == null || sortExpression.Trim() == string.Empty)
57: {
58: sortExpression = "ProductId";
59: }
60:
61: commandText = commandText.Replace("{SORT}", sortExpression);
62: SqlConnection connection = new SqlConnection(connectionString);
63: SqlDataAdapter adapter = new SqlDataAdapter(commandText, connection);
64: adapter.SelectCommand.Parameters.Add(new SqlParameter("@StartRowIndex", startRowIndex));
65: adapter.SelectCommand.Parameters.Add(new SqlParameter("@MaximumRows", maximumRows));
66: connection.Open();
67: adapter.Fill(dt);
68: connection.Close();
69: return dt;
70: }
71: }
72: }
4. 创建 SPGVP1.aspx
我们先试着绑些数据给SPGridView, 再发布到MOSS里看看效果吧.
代码:
1: <%@ Page Language="C#" MasterPageFile="~masterurl/default.master" %>
2:
3: <%@ Register Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WebControls" TagPrefix="cc1" %>
4: <asp:Content ID="Content1" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
5: </asp:Content>
6: <asp:Content ID="Content2" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
7: </asp:Content>
8: <asp:Content ID="Content3" ContentPlaceHolderID="PlaceHolderMain" runat="server">
9: <cc1:SPGridView ID="SPGridView1" runat="server" AutoGenerateColumns="False" DataSourceID="ObjectDataSource1">
10: <Columns>
11: <cc1:SPBoundField DataField="ProductId" HeaderText="Product ID" SortExpression="ProductId" />
12: <asp:HyperLinkField DataTextField="ProductName" DataNavigateUrlFields="ProductId" DataNavigateUrlFormatString="#{0}" HeaderText="Product Name" SortExpression="ProductName" />
13: <cc1:SPBoundField DataField="ProductName" HeaderText="Product Name" SortExpression="ProductName" />
14: <cc1:SPBoundField DataField="CategoryName" HeaderText="Category" SortExpression="CategoryName" />
15: <asp:BoundField DataField="UnitPrice" DataFormatString="${0:F2}" HeaderText="Unit Price" SortExpression="UnitPrice" />
16: <asp:TemplateField HeaderText="Orderable" SortExpression="Discontinued">
17: <itemtemplate>
18: <asp:Label id="lblDiscontinued" runat="server" text='<%# Convert.ToBoolean(Eval("Discontinued")) ? "Yes" : "No" %>'></asp:Label></itemtemplate>
19: </asp:TemplateField>
20: </Columns>
21: </cc1:SPGridView>
22: <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProductList" TypeName="SPGridView_Demo.NorthwindData" SortParameterName="sortExpression"></asp:ObjectDataSource>
23: </asp:Content>
效果:
然后我们给Product Name列加入项菜单吧, 我们计划给它加入2个菜单项, 1个是链接型View Deltail, 另1个是回发型Order Now. 觉得不过瘾再加个带!号的Order Now, 用于区别Unit Price高于$40的产品好了.
我们把Product Name列那行代码
<asp:HyperLinkField DataTextField="ProductName" DataNavigateUrlFields="ProductId" DataNavigateUrlFormatString="#ProductDetail-{0}" HeaderText="Product Name" SortExpression="ProductName" />
<cc1:SPMenuField HeaderText="Product Name" MenuTemplateId="mtProduct" SortExpression="ProductName" TextFields="ProductName" TokenNameAndValueFields="PID=ProductId,PNAME=ProductName" NavigateUrlFields="ProductId" NavigateUrlFormat="#ProductDetail-{0}" />
<cc1:MenuTemplate ID="mtProduct" runat="server">
<cc1:MenuItemTemplate ID="mitView" runat="server" Text="View Detail" ClientOnClickNavigateUrl="#ProductDetail-%PID%" />
<cc1:MenuItemTemplate ID="mitOrder" runat="server" Text="Order Now" />
<cc1:MenuItemTemplate ID="mitOrderWarn" runat="server" Text="Order Now" ImageUrl="/_layouts/images/exclaim.gif" />
</cc1:MenuTemplate>
注意: ID为mitView的菜单项模板使用了ClientOnClickNavigateUrl属性来指定此菜单项的连接, 它类似与HyperLinkField的DataNavigateUrlFormatString属性, 但是变量标识是%Alias%, 而不是{index}或列名. 这里的数据别名(Alias)是在调用此菜单模板的SPMenuField的TokenNameAndValueFields指定的, 格式为 "别名1=列名1,别名2=列名2,...".
特别注意: 我们使用了PNAME 代表ProductName的数据, 如果数据里包含单/双引号等字符, 会导致菜单项失灵. 因为最终控件生成的HTML代码将是 location.href='XXXXX' 或 ''__doPostBack('YYYYYY')". 哈! 像JS注入吧~.
然后我们在SPGridVIew的OnRowDataBound事件中写些判断代码来控制不同情况下菜单模板的显示. 我们不打算让用户订购Discontinued的产品, 并在订购菜单项中使用!号图标提示该产品单价超过了$40.
protected void SPGridView1_RowDataBound(object sender, GridViewRowEventArgs e) { if(e.Row.RowType == DataControlRowType.DataRow) { Microsoft.SharePoint.WebControls.Menu menu = e.Row.Cells[1].Controls[0] as Microsoft.SharePoint.WebControls.Menu; if(menu != null) { bool discontinued = Convert.ToBoolean(DataBinder.Eval(e.Row.DataItem, "Discontinued")); decimal unitPrice = Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "UnitPrice")); if(discontinued) { menu.HiddenMenuItems.Add(this.mitOrder); menu.HiddenMenuItems.Add(this.mitOrderWarn); } if(unitPrice >= 40m) { menu.HiddenMenuItems.Add(this.mitOrder); } else { menu.HiddenMenuItems.Add(this.mitOrderWarn); } } } }
我们还需要给Order Now菜单项添加回发行为.
protected override void OnInit(EventArgs e) { base.OnInit(e); this.mitOrder.ClientOnClickUsingPostBackEventFromControl(this.SPGridView1, "Order:%PID%"); this.mitOrderWarn.ClientOnClickUsingPostBackEventFromControl(this.SPGridView1, "Order:%PID%"); }
private void Order(string pid) { Response.Write(pid + " is ordered."); } protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument) { base.RaisePostBackEvent(sourceControl, eventArgument); if(eventArgument == null || eventArgument.Trim() == string.Empty) { return; } if(eventArgument.Contains(":")) { int posIndex = eventArgument.IndexOf(":"); string commandName = eventArgument.Substring(0, posIndex); string argument = eventArgument.Remove(0, posIndex + 1); switch(commandName) { case "Order": this.Order(argument); break; } this.SPGridView1.DataBind(); } }
<%@ Page Language="C#" MasterPageFile="~masterurl/default.master" %> <%@ Register Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WebControls" TagPrefix="cc1" %> <script runat="server">1:
2: private void Order(string pid)3: {
4: Response.Write(pid + " is ordered.");5: }
6:
7: protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)8: {
9: base.RaisePostBackEvent(sourceControl, eventArgument);10:
11: if(eventArgument == null || eventArgument.Trim() == string.Empty)12: {
13: return;14: }
15:
16: if(eventArgument.Contains(":"))17: {
18: int posIndex = eventArgument.IndexOf(":");19: string commandName = eventArgument.Substring(0, posIndex);20: string argument = eventArgument.Remove(0, posIndex + 1);21:
22: switch(commandName)23: {
24: case "Order":25: this.Order(argument);26: break;27: }
28:
29: this.SPGridView1.DataBind();30: }
31: }
32:
33: protected override void OnInit(EventArgs e)34: {
35: base.OnInit(e);36:
37: this.mitOrder.ClientOnClickUsingPostBackEventFromControl(this.SPGridView1, "Order:%PID%");38: this.mitOrderWarn.ClientOnClickUsingPostBackEventFromControl(this.SPGridView1, "Order:%PID%");39: }
40:
41: protected void SPGridView1_RowDataBound(object sender, GridViewRowEventArgs e)42: {
43: if(e.Row.RowType == DataControlRowType.DataRow)44: {
45: Microsoft.SharePoint.WebControls.Menu menu = e.Row.Cells[1].Controls[0] as Microsoft.SharePoint.WebControls.Menu;46:
47: if(menu != null)48: {
49: bool discontinued = Convert.ToBoolean(DataBinder.Eval(e.Row.DataItem, "Discontinued"));50: decimal unitPrice = Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "UnitPrice"));51:
52: if(discontinued)53: {
54: menu.HiddenMenuItems.Add(this.mitOrder);55: menu.HiddenMenuItems.Add(this.mitOrderWarn);56: }
57:
58: if(unitPrice >= 40m)59: {
60: menu.HiddenMenuItems.Add(this.mitOrder);61: }
62: else63: {
64: menu.HiddenMenuItems.Add(this.mitOrderWarn);65: }
66: }
67: }
68: }
</script> <asp:Content ID="Content1" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server"> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server"> </asp:Content> <asp:Content ID="Content3" ContentPlaceHolderID="PlaceHolderMain" runat="server"> <cc1:SPGridView ID="SPGridView1" runat="server" AutoGenerateColumns="False" DataSourceID="ObjectDataSource1" OnRowDataBound="SPGridView1_RowDataBound"> <Columns> <cc1:SPBoundField DataField="ProductId" HeaderText="Product ID" SortExpression="ProductId" /> <cc1:SPMenuField HeaderText="Product Name" MenuTemplateId="mtProduct" SortExpression="ProductName" TextFields="ProductName" TokenNameAndValueFields="PID=ProductId,PNAME=ProductName" NavigateUrlFields="ProductId" NavigateUrlFormat="#ProductDetail-{0}" /> <cc1:SPBoundField DataField="ProductName" HeaderText="Product Name" SortExpression="ProductName" /> <cc1:SPBoundField DataField="CategoryName" HeaderText="Category" SortExpression="CategoryName" /> <asp:BoundField DataField="UnitPrice" DataFormatString="${0:F2}" HeaderText="Unit Price" SortExpression="UnitPrice" /> <asp:TemplateField HeaderText="Orderable" SortExpression="Discontinued"> <itemtemplate> <asp:Label id="lblDiscontinued" runat="server" text='<%# Convert.ToBoolean(Eval("Discontinued")) ? "Yes" : "No" %>'></asp:Label> </itemtemplate> </asp:TemplateField> </Columns> </cc1:SPGridView> <cc1:MenuTemplate ID="mtProduct" runat="server"> <cc1:MenuItemTemplate ID="mitView" runat="server" Text="View Detail" ClientOnClickNavigateUrl="#ProductDetail-%PID%" /> <cc1:MenuItemTemplate ID="mitOrder" runat="server" Text="Order Now" /> <cc1:MenuItemTemplate ID="mitOrderWarn" runat="server" Text="Order Now" ImageUrl="/_layouts/images/exclaim.gif" /> </cc1:MenuTemplate> <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProductList" TypeName="SPGridView_Demo.NorthwindData" SortParameterName="sortExpression"></asp:ObjectDataSource> </asp:Content>