65.9K
CodeProject 正在变化。 阅读更多。
Home

带有设计时支持的增强型 Web Tab 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.28/5 (9投票s)

2006 年 12 月 4 日

CPOL

6分钟阅读

viewsIcon

84087

downloadIcon

615

一个完全可定制的 Web Tab 控件。

shTabControl

引言

两个月前,我写了我在 CodeProject 的第一篇文章,shSimplePanel,一个具有良好设计时支持的控件。现在,我完成了另一个名为 shTabControl 的控件。这个 Web 控件是用 ASP.NET 2.0 编写的,基于 Mohammed Mahmoud 的Web Tab ControlshSimplePanel,但这个控件增强了设计时支持,外观和感觉也更好。我希望它能对您的项目有所帮助。如果您发现我的代码中有任何错误,或者我的英语不好,请不要犹豫给我发邮件。

背景

如果您查看 Mohammed Mahmoud 的文章,您会发现我采用了相同的架构来构建这个控件,但只使用了 TableTableRowTableCell。看一下这个

Arch of shTabControl

shTabControl 是一个复合控件,以 Table 控件作为主容器。第一行是 RowHeader,它包含一个 TableCell 来显示每个添加的标签页,最后,我放置了一个空的 TableCell 来显示空白区域。接下来,为每个标签页添加一个 TableRow - TableCell,因为它是子控件的容器。通过这种架构,我们可以轻松地添加一个用于设置 TitlePositionTopBottom 的方便属性。

此外,我们还可以为每个添加的标签页在 CellPage 中添加一些样式,例如不透明背景、鼠标悬停、标签页图片已启用和标签页图片已禁用效果。稍后我将解释这些属性。

使用代码

实现代码非常容易,借助 VS.NET 2005 的 IntelliSense。shTabControl 包含另一个名为 shTabPage 的控件,它在 ContentTemplate 属性内的 Controls 属性中持久化子控件。

<shw:shTabControl ID="TabControl1" runat="server" CurrentPage="0" 
Height="350px" HeightCell="26px" TitlePosition="Top" Width="515px">
<TabPages>
   <shw:shTabPage ID="TabPage0" runat="server" BackColor="#C0C000" 
                  Height="350px"Text="Page0" Width="81px">
      <ContentTemplate>
           The contents and childs control here...
      </ContentTemplate>
   </shw:shTabPage>
</TabPages>
</shw:shTabControl>

shTabControl 具有以下属性来控制其在设计时的外观和感觉

  • TabPagesshTabPage 类的集合属性。
  • HeightCell:所有 CellHeader 的高度。
  • TitlePositionCellHeader 的位置。必须是 TopBottom
  • ShowBorderCell:一个标志,指示 CellHeader 是否可以显示其边框。
  • ImageHeader:第一个(顶部)或最后一个(底部)RowPage 上的背景图像。
  • CurrentPage:当前选中的页面。

为了实现这个控件,我使用了一些 JavaScript 来生成客户端行为和回发 :-)。shTabControl 使用 UITypeEditor 实现 shTabPage 的集合属性。所有这些概念我都会在这里解释。

SetVisiblePage JavaScript 客户端行为

SetVisiblePage 是一个 JavaScript 函数,用于生成回发并在回发后显示选中的页面。

<script language="javascript">
function SetVisiblePage(shTabName, value)
{      
    var obj= document.getElementById(shTabName + "_" + "hdCurrentPage");    
    if(obj!=null)
    {
        obj.value = value;
        obj.form.submit();
    }   
}
</script>

此函数用于每个 _CellHeaderOnClick 事件,其中 shTabName 是一个包含 shTabControlClientID 的字符串,而 value 参数是当前选中的页面。我将此字符串插入到项目的资源中。此函数在运行时在 protected Render 方法内渲染。

//On run time write the javascript

if (!DesignMode)
{
 string script = ShWebTabControl.Properties.Resources.SetVisiblePageScript;
 script = script.Replace("SetVisiblePage", ClientID + "SetVisiblePage");
 writer.Write(script);
 ... ...
}

