ASP.NET 应用程序的选项卡控件

分享于 

21分钟阅读

Web开发

  繁體

介绍

这里选项卡控件是为 ASP.NET 应用程序设计的。 虽然你可以从不同 3rd 方找到许多 ASP.NET 选项卡控件,但是这种控件的妙处在于它的源代码揭示了 ASP.NET 服务器控件的内部工作。 一旦理解了这些深层内部结构,就可以像 3rd 方控件一样编写自己的专业控件。

背景

在 ASP.NET 1.1和 2.0中没有选项卡控件。 用户使用 IE 网页控件或者 3rd 方控件控制选项卡控件功能。 有些人通过将内容放在 <DIV> 标记中,并在选项卡标题单击时将它的内容写入它的中。 甚至可以使用 ASP.NET 2.0 MultiView控件设计选项卡控件,并使用它的ActiveViewIndex 属性切换视图。 但是 TabsView 控件是由地面编写的,意味着你可以根据具体需要更改它。

使用代码

可以在TabsView控件的选项卡属性中添加不同的选项卡页。 代码 below 显示如何在控件中添加选项卡页。

<cc1:TabsViewID="TabsView1"runat="server"CurrentTabIndex="0"SelectedTabCSSClass="SelectedStyle"UnSelectedTabCSSClass="UnSelectedStyle"><Tabs><cc1:TabPagerunat="server"ID="t1"Text="Karachi"><asp:TextBoxID="TextBox1"runat="server">Marko Cash&amp;Carry
 </asp:TextBox><asp:TextBoxID="TextBox2"runat="server"></asp:TextBox> This is Karachi the heart of Pakistan. 
 </cc1:TabPage><cc1:TabPagerunat="server"ID="t2"Text="Islamabad"><asp:TextBoxID="TextBox15"runat="server">1234 S</asp:TextBox><asp:ButtonID="Button2"runat="server"Text="Click Me"/> This is Islamabad the capital of Pakistan. <br/><asp:LabelID="Label1"runat="server"Text="Lux"Font-Bold="True"></asp:Label></cc1:TabPage><cc1:TabPagerunat="server"ID="t3"Text="Lahore"> This is Lahore the second largest city of Pakistan.
 </cc1:TabPage></Tabs></cc1:TabsView>

你可以在VS2005中使用SmartTags添加标签页。 简单右键单击控件并单击显示智能标记。

已知问题

  • 当你从智能标记中添加新的选项卡页时,你必须自己为新标签页指定on属性。
  • 无论什么时候,在设计器上拖动新的服务器控件时,都不保证生成该控件的唯一 id。 因此,你必须指定唯一的id来控制。

控件的工作方式

TabsView控件是复合服务器控件。 复合服务器控件是包含其他服务器控件的服务器控件,这些控件被称为子控件。 内部TabsView控件包含表和面板服务器控件。 在 ASP.NET 中,复合服务器控件由CompositeControl类表示。 TabsView控件是从此类派生的。

publicclass TabsView : CompositeControl

TabsView类包含设计器属性,它表示负责为TabsView控件提供设计时间功能的设计器。

Designer(typeof(TabViewDesigner))

我们在重写 CreateChildControls ( ) 方法中创建实际子控件。 每当执行回发或者首次加载页时都会调用这里方法。 在开发复合服务器控件时,始终在这里方法中创建子控件。 重要的是,所有子控件都应该具有惟一的标识,以避免命名冲突。 感谢 INamingContainer 接口,它完成生成控件惟一ID所需的所有工作。 CompositeControl 类已经实现这个接口,所以不需要在我们的代码中实现这个接口。

TabsView控件包含两个部分:

  • 标题:包含标签页标题。 每个头都由 LinkButton 服务器控件的实例表示。 每个标题都包含在表格单元格中,以便正确对齐。
  • 内容:包含选项卡页的内容。 内容由 TabPage 类的实例表示。

页眉渲染

TabsView 控件需要存储每个选项卡页标题和选项卡页内容的id。 一个客户端的id存储在数组中。 如果TabsView拥有 ID TabsView1,而是 tabButtons_TabsView1 ( 标头的ID ),则这些ID的名称是限定的含义。这是网页中包含多个TabsView服务器控件实例的必需。

标题 array:

var tabButtons_TabsView1 = new Array("TabsView1_tp0","TabsView1_tp1",
 "TabsView1_tp2");

这是由 CreateTabHeaders helper 方法提供的。 这里方法接受一个表参数,即TabsView控件控制整个控件的父表。 上面 脚本 array的内容存储在一个变量arrBtns中,它是 StringBuilder 类的一个实例。

