在网格中交互控件

时间:2010-11-27 02:11:31

标签: c# asp.net

我一直在设计一个网页上有一个网格。网格中有多个组合框。这些组合框相互作用。即,当用户更改了一个值时,另一个值会更改或禁用/启用等等。

我发现为了做到这一点,我必须经常使用FindControl。就像在一个组合框的selectedindexchanged事件中一样,我需要找到另一个组合框。

这似乎是一种混乱的做事方式。看起来它似乎让系统开放出错,如果组合框的ID在以后的行中被更改,编译器将无法找到它。

有人能告诉我有没有更好的方法来解决这个问题?

5 个答案:

答案 0 :(得分:1)

我有一个Web应用程序,它也广泛使用各种FindControl排列,以实现您描述的内容。虽然它很脆弱(不经测试就不改变控制ID),但通过一些实用功能可以减少它的麻烦。以下是我使用的所有FindControl类型的函数 - 这至少可以帮助你。

/// <summary>
/// Recursively searches for a control within the heirarchy of a given control.
/// </summary>
/// <param name="root">The control to search within.</param>
/// <param name="id">The ID of the control to find.</param>
/// <returns>The Control object of the found control, or null if the control isn't found.</returns>
public static Control FindControlRecursive(Control root, string id)
{
    if (root.ID == id) return root;

    foreach (Control c in root.Controls)
    {
        Control t = FindControlRecursive(c, id);
        if (t != null) return t;
    }

    return null;
}

/// <summary>
/// Recursively searches for a control within the heirarchy of a given control using the Client ID
/// of the control. Calling this too early in the lifecycle may not behave as expected.
/// </summary>
/// <param name="root">The control to search within.</param>
/// <param name="clientID">The Client ID of the control to find.</param>
/// <returns>The Control object of the found control, or null if the control isn't found.</returns>
public static Control FindControlRecursiveByClientID(Control root, string clientID)
{
 if (0 == String.Compare(root.ClientID, clientID, true)) return root;

 foreach (Control c in root.Controls)
 {
  Control t = FindControlRecursiveByClientID(c, clientID);
  if (t != null) return t;
 }

 return null;
}

/// <summary>
/// Recursively searches for a group of controls within the heirarchy of a given control tree using the ID
/// of the control.
/// </summary>
/// <param name="root">The control tree to search within.</param>
/// <param name="id">The ID of the control to find.</param>
/// <returns>
/// A collection of the found controls. The collection will be empty if none are found.
/// </returns>
public static List<Control> FindControlsRecursive(Control root, string id)
{
 List<Control> collection = new List<Control>();
 FindControlRecursive(root, id, collection);
 return collection;
}

private static void FindControlRecursive(Control root, string id, List<Control> collection)
{
 foreach (Control c in root.Controls)
 {
  if (0 == String.Compare(c.ID, id, true)) collection.Add(c);
  else FindControlRecursive(c, id, collection);
 }
}

/// <summary>
/// Recursively searches for a control within the heirarchy of a given control using the type
/// of the control.
/// </summary>
/// <typeparam name="T">The type of the control to find.</typeparam>
/// <param name="root">The control to search within.</param>
/// <returns>
/// The Control object of the found control, or null if the control isn't found.
/// </returns>
public static T FindControlRecursiveByType<T>(Control root)
 where T : Control
{
 if (root is T) return (T)root;
 foreach (Control c in root.Controls)
 {
  Control t = FindControlRecursiveByType<T>(c);
  if (t != null) return (T)t;
 }
 return null;
}

/// <summary>
/// Recursively searches for a set of controls within the heirarchy of a given control using the type
/// of the control.
/// </summary>
/// <typeparam name="T">The type of the control to find.</typeparam>
/// <param name="root">The control to search within.</param>
/// <returns>
/// A generic List object containing the controls found, or an empty List of none were found.
/// </returns>
public static List<T> FindControlsRecursiveByType<T>(Control root)
 where T : Control
{
 List<T> collection = new List<T>();
 FindControlRecursiveByType<T>(root, collection);
 return collection;
}

private static void FindControlRecursiveByType<T>(Control root, List<T> collection)
 where T : Control
{
 foreach (Control c in root.Controls)
 {
  if (c is T) collection.Add((T)c);
  else FindControlRecursiveByType<T>(c, collection);
 }
}

答案 1 :(得分:0)

正是出于这个原因,我从ASP.NET切换到在Silverlight中开发并利用其MVVM模式。即使使用ASP.NET的GridView项目模板,每个项目也不能直接绑定,可以识别或引用同一模板中的其他项目。您的代码必须在某种程度上(通常是最充分的)知道视图控件的组合层次结构。

