创建基于视图状态在Page_ preRender动态控件导致按钮onclick事件无法工作 [英] Creating dynamic controls in Page_PreRender based on viewstate causes button OnClick event to not work
问题描述
我知道,动态控件应在的Page_Load和Page_Init为了创建为他们在控件树进行登记。
我创建需要一个按钮OnClick事件中使用的ViewState的自定义控件。然后,这个ViewState是用于动态创建控件。
由于生命周期会:页面加载 - >按钮点击 - >页面preRender。视图状态不会被更新,直到点击链接,因此我创造我的第preRender动态控件。但是,创建一个按钮,并在Page_ preRender编程分配的OnClick事件处理程序不起作用。
有谁知道我能得到这个工作?
btn_DeleteTableRow_Click不会闪光。这是CreatePartRows设置()
下面是我的例子:
< ASP:的UpdatePanel ID =up_RMAPart=服务器的UpdateMode =条件的EnableViewState =真ChildrenAsTriggers =真>
<&的ContentTemplate GT;
< DIV CLASS =button风格=宽度:54px;保证金:0像素;浮动:权利;>
< ASP:按钮的ID =btn_AddPart=服务器文本=添加的OnClick =btn_AddPart_Click/>
< / DIV>
< ASP:表ID =Table_Parts=服务器的CssClass =HOR-斑马>
< / ASP:表>
< DIV CLASS =清除>< / DIV>
< /&的ContentTemplate GT;
<&触发器GT;
< ASP:AsyncPostBackTrigger控件ID =btn_AddPart事件名称=点击/>
< /触发器>
code背后:
[Serializable接口]
公共结构部分
{
公共字符串零件名称;
公众诠释数量;
公众诠释PARTID; 公共部分(字符串sPartName,诠释iQuantity,诠释iPartID)
{
零件名称= sPartName;
数量= iQuantity;
PARTID = iPartID;
}
}公共部分类RMAPart:System.Web.UI.UserControl
{
私人字典<字符串,部分> m_RMAParts;
私人INT m_RowNumber = 0; 公共字典<字符串,部分> RMAParts
{
得到
{
如果(的ViewState [m_RMAParts]!= NULL)
回报(字典<字符串,部分>)的ViewState [m_RMAParts];
其他
返回null;
}
组
{
的ViewState [m_RMAParts] =值;
}
} 公众诠释ROWNUMBER
{
得到
{
如果(的ViewState [m_RowNumber]!= NULL)
返回Convert.ToInt32(的ViewState [m_RowNumber]);
其他
返回0;
}
组
{
的ViewState [m_RowNumber] =值;
}
} 保护无效的Page_Load(对象发件人,EventArgs的发送)
{
如果(!Page.IsPostBack)
{
RMAParts =新词典<字符串,部分>();
ROWNUMBER = 0;
RMAParts.Add(PartRow_+ RowNumber.ToString(),新的部分());
ROWNUMBER = 1;
CreatePartRows();
}
} 保护无效Page_ preRender(对象发件人,EventArgs的发送)
{
CreatePartRows();
} 私人无效CreatePartRows()
{
Table_Parts.Controls.Clear(); TableHeaderRow THR =新TableHeaderRow(); TableHeaderCell THC1 =新TableHeaderCell();
thc1.Controls.Add(新LiteralControl(部件));
thr.Cells.Add(THC1); TableHeaderCell THC2 =新TableHeaderCell();
thc2.Controls.Add(新LiteralControl(数量));
thr.Cells.Add(THC2); TableHeaderCell thc3 =新TableHeaderCell();
thc3.Controls.Add(新LiteralControl());
thr.Cells.Add(thc3); Table_Parts.Rows.Add(THR); 的foreach(KeyValuePair<字符串,部分> KVP在RMAParts)
{
字符串[] = SKEY kvp.Key.Split('_'); TR的TableRow =新的TableRow();
tr.ID = kvp.Key; TableCell的TC1 =新的TableCell();
文本框tb_Part =新的TextBox();
tb_Part.ID =tb_Part_+ SKEY [1];
tb_Part.CssClass =TextBox1的;
tc1.Controls.Add(tb_Part);
tr.Cells.Add(TC1); TableCell的TC2 =新的TableCell();
文本框tb_Quantity =新的TextBox();
tb_Quantity.ID =tb_Quanitty_+ SKEY [1];
tb_Quantity.CssClass =TextBox1的;
tc2.Controls.Add(tb_Quantity);
tr.Cells.Add(TC2); TableCell的TC3 =新的TableCell();
按钮btn_Delete =新按钮();
btn_Delete.ID =btn_Delete_+ SKEY [1];
btn_Delete.CommandArgument = tr.ID;
btn_Delete.Click + =新的EventHandler(btn_DeleteTableRow_Click);
btn_Delete.Text =删除;
tc3.Controls.Add(btn_Delete);
tr.Cells.Add(TC3); Table_Parts.Rows.Add(TR);
} } 公共无效复位()
{
Table_Parts.Controls.Clear();
RMAParts.Clear();
ROWNUMBER = 0;
RMAParts.Add(PartRow_+ RowNumber.ToString(),新的部分());
ROWNUMBER = 1;
CreatePartRows();
} 保护无效btn_AddPart_Click(对象发件人,EventArgs的发送)
{
RMAParts.Add(PartRow_+ RowNumber.ToString(),新的部分());
ROWNUMBER ++;
} 保护无效btn_DeleteTableRow_Click(对象发件人,EventArgs的发送)
{
按钮BTN =(按钮)发送;
TR的TableRow =(的TableRow)Table_Parts.FindControl(btn.CommandArgument);
Table_Parts.Rows.Remove(TR);
RMAParts.Remove(btn.CommandArgument);
}
}
要确保输入字段的值持续在回传和服务器事件被触发:
- 使用视图状态来跟踪动态创建控件。
- 与
LoadViewState
相同的ID(重新创建控件的不的加载
或preRender
,因为这时输入字段的值将丢失)。
这个答案的其余部分详细介绍如何修改code到得到它的工作。
RMAPart.ascx
只是为了方便起见,可以在申报名为.ascx的标题行:
< ASP:表ID =Table_Parts=服务器的CssClass =HOR-斑马>
< ASP:&的TableRow GT;
< ASP:TableHeaderCell文本=部分/>
< ASP:TableHeaderCell文本=数量/>
< ASP:TableHeaderCell />
< / ASP:&的TableRow GT;
< / ASP:表>
RMAPart.ascx.cs
要跟踪动态创建的行,保持行ID的视图状态的列表:
公共部分类RMAPart:System.Web.UI.UserControl
{
私人列表<串GT;的rowid
{
{返回(列表<串GT;)的ViewState [m_RowIDs]; }
集合{的ViewState [m_RowIDs] =值; }
}
在 btn_AddPart_Click
处理,生成新的行ID和创建控件的新行:
保护无效btn_AddPart_Click(对象发件人,EventArgs的发送)
{
字符串ID = GenerateRowID();
RowIDs.Add(ID);
CreatePartRow(ID);
} 私人字符串GenerateRowID()
{
INT ID =(INT)的ViewState [m_NextRowID];
的ViewState [m_NextRowID] = ID + 1;
返回id.ToString();
} 私人无效CreatePartRow(字符串ID)
{
TR的TableRow =新的TableRow();
tr.ID = ID; TableCell的TC1 =新的TableCell();
文本框tb_Part =新的TextBox();
tb_Part.ID =tb_Part_+ ID;
tb_Part.CssClass =TextBox1的;
tc1.Controls.Add(tb_Part);
tr.Cells.Add(TC1); TableCell的TC2 =新的TableCell();
文本框tb_Quantity =新的TextBox();
tb_Quantity.ID =tb_Quantity_+ ID;
tb_Quantity.CssClass =TextBox1的;
tc2.Controls.Add(tb_Quantity);
tr.Cells.Add(TC2); TableCell的TC3 =新的TableCell();
按钮btn_Delete =新按钮();
btn_Delete.ID =btn_Delete_+ ID;
btn_Delete.CommandArgument = ID;
btn_Delete.Click + = btn_DeleteTableRow_Click;
btn_Delete.Text =删除;
tc3.Controls.Add(btn_Delete);
tr.Cells.Add(TC3); Table_Parts.Rows.Add(TR);
}
在 btn_DeleteTableRow_Click
处理程序,删除点击行和更新视图状态:
保护无效btn_DeleteTableRow_Click(对象发件人,EventArgs的发送)
{
按钮BTN =(按钮)发送;
TR的TableRow =(的TableRow)Table_Parts.FindControl(btn.CommandArgument);
Table_Parts.Rows.Remove(TR);
RowIDs.Remove(btn.CommandArgument);
}
挂钩的Page_Load
,并通过创建第一行开始做事了:
保护无效的Page_Load(对象发件人,EventArgs的发送)
{
如果(!的IsPostBack)
{
重置();
}
} 公共无效复位()
{
而(Table_Parts.Rows.Count→1)
Table_Parts.Rows.RemoveAt(Table_Parts.Rows.Count - 1); 的ViewState [m_NextRowID] = 0;
字符串ID = GenerateRowID();
的rowid =新的List<串GT; { ID };
CreatePartRow(ID);
}
覆盖 LoadViewState
并重新创建使用存储在视图状态的ID的行:
保护覆盖无效LoadViewState(对象savedState)
{
base.LoadViewState(savedState); 的foreach(在的ROWID字符串ID)
{
CreatePartRow(ID);
}
}
}
与零件处理
在code以上不使用你的部分
结构可言。要真正的业务对象和用户控件之间移动数据,可以添加需要部分
收集和使用它来创建行和填充文本框,然后一个公共方法补充一点,读出的文本框中的值到部分
收集另一种公共方法。
I realize that dynamic controls should be created within Page_Load and Page_Init in order for them to be registered in the control tree.
I have created a custom control that requires the use of ViewState in a button OnClick event. This ViewState is then used to dynamically create controls.
Since the life-cycle will go: Page Load -> Button Click -> Page PreRender. The view-state will not be updated until "Button Click", thus I am creating my dynamic controls in Page PreRender. However, creating a button and programatically assigning the OnClick EventHandler in Page_PreRender does not work.
Does anyone know how I can get this to work?
btn_DeleteTableRow_Click will not fire. This is setup in CreatePartRows()
Here is my example:
<asp:UpdatePanel ID="up_RMAPart" runat="server" UpdateMode="Conditional" EnableViewState="true" ChildrenAsTriggers="true">
<ContentTemplate>
<div class="button" style="width: 54px; margin: 0px; float: right;">
<asp:Button ID="btn_AddPart" runat="server" Text="Add" OnClick="btn_AddPart_Click" />
</div>
<asp:Table ID="Table_Parts" runat="server" CssClass="hor-zebra">
</asp:Table>
<div class="clear"></div>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btn_AddPart" EventName="Click" />
</Triggers>
Code Behind:
[Serializable]
public struct Part
{
public string PartName;
public int Quantity;
public int PartID;
public Part(string sPartName, int iQuantity, int iPartID)
{
PartName = sPartName;
Quantity = iQuantity;
PartID = iPartID;
}
}
public partial class RMAPart : System.Web.UI.UserControl
{
private Dictionary<string,Part> m_RMAParts;
private int m_RowNumber = 0;
public Dictionary<string, Part> RMAParts
{
get
{
if (ViewState["m_RMAParts"] != null)
return (Dictionary<string, Part>)ViewState["m_RMAParts"];
else
return null;
}
set
{
ViewState["m_RMAParts"] = value;
}
}
public int RowNumber
{
get
{
if (ViewState["m_RowNumber"] != null)
return Convert.ToInt32(ViewState["m_RowNumber"]);
else
return 0;
}
set
{
ViewState["m_RowNumber"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
RMAParts = new Dictionary<string, Part>();
RowNumber = 0;
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber = 1;
CreatePartRows();
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
CreatePartRows();
}
private void CreatePartRows()
{
Table_Parts.Controls.Clear();
TableHeaderRow thr = new TableHeaderRow();
TableHeaderCell thc1 = new TableHeaderCell();
thc1.Controls.Add(new LiteralControl("Part"));
thr.Cells.Add(thc1);
TableHeaderCell thc2 = new TableHeaderCell();
thc2.Controls.Add(new LiteralControl("Quantity"));
thr.Cells.Add(thc2);
TableHeaderCell thc3 = new TableHeaderCell();
thc3.Controls.Add(new LiteralControl(""));
thr.Cells.Add(thc3);
Table_Parts.Rows.Add(thr);
foreach (KeyValuePair<string, Part> kvp in RMAParts)
{
string[] sKey = kvp.Key.Split('_');
TableRow tr = new TableRow();
tr.ID = kvp.Key;
TableCell tc1 = new TableCell();
TextBox tb_Part = new TextBox();
tb_Part.ID = "tb_Part_" + sKey[1];
tb_Part.CssClass = "textbox1";
tc1.Controls.Add(tb_Part);
tr.Cells.Add(tc1);
TableCell tc2 = new TableCell();
TextBox tb_Quantity = new TextBox();
tb_Quantity.ID = "tb_Quanitty_" + sKey[1];
tb_Quantity.CssClass = "textbox1";
tc2.Controls.Add(tb_Quantity);
tr.Cells.Add(tc2);
TableCell tc3 = new TableCell();
Button btn_Delete = new Button();
btn_Delete.ID = "btn_Delete_" + sKey[1];
btn_Delete.CommandArgument = tr.ID;
btn_Delete.Click += new EventHandler(btn_DeleteTableRow_Click);
btn_Delete.Text = "Remove";
tc3.Controls.Add(btn_Delete);
tr.Cells.Add(tc3);
Table_Parts.Rows.Add(tr);
}
}
public void Reset()
{
Table_Parts.Controls.Clear();
RMAParts.Clear();
RowNumber = 0;
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber = 1;
CreatePartRows();
}
protected void btn_AddPart_Click(object sender, EventArgs e)
{
RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
RowNumber++;
}
protected void btn_DeleteTableRow_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
TableRow tr = (TableRow)Table_Parts.FindControl(btn.CommandArgument);
Table_Parts.Rows.Remove(tr);
RMAParts.Remove(btn.CommandArgument);
}
}
To ensure that the values of input fields persist across postbacks and that server events are raised:
- Use view state to keep track of dynamically created controls.
- Re-create the controls with the same IDs in
LoadViewState
(notLoad
orPreRender
, because then the values of input fields will be lost).
The rest of this answer details how I modified your code to get it to work.
RMAPart.ascx
Just for convenience, you can declare the header row in the .ascx:
<asp:Table ID="Table_Parts" runat="server" CssClass="hor-zebra">
<asp:TableRow>
<asp:TableHeaderCell Text="Part" />
<asp:TableHeaderCell Text="Quantity" />
<asp:TableHeaderCell />
</asp:TableRow>
</asp:Table>
RMAPart.ascx.cs
To keep track of dynamically created rows, maintain a list of row IDs in view state:
public partial class RMAPart : System.Web.UI.UserControl
{
private List<string> RowIDs
{
get { return (List<string>)ViewState["m_RowIDs"]; }
set { ViewState["m_RowIDs"] = value; }
}
In the btn_AddPart_Click
handler, generate a new row ID and create the controls for the new row:
protected void btn_AddPart_Click(object sender, EventArgs e)
{
string id = GenerateRowID();
RowIDs.Add(id);
CreatePartRow(id);
}
private string GenerateRowID()
{
int id = (int)ViewState["m_NextRowID"];
ViewState["m_NextRowID"] = id + 1;
return id.ToString();
}
private void CreatePartRow(string id)
{
TableRow tr = new TableRow();
tr.ID = id;
TableCell tc1 = new TableCell();
TextBox tb_Part = new TextBox();
tb_Part.ID = "tb_Part_" + id;
tb_Part.CssClass = "textbox1";
tc1.Controls.Add(tb_Part);
tr.Cells.Add(tc1);
TableCell tc2 = new TableCell();
TextBox tb_Quantity = new TextBox();
tb_Quantity.ID = "tb_Quantity_" + id;
tb_Quantity.CssClass = "textbox1";
tc2.Controls.Add(tb_Quantity);
tr.Cells.Add(tc2);
TableCell tc3 = new TableCell();
Button btn_Delete = new Button();
btn_Delete.ID = "btn_Delete_" + id;
btn_Delete.CommandArgument = id;
btn_Delete.Click += btn_DeleteTableRow_Click;
btn_Delete.Text = "Remove";
tc3.Controls.Add(btn_Delete);
tr.Cells.Add(tc3);
Table_Parts.Rows.Add(tr);
}
In the btn_DeleteTableRow_Click
handler, delete the clicked row and update view state:
protected void btn_DeleteTableRow_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
TableRow tr = (TableRow)Table_Parts.FindControl(btn.CommandArgument);
Table_Parts.Rows.Remove(tr);
RowIDs.Remove(btn.CommandArgument);
}
Hook Page_Load
and start things off by creating the first row:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Reset();
}
}
public void Reset()
{
while (Table_Parts.Rows.Count > 1)
Table_Parts.Rows.RemoveAt(Table_Parts.Rows.Count - 1);
ViewState["m_NextRowID"] = 0;
string id = GenerateRowID();
RowIDs = new List<string> { id };
CreatePartRow(id);
}
Override LoadViewState
and re-create the rows using the IDs stored in view state:
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
foreach (string id in RowIDs)
{
CreatePartRow(id);
}
}
}
Dealing with Parts
The code above doesn't use your Part
structure at all. To actually move data between your business objects and the user control, you can add a public method that takes a Part
collection and uses it to create rows and populate text boxes, and then add another public method that reads out the values of the text boxes into a Part
collection.
这篇关于创建基于视图状态在Page_ preRender动态控件导致按钮onclick事件无法工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!