为什么c#中的FindControls不返回虚拟控件,而它已用于在aspx页面中查找控件

flseospp  于 2023-07-01  发布在  .NET
关注(0)|答案(2)|浏览(104)

我有一个公共文件,其中放置了最常用的函数和方法,然后由其他页面使用其名称和点操作符调用。在这些函数中,我实现了一个使用FindControls的函数。它返回的不是一个控件,而是null,我得到了以下错误:
对象引用未设置为对象且为空

mycontrol = (global::System.Web.UI.WebControls.Repeater)Page.FindControl("Repeater1");
mycontrol.DataSource = message; // getting that error at this place

我期望FindControls方法从页面返回相应的控件。我做错了什么?

eit6fx6z

eit6fx6z1#

没有什么能阻止你为你想要在你所有的webforms中共享的一堆通用代码例程创建一个通用代码模块。
要使此功能正常工作,您需要:
通用代码例程应为静态类。你必须遵循的唯一真实的的规则是,类不能使用类作用域变量,因为它们在所有用户之间共享。这是一个小问题,简单地采用一种设计模式,即您 * 从不 * 尝试在共享代码中持久化类作用域的值,您就可以了。
下一个:
一些不属于webforms代码的蓝色代码模块,因此不知道要操作哪个表单/网页,是吗?
我的意思是,如果我打开了5个浏览器选项卡,我调用一些常规代码例程,这些代码将在哪个网页上操作?(答案:没有线索)。
因此,简单的解决方案是始终传递当前页面,或当前div或任何您希望这些通用助手例程操作的内容。
因此,find control可以在这样的例程中使用,但您仍然必须将当前页传递给这样的例程,以便find control或任何仍然可以使用的内容。
让我们创建一个例程来填写GridView。
所以,我们可以在页面上使用这种格式

string strSQL =
            @"SELECT * FROM tblHotelsA
             ORDER BY HotelName";

        General.LoadGridData(GridView1, strSQL);

我们的静态类(我们的共享代码库)是这样的:

public static class General
{

    public static void LoadGridData(GridView GV, string strSQL)
    {
        GV.DataSource = MyRst(strSQL);
        GV.DataBind();

    }

    public static DataTable MyRst(string strSQL, string sConn = "")
    {
        DataTable rstData = new DataTable();

        if (sConn == "")
            sConn = Properties.Settings.Default.TEST4;

        using (SqlConnection conn = new SqlConnection(sConn))
        {
            using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
            {
                cmdSQL.Connection.Open();
                rstData.Load(cmdSQL.ExecuteReader());
            }
        }
        return rstData;
    }

所以,我们真的不需要在这里找到控制,是吗?
然而,让我们说一些奇怪的原因,我们确实想在我们的代码中使用find控件。
所以,我们可以说一般来说:

public static void LoadGridDataS(Page MyPage, string sGV, string strSQL)
    {
        GridView GV = (GridView)MyPage.FindControl(sGV);
        GV.DataSource = MyRst(strSQL);
        GV.DataBind();

    }

因此,我们将从网页代码中使用以下内容:

protected void cmdLoadGrid_Click(object sender, EventArgs e)
    {
        string strSQL =
            @"SELECT * FROM tblHotelsA
             ORDER BY HotelName";

        General.LoadGridDataS(Page,"GridView1", strSQL);

    }

因此,如上所示,我们可以使用find控件,但实际上,在大多数情况下,您不需要使用find控件,而只需首先传递控件类型!
而且我想在第一天(大约)之后,我变得厌倦了在一个页面上填写控件。同样的代码是在页面上填写一些表单数据。
所以,我在General中构建了一个例程,在其中我将页面上的div和一个数据行发送给它。(所以现在代码可以自动为我填充控件)。
所以,假设我有这个标记:
(我将发布这个标记-真的不重要,你可以跳过大部分标记)。
然而,我所做的是为每个控件添加一个名为“f”的虚构属性,这意味着数据行的列名。
所以,现在有了这个标记:

<div id="EditRecord" runat="server" style="float: left; display: none; padding: 15px">
    <div style="float: left" class="iForm">
        <label>HotelName</label>
        <asp:TextBox ID="txtHotel" runat="server" Width="280" f="HotelName" /><br />
        <label>First Name</label>
        <asp:TextBox ID="tFN" runat="server" Width="140"  f="FirstName"/><br />
        <label>Last Name</label>
        <asp:TextBox ID="tLN" runat="server" Width="140" f="LastName" /><br />
        <label>City</label>
        <asp:TextBox ID="tCity" runat="server" Width="140" f="City" ClientIDMode="Static" /><br />
        <label>Province</label>
        <asp:TextBox ID="tProvince" runat="server" Width="75" f="Province" /><br />
    </div>
    <div style="float: left; margin-left: 20px" class="iForm">
        <label>Description</label>
        <br />
        <asp:TextBox ID="txtNotes" runat="server" Width="400" TextMode="MultiLine"
            Height="150px" f="Description"></asp:TextBox><br />
        <asp:CheckBox ID="chkActive" Text=" Active" runat="server" 
            TextAlign="Right" f="Active" />
        <asp:CheckBox ID="chkBalcony" Text=" Has Balcony" runat="server" 
            TextAlign="Right" f="Balcony"/>
    </div>
    <div style="clear: both"></div>
    <button id="cmdSave" runat="server" class="btn myshadow" type="button"
        onserverclick="cmdSave_ServerClick">
        <span aria-hidden="true" class="glyphicon glyphicon-floppy-saved">Save</span>
    </button>

