導航組件是所有網站的基本組件。雖然把用戶從一個網頁轉換到另一個網頁非常的容易,但創建一個整個網站適用的統一的導航系統就不那么簡單了。雖然可以通過少量的鏈接(大量的工作)建立自己的導航系統,但何不使用 ASP.NET 已經有的內建導航系統?
本章涉及 3 個主要概念:
- MultiView 控件和 Wizard 控件。它們可以把若干個步驟濃縮到一個頁面里,把幾頁的工作組合在一起簡化導航。
- 站點地圖模型。它允許定義網站的導航結構並直接綁定到富控件。
- 富導航控件。這類控件包括 TreeView 和 Menu。雖然它們並不僅僅限於導航,但它們非常適合用於導航。
ASP.NET 4 中導航的變化
- 路由。它最初被作為 ASP.NET MVC 的一部分引入,但現在也可以用於處理 URL 並把請求重定向到合適的 Web 表單。路由的主要優點是支持更清晰、更合乎邏輯的 URL 系統,它能夠讓搜索引擎更輕易的發現和索引網站的內容。
- 更靈活的向導。Wizard 控件現在支持一個全新的 LayoutTemplate,如果要用我們自己設計的布局代替內置的布局,那么它能夠帶給你更多的支持。
- 更標准的菜單。Menu 控件不再使用 HTML 表,而是通過一組合理設置的 CSS 樣式創建自己的布局。
多視圖頁面
大多數網站把任務分解到幾個頁面里。例如,在一個電子商務網站里,如果往購物車里添加一個項並把它帶到結賬處,就需要從一個頁面跳轉到另一個頁面。這是最簡單的方式,同時也容易通過編程實現(只要通過某種狀態管理技術把信息從一個頁面傳送到另一個頁面)。
在其他一些情形下,你可能會希望把原本在幾個不同頁面里的代碼放到同一個頁面。例如,你希望提供同一數據的不同視圖,用戶可以切換視圖而不必離開當前頁面;或者,你可能需要處理一個多步驟的小任務(好比注冊賬號的流程)而不希望為如何在頁面間傳送相關信息傷腦筋。
使用多個頁面還是在一個頁面中提供多個視圖,從用戶的角度來說可能沒什么區別。在一個設計良好的網站里,用戶看到的只是采用多個視圖的方式保持 URL 不變。主要的差別在於編程模型。
使用多個頁面,代碼能更好的分離,但需要花更多精力處理頁面如何交互(它們共享或傳送信息的方式)。
使用多個視圖,你會失去代碼分離的好處,但更容易為不可分解的細小任務編碼。
在 ASP.NET 1.x 里,在一個頁面中建立多個視圖的唯一辦法是在頁面中添加若干個 Panel 控件,每個面板可以代表一個視圖或一個步驟,然后使用 Visible 屬性進行控制。你不得不在頁面里添加管理面板的額外代碼,此外,它不是很健壯,一些微小的錯誤可能導致同時顯示兩個面板。
有了 ASP.NET 4,你不必再從零開始設計自己的多視圖系統,而是可以使用兩個更高級的控件來幫助簡化設計:MultiView 和 Wizard 。
MultiView 控件
MultiView 是兩個多視圖控件中較簡單的一個。就本質而言,MultiView 提供了一個聲明多個視圖但每次只顯示其中一個的方式。MultiView 沒有默認的用戶界面,它和前面介紹的自定義面板的方式等效。
創建 MultiView 的過程非常的直觀,看下面示例即能明白:
<asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0">
<asp:View ID="View1" runat="server">
<b>Showing View #1<br /><br /><asp:Image ID="Image1" runat="server" ImageUrl="../images/cookies.jpg" /></b>
</asp:View>
<asp:View ID="View2" runat="server">
<b>Showing View #2</b><br /><br />Text Content.
</asp:View>
<asp:View ID="View3" runat="server">
<b>Showing View #3</b><br /><br /><asp:Calendar ID="Calendar1" runat="server"></asp:Calendar>
</asp:View>
</asp:MultiView>
另外,也可以通過編程添加視圖,實例化一個新的視圖對象通過 Views 集合的 Add()或 AddAt()把它加入到 MultiView 。
添加一些控件對 MultiView 進行測試:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DropDownList1.DataSource = MultiView1.Views;
DropDownList1.DataTextField = "ID";
DropDownList1.DataBind();
}
}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
MultiView1.ActiveViewIndex = DropDownList1.SelectedIndex;
}
protected void MultiView1_ActiveViewChanged(object sender, EventArgs e)
{
DropDownList1.SelectedIndex = MultiView1.ActiveViewIndex;
}
所有按鈕(Button、ImageButton、LinkButton)觸發的代碼邏輯當然可以手工實現,不過 ASP.NET 也提供了智能響應,只要依照下表對按鈕的 CommandName 屬性正確設置即可:
命令名稱 |
MultiView 字段 |
描 述 |
PreView | PreviousViewCommandName | 移動到前一視圖 |
NextView | NextViewCommandName | 移動到后一視圖 |
SwitchViewByID | SwitchViewByIDCommandName | 移動到具有特定 ID 的視圖,這個 ID 從按鈕控件的 CommandArgument 屬性獲得 |
SwitchViewByIndex | SwitchViewByIndexCommandName | 移動到特定數字序號的視圖。這個序號從按鈕控件的 CommandArgument 屬性獲得 |
性能問題
關於 MultiView 要知道的最重要的細節是:
- 它和富數據控件(GridView、FormView 等)不同,MultiView 不是命名容器,簡單地說,MultiView 中不能存在相同名稱的控件,如,兩個叫 textBox1 的文本框。
- 視圖里引發的事件可以很方便的配置其他視圖里的控件。
- 加入視圖里的控件,和頁面其他部分的控件沒有什么區別。
由於這些原因,用 MultiView 創建的頁面比普通頁面的負載大,這是因為整個控件模型(包括所有視圖上的控件),都在每次回發時創建並持久化到視圖狀態里。
多數情況下,它們對性能不會有太大的影響。除非你正在通過編程處理大量的控件(此時你可能需要設置這些控件的 EnableViewState 為 false);或者你正在使用多個數據源,每次回發時所有的數據源都執行自己的查詢,所有的視圖都會被重新綁定(包括那些不顯示的視圖),為了避免這個情況可以默認控件不綁定,而通過編程來綁定,或者取消當前不可見的視圖的綁定過程。
當然,不是所有的 MultiView 都會涉及數據綁定。應用 MultiView 的理想場景是處理一組擴展的輸入控件。例如,把一個調查問卷窗體分解成幾個視圖,這樣用戶不需要頻繁使用滾動條了。這個場景 MultiView 會取得很好的效果,調查結束后可以讀取所有視圖中控件的數據。
Wizard 控件
Wizard 控件遠比 MultiView 控件更富魅力。它還支持每次顯示幾個視圖中的一個,且還包含一系列自定義的內建行為,包括導航按鈕、帶有分步鏈接的側欄、樣式和模板。
Wizard 控件提供導航按鈕並在左邊提供一個帶有每個步驟鏈接的側欄,設置 Wizard.DisplaySideBar 可 顯示 / 隱藏 側欄。
1. 向導步驟
屬性 |
描 述 |
Title | 用在側欄作為鏈接顯示的文字 |
StepType | 它的值來自 WizardStepType 枚舉。這個值確定步驟顯示導航按鈕的類型。 |
AllowReturn | 表示用戶是否可以回到這一步。如果設為 false,一旦完成這一步就再也不能返回這里。側欄的鏈接對這個步驟不起作用,它的下一步驟的 Previous 按鈕要么跳過這一步,要么徹底隱藏(取決於先前一個步驟的 AllowReturn 值) |
下面的向導包括 4 個步驟,它們一起組成一個問卷。問卷結束時添加了 Complete 步驟,它顯示一些匯總信息。導航按鈕和側欄是自動加入的:
<asp:Wizard ID="Wizard1" runat="server" Width="448px" BackColor="#EFF3FB" BorderColor="#B5C7DE"
BorderWidth="1px" Font-Names="Verdana" CellPadding="5" ActiveStepIndex="0" Font-Size="Small"
OnFinishButtonClick="Wizard1_FinishButtonClick">
<WizardSteps>
<asp:WizardStep ID="WizardStep1" runat="server" Title="Personal">
<h3>
Personal Profile</h3>
Preferred Programming Language:
<asp:DropDownList ID="lstLanguage" runat="server">
<asp:ListItem>C#</asp:ListItem>
<asp:ListItem>VB</asp:ListItem>
<asp:ListItem>J#</asp:ListItem>
<asp:ListItem>Java</asp:ListItem>
<asp:ListItem>C++</asp:ListItem>
<asp:ListItem>C</asp:ListItem>
</asp:DropDownList>
<br />
</asp:WizardStep>
<asp:WizardStep ID="WizardStep2" runat="server" Title="Company">
<h3>
Company Profile</h3>
Number of Employees:
<asp:TextBox ID="txtEmpCount" runat="server"></asp:TextBox><br />
Number of Locations: <asp:TextBox ID="txtLocCount" runat="server"></asp:TextBox>
</asp:WizardStep>
<asp:WizardStep ID="WizardStep3" runat="server" Title="Software">
<h3>
Software Profile</h3>
Licenses Required:
<asp:CheckBoxList ID="lstTools" runat="server">
<asp:ListItem>Visual Studio 2008</asp:ListItem>
<asp:ListItem>Office 2007</asp:ListItem>
<asp:ListItem>Windows Server 2008</asp:ListItem>
<asp:ListItem>SQL Server 2008</asp:ListItem>
</asp:CheckBoxList>
</asp:WizardStep>
<asp:WizardStep ID="Complete" runat="server" Title="Complete " StepType="Complete">
<br />
<asp:Label ID="lblSummary" runat="server" Text="Label"></asp:Label>
<br />
<br />
Thank you for completing this survey.<br />
Your products will be delivered shortly.<br />
<br />
</asp:WizardStep>
</WizardSteps>
<SideBarStyle VerticalAlign="Top" />
</asp:Wizard>
2. 向導事件
可以編寫響應幾個事件的代碼來增強向導,如下表:
ActiveStepChanged | 控件每次切換步驟時發生。(用戶點擊了導航按鈕 or 代碼修改了 ActiveStepIndex 屬性) |
CancelButtonClick | 默認情況不會顯示此按鈕。設置 Wizard.DisplayCancelButton 屬性可把它添加到每個步驟。通常,單擊 Cancel 按鈕會退出向導。如果不需要執行任何清理代碼,只要設置 CancelDestinationPageUrl 屬性就會自動重定向。 |
FinishButtonClick | 不解釋 |
NextButtonClick | 不解釋 |
PreviousButtonClick | 不解釋 |
SideBarButtonClick | 不解釋 |
總體而言,有兩種編程類型的向導編程模型:
- 逐步提交。如果每個步驟包括一個不可回撤的原子操作,應采取這種方式。例如,如果處理的訂單信息涉及信用卡授權,在這之后是最終的購買,你就不能允許用戶回退到上一步重新編輯信用卡號。要支持這種模型,就需要把某些或所有步驟的 AllowReturn 屬性設為 false,並且響應 ActiveStepChanged 事件為每個步驟提交變更。
- 最后提交。如果每個步驟都是為最后要執行的操作收集數據,應使用這種方式。例如,收集用戶信息進行注冊。最后可以響應 FinishButtonClick 事件。
上一示例,響應了 FinishButtonClick 事件,最后呈現了一個信息的匯總:
protected void Wizard1_FinishButtonClick(object sender, WizardNavigationEventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.Append("<b>You chose: <br />");
sb.Append("Programming Language: ");
sb.Append(lstLanguage.Text);
sb.Append("<br />Total Employees: ");
sb.Append(txtEmpCount.Text);
sb.Append("<br />Total Locations: ");
sb.Append(txtLocCount.Text);
sb.Append("<br />Licenses Required: ");
foreach (ListItem item in lstTools.Items)
{
if (item.Selected)
{
sb.Append(item.Text);
sb.Append(" ");
}
}
sb.Append("</b>");
lblSummary.Text = sb.ToString();
}
如果希望知道用戶在向導里執行的是哪一步驟,可以使用 Wizard.GetHistory()方法。它返回目前已經被訪問的 WizardStepBase 對象集合,按時間反向排序。也就是說,集合的第一項代表前一個步驟,以此類推。
3. 向導樣式、模板、布局
毫無疑問,Wizard 控件最強大的功能是允許定制外觀,根據希望對外觀修改程度的不同,你有不同的選擇。對於不大的修改,可以設置各種最上層的屬性。例如,和其他 ASP.NET 控件一樣,可以控制顏色、字體、空格、邊框樣式等。
利用樣式可以獲得更多控制。和其他基於樣式的控件一樣,樣式沖突時,較具體的樣式(如 SideBarStyle、StartNextButtonStyle)會覆蓋(ControlStyle、NavigationButtonStyle)。
ControlStyle | 作用於 Wizard 控件的所有區域 |
HeaderStyle | 作用於 Wizard 控件的所有區域,它只在設置了 HeaderText 屬性后可見 |
SideBarStyle | 作用於 Wizard 控件的所有區域 |
SideBarButtonStyle | 只作用於側欄里的按鈕 |
StepStyle | 作用於控件中定義步驟內容的區域 |
NavigationStyle | 作用於控件底部顯示的導航按鈕的區域 |
NavigationButtonStyle | 只作用於導航區域的導航按鈕 |
StartNextButtonStyle | 作用於第一個步驟里的“下一步”導航按鈕(StepType 為 Start 時) |
StepNextButtonStyle | 作用於中間步驟的“下一步”導航按鈕(StepType 為 Step 時) |
StepPreviousButtonStyle | 作用於中間步驟的“上一步”導航按鈕(StepType 為 Step 時) |
FinishPreviousButtonStyle | 作用於最后一個步驟里的“上一步”導航按鈕(StepType 為 Finish 時) |
CancelButtonStyle | 如果把 Wizard.DisplayCancelButton 設為 true,它作用於“取消”按鈕 |
如果不能通過屬性和樣式表達到期望的自定義級別,還可以借助模板完全定義 Wizard 控件的外觀。如標題、側欄、按鈕等。所有的模板都是獨立於步驟內容而聲明的。下表顯示模板的完整列表:
HeaderTemplate | 定義標題區域的內容 |
SideBarTemplate | 定義側欄,通常它包含每個步驟的導航鏈接 |
StartNavigationTemplate | 定義第一個步驟的導航按鈕 |
StepNavigationTemplate | 定義中間步驟的導航按鈕 |
FinishNavigationTemplate | 定義最后一個步驟的導航按鈕 |
LayoutTemplate | 定義標題、側欄、步驟區域、導航按鈕的總體布置 |
下面這個標題模板使用數據綁定表達式顯示當前步驟的標題:
</WizardSteps>
...
</WizardSteps>
<HeaderTemplate>
<i>Header Template</i> - <b><%= Wizard1.ActiveStep.Title %></b><br /><br />
</HeaderTemplate>
還可以加入下列模版來定制導航按鈕。這個示例保留了標准按鈕(通過顯式聲明)並加入了一些斜體文字,這樣你可以看到每個模板什么時候在使用:
<StartNavigationTemplate>
<i>StartNavigationTemplate</i><br />
<asp:Button ID="StartNextButton" runat="server" CommandName="MoveNext" Text="Next" />
</StartNavigationTemplate>
<StepNavigationTemplate>
<i>StepNavigationTemplate</i><br />
<asp:Button ID="StepPreviousButton" runat="server" CausesValidation="False" CommandName="MovePrevious"
Text="Previous" />
<asp:Button ID="StepNextButton" runat="server" CommandName="MoveNext" Text="Next" />
</StepNavigationTemplate>
<FinishNavigationTemplate>
<i>FinishNavigationTemplate</i><br />
<asp:Button ID="FinishPreviousButton" runat="server" CausesValidation="False" CommandName="MovePrevious"
Text="Previous" />
<asp:Button ID="FinishButton" runat="server" CommandName="MoveComplete" Text="Finish" />
</FinishNavigationTemplate>
使用模板的秘訣在於確保使用正確的命令名,這樣 Wizard 控件可以關聯到標准邏輯(否則,你將不得不自己實現導航和排序的代碼,它們冗長且易出錯)。如果你不太確定要使用什么樣的命令名,可以利用智能標簽中的轉換模板來看一看。
驗證控件也可以毫無問題的在 Wizard 里使用。如果驗證控件發現了無效數據,它會阻止用戶單擊側欄的所有鏈接並防止用戶單擊“下一步”繼續操作。
最后,你可以使用 LayoutTemplate 模板來突破目前所見到的所有的基於表格的結構。從本質上,通過 LayoutTemplate 可以告訴 ASP.NET 如何對各個模板進行相對定位。借助正確的 PlaceHolder 控件插入各個模板:
<LayoutTemplate>
<asp:PlaceHolder ID="navigationPlaceHolder" runat="server" />
<asp:PlaceHolder ID="sideBarPlaceHolder" runat="server" />
<asp:PlaceHolder ID="WizardStepPlaceHolder" runat="server" />
<asp:PlaceHolder ID="headerPlaceHolder" runat="server" />
</LayoutTemplate>
為了獲得更理想的布局,還需要把各個 PlaceHolder 對象放到表格的單元格里或者放到通過 CSS 樣式屬性定位的 <div> 元素里。