1. 引言
在前幾次任務裡開發的星級控制項僅適用於靜態展示,例如標明某個軟體的受歡迎度,但是實際上很多網站還希望能夠由使用者對某一資訊進行評分,最終計算出該資訊的受歡迎程度,使資料更為客觀和可信,由此需要在原有的星級控制項上加以改進,使使用者能夠動態評分,實際的看起來如所示:
圖中第一行是經過評分後控制項的狀態,開發人員處理了評分事件並在頁面輸出了選擇的分數;圖中第二行顯示了另外一種評分狀態——滑鼠移動到了星形圖案上,此時使用紅色的星形提示使用者。
2. 分析
對於該控制項我們要在原有控制項的基礎上加入兩個特性:
1. 加入滑鼠懸浮指示,當滑鼠移至上方時顯示出使用者選擇的分數。
2. 在滑鼠點擊時能夠回傳到伺服器並將相應事件暴露出來由開發人員處理。
對於第一個需求,可以處理JavaScript中的滑鼠事件,當觸發onmouseover事件時判斷當前滑鼠移至上方在第幾個星形圖案,接著顯示紅色星形圖案。顯示圖案時同樣有一些技巧,在原有顯示星形圖案的層(div)中嵌套層,並將該層的背景設定為紅色星形圖案,在頁面載入的時候該層不應該被顯示出來(設定寬度為0即可),並且在滑鼠移至上方時設定該層的寬度,我們只需要注意能夠使該層將背景層覆蓋即可。
同樣要考慮的是,在滑鼠移出的時候需要將該層隱藏起來,那麼只需要處理onmouseout事件,將層的寬度再次設定為0即達到了隱藏層的目的。
對於第二個需求,首先要在自訂控制項中暴露一個公開事件使開發人員能夠訂閱該事件,接下來就是在用戶端產生一個回傳指令碼,使得在點擊(JavaScript中的onclick事件觸發)時執行此回傳指令碼提交到伺服器即可。
為了產生回傳指令碼,使自訂控制項實現IPostBackEventHandler介面,該介面定義了ASP.NET伺服器控制項為處理回傳事件而必須實現的方法。
在提交到伺服器之後,自訂控制項調用公開的事件,並且需要星形圖案替換為黃色背景標識使用者評分,那麼我們同樣可以在背景層中再加入一個層,使用和第一種需求相同的演算法將背景層覆蓋就可以了。
需要確認的是,在使用者評分之後就不允許再次評分了(將使用者當前滑鼠移至上方選擇評分的層隱藏起來即可)。
最後要考慮的問題是,自訂控制項中使用了伺服器端控制項作為容器顯示圖片,為了避免頁面上放置多個自訂控制項發生問題,需要保證此時各伺服器端控制項產生的用戶端編號唯一,您一定已經想到瞭解決方法,就是實現INamingContainer介面。
3. 實現
3.1 在ControlSolution解決方案的ControlLibrary類庫中建立繼承自WebControl的PostStart類,並使其實現IPostBackEventHandler和INamingContainer介面:
using System;using System.ComponentModel;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls; namespace ControlLibrary{ public class PostStar : WebControl,IPostBackEventHandler,INamingContainer { }}
3.2 為自訂控制項添加得分和注釋屬性:
[DefaultValue(0)]public int Score{ get { object obj = ViewState["Score"]; return obj == null ? 0 : Convert.ToInt32(obj); } internal set { ViewState["Score"] = value; }} public string Comment{ get { object obj = ViewState["Comment"]; return obj == null ? string.Empty : Convert.ToString(obj); } set { ViewState["Comment"] = value; }}
3.3 重寫CreateChildControls方法建立子控制項層次,在該方法中調用了CreateControlHierarchy方法:
protected override void CreateChildControls(){ base.CreateChildControls(); CreateControlHierarchy();}
3.4 編寫CreateControlHierarchy方法,在該方法中首先在子控制項集合中建立了一個一行兩列的表格,接下來調用CreateComment方法在第一頁中輸出注釋,然後調用了CreateStart方法建立星形圖案:
protected virtual void CreateControlHierarchy(){ Table table = new Table(); TableRow row = new TableRow(); table.Rows.Add(row); TableCell comment = new TableCell(); CreateComment(comment); row.Cells.Add(comment); TableCell stars = new TableCell(); CreateStars(stars); row.Cells.Add(stars); this.Controls.Add(table);}
3.5 實現CreateComment方法,簡單的將注釋文本賦值給儲存格的Text屬性:
private void CreateComment(TableCell cell){ cell.Text = Comment;}
3.6 編寫建立星形圖案的CreateStars方法,在該方法中調用了RegisterCSS方法向頁面註冊使用的CSS樣式表檔案(該檔案作為資源檔發布),接下來分別調用了CreateBackPanel、CreateCurrentPanel、和CreateChangePanel方法用於建立背景層、標識選中以後的層和表示當前滑鼠移至上方的層,而且還調用了CreateList方法以建立列表,最後按照階層將層和列表組織起來(使用Panel控制項展示層):
private void CreateStars(TableCell cell){ RegisterCSS(); string starPath = Page.ClientScript.GetWebResourceUrl(this.GetType(), "ControlLibrary.Image.stars.gif"); Panel panBg = CreateBackPanel(starPath); cell.Controls.Add(panBg); Panel panCur = CreateCurrentPanel(starPath); Panel panChange =CreateChangePanel(starPath); HtmlGenericControl ul = CreateList(); panBg.Controls.Add(ul); panBg.Controls.Add(panCur); panCur.Controls.Add(panChange); }
3.7 實現RegisterCSS方法,取得樣式表資源檔並使用HtmlLink類註冊樣式表:
private void RegisterCSS(){ string css = Page.ClientScript.GetWebResourceUrl(this.GetType(), "ControlLibrary.CSS.star.css"); HtmlLink link = new HtmlLink(); link.Href = css; link.Attributes.Add("rel", "stylesheet"); link.Attributes.Add("type", "text/css"); Page.Header.Controls.Add(link);}
3.8 編寫CreateBackPanel、CreateCurrentPanel、CreateChangePanel和CreateList方法實現:
private Panel CreateBackPanel(string starPath){ Panel panBg = new Panel(); panBg.ID = "divBg"; panBg.Style.Add(HtmlTextWriterStyle.BackgroundImage, starPath); panBg.CssClass = "stars"; return panBg;} private Panel CreateCurrentPanel(string starPath){ Panel panCur = new Panel(); panCur.ID = "divCur"; panCur.Style.Add(HtmlTextWriterStyle.BackgroundImage, starPath); panCur.CssClass = "current"; return panCur;} private Panel CreateChangePanel(string starPath){ Panel panChange = new Panel(); panChange.ID = "divChange"; panChange.Style.Add(HtmlTextWriterStyle.BackgroundImage, starPath); panChange.CssClass = "change"; return panChange;} private HtmlGenericControl CreateList(){ HtmlGenericControl ul = new HtmlGenericControl("ul"); ul.ID = "ulist"; ul.Attributes.Add("class", "ulist"); for (int i = 0; i < 5; i++) { HtmlGenericControl li = new HtmlGenericControl("li"); li.Attributes.Add("value", (i + 1).ToString()); ul.Controls.Add(li); } return ul;}
3.10 重寫Render方法呈現控制項,該方法調用了PrepareControlForReader方法:
protected override void Render(HtmlTextWriter writer){ PrepareControlForRender(); base.Render(writer);}
3.11 實現PrepareControlForRender,在該方法中按照控制項層次取出列表中的清單項目並註冊JavaScript事件:
private void PrepareControlForRender(){ if (this.Controls.Count < 1) return; Table table = (Table)this.Controls[0]; table.CellSpacing = 0; table.CellPadding = 0; TableCell cell = table.Rows[0].Cells[1]; Panel panCur = (Panel)cell.Controls[0].Controls[1]; Panel panChange = (Panel)panCur.Controls[0]; HtmlGenericControl ul = (HtmlGenericControl)cell.Controls[0].Controls[0]; for (int i = 0; i < ul.Controls.Count; i++) { HtmlGenericControl li = (HtmlGenericControl)ul.Controls[i]; li.Attributes.Add("onmouseover", "document.getElementById('" + panChange.ClientID + "').style.width='" + 16 * (i + 1) + "px';"); li.Attributes.Add("onmouseout", "document.getElementById('" + panChange.ClientID + "').style.width='0px';"); li.Attributes.Add("onclick", Page.ClientScript.GetPostBackClientHyperlink(this, (i + 1).ToString())); }}
可以看到在清單項目的點擊事件(onclick)中觸發了伺服器回傳,這裡是通過Page.ClientScript.GetPostBackClientHyperlink方法完成的,該方法傳遞兩個參數,第一個參數引用引起回傳的伺服器控制項,第二個參數標識回傳時傳遞的參數,這裡傳遞了星形圖案的索引。
|
為什麼不在建立列表的同時註冊JavaScript指令碼,這是由於在CreateChildControls方法執行時,各伺服器端控制項的ClientID尚未產生,此時註冊的指令碼在用戶端操作時會發生錯誤。 |
3.12 為了能夠觸發事件,首先編寫事件參數類StarEventArgs,該類定義了Score屬性儲存得分:
public class StarEventArgs : EventArgs{ public int Score { get; set; }}
3.13 在PostStar中聲明事件屬性,注意這裡和上次任務不同,將事件聲明為私人欄位並通過屬性暴露出來(相比以前的用法更推薦使用這種用法):
private event EventHandler<StarEventArgs> _postScore; public event EventHandler<StarEventArgs> PostScore{ add { _postScore += value; } remove { _postScore -= value; }}
3.14 編寫OnPostScore方法呼叫事件並從控制項層次中取得相應的層以固定得分顯示:
private void OnPostScore(object sender, StarEventArgs e){ if (_postScore != null) _postScore(sender, e); TableCell cell = ((Table)this.Controls[0]).Rows[0].Cells[1]; Panel panCur = (Panel)cell.Controls[0].Controls[1]; panCur.Style.Add("width", e.Score * 16 + "px"); Panel panChange = (Panel)panCur.Controls[0]; panChange.Style.Add("display", "none"); HtmlGenericControl ul = (HtmlGenericControl)cell.Controls[0].Controls[0]; ul.Style.Add("display", "none");}
3.15 實現IPostBackEventHandler介面的RaisePostBackEvent方法,在用戶端引起伺服器回傳後會執行此方法並傳遞相應的參數,我們只需要在此方法中確保已建立了所有的子控制項後引發相應的事件以設定屬性即可:
public void RaisePostBackEvent(string args){ if (!string.IsNullOrEmpty(args)) { EnsureChildControls(); int score = Convert.ToInt32(args); StarEventArgs e = new StarEventArgs(); e.Score = score; OnPostScore(this, e); }}
3.16 在Web網站中建立測試頁面,註冊並聲明自訂控制項:
<%@ Register TagPrefix="cc" Assembly="ControlLibrary" Namespace=?ControlLibrary? %><cc:PostStar ID="star" runat="server" Comment="Windows XP" OnPostScore="star_PostScore" />
附錄:樣式表檔案:
.stars {width: 80px; height: 16px; text-align: left; overflow: hidden; position: relative; background: url(stars.gif) 0px -32px repeat-x;}.stars .ulist { list-style: none; position: absolute; bottom:0px; margin:0px; padding: 0; }.stars .ulist li { display: inline; float: left; width: 16px; height: 16px; cursor: pointer; overflow: hidden; }.stars .current {width: 0px; height: 16px; background: url(stars.gif) 0px 0px repeat-x;}.stars .change {width: 0px; height: 16px; background: url(stars.gif) 0px -16px repeat-x;}
4.總結
本次任務裡我們建立了一個可以由使用者評分的自訂控制項,PostStart類繼承了IPostEventHandler介面以引發伺服器提交,通過引入該介面,我們可以根據需要使任何一個控制項引起伺服器提交——只需要註冊相應的JavaScript事件即可;同時為了防止頁面使用多個評等控制項時出現錯誤,實現了INamingContainer介面。在接下來的任務裡,將介紹自訂資料繫結控制項的開發方法。
ASP.NET自訂控制項系列文章
前言
第一天 簡單的星級控制項
第二天 帶有自訂樣式的星級控制項
第三天 使用控制項狀態的星級控制項
第四天 摺疊面板自訂控制項
第五天 可以評分的星級控制項
第六天 可以綁定資料來源的星級控制項
第七天 開發具有豐富特性的清單控制項
第八天 顯示多個條目星級評等的資料繫結控制項
第九天 自訂GridView
第十天 實現分頁功能的DataList
全部源碼下載
本系列文章PDF版本下載