StringBuilder arrBtns = new StringBuilder();//contains header js

我使用了表tblTabs来控制头的对齐方式。

Table tblTabs = new Table();
tblTabs.CellPadding = tblTabs.CellSpacing = 0;

我们确保CurrentTabIndex属性应小于选项卡页的总页数。 这是在 VerifyTabIndex 方法中完成的。

privatevoid VerifyTabIndex()
{
 if (CurrentTabIndex >= Tabs.Count)
 thrownew Exception("Invalid Tab Index");
}

为了确保 TabsView 控件的正确选项卡页索引,在 CreateTabHeaders 方法中调用这里方法。 在 Collection 属性中遍历存储在选项卡属性中的每个选项卡页,并在表单元格中添加 LinkButtonLinkButtonCommandArgument 用 0个基于索引的索引初始化。

VerifyTabIndex();foreach (TabPage tp in Tabs)
{
 tc = new TableCell();
 lk = new LinkButton();
 lk.ID = "tp" + i.ToString();
 lk.Text = "&nbsp;" + tp.Text + "&nbsp;";
 lk.CommandArgument = i.ToString();
 //rest of the code.. 

在自动回发模式下,我们确保当前选项卡页在回发后设置为焦点。 这是按如下方式

if (AutoPostBack) lk.Click += new EventHandler(lk_Click);

lk_Click 方法中,我调用了一个java脚本函数 SelectTab,它将焦点设置为当前标签。

void lk_Click(object sender, EventArgs e)
{
 LinkButton lk = (LinkButton)sender;
 this.CurrentTabIndex = Convert.ToInt32(lk.CommandArgument);
 //select the current tab.this.Page.ClientScript.RegisterStartupScript(this.Page.GetType(),
 "SelectTab_LinkButton", "<script language="'JavaScript'">SelectTab(" 
 + CurrentTabIndex +
 ",'" + this.ClientID + "','" + this.ClientID + "_hf" + "','" +
 UnSelectedTabCSSClass + "','" + SelectedTabCSSClass + "')" + 
 "</script>");
}

如果没有回发,我们只在LinkButton属性的OnClientClick属性上调用SelectTab方法。 隐藏字段实例hf用于存储TabsView控件的当前选项卡索引。

lk.OnClientClick = "return OnTabClick(this," + i.ToString() + ",'" +
 this.ClientID + "','" + this.ClientID + "_" + hf.ClientID + "','" +
 UnSelectedTabCSSClass + "','" + SelectedTabCSSClass + "');";

我们将链接按钮添加到表单元格中,包装表格单元格对齐并从函数中返回 arrBtns的字符串表示。

 tc.Controls.Add(lk);
 tr.Cells.Add(tc);
 ++i;
 }
 tblTabs.Rows.Add(tr);
 tc = new TableCell();
 tc.Controls.Add(tblTabs);
 tc.Controls.Add(hf);
 tr = new TableRow();
 tr.Cells.Add(tc);
 tbl.Rows.Add(tr);
 return arrBtns.ToString();
}

内容呈现

CreateTabContents 方法中,我们接受父表参数,该参数负责控制 TabsView 控件的整个布局。 标题呈现类似,该函数包含 tblContents 一个表实例,该实例控制内容的布局。 在 foreach 循环中,我们将每个选项卡页添加到表格的单元格中,并在 StringBuilder 实例 arrTabPages 中附加每个选项卡页的。

foreach (TabPage tp in Tabs)
{
 tpId = this.ClientID + "_" + tp.ID; 
 tc.Controls.Add(tp);
 if (i == tabs.Count - 1)
 arrTabPages.AppendFormat(""{0}"", tpId);
 else arrTabPages.AppendFormat(""{0}",", tpId);
 ++i; 
}

子控件创建

CreateChildControls 覆盖的method.These 函数调用 CreateTabHeadersCreateTabContents,返回包含of头链接按钮和选项卡页的字符串 array。 使用这些 id register script java脚本 array 声明 ClientScript。 RegisterArrayDeclaration 方法。

//create tab headers.js = CreateTabHeaders(ref tbl);this.Page.ClientScript.RegisterArrayDeclaration("tabButtons_" 
 + this.ClientID, js);//create tab contents. js = CreateTabContents(ref tbl);this.Page.ClientScript.RegisterArrayDeclaration("tabContents_" + 
 this.ClientID, js);

现在,当用户在不同的标签上移动时,TabsView 控件如何知道当前索引。 这就是为什么我们使用隐藏字段实例,以便我们可以使用java脚本( tabsview。js OnTabClick 函数) 存储索引。