我使用 Replace 方法来添加 shTabControlClientID 属性,因为我想在页面加载时生成一个唯一的函数;但是,如果您愿意,也可以删除它。此函数与 _CellHeader 的实现集成在一个名为 CreateCellHeaderprivate 方法中。

private TableCell CreateCellHeader(shTabPage item, int nTabPage)
{
 TableCell CellHeader = new TableCell();            
 CellHeader.ID = "_CellHeader" + nTabPage.ToString();
 //Please see the code for more information

 CellHeader.Attributes["onclick"] = ClientID + "SetVisiblePage(\"" + ClientID.ToString() + 
                                    "\"," + nTabPage.ToString() + ");";
 //Please see the code for more information

 return CellHeader;
}

CreateCellHeadershTabPage 创建 TableCell 的结构。所有 CellHeader 都有一个唯一的 ID。shTabPage 类实现了配置每个 CellHeader 的所有属性。

shTabPage 类

shTabPage 类包含配置 shTabControl 的每个标签页的所有属性。此类继承自 WebControlINamingContainerIStateManager,用于实现属性的 ViewState,并在设计和运行时持久化更改。

    [ToolboxData("<{0}:shTabPage runat="server">")]    
    [NonVisualControlAttribute()]
    [ParseChildren(true)]
    [PersistChildren(false)]
    public class shTabPage : WebControl, IStateManager, INamingContainer
    {            
        private bool _isTrackingViewState;        
        private ITemplate _contentTemplate;                
        public shTabPage():base(HtmlTextWriterTag.Div)  {  }
        //Properties Sections

             ...
        //IStateManager Members

        //Please see the code for more methods of IStateManager

        void IStateManager.TrackViewState()
        {
            _isTrackingViewState = true;
            if (ViewState != null)
               ((IStateManager)ViewState).TrackViewState();
        }        
        internal void SetDirty()
        {            
            if (ViewState != null)
            {                
                ICollection Keys = ViewState.Keys;
                foreach (string key in Keys)
                {
                    ViewState.SetItemDirty(key, true);
                }
            }
        }    
    }

此类基于 DIV。此类用于获取其属性,然后将其应用于 shTabControl。之所以使用 IStateManager 接口,是因为在实现 shTabPage 集合时,我们需要强制 ViewState。SetDirty 方法执行此操作。这里展示了 shTabPage 的一些属性

  • TextCellHeader 的标题文本。
  • TitleWidthCellHeader 的宽度。
  • OpacityCellPage 的不透明度。
  • ImageEnableCellHeader 选中时的图像。
  • ImageDisableCellHeader 未选中时的图像。

shTabPageCollection 类

此类继承自 CollectionBase 并实现 IStateManager 接口。使用 IStateManager 是因为我们希望保存每个 shTabPage 的状态。LoadViewStateSaveViewState 执行主要工作。有关更多信息,请参见代码。

public int Add(shTabPage shtabpage)
{
 List.Add(shtabpage);            
 if (_isTrackingViewState)
 {                
  ((IStateManager)shtabpage).TrackViewState();
  shtabpage.SetDirty();
 }
 return List.Count - 1;
}
object IStateManager.SaveViewState()
{
 if (_saveAll == true)
 {
  object[] states = new object[Count];
  for (int i = 0; i < Count; i++)
  {
   shTabPage shtabpage = (shTabPage)List[i];
   shtabpage.SetDirty();
   states[i] = ((IStateManager)shtabpage).SaveViewState();
  }
  if (Count > 0)                
    return states;                
  else                
    return null;                
 }
 else
 {
  ArrayList indices = new ArrayList();                
  ArrayList states = new ArrayList();
  for (int i = 0; i < Count; i++)
  {
   shTabPage shtabpage = (shTabPage)List[i];
   object state = ((IStateManager)shtabpage).SaveViewState();
   if (state != null)
   {
    states.Add(state);
    indices.Add(i);                        
   }
  }
  if (indices.Count > 0)                
   return new Pair(indices, states);                
  return null;
 }
}
void IStateManager.TrackViewState()
{
  _isTrackingViewState = true;
  for(int i=0; i < Count; i++)
  {
   shTabPage shtabpage = (shTabPage)List[i];                
   ((IStateManager)shtabpage).TrackViewState();
  }
}