    <button id="cmdCancel" runat="server" class="btn myshadow" style="margin-left: 15px"
        type="button"
        onclick="$('#EditRecord').dialog('close');return false;" >
        <span aria-hidden="true" class="glyphicon glyphicon-arrow-left">Back/Cancel</span>
    </button>

    <button id="cmdDelete" runat="server" class="btn myshadow" style="margin-left: 15px"
        type="button"
        onserverclick="cmdDelete_ServerClick"
        onclick="if (!confirm('Delete Record?')) {return false};">
        <span aria-hidden="true" class="glyphicon glyphicon-trash">Delete</span>
    </button>

    </div>

我可以在上面填写这个:

protected void cmdEdit_Click(object sender, EventArgs e)
    {
        Button cmdEdit = (Button)sender;
        GridViewRow gRow = (GridViewRow)cmdEdit.NamingContainer;

        string PKID = GridView1.DataKeys[gRow.RowIndex]["ID"].ToString();
        ViewState["PKID"] = PKID;

        string strSQL
            = $"SELECT * FROM tblHotelsA WHERE ID = {PKID}";
        DataTable rstData = General.MyRst(strSQL);
        General.FLoader(EditRecord, rstData.Rows[0]);  // send table row to div

        // now call the jQuery.ui pop dialog routine.
        string MyJava = $"pophotel('{cmdEdit.ClientID}')";
        Debug.Print(MyJava);

        ClientScript.RegisterStartupScript(Page.GetType(), "mypop", MyJava, true);

    }

因此,在上面的代码中,我们使用了几个通用例程,包括floader。(您向它传递一个数据行)。
因此,上面的所有标记都被填充。
注意在这个例子中我是如何传递div的。我使用了一个div,因为我可能希望页面上有几个不同数据源的部分。
因此,这个floader的代码必须循环所有控件(在那个div中,但我可以传递整个页面)。
代码看起来像这样:

public static void FLoader(HtmlGenericControl F, DataRow rst)
    {
        foreach (System.Web.UI.Control c in F.Controls)
        {
            if (c.GetType() == typeof(TextBox))
            {
                TextBox ctlC = c as TextBox;
                if (ctlC.Attributes["f"] != null)
                    if (rst.Table.Columns.Contains(ctlC.Attributes["f"]))
                        ctlC.Text = (DBNull.Value.Equals(rst[ctlC.Attributes["f"]]) ? "" : rst[ctlC.Attributes["f"]].ToString());
            }
            else if (c.GetType() == typeof(Label))
            {
                Label ctlC = c as Label;
                if (ctlC.Attributes["f"] != null)
                    if (rst.Table.Columns.Contains(ctlC.Attributes["f"]))
                        ctlC.Text = (DBNull.Value.Equals(rst[ctlC.Attributes["f"]]) ? "" : rst[ctlC.Attributes["f"]].ToString());

            }
            else if (c.GetType() == typeof(DropDownList))

       .etc for more control types.

因此,上面的代码循环了控件,检查了“f”属性,并填写了表单(然后我使用jQuery.ui弹出它)。
结果如下:

所以,我在General类中有很多例程,你可以自由地从Web表单中调用/使用这些代码,包括使用find控件,传递控件,或者经常传递整个页面。
因此,在上面的例子中,当用户点击一个网格视图行时,我使用floader例程来填充该div中的“酒店控件”,然后使用jquery. ui弹出该div。但是,正如你所看到的,当你在General中构建例程时,毫无疑问,你编写的代码越来越少,有一个很好的实用程序例程“抓取包”来做基本的事情,比如在div中填写控件,而不必一遍又一遍地编写代码。

unguejic

unguejic2#

我认为答案是由@Albert发布的。卡拉尔是一个更完整和深入的解释如何去做。
但既然你问我了,我会试着在这里给予我的观点(不要把这当作一个答案-它只是更好地张贴它作为一个答案比大量的评论块)
我试着将你的代码从3条评论中复制并粘贴到一个代码块中,结果如下。

public static bool DisplayValidationMessages(Control Page, String string) 
{ 
    if (SessionState.Instance.ValidationStatusMessage != null) 
    { 
        Control control = Page; 
        global::System.Web.UI.WebControls.Repeater Repeater1 = null; 
        if (SessionState.Instance.ValidationStatusMessage.MessageList.Count != 0) 
        {
            List<MessageListContract> listMessages = new 
            List<MessageListContract>(SessionState.Instance.ValidationStatusMessage.MessageList); 
            List<MessageListContract> error = new List<MessageListContract>(); 
            ///// Facing error at this line since the FindControl returns null instead of returning a control from another page 
            myrepeater = (global::System.Web.UI.WebControls.Repeater)control.FindControl("Repeater1"); 
            foreach (var msg in listMessages) 
            { 
                Service.Exception TypeOfMessage = msg.MessageType; 
                switch (TypeOfMessage)
                { 
                    case Service.Exception.error: 
                        error.Add(msg); 
                } 
            }
            ////Facing error at this line since the list error is not assigned to a null valued object 
            myrepeater.DataSource = error; 
            myrepeater.DataBind(); 
            if (Count >= 1) 
            { 
                errorRepeater = (global::System.Web.UI.HtmlControls.HtmlGenericControl)control.FindControl("errorRepeater"); 
                errorRepeater.Visible = true; 
            } 
            // "}" I cannot match this curly bracket to another so have remarked/remmed it out?
        } 
        return false; 
    } else { 
        return true; 
    } 
}

我很抱歉我的C#不是很好,所以我结束了一个额外的结束花括号?无论如何,我记住了我认为不正确的那个。
通常,当我们遇到问题时,隔离它们的最佳方法是将它们分解为各个部分,我的意思是,您正在使用FindControl方法/函数,所以如果您首先将FindControl方法的代码隔离到一个单独的函数中,并在不同的网页上使用一些控件进行测试,那会更好。
从VB的背景来看,首先我只是把一个函数放在一起,在PAGE对象中搜索一个子控件。它只搜索控件,没有其他任何东西,没有错误消息,尽管我已经对它进行了注解,以显示错误的位置/内容。
然后我使用Telerik免费在线代码转换器将其转换为C#。
首先这里是VB代码,注意它只搜索页面的顶层,它会递归遍历每个子对象/控件,这些子对象/控件本身可能包含其他对象/控件。
您可能需要创建一个单独的函数或修改您的findcontrol函数来考虑子对象(特别是如果您的repeater不在直接页上,而是嵌套在另一个对象/控件中)。

'--------------------------------------------------------------------
'FindControl Method
'--------------------------------------------------------------------
Public Shared Function FindThisControl(ByVal PageObj As Page, FindCtrlName As String) As System.Web.UI.Control
   '
   'find control in page and return found control
   'if no match is found then return nothing/null

   Dim FoundCtrl As System.Web.UI.Control = Nothing
   FindThisControl = Nothing
   If IsNothing(PageObj) Then
      'Error - Page object was nothing/null
   ElseIf isnothing(FindCtrlName) Then
      'Error - FindCtrlName was nothing/null
   ElseIf Trim(FindCtrlName) = "" Then
      'Error - FindCtrlName was empty string
   Else
      FoundCtrl = PageObj.FindControl(FindCtrlName)
      If IsNothing(FoundCtrl) Then
         'Error - Control FindCtrlName was not found in PageObj page 
      Else
         FindThisControl = FoundCtrl
      End If
   End If
   '
End Function

然后转换成C#代码

// --------------------------------------------------------------------
// FindControl Method
// --------------------------------------------------------------------
public static System.Web.UI.Control FindThisControl(Page PageObj, string FindCtrlName)
{
   // 
   // find control in page and return found control
   // if no match is found then return nothing/null
   //

   System.Web.UI.Control FoundCtrl = null;
   FindThisControl = null;
   if (PageObj == null)
   {
      // Error - PageObj page was nothing/null
   }
   else if (FindCtrlName == null))
   {
      // Error - FindCtrlName the name of the control to find was nothing/null
   }
   else if (Strings.Trim(FindCtrlName) == "")
   {
      // Error - FindCtrlName the name of the control to find was an empty string
   }
   else
   {
      //Try to find the control
      FoundCtrl = PageObj.FindControl(FindCtrlName);
      if (FoundCtrl == null)
      {
         // Error - No matching named control was found
      }
      else
         FindThisControl = FoundCtrl;
   }
}

无论VB或C#代码是否会找到你的Repeater,如果它嵌套在另一个控件中,你必须循环通过每个子控件,检查它是否有任何子控件,并循环通过,所以它可以是相当递归的,但良好的规划和坚持基础将有所帮助。这就是为什么我会选择阿尔伯特D。Kallal方法,并尝试一步一步地完成它,直到你明白发生了什么。
我只是把这个贴在这里,因为你特别问我。
更多阅读:Microsoft文档https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.page.findcontrol?view=netframework-4.8.1#system-web-ui-page-findcontrol(system-string)

相关问题