//store the current index in hidden field.document.getElementById(hfId).value = i;

我们使用请求对象读取这个隐藏字段的值。 从下面的代码中可以看到,在读取请求对象之前,如果条件。

if (!this.DesignMode /*HttpContext.Current!= null*/)

这是因为在设计器中请求对象为空,因此,读取请求对象时产生错误。 使用控件基类 DesignMode 属性检查这里条件。 你也可以在这里使用 HttpContext.Current!= null

属性.

  • 选项卡:选项卡是 Collection 属性。 无论什么时候使用 Collection 属性,最合适的方法是编写自己的自定义 Collection 类。 虽然用户也可以使用内置的集合如 ArrayListStack 等等 实现了标签作为内部默认属性。 无论什么时候使用内部属性,都必须指定它的持久性模式。 没有持久模式 ASP.NET 就不知道如何持久化这些属性。 你无法通过 Browsable(false) 属性在属性窗口中看到这里属性。
    [ PersistenceMode(PersistenceMode.InnerDefaultProperty),
     DefaultValue(null), 
     Browsable(false)]publicvirtual TabPageCollection Tabs
    {
     get {
     if (tabs == null)
     {
     tabs = new TabPageCollection(this);
     }
     return tabs;
     }
    }

    每个选项卡页都是从面板服务器控件继承的。 因此,面板控件的所有属性都适用于单个选项卡页。 例如可以创建可以滚动的选项卡页。

    <cc1:TabPagerunat="server"ID="t3"Text="Text"ScrollBars="Auto"><!--yourcontents--></cc1:TabPage>
  • CurrentTabIndex: 获取当前选项卡索引。 设置部分相当简单。 在get部分i 当新索引和上一个索引不相同时引发TabSelectionChangingObject事件

  • SelectedTabCSSClassUnSelectedTabCSSClass 很简单,它们使用ViewState在 postback 之间保持值。

事件

  • §: 任何事件都是通过事件属性公开的,其中包含与服务器 control.This 列表相关的所有事件。 使用AddHandler和RemoveHandler将事件附加到服务器控件并将它的移除。
publicevent TabSelectionChangingHandler TabSelectionChanging
 {
 add { Events.AddHandler(TabSelectionChangingObject, value); }
 remove { Events.RemoveHandler(TabSelectionChangingObject, value); }
 }

设计器工作

使用 TabsViewDesigner class.This 类编写TabsView设计器继承自 CompositeControlDesigner,它提供了复合服务器 control.To的desin时间行为,了解它的工作方式,如下步骤。

首先,你需要捕获实际复合服务器控件的一个实例。 这在重写的初始化方法中完成。

publicoverridevoid Initialize(IComponent component)
{
 base.Initialize(component);
 tabView = (TabsView)component;
}

上的代码,我们必须关联一个 id,以便我们能够知道哪个区域是真正被点击的,而不是在重写方法中创建子控件。 为了每个TabsView内容区域,我为TabsView头和单个id关联了不同的id,因为在设计器中只有单一内容处于活动。

if (tabViewTable.Rows.Count >0)
 {
 //header table  headerTable = (Table)tabViewTable.Rows[0].Cells[0].Controls[0];
 //make sure we have headers.if (headerTable!= null)
 {
 //we are creating designer regions consiting of headers and //contents.int i;
 //header tabs.for (i = 0; i < headerTable.Rows[0].Cells.Count; i++)
 headerTable.Rows[0].Cells[i].Attributes[
 DesignerRegion.DesignerRegionAttributeName] = i.ToString();
 //header contents.Single region for all contents b/c only single //content is active at a time. contentsTable = (Table)tabViewTable.Rows[1].Cells[0].Controls[0];
 contentsTable.Rows[0].Cells[0].Attributes[
 DesignerRegion.DesignerRegionAttributeName] = i.ToString();
 }
 }

事件处理程序 onclick 事件。 当你单击TabsView标头区域时,将调用。 要确保单击标题,请检查标题的NAME。

if (e.Region.Name.IndexOf("Header") == -1)
 return;
 if (e.Region!= null)
 {
 currentTabIndex = tabView.CurrentTabIndex = 
 int.Parse(e.Region.Name.Substring("Header".Length));
 base.UpdateDesignTimeHtml();
 }

你需要了解设计器区域和可以编辑设计器区域的情况。 我们的头是设计器区域,内容选项卡是可以编辑设计器区域。 指定这里选项后,使用 R 选择当前标签 egions[tabView.CurrentTabIndex].Highlight = true;