这是你可以做些什么来接近“更好的约束世界”。您仍然会将组合框绑定到相同的列表数据源,但是当创建每行项目/控件时,您将每个项目与一个对象(即标记项目)相关联。然后在控件的事件处理中,您将检索与引发事件的控件关联的标记项关联的其他控件,并按照它们执行的操作。

我知道,不是最好的主意,而是我的头脑。也许当我有时间考虑更多时,我可以更新这篇文章。

答案 2 :(得分:0)

如何使用事件进行通知?

答案 3 :(得分:0)

这些非常混乱。

这是一个非常简单而优雅的解决方案。假设您的网格需要在包含3列的表中显示数据。数据来自具有以下结构的对象:

[Serializable]
public class Foo
{
    public string Bar1 { get; set; }
    public string Bar2 { get; set; }
    public string Bar3 { get; set; }
}

然后您的用户控件将具有以下标记:

标记(GridDisplayRow.ascx):

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="GridDisplayRow.ascx.cs" Inherits="GridDisplayRow" %>
<div>
    <div class="cell">
        <asp:TextBox id="TxtProperty1" runat="server"/>
    </div>
    <div class="cell">
        <asp:DropDownList ID="DDLProperty2" runat="server" OnSelectedIndexChanged="DDLProperty2_OnSelectedIndexChanged" AutoPostBack="true">
        </asp:DropDownList>
    </div>
    <div class="cell">
        <input type="radio" id="RadProperty3" runat="server">
    </div>
</div>

注意所有div标签。儿童div是浮动的......所以它们并排显示。

Codebehind(GridDisplayRow.ascx.cs):

class GridDisplayRow:UserControl
{
   public event EventHandler<System.EventArgs> SomethingHappened;

   protected void Page_Load(object sender, EventArgs e)
   {
       //Initialize the drop down with something;
   }

   //this is where we handle internal events generated by children controls.
   //Eg: the drop down's index changed.
   protected void DDLProperty2_OnSelectedIndexChanged(object sender, EventArgs e)
   {
       //we update some other child control in this...
       this.TxtProperty1.Text = this.DDLProperty2.Value;

       //and maybe we want to signal the page that some event happened
       if(SomethingHappened != null)
       {
          //Notify the page that SomethingHappened event occured
          SomethingHappened(this, EventArgs.Empty);
       } 
   }

   //This is where the binding happens
   public object BindingObject
   {
      set
      {
           Foo temp = (Foo)value;
           this.TxtProperty1.Text = temp.Bar1;
           this.DDLProperty2.Value = temp.Bar2;
           this.RadProperty3.Value = temp.Bar3;

           this.ViewState["Foo"] = temp;   
      }
   }
}

所以在上面的代码中我们处理绑定到Foo,换句话说,我们在控件的每个单元格(div)中显示Foo的属性。需要强制转换是因为上面的属性是object类型,并且在Grid / Repeater的ItemTemplate中/你有什么,你将GridDisplayRow的一个实例绑定到Container.DataItem对象,如下所示。请记住,如果您的数据源是DataSet,则必须转换为DataRow,或者需要任何适当的数据类型:

页面标记:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Test.aspx.cs" Inherits="Test" %>
<%@ Register src="GridDisplayRow.ascx" tagname="GridRow" tagprefix="ctrl" %>

<asp:GridView ID="GridView1" runat="server">
    <Columns>
        <asp:TemplateField>
            <HeaderTemplate>
                <div>
                    <div class="header cell">Header 1</div>
                    <div class="header cell">Header 2</div>
                    <div class="header cell">Header 3</div>
                </div>
            </HeaderTemplate>
            <ItemTemplate>
                <ctrl:GridRow ID="Row" runat="server" BindingObject='<%# Container.DataItem %>' OnSomethingHappened="Row_SomethingHappened" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

我不喜欢使用网格,因为它们很麻烦且很麻烦。在这个例子中,我们欺骗网格创建一个每行有一个单元格的表格,并通过样式表处理该单元格中的布局。

请注意,使用中继器可以更好地工作,因为它非常高效且开销更少!

现在绑定网格: 任何实现IEnumerable的源都可以用于绑定到网格。因此像List这样的集合就足够了。

当GridRow控件触发其事件时,通过对象发送方发送对控件本身的引用,因此如果您将发送方转换为正确的类型,则可以获取控件的内部属性....可能性是无穷无尽的。

这种方法的优点是抽象。每个控件都可以处理自己的事件,或者选择通知页面内部发生的事件.... bla bla bla。你明白了。

答案 4 :(得分:0)

尝试使用ListView更容易管理这类要求。如果您将ListView放在Update Panel内,则可以获得更好的用户体验