英文:
Why FindControls in c# does not returning a virtual control while it has been used to find a control in aspx page
问题
我有一个通用文件,在其中放置了最常用的函数和方法,然后其他页面通过其名称和点运算符调用这些函数。在这些函数中,我实现了一个使用FindControls
的函数。但是它不是返回一个控件,而是返回null
,我得到以下错误:
> 对象引用未设置为对象并为空
mycontrol = (global::System.Web.UI.WebControls.Repeater)Page.FindControl("Repeater1");
mycontrol.DataSource = message; // 在这个地方出现错误
我期望FindControls
方法返回页面中相应的控件。我做错了什么?
英文:
I have a common file in which I place the most commonly used functions and methods which then get called by other pages, using its name and dot operator. Among these functions, I have implemented a function that uses FindControls
. Instead of returning a control, it's returning null
and I am getting the following error:
> Object reference is not set to an object and null
mycontrol = (global::System.Web.UI.WebControls.Repeater)Page.FindControl("Repeater1");
mycontrol.DataSource = message; // getting that error at this place
I am expecting the FindControls
method to return the corresponding control from the page. What am I doing wrong?
答案1
得分: 1
以下是翻译好的部分:
没有任何阻止你创建一个通用代码模块,用于一堆你想在所有 web 表单之间共享的通用代码例程。
要使这个工作,你需要:
通用代码例程应该是静态类。你唯一需要遵循的真正规则是类不能使用类作用域变量,因为它们在所有用户之间共享。这是一个小问题,简单地采用一个设计模式,你永远不要尝试在共享代码中保留类作用埕的值,你就会没事。
接下来:
一些莫名其妙的代码模块,不是 web 表单代码后台的一部分,因此它不知道它要在哪个表单/网页上运行,对吗?
我的意思是,如果我打开了 5 个浏览器标签,然后调用一些通用代码例程,那么这段代码将在哪个网页上运行?(答案是:它不知道)。
因此,简单的解决方案是始终传递当前页面,或者当前的 div 或者你想要这些通用辅助例程操作的任何其他东西。
因此,查找控件可以在这样的例程中使用,但你仍然必须将当前页面传递给这样的例程,以便查找控件或其他任何东西仍然可以使用。
让我们想象一个填充 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;
}
}
这部分描述了如何创建一个通用的代码模块,以及如何在网页中使用它来填充 GridView。其中也包括了关于如何处理共享代码中的类作用域变量的说明。
英文:
There is nothing stopping you from say creating a general code module for a hodge-podge of general code routines that you want to share among all webforms you have.
To make this work, you need:
The general code routine(s) should be static class. And the only real rule you have to follow is that class cannot use class scoped variables, since they are shared among all users. This is a minor issue, and simple adopting a design pattern that you never try to persist class scoped values in that shared code, and you'll be just fine.
Next up:
Some out of the blue code module that is NOT part of a webforms code behind thus has no clue which form/web page it is to operate on, does it?
I mean, if I have 5 browser tabs open, and I call some general code routine, which web page is that code going to operate on? (Answer: it has no clue).
So, the simple solution is to always pass the current page, or the current div or whatever you want these general helper routines to operate on.
So, find control can be used in such routines, but you still have to pass the current page to such routines so find control or whatever still can be used.
Let's make up a routine to say fill out a GridView.
So, we could use this format on the page
string strSQL =
@"SELECT * FROM tblHotelsA
ORDER BY HotelName";
General.LoadGridData(GridView1, strSQL);
And our static class (our shared code library) is thus this:
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;
}
So, we really did not require find control here, did we?
However, let's say for some strange reason, we did want to use find control in our code.
So, we could say have this in general:
public static void LoadGridDataS(Page MyPage, string sGV, string strSQL)
{
GridView GV = (GridView)MyPage.FindControl(sGV);
GV.DataSource = MyRst(strSQL);
GV.DataBind();
}
And thus we would then use from the web page code this:
protected void cmdLoadGrid_Click(object sender, EventArgs e)
{
string strSQL =
@"SELECT * FROM tblHotelsA
ORDER BY HotelName";
General.LoadGridDataS(Page,"GridView1", strSQL);
}
So, as above shows, we CAN use find control, but really, in most cases you don't need to use find control, but just pass the control type in the first place!
And I think after the first day (or so) I became tired of filling out controls on a page. That same code was to fill out some form data on a page.
So, I built a routine in General in which I send it the div on the page, and a data row. (So now code can automatically fill out controls for me).
So, say I have this markup:
(I going to post this markup - really not important, and you can skip most of this markup).
However, what I did was add a made-up attribute for each control called "f" which means the data row column name.
So, now with this markup:
<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>
I can fill out above with this:
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);
}
So, in above, we use several general routines, including the floader. (You pass it a data row).
So, all of the above markup gets filled.
Note how in this case I passed a div. I used a div since I might want several sections on the page with different data source.
The code for this floader thus has to loop all of the controls (in that div, but I could pass the whole page).
the code looks like this:
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.
So, the above loops the controls, and checks for a "f" attribute, and fills out the form, (and then I pop it using jQuery.ui).
The result looks like this:
So, I have many routines in my General class, and you are rather free to call/use such code from a web form, and that includes using find control, passing controls, or often passing the WHOLE page as per my example.
So, in above, when user clicks on a grid view row, then I use that floader routine to fill out the "hotel controls" in that div, and then I pop that div using jquery.ui. But, as you can see, as you build up the routines in General, then without question, you write less and less code with a nice "grab bag" of utility routines to do basic things like filling out controls in a div without having to hand code what amounts to the same code over and over.
答案2
得分: 0
以下是您要翻译的内容:
"我认为@Albert. D. Kallal发布的答案更完整,更深入地解释了如何操作。
但既然你问了我,我会尽量在这里给出我的观点(不要把这当作答案 - 最好将它发布为答案,而不是大量的评论块)。
我尝试将你的代码从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>();
///// 在这一行面临错误,因为FindControl返回null而不是返回另一页的控件
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);
}
}
//// 在这一行面临错误,因为列表error没有分配给空值的对象
myrepeater.DataSource = error;
myrepeater.DataBind();
if (Count >= 1)
{
errorRepeater = (global::System.Web.UI.HtmlControls.HtmlGenericControl)control.FindControl("errorRepeater");
errorRepeater.Visible = true;
}
// "}" 我无法将这个大括号与另一个大括号匹配,所以已经注释掉了吗?
}
return false;
}
else
{
return true;
}
}
我很抱歉,我的C#不太好,所以我多了一个多余的闭合大括号?不管怎样,我注释掉了我认为不正确的那个。
而且,通常在遇到问题时,最好的方法是将其分解为各个部分,我的意思是,你在FindControl方法/函数中遇到了问题,所以最好是先将FindControl方法的代码分离到一个单独的函数中,并在不同的Web页面上测试它。
来自VB背景的我,首先只是组合了一个函数来搜索PAGE对象中的子控件。它只搜索控件,不显示错误消息或其他内容,虽然我已经注释掉了以显示错误在哪里/是什么。
然后我使用Telerik免费在线代码转换器将其转换为C#代码。
首先,这是VB代码。还要注意,它仅搜索页面的顶级,不会递归遍历每个子对象/控件,这些子对象/控件可能包含其他对象/控件。
如果你的Repeater不是直接位于页面上而是嵌套在另一个对象/控件中,你可能需要创建一个单独的函数或修改你的findcontrol函数以考虑子对象(特别是如果你的repeater不是直接在页面上而是嵌套在另一个对象/控件中)。
'--------------------------------------------------------------------
' FindControl方法
'--------------------------------------------------------------------
Public Shared Function FindThisControl(ByVal PageObj As Page, FindCtrlName As String) As System.Web.UI.Control
'
'在页面中查找控件并返回找到的控件
'如果找不到匹配项,则返回nothing/null
'
Dim FoundCtrl As System.Web.UI.Control = Nothing
FindThisControl = Nothing
If IsNothing(PageObj) Then
'错误 - 页面对象为nothing/null
ElseIf isnothing(FindCtrlName) Then
'错误 - FindCtrlName为nothing/null
ElseIf Trim(FindCtrlName) = ""
'错误 - FindCtrlName为空字符串
Else
FoundCtrl = PageObj.FindControl(FindCtrlName)
If IsNothing(FoundCtrl) Then
'错误 - 在PageObj页面中未找到名为FindCtrlName的控件
Else
FindThisControl = FoundCtrl
End If
End If
'
End Function
然后转换为C#代码
// --------------------------------------------------------------------
// FindControl方法
// --------------------------------------------------------------------
public static System.Web.UI.Control FindThisControl(Page PageObj, string FindCtrlName)
{
//
// 在页面中查找控件并返回找到的控件
// 如果找不到匹配项,则返回nothing/null
//
System.Web.UI.Control FoundCtrl = null;
FindThisControl = null;
if (PageObj == null)
{
// 错误 - PageObj页面为nothing/null
}
else if (FindCtrlName == null))
{
// 错误 - FindCtrlName要查找的控件的名称为nothing/null
}
else if (Strings.Trim(FindCtrlName) == "")
{
// 错误 - FindCtrlName要查找的控件的名称为空字符串
}
else
{
//尝试查找控件
FoundCtrl = PageObj.FindControl(FindCtrlName);
if (FoundCtrl == null)
{
// 错误 - 未找到匹配的命名控件
}
else
FindThisControl = FoundCtrl;
}
}
无论是VB还是C#代码,如果Repeater嵌套在另一个控件中,都不会找到它,你必须循环遍历每个子控件,检查它是否有子控件,并递归地遍历它们,所以可能会有递归,但良好的规划和坚持基本原则会有所帮助。这就是为什么我会选择Albert D. Kallal的方法,并尝试一步一步地进行调试,
英文:
I think that the answer posted by @Albert. D. Kallal is a much more complete and an in depth explanation of how to go about it.
But since you asked me, I'll try to give my view here (don't treat this as an answer - Its just better to post it as an answer than lots of blocks of comments)
I tried to copy and paste your code from the 3 comments into one block of code, and ended up with the below.
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;
}
}
I'm sorry my C# is not so good, so I ended up with an extra closing curly bracket? AnywaynI remmed out the one which I thought was incorrect.
And often when we have issues the best way to isolate them is to break them down into their individual parts, what I mean by this is that you are havig problems with the FindControl method/function, so it would be better if you isloated the code for the FindControl method into a seperate function first and tested it with a few controls on different web pages.
And coming fro a VB background, first I just put a function together to search in a PAGE object for a child control. It only searches for the control and nothing else, no error messages anything else, though I have commented it to show where/what the errors were.
Then I converted it to C# using Telerik free online code converter.
So first here is the VB code
Note also that it ONLY searches the top level of the page, it does recurse through eeach of the child objects/controls, which may themselves contain other objects/controls.
You may need to create a separate function or modify your findcontrol function to account for child objects (especially if your repeater is not on the directly page but is nested inside another object/control.
'--------------------------------------------------------------------
'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
And then converted to C# code
// --------------------------------------------------------------------
// 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;
}
}
Niether the VB or C# code will find your Repeater if it is nested inside another control, you MUST loop through each of the child cotrols, check if it has any children, and loop through the, so it can be quite recursive, but good planning and sticking to the basics will help. And that is why I would go with the Albert D. Kallal method, and try to step through it step by step until you understand whats going on.
I simply posted this here since you asked me specifically.
Some further reading:
Microsoft documentation
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论