下面的代码演示了怎样绑定数据到datagrid控件的模板列。  
   <asp:TemplateColumn runat="server" 
   HeaderText="heading">        
     <itemtemplate>
       ...ASP.NET layout goes here...
     </itemtemplate>
   </asp:TemplateColumn>
注意模板列和其它类型的列一样,也有一个可用于排序的标题文字。模板列没有直接用于绑定数据源字段的属性。也就是在TemplateColumn类的成员里面,你找不到任何DataField或DataTextField属性。
缺少直接绑定数据源字段的属性是为了更灵活的处理列的显示布局。要呈现一个列,你可以用label控件的text属性,当然也可以用dropdownlist控件或者image控件,这两个控件都没有类似text的属性。结果一样,你都必须用一个数据绑定表达式来绑定数据。在我看来,虽然要写冗长的数据绑定表达式,但它却给了你极大的灵活性。
 下面的代码片断演示怎样正确绑定内容到item template:
   <asp:label runat="server" 
     Text='<%# DataBinder.Eval(Container.DataItem, 
          "lastname") %>' 
   />        
通过用DataBinder.Eval方法,你能够访问当前数据源的任何一个字段。除此以外,你还可以结合任何的排序表达式来对字段进行排序。这是用其它简单的模板列无法实现的。 
Figure 2 Column在图2中,你能看到一个基于模板列实现的例子,它演示了怎样在一列中显示数据源中两个字段。在不是基于模板的列中只能获得(显示)一个字段。这个模板的代码如下所示:
<itemtemplate>
<%#
  "<b>" + 
  DataBinder.Eval(Container.DataItem, "lastname") +
  "</b>, " + 
  DataBinder.Eval(Container.DataItem, "firstname") 
%>
</itemtemplate>
如果你需要结合更多的字段在同一个列中显示,模板列是唯一的方法。如果你需要对某个单元格应用一种特殊的格式,你最好就用datagrid的ItemCreated 事件或者ItemDataBound事件。例如,如果你要改变某个单元格的背景色,或者你要根据某种条件来对单元格应用其它的样式表,你可以在ItemCreated的处理事件中,确定正在创建的项(item)是你需要处理的类型(item 或者AlternatingItem类型),然后应用新的或已存在的样式表。在ItemCreated事件触发时,仍不能保证那一项(item)已经绑定了数据。通过ItemCreated事件的参数e的dataitem属性可以得到触发该事件的datagriditem对象。对于datagrid的ItemCreated事件,它的事件参数是DataGridItemEventArgs类型:
void ItemCreated(Object sender, DataGridItemEventArgs e)
  e.Item.DataItem的值在项被创建时根据数据源的不同赋予不同值。如果datagrid绑定的是一个datatable,那么DataItem就是一个DataRow对象。正如我刚才所说,虽然大多数情况下数据绑定(data binding)事件在ItemCreated触发时还没有触发。但实际上,数据绑定事件(data binding)通常发生在ItemDataBound事件触发时。这个规则只有一种情况下例外,就是在基于模板的列中。一个模板列不止能绑定数据源中的某个字段;它还能访问整个的数据源。这使我们可以在ItemCreated事件中做一些处理工作,而不必等ItemDataBound事件触发,因为itemdatabound事件在ItemCreated事件之后触发。对于bound、button以及 hyperlink列,你可能需要ItemDataBound事件来获得datagrid将要显示的数据绑定的文字。