Table tabViewTable = (Table)tabView.Controls[0];
 int i = 0, designerRegionIndex = 0;
 string designTimeHtml = "";
 if (tabViewTable.Rows.Count >0)
 {
 Table headerTable = (Table)tabViewTable.Rows[0].Cells[0].Controls[0];
 for (i = 0; i < headerTable.Rows[0].Cells.Count; i++)
 {
 regions.Add(new DesignerRegion(this, "Header" +
 designerRegionIndex.ToString()));
 ++designerRegionIndex;
 }
 Table contentTable = (Table)tabViewTable.Rows[1].Cells[0].Controls[0];
 //for (i = 1; i </*2*/tabView.Tabs.Count; i++)//for (i = 0; i </*2*/tabView.Tabs.Count; i++) {
 EditableDesignerRegion editableRegion =
 new EditableDesignerRegion(this,
 "Content" + tabView.CurrentTabIndex, false);
 ++designerRegionIndex;
 editableRegion.UserData = i;
 regions.Add(editableRegion);
 }
 //Set the highlight for the selected regionif (tabView.Tabs.Count >0)
 regions[tabView.CurrentTabIndex].Highlight = true;
 designTimeHtml = base.GetDesignTimeHtml(regions);
 }
 else designTimeHtml = GetDesignTimeHtml();
 return designTimeHtml;

使用覆盖的GetEditableDesignerRegionContent在各个标签之间提供持久性,SetEditableDesignerRegionContent.I 使用 ControlPersister.PersistControl(tb, host) . 持久化单个选项卡内容

publicoverridestring GetEditableDesignerRegionContent(
 EditableDesignerRegion region)
 {
 //Get a reference to the designer host IDesignerHost host = (IDesignerHost)Component.Site.GetService(
 typeof(IDesignerHost));
 if (host!= null)
 {
 TabPage tb;
 tb = (TabPage)tabView.Tabs[tabView.CurrentTabIndex]; 
 return ControlPersister.PersistControl(tb, host);
 }
 returnString

对于每个单独的选项卡内容的更改,我们必须更新 TabsView control.The SetEditableDesignerRegionContent。 它从它的字符串表示形式解析单个选项卡内容并更新 TabsView 控件。

publicoverridevoid SetEditableDesignerRegionContent(
 EditableDesignerRegion region, string content)
 {
 if (content == null)
 return;
 // Get a reference to the designer host IDesignerHost host = 
 (IDesignerHost)Component.Site.GetService(typeof(IDesignerHost));
 if (host!= null)
 {
 TabPage view = (TabPage)ControlParser.ParseControl(host, content);
 if (view!= null)
 {
 int tabIndex = int.Parse(region.Name.Substring("Content".Length ));
 tabView.Tabs.RemoveAt(tabIndex);
 tabView.Tabs.AddAt(tabIndex, view);
 }
 }
 }

添加自定义操作列表

要在智能标记中添加新面板,必须重写 ActionLists 属性,并需要创建 DesignerActionListCollection 类的实例。

publicoverride DesignerActionItemCollection GetSortedActionItems()
 {
 if (items == null)
 {
 // Create the collection items = new DesignerActionItemCollection();
 // Add Tab Page command items.Add(new DesignerActionMethodItem(this, "CreateTabPage",
 "Add Tab Page", true));
 }
 return items;
 }

这里面板中的每个项都由 DesignerActionMethodItem 类的实例表示。 单个面板项通过调用 DesignerActionList 类的GetSortedActionItems 方法关联在一起形成一个面板。

// Get the sorted list of Action itemspublicoverride DesignerActionItemCollection GetSortedActionItems()
 {
 if (items == null)
 {
 // Create the collection items = new DesignerActionItemCollection();
 // Add Tab Page command items.Add(new DesignerActionMethodItem(this, "CreateTabPage", 
 "Add Tab Page", true)); 
 }
 return items;
 }

每当执行Add页command命令时,都会调用在类中定义的CreateTabPage 方法。 这里方法依次添加新的选项卡页回调。

publicvoid CreateTabPage()
{
 TabsView tabView = (TabsView)_parent.Component;
 // Create the callback TransactedChangeCallback toCall =
 new TransactedChangeCallback(AddTabPage);
 // Create the transacted change in the control ControlDesigner.InvokeTransactedChange(tabView, toCall, null, 
 "Add Tab Page");
}

这里我们需要理解设计器事务的概念。 每当你使用智能标记添加新标签页时,就会使用它执行新的标签页并添加新的标签页。 无论何时,撤消这里操作,它将只回滚最后一个操作,从而删除上次添加的选项卡页。 你可以在编辑菜单中看到撤销选项,如下所示:


控制  asp  asp-net  tab  
相关文章