此类的一些方法检查 _isTrackViewState 是否为 true。当此标志打开时,将调用 TrackViewState 方法,然后调用 SetDirty 方法(参见 AddClearInsert 方法),这会导致状态在 shTabPageCollection 的 view state 中持久化。

_saveAlltrue 时,SaveViewSate 将所有项保存在一个 objects 数组中。当 _saveAllfalse 时,SaveViewState 只保存已更改的项,并将其存储在两个 ArrayList 中:第一个存储已更改项的索引,第二个存储状态,然后返回一个 Pair 对象,该对象包含 shTabPageCollection 的 view state。

LoadViewState 方法执行与此方法相反的逻辑。有关更多信息,请参见代码。

shTabControl 类

这是包含 shTabPageCollection 属性的主类。

public shTabPageCollection TabPages
{
 get{
      if (_TabPages == null){
         _TabPages = new shTabPageCollection();
         if (IsTrackingViewState)
            ((IStateManager)_TabPages).TrackViewState();
      }
      return _TabPages;
    }
}

此属性保存在 shTabControl 的 ViewState 中。实现与我在shSimplePanel 中使用的相同;有关自定义状态管理的更多信息,请参见该文章。

shTabControl 继承自 CompositeControlIPostBackDataHandler,因为我们希望在 CurrentPage 属性更改时实现事件。此类使用一个隐藏字段来保存此属性。此控件是一个 HtmlInputHidden,名为 hdCurrentPage,它在 **SetVisiblePage 脚本** 和 IPostBackDataHandler 中使用。

protected virtual void OnCurrentPageChanged(EventArgs e)
{
 EventHandler currentPageChangedHandler = (EventHandler)Events[EventCurrentPageChanged];
 if (currentPageChangedHandler != null)
 {
  currentPageChangedHandler(this, e);
 }
}

bool IPostBackDataHandler.LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection){
 int current = CurrentPage;
 string posted = postCollection[postDataKey + "$hdCurrentPage"];
            
 if((posted != null) && (posted.Length>0))
    CurrentPage = Convert.ToInt32(posted);

 //CurrentPage state has changed

 if (current != CurrentPage)
    return true;
 return false;
}

void IPostBackDataHandler.RaisePostDataChangedEvent()
{
 //raise our event notifying the CurrentPage change

 OnCurrentPageChanged(EventArgs.Empty);              
}

当用户单击标签页时,脚本执行提交,LoadPostData 方法检查 hdCurrentPage 的值是否与 CurrentPage 属性不同;如果不同,则启动 RaisePostDataChangedEvent。隐藏字段在执行回发时更改其名称。我在调试模式下运行页面时看到了这个消息。

此处展示了 shTabControlCreateChildControls 方法。

protected override void CreateChildControls()
{            
  //See the code please

  //Creates all Cell Header from tab pages 
  //and the Row of Content, then add to Main Table

  int shTabPageIndex = 0;
  TableRow RowHeader = new TableRow();
  RowHeader.ID = "_RowHeader";
  foreach (shTabPage item in TabPages)
  {
   TableCell CellHeader = CreateCellHeader(item, shTabPageIndex);
   RowHeader.Cells.AddAt(shTabPageIndex, CellHeader);
   TableRow RowContent = CreateRowContent(item, shTabPageIndex);
   cTableMain.Rows.Add(RowContent);                  
   ++shTabPageIndex;
  }
  //Add a empty cell to Row Header

  TableCell CellHeaderEmpty = CreateCellEmpty();
  RowHeader.Cells.AddAt(shTabPageIndex, CellHeaderEmpty);            
  //Add on Top or Bottom the Row Header created

  if (TitlePosition == shTitlesPosition.Top)
     cTableMain.Rows.AddAt(0, RowHeader);
  else
     cTableMain.Rows.Add(RowHeader);
  //Add the controls to Controls Property            

  Controls.Add(hdCurrentPage); 
  Controls.AddAt(0,cTableMain);
}