解决方案 »

  1.   

    模板列的头
    既然TemplateColumn类给了HeaderTemplate(FooterTemplate)属性,你就可以定制给定列的头(header)和尾(footer)。说到定制,它在这里是非常重要的一点。因为在普通的数据绑定列中没有header模板和footer模板。HeaderTemplat只能应用在TemplateColumn 类的一个实例中。对于这个实例,如果你想用一个非标准的方法编辑该列的内容(例如说你想添加对内容的验证),你可以用headertemplate,也可以用一个简单的BoundColumn类来呈现。
    真的可以改变列的header布局吗?如果你需要根据某个表达式对列进行排序,排序机制是datagrid自动在列的头部插入一个hyperlink控件,当用户单击hyperlink控件时,asp.net会根据hyperlink控件的href属性触发一个回发事件,从而对该列进行排序操作。就算你不需要排序你也可以很自由的更改列的header。如果你需要用ItemCreated事件在header中增加其它的控件及脱离datagrid控件做其它的任何事情。下面请看一个例子:
    Figure 3 Adding the Sort Dropdown 
    public void ItemCreated(Object sender, DataGridItemEventArgs e)
    {
        ListItemType lit = e.Item.ItemType;
        if (lit == ListItemType.Header)
        {
            // Create and fill a dropdown list control
            DropDownList dd = new DropDownList();
            dd.ID = "ddSort";
            ListItem li1, li2, li3;        // ListItem constructor takes Text and Value for the item
            li1 = new ListItem("Title of courtesy", "titleofcourtesy");
            dd.Items.Add(li1);        li2 = new ListItem("Last Name", "lastname");
            dd.Items.Add(li2);        li3 = new ListItem("First Name", "firstname");
            dd.Items.Add(li3);        // Selects the item, if any, that was selected last time
            dd.SelectedIndex = Convert.ToInt32(grid.Attributes["FieldIndex"]);        // Add the dropdown list to the header of the 2nd column
            TableCell cell = (TableCell) e.Item.Controls[1];
            cell.Controls.Add(dd);
        }
    }public void SortCommand(Object sender, DataGridSortCommandEventArgs e)
    {
        // Code that retrieves the grid's data source GOES HERE
        &#8226;&#8226;&#8226;    // Sort by the specified expression or figure it out 
        if (e.SortExpression != "*")
            dv.Sort = e.SortExpression;
        else
        {
            // Retrieves the dropdown list control through its ID 
            DataGridItem dgi = (DataGridItem) e.CommandSource;
            DropDownList dd = (DropDownList) dgi.FindControl("ddSort");        // Retrieves the sorting expression from the list
            dv.Sort = dd.SelectedItem.Value;         // Persists the currently selected dropdown item
            grid.Attributes["FieldIndex"] = dd.SelectedIndex.ToString();
        }
        
        // Refreshes the grid
        grid.DataBind();
    }如图3所示,我们在模板列的header中动态的添加了一个dropdownlist控件,让用户可以选择排序的表达式。当你用模板列组织显示多个数据源字段时定制header就非常有用了。在这种情况下,你可能想允许客户可以自己选择按哪个字段排序,如图4所示。
      
     
    Figure 4 Sort by Field
    图3所示的代码动态的增加一个dropdownlist控件用于选择排序的表达式。当用户单击头部的hyperlink控件时,从dropdownlist控件中获得当前用户选择的排序表达式。模板列中的头部文字(headertext属性)必须设置为一个非空的字符,以使排序工作能正常的进行。
    <asp:TemplateColumn runat="server" 
        HeaderText="Sort by" 
        SortExpression="*">
     注意上面的SortExpression属性的值设为“*”,它使排序处理事件(如图3)知道客户要求对这一列进行排序。实际上SortExpression属性只是你单击的列中一个元素。 
    Figure 5 Grouping Columns
    在图5中,你可以看到另外一种特殊类型的列头,它合并了两列或更列列的列头。当你想用模板列显示组合的几列数据的时候,这个技巧是非常有用的。特别是,这个例子利用一个技巧来使名和姓按风俗习惯排列(外国人是名在前,姓在后)。表格包含两个明显不同的列:一个用于显示称呼BoundColumn列,一个用于显示雇员姓名的列。两个列有它们各自的单元格,但是在ItemCreated事件中,我们删除了其中的一个列的标题单元格,另一个单元通过其columnspan属性合并了被删除的哪个单元格。
    // 1 表示表中的第二列
    cell = (TableCell) e.Item.Cells[1];            
    e.Item.Cells.Remove(cell);
    cell = (TableCell) e.Item.Cells[1]; 
    cell.ColumnSpan = 2;
      

  2.   

    改变用户界面的数据
    基于模板的列可以帮助你模仿控件的用户界面来显示实际数据。如果所有datagrid控件中的数据都以纯文本形式呈现,可能不便于用户阅读和理解。比较明显的是布尔型数据、图像和数组。根据它们在程序中要表达的意思,布尔型数据最好的显示成一对相反(是/不是)的文字或者用一个小图片来表示选中(是)或未选中(不是)状态。用图片来替代checkbox控件是很有效的,因为图片是不能被单击,它也永远不会获得焦点,其应用显示界面如图6所示:
      
     
    Figure 6 Column with Checkboxes
    <asp:image>标签的imageurl属性可以用数据绑定表达式来绑定数据源。
       <itemtemplate>
         <asp:image runat="server" 
           imageurl='<%# GetProperFile( (int) 
           DataBinder.Eval(Container.DataItem, 
           "paid")) %>' 
         />
       </itemtemplate> 
     在上述代码里,数据绑定表达式由用户自己定义函数组成,它把布尔型字段值作为函数的参数。函数返回相应的GIF文件路径用于HTML <img>标签imageurl属性中。
    但是有时候我们也没必要用两个互斥文字或图像来显示布尔型数据。例如,某列要表示是否付款,就可以用一个检查标志来表示,其中钩表示已付,空白表示没付。在这种简单的情况下, 最好的解决方案是在ItemDataBound事件中重新设置单元格的显示值.代码如下所示:
    Figure 7 Intercepting Cell Creation Event 
    /*ItemDataBound 事件在一个item被数据绑定到期datagrid控件时触发。Datalist和repeater控件也有的ItemDataBound事件。*/
    public void ItemDataBound(Object sender, DataGridItemEventArgs e)
    {
        // 获得源数据行(强制转换成DataRowView对象),确定它不为null object
        DataRowView drv = (DataRowView) e.Item.DataItem;
        if (drv == null)
            return;  // 访问我们要的列数据,如果得到的值>=0,则用webdind字体的a作为单元格的显示值。 
        if (((int) drv["boss"]) >0)
        {
            e.Item.Cells[3].Font.Name = "Webdings";
            e.Item.Cells[3].Text = "a";  
        }
        else
            e.Item.Cells[3].Text = "";
    },显示结果如图8 所示:
     
    Figure 8 Column with Checks 
    一般情况下,你可以用不是基于模板的列来显示数据,但这并不意味着所有的都这样做。模板列是强大和有效的,但是它们总和一些事件挂钩,如ItemDataBound 和 ItemCreated。在执行期间,这两种方法的不同之处在于对大多数的应用程序不是非常有意义。它们也有缺点就是都必须与事件挂钩。
    图7的演示代码有一点问题,就是如果要在给定的列中显示图片而不是一个字符呢?你要怎么实现呢?图片对一行来说是不是无效的呢?你也许会想到显示一些交替的文字或简单级别的空格。要在一列中插入图片有两种方法:你可以写一个新的列对象(column object)或者用模板列(在模板列中用<asp:image>控件来显示图片)。用模板列的方法更好,因为所有的详细内容将在视图中隐藏,放在后台代码中。也可以用一个新类型的列来处理在图片不存在或不能显示时替换显示的内容。同样的,一个image列也能实现直接从数据库的blob字段中读出表示图像的二进制流,而不是通过读出图像路径来显示图片。我将在以后的专题的详细讲解这个技巧。在本文中,你将看到怎样用模板列在同一个列中显示图片和文字。你可以从中学到一个技巧就是以运行时的值为条件,动态的从两个或两个以上列布局中选择其中的一个来显示列数据。
    解决方案是在同一个模板列中定义两个布局,但在同一时刻只显示其中的一个。显示的布局可以在ItemCreated处理事件中选择或通过一个简单的数据绑定表达式来选择。图9显示了以datagrid控件为例的模板列的代码,模板列中有一个<img>和一个<asp:label>元素。它们都明显的设置了它们的visible属性。Label控件用于显示文字。通常你应该避免用label控件显示静态的文字。当你的文字无须动态的改变时,最好用客户端控件如<span>控件,它们不会在服务器端工作(它们没有runat=server属性).在当前情况下,因为visible属性要在服务器端才能设置,而且它所显示文字也不是静态的,所以必须用 <asp:label>控件。
    下面的代码是用于控制在列中显示哪个布局的函数:
    bool IsImageAvailable(String strLastName)
    {
      String strImageFile = "images\\";
      strImageFile = strLastName + ".bmp";
      return File.Exists(
          Server.MapPath(strImageFile));
    }
    Figure 9 Two Templates 
    <asp:DataGrid id="grid" runat="server"  
        AutoGenerateColumns="false"
        CssClass="Shadow" BackColor="white"
        CellPadding="2" CellSpacing="0" 
        BorderStyle="solid" BorderColor="black" BorderWidth="1"
        font-size="x-small" font-names="verdana"
        AllowPaging="true"
        PageSize="4"
        DataKeyField="employeeid"
        OnPageIndexChanged="PageIndexChanged"><AlternatingItemStyle BackColor="palegoldenrod" />
    <ItemStyle BackColor="beige" VerticalAlign="top" />
    <EditItemStyle BackColor="yellow" Font-Bold="true" />
    <PagerStyle Mode="NumericPages" />
    <HeaderStyle ForeColor="white" BackColor="brown" HorizontalAlign="center" 
        Font-Bold="true" />    <columns>   <asp:BoundColumn runat="server" DataField="employeeid" 
                        HeaderText="ID">    
        <itemstyle backcolor="lightblue" font-bold="true" 
                   HorizontalAlign="right" />
       </asp:BoundColumn>   <asp:TemplateColumn runat="server" HeaderText="Employee Name">        
        <itemtemplate>
            <asp:label runat="server" 
                Text='<%# DataBinder.Eval(Container.DataItem, 
                "TitleOfCourtesy") + "<b> " + 
                DataBinder.Eval(Container.DataItem, "LastName") + 
                "</b>" + ", " + 
                DataBinder.Eval(Container.DataItem, "FirstName") %>' />            
        </itemtemplate>
       </asp:TemplateColumn>       <asp:TemplateColumn runat="server" HeaderText="Photo">
        <itemtemplate>
            <img runat="server" width="50"  
               visible='<%# IsImageAvailable(DataBinder.Eval
               (Container.DataItem, "lastname")
               ToString()) %>'    
               src='<%# "images\\" + DataBinder.Eval(Container.DataItem, 
               "lastname") + ".bmp" %>' />
            <asp:label runat="server" text="<i><small>No picture available.
                       </small></i>" 
               visible='<%# !IsImageAvailable(DataBinder.Eval
               (Container.DataItem, 
               "lastname").ToString()) %>' />
        </itemtemplate>
           </asp:TemplateColumn></columns>
    </asp:DataGrid>
     
    函数假设要显示的图片为bmp类型,它位于应用程序根目录的images子目录下,而且图片文件的名称为雇员的名字。当然,这是武断的。在你自己的应用程序里,你也许会用其他更聪明的办法来解决。图10显示了当某些图片不存在的时候,页面将显示: 
     
    Figure 10 Missing Image
      

  3.   

    实现ITemplate
    如果你想完全地在内存中创建一个的模板,你不得不首先编码,然后实例化一个类,该类实现ITemplate接口. ITemplate接口只有一个方法,名为InstantiateIn. asp.net中所有具有template属性的服务器控件都通过这个属性暴露实现了ITemplate接口的类.这个接口简单地定义了用于组合与模板一致的子控件的实例的方法来填充容器控件.图11演示了实现itemplate接口大部分的代码,它为一个datagrid在内存中创建了一个的模板列.同样,也可以把这些代码用于datalist和repeater控件中。唯一不同的是在OnDataBinding事件处理上。类的结构你可以写成类似下面:
    class LastFirstNameTemplate : ITemplate
    {
      public void InstantiateIn(Control container) 
      {...}  private void BindLastName(Object s, EventArgs e)
      {...}  private void BindFirstName(Object s, EventArgs e)
      {...}
    }Figure 11 In-memory Templated Column 
    // 动态的创建模板列函数
    public void AddTemplateColumnFromITemplate(String strHeader)
    {
        TemplateColumn bc = new TemplateColumn();
        bc.HeaderText = strHeader;
        bc.ItemTemplate = new LastFirstNameTemplate();
        grid.Columns.Add(bc);
    }// 这个类实现了一个自定义模板。新控件与容器本身的控件组合创建一个新的模板类,如// 果控件要绑定数据,你可以注册你自己的绑定处理事件(OnDataBinding事件)
    public class LastFirstNameTemplate : ITemplate
    {    // Instantiate the elements of the template in the given
        // container. In this case, a DataGridItem element.    public void InstantiateIn(Control container) 
        {
            container.Controls.Add(new LiteralControl("<b>"));        Label lblLastName = new Label();
            lblLastName.DataBinding += new EventHandler(this.BindLastName);
            container.Controls.Add(lblLastName);        container.Controls.Add(new LiteralControl("</b>, "));        Label lblFirstName = new Label();
            lblFirstName.DataBinding += new EventHandler(this.BindFirstName);
            container.Controls.Add(lblFirstName);
        }    // Handler of the OnDataBinding event for the Label element
        // that renders the lastname column in the template.    private void BindLastName(Object sender, EventArgs e)
        {
                Label l = (Label) sender;
                DataGridItem container = (DataGridItem) l.NamingContainer;
                l.Text = ((DataRowView)container.DataItem)
                    ["lastname"].ToString();
        }    // Handler of the OnDataBinding event for the Label element
        // that renders the firstname column in the template.    private void BindFirstName(Object sender, EventArgs e)
        {
                Label l = (Label) sender;
                DataGridItem container = (DataGridItem) l.NamingContainer;
                l.Text = ((DataRowView)container.DataItem)
                    ["firstname"].ToString();
        }
    }
    这个类可以在asp.net页的<script>部分中定义,也可以在一个单独的类文件中定义.另一种的好的方法是把这定义类的代码放在asp.net页的后台代码源文件中. 在InstantiateIn方法中,简单地创建控件的实例并把它们添加到容器控件中。对于datagrid控件来说,容器控件是一个datagriditem对象。datalist 容器控件是DataListItem对象。容器可以是任何一个实现了INamingContainer接口的类.
    Label lblName = new Label();
    lblName.DataBinding += new EventHandler(this.BindName);
    container.Controls.Add(lblName);
      如果添加到的容器的控件集合中的控件要绑定数据源字段,那么还要为注册您自己的databinding事件处理程序。当databinding事件触发时,从数据源检索数据生成文本并刷新控件的用户界面.如果是自定义服务器控件,databinding事件处理程序需要解析服务器控件以及它所有的子控件中的所有数据绑定表达式。
    void BindName(Object sender, EventArgs e)
    {
      Label l = (Label) sender;
      DataGridItem container;
      container = (DataGridItem) l.NamingContainer;  DataRowView drv;
      drv = ((DataRowView) container.DataItem);
      l.Text = drv["lastname"].ToString();
    }
    databinding事件处理程序完成两项任务。如果编写正确,它应该首先提取并且保持底层数据项;其次,刷新绑定控件的用户界面以反映出数据绑定。
    通过sender参数可以获得相关对象的引用。包含该控件的容器可以由控件本身的namingcontainer属性返回.这时,你就有了设置和使用另一个asp.net表达式Container.DataItem所需要的所有内容。数据项的类型取决于与datagrid关联的数据源。在大多数的实际应用中,它会是DataRowview。剩下的就是访问行中的特定的列并设置控件的绑定属性。可以多选的datagrid
    在讨论完asp.net的模板时,让我举一个实际的例子详细的说明,这个例子是动态的创建一个模板列,用于多选.
    <%@ Register TagPrefix="expo" 
    Namespace="BWSLib" 
    Assembly="MultiGrid" %>
    &#8226;&#8226;&#8226;
    <expo:MultiGrid id="grid" runat="server" 
        AutoGenerateColumns="false"
        AllowMultiSelect="true"
        AllowMultiSelectFooter="true"
        font-size="x-small" font-names="verdana"
        BorderStyle="solid" BorderWidth="1"
        GridLines="both">
    它是一个新的控件,继承自datagrid控件。此外,它也能被实例化及像datagrid一样的配置. 这个新的控件添加了一个新的列,放了一个checkbox控件以允许选择。另外, footer被修改成用一个链接按钮用于取消选定的行。
    图12列举了MultiGrid控件的新方法和属性定义。图13演示了应用该控件的情况。
    Figure 12 Methods and Properties 
    Property/Method Description
    AllowMultiSelect Boolean property that enables or disables the multi-selection feature. Defaults to False
    AllowMultiSelectFooter Boolean property that enables or disables the tailor-made footer for the grid. Defaults to False. If enabled, it overrides the default footer. If disabled but the grid has a footer anyway, then the contents of this special footer are displayed as the footer of the checkbox column
    SelectedItems Read-only property that returns an array with the DataGridItem objects currently selected
    ClearSelection Method that clears all the currently selected itemsFigure 13 The Control in Action
     
     
    最后,在图14中,我写了该控件的一些关键的代码。这些是结合ITemplate类来提供ItemTemplate和FooterTemplate属性的.
      

  4.   

    项目总结七是想送给大家做五一节的礼物,贴的时候忘了写了,标题应该是:
    五一节的礼物---项目总结(七)理解asp.net中的模板
    祝大家五一节快乐!!
    身体健康也很重要!!
      

  5.   

    这是支持多选的datagrid控件的源代码,只包含关键的源代码,所有的源码请到msdn下载:namespace BWSLib
    {
        public class MultiGrid : DataGrid
        {
            // Constructor that sets some styles and graphical properties    
            public MultiGrid()
            {
                AllowMultiSelect = false;
                AllowMultiSelectFooter = false;            // Set event handlers
                Init += new EventHandler(OnInit);
                ItemCreated += new DataGridItemEventHandler(OnItemCreated);
            }        // PROPERTY: SelectedItems
            public ArrayList SelectedItems 
            {
                get 
                {
                    if (!AllowMultiSelect)    return null;                ArrayList a = new ArrayList();
                    foreach(DataGridItem dgi in Items)
                    {
                        CheckBox cb = (CheckBox) dgi.Cells[0].Controls[0];
                        if (cb.Checked)
                            a.Add(dgi);
                    }
                    return a;
                }
            }        // PROPERTY: AllowMultiSelect 
            public bool AllowMultiSelect = false;        // PROPERTY: AllowMultiSelectFooter 
            public bool AllowMultiSelectFooter = false;        // METHOD: ClearSelection
            public void ClearSelection()
            {
                foreach(DataGridItem dgi in Items)
                {
                    CheckBox cb = (CheckBox) dgi.Cells[0].Controls[0];
                    cb.Checked = false;
                }
            }        ///////////////////////////////////////////////////////////////////
            // Event Handlers
            // EVENT HANDLER: Init            
            private void OnInit(Object sender, EventArgs e)
            {
                // Add a templated column that would allow for selection.
                // The item template contains a checkbox. It also features a 
                // templated footer containing links for Unselect/Select all 
                if (AllowMultiSelect)
                    AddSelectColumn();
            }        // EVENT HANDLER: Deselect
            private void OnDeselect(Object sender, EventArgs e)
            {
                ClearSelection();
            }        // EVENT HANDLER: ItemCreated            
            private void OnItemCreated(Object sender, DataGridItemEventArgs e)
            {
                // Get the newly created item
                ListItemType itemType = e.Item.ItemType;            ///////////////////////////////////////////////////////////////
                // FOOTER
                if (itemType == ListItemType.Footer && AllowMultiSelectFooter 
                    && AllowMultiSelect)
                {
                    // Look for a link button called "lnkSelect" in the context 
                    // of the grid item that represents the footer
                    LinkButton lb = (LinkButton) 
                        e.Item.FindControl("lnkDeselect");                // Now you hold the living instance of the link 
                    // button in the footer and can bind it to any code in the 
                    // context of the MultiGrid control
                    lb.Click += new EventHandler(OnDeselect);
                    
                    // Force ShowFooter to true
                    ShowFooter = true;                // Removes all the cells but the first 
                    TableCell cell = e.Item.Cells[0];
                    for (int i=1; i<Columns.Count; i++)
                    {
                        e.Item.Cells.RemoveAt(1);
                    }
                    cell.ColumnSpan = Columns.Count;
                }
            }        ///////////////////////////////////////////////////////////////////
            // Helper Functions        private void AddSelectColumn()
            {
                // Create the new templated column
                TemplateColumn tc = new TemplateColumn();
                tc.ItemStyle.BackColor = Color.SkyBlue;
                tc.ItemTemplate = new SelectColumnTemplate();
                tc.FooterTemplate = new SelectFooterTemplate();
                Columns.AddAt(0, tc);
            }
        }    ///////////////////////////////////////////////////////////////////////
        // Template Classes    public class SelectColumnTemplate : ITemplate
        {
            public void InstantiateIn(Control container)
            {
                CheckBox cb = new CheckBox();
                container.Controls.Add(cb);
            }
        }    public class SelectFooterTemplate : ITemplate
        {
            public void InstantiateIn(Control container)
            {
                LinkButton lb = new LinkButton();
                lb.Text = "Deselect all";
                lb.ID = "lnkDeselect";
                container.Controls.Add(lb);
            }
        }
    }