服务器控件开发——组合控件(5)

       组合控件, 顾名思义就是指由2 个或2 个以上的已存在的控件组合在一起, 协同工作从而完成新功能的新的服务器控件组合控件由于

够重用已经存在控件的功能, 能够最大限度的提升我们的开发效率。组合控件就像一台精密的机器, 而起所引用的子控件就像是机械的零件,

通力合作,共同完成新的功能。下面就让我们看看这台机器是如何将各个零件完美的组装在一起的。
     提到组合控件不得不重新来审视我们第一篇所提到的Control基类,Control基类为我们开发组合控件提供了很好的支持。我们要做的只需

要根据需求填充Control 基类为我们提供的模板。 下面我们来看看这个模板的内容。

 

1.  实现INamingContainer  接口。 这个接口没有任何方法, 纳闷呢, 没有任何方法的接口有什么用啊! 当然有用了在Control类的实现中,

 Control类通过 if(this is INamingContainer) 来判断控件是否继承INamingContainer 接口。 并且根据是否为True 来决定采用何种方式

给子控件的ID赋值。 事实上, INamingContainer 的主要作用就是为了保障子控件UniqueID在整个页面的唯一性。让我们来做一个简单的试验:

我们来做一个类似与上一篇的SimpleTextBoxControl 的SimpleCompositeControl 控件。

 

ContractedBlock.gif ExpandedBlockStart.gif Code
  public class SimpleCompositeControl : System.Web.UI.WebControls.WebControl
    {
        TextBox  tbInput 
= null;
        Button btSubmit 
= null;
        
protected override void CreateChildControls()
        {
            tbInput 
= new TextBox();
            tbInput.EnableViewState 
= false;
            tbInput.ID 
= "tbInput";
            
this.Controls.Add(tbInput);

            btSubmit 
= new Button();
            btSubmit.ID 
= "btnSubmit";
            btSubmit.Text 
= "提交";
            btSubmit.CommandName 
= "submit";
            btSubmit.CommandArgument 
= "arg";
            
this.Controls.Add(btSubmit);
        }
    }

我想这应该够简单的, 只是将一个TextBox 与 Button 控件在页面上呈现出来。 在测试页面拖入一个的SimpleCompositeControl控件,

 并且查看HTML代码,我们将看到类似于这样的源代码。
    <span id="SampleCompositeControl1" style="display:inline-block;width:197px;">
        <input name="tbInput" type="text" id="tbInput" />
        <input type="submit" name="btnSubmit" value="提交" id="btnSubmit" />
    </span>
    
我们注意到2 个Input 标签的ID 属性 正是我们给控件所分配的ID。 这看起来没有什么问题。 但是如果我们在页面
当中存在2 个SimpleCompositeControl 呢,那岂不是在同一个页面里面存在2 个ID 为 tbInput的Input呢。 假设控件PostBack 数据,

我们又如何知道到底是那个Input 里面的数据呢?       显然这样是行不通的,我们需要INamingContainer 接口来保障我们控件的 ID 的唯

一性。将上面的控件改为实现INamingContainer 接口。
    public class SimpleCompositeControl : System.Web.UI.WebControls.WebControl,INamingContainer
同样查看源代码。 控件HTML 变为
     <span id="SampleCompositeControl1" style="display:inline-block;width:197px;">
      <input name="SampleCompositeControl1$tbInput" type="text" id="SampleCompositeControl1_tbInput" />
     <input type="submit" name="SampleCompositeControl1$btnSubmit" value="提交"   id="SampleCompositeControl1_btnSubmit" />
    </span>
控件的ID 变为 父控件ID +子控件ID的形式, INameContainer 改变了组合控件子控件Render 自身ID 的行为, 从而保障了子控件ID 在页
面的

全局唯一性。事实上, 开发组合控件必须继承INamingContainer 接口, 否则将会导致各种奇怪的错误,比如说无法保持子控件视图状态,

 无法触发Button 事件等。

2.  重载CreateChildControls 方法
    这个比较简单,如前例所示, 只要将我们的子控件的属性一个一个的定义好, 然后加入到组合控件的子控件集合中。 为了保障子控件不被多次创建
(可能由控件的继承者不当调用CreateChildControls导致)我们将我们的CreateChildControls 稍微改良一下

    protected   override   void  CreateChildControls()
ExpandedBlockStart.gifContractedBlock.gif        
{
            Controls.Clear();           
// 清除子控件
            if (HasChildViewState)       //HasChildViewState 标识是否存在子控件视图信息。
                ClearChildViewState();   //清除子控件视图信息,看过我第一篇的朋友都知道, 在移除子控件的时候
                                            
//视图信息并不会被一次性移除。                                         
                                             
// 而会 是保存在一个 一个HashTable 中, ClearChildViewState方
                                             
//法将清空这个HashTable。
            tbInput = new TextBox();
            tbInput.EnableViewState 
= false;
            tbInput.ID 
= "tbInput";
            
this.Controls.Add(tbInput);

            btSubmit 
= new Button();
            btSubmit.ID 
= "btnSubmit";
            btSubmit.Text 
= "提交";
            btSubmit.CommandName 
= "submit";
            btSubmit.CommandArgument 
= "arg";
            
this.Controls.Add(btSubmit);
        }

3. EnsureChildControls 的调用,
  这个方法实在是太重要了, 尽管我在第一篇的时候已经把代码贴出来过, 我想我有必要再贴出来让大家看看:

ContractedBlock.gif ExpandedBlockStart.gif Code
       protected virtual void EnsureChildControls() 
            {
                
if (ChildControlsCreated == false && !creatingControls)
                {
                    creatingControls 
= true;
                    CreateChildControls();
                    ChildControlsCreated 
= true;
                    creatingControls 
= false;
                }
            }