此方法创建 shTabControl 的结构。foreach 循环执行 CreateCellHeaderCreateRowContent,最后执行 CreateCellEmpty。这些私有方法用于创建 RowHeader 的单元格标题以及创建 RowPage-CellPage 的内容。最后,CreateHeaderEmpty 在所有单元格标题的末尾创建一个没有边框的单元格。

一个名为 SetTabPage 的方法在 Render 方法中调用,用于在设计/运行时用户单击时设置 RowPage-CellPage 的可见性。在此方法中,我实现了鼠标悬停效果。

最后,我将展示 shTabControlControlDesigner。此类继承自 CompositeControlDesigner,并基于 Mohammed Mahmoud 的WebTabControlshSimplePanel。首先,我实现了一个 TemplateGroups

public override TemplateGroupCollection TemplateGroups
{
 get{
   TemplateGroupCollection collection = new TemplateGroupCollection();
   TemplateGroup group = new TemplateGroup(_shTabControl.ID);
   for (int i = 0; i < _shTabControl.TabPages.Count; i++){                    
    TemplateDefinition definition = new TemplateDefinition(
                                  this,HEADER_PREFIX + i.ToString(), 
                                   _shTabControl.TabPages[i], "ContentTemplate", false);
    group.AddTemplateDefinition(definition);                    
   }
   collection.Add(group);
   return collection;
 }
}

现在,在设计时,我们在 CreateChildControls 方法中设置用作模板的区域。在此方法中,我们检查 TitlePosition 属性是 Top 还是 Bottom,以设置 CellHeader 的源。如果它是 Top,则第一行是 RowHeader;否则,最后一行为 RowHeader。当用户单击时,将触发 OnClick,然后我们检查单击是否发生在 HeaderCell 上。在这里,我们在 OnClick 事件中调用 UpdateDesignTimeHtml 时调用 GetDesignTimeHtml 方法。GetDesignTimeHtml 方法基于 CurrentPage 设置一个可编辑区域。

protected override void CreateChildControls()
{
 //See the code ...

 for (int i = 0; i < _shTabControl.TabPages.Count; i++){
   if(_shTabControl.TitlePosition == shTabControl.shTitlesPosition.Top)
      Tbl.Rows[0].Cells[i].Attributes[
          DesignerRegion.DesignerRegionAttributeName] = i.ToString();
   else
      Tbl.Rows[Tbl.Rows.Count-1].Cells[i].Attributes[
               DesignerRegion.DesignerRegionAttributeName]=i.ToString();
 }
 //set the editable region

 if (_shTabControl.CurrentPage != -1){
  if(_shTabControl.TitlePosition == shTabControl.shTitlesPosition.Top)
    Tbl.Rows[1 + _shTabControl.CurrentPage].Cells[0].Attributes[
             DesignerRegion.DesignerRegionAttributeName]=
             _shTabControl.TabPages.Count.ToString();
  else
    Tbl.Rows[_shTabControl.CurrentPage].Cells[0].Attributes[
             DesignerRegion.DesignerRegionAttributeName]=
             _shTabControl.TabPages.Count.ToString();
 }
}

public override string GetDesignTimeHtml(DesignerRegionCollection regions)
{
 this.CreateChildControls();
 for (int i = 0; i < _shTabControl.TabPages.Count; i++)
     regions.Add(new DesignerRegion(this, HEADER_PREFIX + i.ToString()));

 if (_shTabControl.CurrentPage != -1){
  regions.Add(new EditableDesignerRegion(this, 
             CONTENT_PREFIX + _shTabControl.CurrentPage.ToString(),false));
  regions[_shTabControl.CurrentPage].Highlight = true;
 }
 return base.GetDesignTimeHtml(regions);
}

结论

此控件是使用 viewstate 和 ControlDesigner 实现设计时支持的一个示例。shTabControl 的主要创意来源于WebTabControl。希望这个控件对您的项目有所帮助。编码愉快 :-)

更新

  • 2006-02-22:我重写了 shTabPage 类以支持 StateBag 变量。请尝试将 EnableViewState 属性设置为 false 以支持 TabPage 内的控件。请检查一下 :-)
© . All rights reserved.