该方法的本质就是调用CreateChildControls() 创建子控件集合, 只不过他做了必要的限制, 保障子控件不会被重复创建。 在Control 基类中,

有2 处地方调用了这个方法。第一个地方在Control 生命周期的Load 事件之后, PreRender 之前。 另一个就是在用户调用FindControl 的时候,

显然, 为了保障用户在Load 事件以及之前的事件当中的调用能够获取正确的结果,首先必须要调用  EnsureChildControls() 创建子控件。
根据以上的分析,我们可以得出这样的结论, 组合控件中任何与子控件发生交互的属性或方法, 只要我们不能确定用户将在什么时候调用。

都需要首先调用EnsureChildControls方法。比如我们为控件增加一个Text 属性。 这个属性的值与子控件中的textbox 值保持一致, 我们可以这样写:

 

ContractedBlock.gif ExpandedBlockStart.gif Code
       public string Text
        {
            
get
            {
                EnsureChildControls();
                
return tbInput.Text;
            }
            
set
            {
                EnsureChildControls();
                tbInput.Text 
= value;
            }
        }

4. 重载Render 方法。 控制控件的显示样式和布局。
   子控件是有了, 但是所有的子控件简单的叠加在一起显然不是我们所希望的, 我们可以通过重载Render 方法来控制子控件的布局,比如我们

可以将子控件放到一个table 里面来控制子控件的位置。

 

ContractedBlock.gif ExpandedBlockStart.gif Code
        protected override void Render(HtmlTextWriter writer)
        {
            
base.EnsureChildControls();                                    // 在Design Time 时, 控件显示的样式将是直接调用Render 方法的结果, 为了保障我们的组合控件对Design Time 的支持,在此调用base.EnsureChildControls() 是有意义的。

                                                    writer.RenderBeginTag(HtmlTextWriterTag.Table);
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);

            tbInput.RenderControl(writer);                       
// RenderControl 方法将子控件的HTML 写入到客户端。

            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            btSubmit.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderEndTag();
            writer.RenderEndTag();
        }

5 事件冒泡机制,
所谓事件冒泡机制就是将子控件的事件Mapping 为组合控件事件的一种机制。 首先我们利用冒泡机制来为我们的控件添加一个submit 事件,

然后再深入的探讨一下其中的机制。首先还是老方法为控件声明submit 事件

 

ContractedBlock.gif ExpandedBlockStart.gif Code
    private static readonly object submitEvent = new object();
        
public event CommandEventHandler Submit
        {
            add { 
this.Events.AddHandler(submitEvent, value); }
            remove
            {
                
this.Events.RemoveHandler(submitEvent, value);
            }
        }

        
public void OnSubmit(object sender, CommandEventArgs e)
        {
            CommandEventHandler handler 
= Events[submitEvent] as CommandEventHandler;
            
if (handler != null)
            {
                handler(
this, e);
            }
        }

声明之后我们该在什么地方引发事件呢?Control 基类为我们提供了一个OnBubbleEvent 的事件冒泡事件。

 

ContractedBlock.gif ExpandedBlockStart.gif Code
   protected override bool  OnBubbleEvent(object source, EventArgs args)
        {
            CommandEventArgs e 
= args as CommandEventArgs;

            
switch (e.CommandName)
            {
                
case "submit":
                    {
                        
string value = this.tbInput.Text;
                        
this.OnSubmit(this, e);
                        
break;
                    }
            }
            
return true;
        }
       }

设置一下断点, 启动调试,可以发现Submit 事件完美的被激发了. 看起来相当神奇,.NET 是如何做到这一点的呢?我尝试在Control 基类能找到了点
蛛丝马迹, 果然让我找到了RaiseBubbleEvent 方法。

 

ContractedBlock.gif ExpandedBlockStart.gif Code
        protected void RaiseBubbleEvent(object source, EventArgs args)
            {
                Control c 
= Parent;     // 获取父控件的引用
                while (c != null)
                { 
                       
                    
if (c.OnBubbleEvent(source, args))  // 冒泡事件, 如果OnBubbleEvent  返回true 则, 停止向上冒泡, 否则继续向上冒泡。
                        break;
                    c 
= c.Parent;
                }
            } 

对照Button 控件的相关实现:

 

ContractedBlock.gif ExpandedBlockStart.gif Code
void IPostBackEventHandler.RaisePostBackEvent (string eventArgument)
        {
            
if (CausesValidation)
                Page.Validate ();

            OnClick (EventArgs.Empty);
            OnCommand (
new CommandEventArgs (CommandName, CommandArgument));
        }

        
protected virtual void OnClick(EventArgs e)
        {
            
if(Events != null)
            {
                EventHandler eh 
= (EventHandler)(Events[ClickEvent]);
                
if(eh!= null)
                    eh(
this,e);
            }
        }

        
protected virtual void OnCommand(CommandEventArgs e)
        {
            
if(Events != null)
            {
                CommandEventHandler eh 
= (CommandEventHandler)(Events[CommandEvent]);
                
if(eh!= null)
                    eh(
this,e);
            }
            RaiseBubbleEvent(
this, e);
        }
不用我说了吧,神话就此终止。

控件开发系列, 写到这里, 感觉有点写不下去了, 一方面工作又紧张起来了,时间越来越不够了, 另一方面是由于有些东西自己也没有深入理解,

要花很多的时间去Research。
但是无论如何我都会尽自己最大的努力坚持下去, 在这里给自己打一下气。下一篇 服务器控件开发之复杂属性与视图状态管理 可能要等上一阵子才能
出来, 透透气先。
 


 

转载于:https://www.cnblogs.com/wwwyfjp/archive/2008/07/28/1254866.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值