I'm currently trying to create a web based wizard tool. I have a Wizard page that contains navigation buttons and an asp panel that will contain the individual wizard panels.
<asp:Panel ID="wizardControlPanel" runat="server">
<!-- Wizard panel goes here -->
</asp:Panel>
<asp:Button ID="backButton" runat="server" Text="< Back" OnClick="BackButton_Click" />
<asp:Button ID="nextButton" runat="server" Text="Next >" OnClick="NextButton_Click" />
<asp:Button ID="cancelButton" runat="server" Text="Cancel" PostBackUrl="~/"/>
One control dynamically fills a checkboxlist
<asp:Label ID="Title" runat="server" Text=""></asp:Label>
<asp:Label ID="DescriptionLabel" runat="server" Text ="Description for the wizard"></asp:Label>
<asp:CheckBoxList ID="ProjectSelector" runat="server" DataTextField="ProjectName" DataValueField="Id" ></asp:CheckBoxList>
I dynamically load this control into my wizardControlPanel once the checkbox is populated.
WizardControl = (BaseWizardControl)LoadControl(("~/Views/" + e.ControlType.Name + ".ascx"));
wizardControlPanel.Controls.Add(WizardControl);
The problem is; on postback I then need to be able to find out which checkboxes were checked server side, but the control no longer exists.
I can't find it on the _page variable. Running in to problems (I think) because I am adding the control to a content holder. How can I get this control back?
It is there, you just won't be able to access it using an ID. You'll need to find it by looking through the wizardControlPanel.Controls collection. I think there is a property that represents the filename you used. But it would be best to use the debugger to track down either where it is in the collection or an identifier you can use to find it.
Having done this once or twice, I think I also remember that you'll need to recreate the control prior to the OnLoad event of the life cycle so that the postback will be able to populate it.
As Markus says, there is probably a better way to do what you are trying to do. But if you MUST load this dynamically, this is how you should go about locating it.
If you add controls dynamically in ASP.NET WebForms, you need to re-add them manually very early in the page lifecycle of the PostBack (e.g. by overriding OnInit and creating the control with the same id in this code) in order to be able to retrieve the values. See this link for a How-To.
The following sample shows the basic steps. It consists of an ASPX-page that contains a Panel as a placeholder:
<asp:Panel ID="wizardPanel" runat="server">
</asp:Panel>
<asp:Button ID="btn" runat="server" Text="Do a postback" />
<br />
<asp:Label ID="lbl" runat="server" />
This is the codebehind-file:
public partial class DynamicUserControls : System.Web.UI.Page
{
protected UserControl userCtrl;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (Page.IsPostBack)
CreateUserControl();
}
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
CreateUserControl();
else
{
lbl.Text = "The following values were selected: " + string.Join(", ", ((IGetSelectedValues)userCtrl).SelectedValues);
}
}
private void CreateUserControl()
{
if (Request["UserCtrl"] == "A")
{
userCtrl = (UserControl) LoadControl("~/MyUserControlA.ascx");
userCtrl.ID = "myUserCtrl";
wizardPanel.Controls.Add(userCtrl);
}
else if (Request["UserCtrl"] == "B")
{
userCtrl = (UserControl)LoadControl("~/MyUserControlB.ascx");
userCtrl.ID = "myUserCtrl";
wizardPanel.Controls.Add(userCtrl);
}
}
}
The basic steps are the following:
The page determines the user control type to be created upon a Request parameter during Page_Load (or later if necessary). It assigns the ID myUserCtrl to the UserControl.
Upon a PostBack, the page inspects the Request parameters again and re-creates the UserControl with the same ID myUserCtrl. This is important so that ASP.NET can retrieve the new values of the control from the postback data after the page initialization phase. The hardest part is usually to decide which user controls to create, because the data that are available in OnInit is usually not too many.
In Page_Load, the user control can be accessed and the values that were posted back are available. The UserControls in the sample contain a CheckBoxList and implement an interface that allows to retrieve the values that were selected by the user.
In most cases it is easier to find a different approach. Maybe you can use a MultiView control for your wizard that contains the UserControls for the wizard pages as static content. See this link for a description of how to use the MultiView control. If there are not too many (read unlimited) different UserControls to use, this approach is much more stable.
I was looking in the wrong place. Page.Form as opposed to Page.Request.Form. Due to the fact the checkboxlist is already defined in the user control, it's name is traceable in this variable. This way I can keep the current wizard structure.
Related
I have a ASP.NET datagrid which is on a user control. I have a main page which adds the user control ( sometimes multiple copies of the user control ) and restores them when a post back occurs.
The dataGrid has insert / edit / delete links. I can add multiple copies of the user control to the page and the insert / edit delete functionality works perfectly for each separate control.
Yesterday I added some property binding to the main page to which are unrelated to the user control using the format Text='<%# DocumentTitle %>'. Initially I couldn't get this to work until I added Page.DataBind(); to the main page's Page_Load method. At this point the property binding started working correctly but then I noticed the insert functionality had stopped working in the datagrid within each user control....I debugged and found that when the following line executes it finds the text fields in the controls within the dataGrid to be empty or "":
eInfo.Ref = ((TextBox)gvEG.FooterRow.FindControl("txtEmployeeName")).Text;
If I remove the page.DataBind() method from the main page then the property binding stops working but the dataGrid(s) in the userControl start working.
My question is two fold i) Why does the seemingly unrelated change effect the dataGrid and ii) what do I do to get this working?
I've added the relevant sections of my code below...or at least what I think are the relevant sections.
Default.aspx
<div class="general">
<asp:TextBox Width="488" runat="server" placeholder="Document Title" Text='<%# DocumentTitle %>'></asp:TextBox>
</div>
Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Create an empty user control for the first requirements section.
EmployeeSectionUserControl myUserControl1 = (EmployeeSectionUserControl )LoadControl("~/EmployeeSectionUserControl .ascx");
// Add it to the panel control holding all the user controls.
MainPanel.Controls.Add(myUserControl1);
DocumentTitle = "I am the default document title and I'm bound.";
}
else
{
// Do nothing
}
Page.DataBind();
}
EmployeeSectionUserControl.ascx
<asp:GridView ID="gvEG" runat="server" AutoGenerateColumns="False" CssClass="grid"
AlternatingRowStyle-CssClass="gridAltRow" RowStyle-CssClass="gridRow" ShowFooter="True"
EditRowStyle-CssClass="gridEditRow" FooterStyle-CssClass="gridFooterRow" OnRowCancelingEdit="gvEG_RowCancelingEdit"
OnRowCommand="gvEG_RowCommand" OnRowDataBound="gvEG_RowDataBound" OnRowDeleting="gvEG_RowDeleting"
OnRowEditing="gvEG_RowEditing" OnRowUpdating="gvEG_RowUpdating" DataKeyNames="Id" ShowHeaderWhenEmpty="true">
<Columns>
<asp:TemplateField HeaderText="Id" HeaderStyle-HorizontalAlign="Left" ControlStyle-Width="50px">
<ItemTemplate>
<%# Eval("Id")%>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Ref" HeaderStyle-HorizontalAlign="Left" ControlStyle-Width="90px">
<EditItemTemplate>
<asp:TextBox ID="txtEmployeeName" runat="server" Text='<%# Bind("Ref") %>'
Width="90px"></asp:TextBox>
</EditItemTemplate>
<FooterTemplate>
<asp:TextBox ID="txtEmployeeName" runat="server" Width="90px"></asp:TextBox>
</FooterTemplate>
EmployeeSectionUserControl.ascx.cs
protected void gvEG_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName.Equals("Insert"))
{
employeeInfo eInfo = new employeeInfo();
eInfo.Id = 999;// Convert.ToInt32(((TextBox)gvEG.FooterRow.FindControl("Id")).Text);
// If we're inserting from the EmptyDataTemplate( ie an empty table ) of the gridview then we need to retreive the data differently.
// So we perform a check on the gridView FooterRow and if it's null then we can assume it's an empty table.
if (gvEG.FooterRow == null)
{
TextBox referenceTxtBox = (((Control)e.CommandSource).NamingContainer).FindControl("txtEmployeeName") as TextBox;
eInfo.Ref = referenceTxtBox.Text;
}
else
{
eInfo.Ref = ((TextBox)gvEG.FooterRow.FindControl("txtEmployeeName")).Text;
eInfo.Need =
}
// Store Update and Re-bind data to grid.
}
}
Page.DataBind() calls DataBind on it's children, so it updates DocumentTitle in the text box but it also DataBinds your grid. I didn't see a DataSource set in your grid, like an EntityDataSource, so I am assuming you are doing the smart retrieving (and preparation) of the data yourself in code and set the DataSource yourself:
gvEg.DataSource = someCollection;
gvEg.DataBind();
On the get your are loading the user-control and probably call this DataBind with specifying the DataSource. It binds and then you call Page.DataBind() which probably also triggers another DataBind for gvEg but since DataSource is set it shows the same.
On the post back you shouldn't do a DataBind() before handling events. Your call of Page.DataBind() does that. It triggers a DataBind() without a DataSource. Then the rowCommand comes and checks for the TextBox in the Footer which is cleared due to a DataBind with no elements.
What you should do is:
You shouldn't use Page.DataBind(). If you do so, you need todo it at a moment when all DataSources are set correctly and it shouldn't be kicked of during a post back. In general, I would not recommend using it because it makes it more complex and it's probably only helping a bit if you haven't set up your application correctly.
In your case it's definitely not necessary. Your textBox is a server control that's not part of any binding (GridView, Repeater, ListView). So your textBox is immediately available in your code behind.
So you should:
Give the textBox an ID you can use like txtDocumentTitle
<asp:TextBox Width="488" ID="txtDocumentTitle" runat="server" placeholder="Document Title"></asp:TextBox>
Replace setting DocumentTitle (unless you need it for something else too) with:
txtDocumentTitle.Text = "I am the default document title and I'm bound.";
Remove Page.DataBind();
So access server controls you have access immediately since they are also properties in your page or control.
Working with C# .NET 4.0 and Umbraco
I have a user control on which there is a button allowing user to dynamically add additional text boxes to the form. I've tried a myriad of methods but have been unable to persist any data entered into the dynamic text boxes between page loads.
Here's the UI:
<div id="new-item">
<h3>Add new menu item</h3>
<div><label for="ItemTitle">Title</label><asp:TextBox ID="ItemTitle" runat="server" CssClass="title"></asp:TextBox></div>
<div><label for="ItemDescription">Text</label><textarea id="ItemDescription" runat="server" rows="5" cols="20"></textarea></div>
<div><label for="ItemPrice">Price</label><asp:TextBox ID="ItemPrice" runat="server" CssClass="price"></asp:TextBox></div>
<div class="new-item-options">
<p><strong>Menu item options (optional) </strong><asp:Button ID="AddMenuItemOption" runat="server" Text="Add Option" OnClick="AddOption" /></p>
<asp:Panel ID="MenuItemOptionsPanel" runat="server"></asp:Panel>
<asp:HiddenField ID="ItemOptionCount" runat="server" />
</div>
<div><asp:Button ID="AddItemButton" runat="server" Text="Save" /></div>
<asp:HiddenField ID="ItemID" runat="server" />
<asp:HiddenField ID="ItemOrder" runat="server" />
And in the AddOption button click event:
protected void AddOption(object sender, EventArgs e)
{
var dynamicControlCount = Convert.ToInt32(ItemOptionCount.Value) + 1;
for (var option = 0; option < dynamicControlCount; option++)
{
MenuItemOptionsPanel.Controls.Add(new Literal { ID = "OptionTextLiteral" + option, Text = "<label>Option Text</label>" });
MenuItemOptionsPanel.Controls.Add(new TextBox { ID = "OptionTextBox" + option });
MenuItemOptionsPanel.Controls.Add(new Literal { ID = "OptionPriceLiteral" + option, Text = "<label>Option Price</label>" });
MenuItemOptionsPanel.Controls.Add(new TextBox { ID = "OptionPriceBox" + option });
}
ItemOptionCount.Value = dynamicControlCount.ToString();
}
I know I need to create these dynamic controls in the Page_Load or OnInit methods but the problem is when the page loads for the first time, I don't want any dynamic controls create, only once the user clicks the button each time I'm like additional control added and those that already exist to have any data in them persisted.
Any ideas? I don't think I'm too far off hopefully :)
Can you test the confition (perhaps IsPostback) in page_load, then if you need to generate the controls, do it?
Perhaps you have to look at this (series of) article. It explains in details how to work with dynamically created controls, especially about how to persist ViewState.
Otherwise, I would suggest you to go the AJAX way (create controls using JavaScript, post the data back to server using AJAX for the server to process). In this case, you do not have to worry about PostBack and ViewState, and it makes the user experience better too!
ASP.Net web forms, .NET 2.0
I have a dilemma. I have an aspx page that has a custom control:
<def:CustomControl ID="customID" runat="server" />
The custom control can be added multiple times to the ASPX page if certain criteria is met:
<asp:Repeater runat="server" ID="options" OnItemDataBound="options_OnItemDataBound">
<HeaderTemplate>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</HeaderTemplate>
<ItemTemplate>
<td>
<span>
<asp:Label runat="server" ID="optionName">
</asp:Label>
<asp:DropDownList runat="server" ID="optionValues" CssClass="PartOption" >
</asp:DropDownList>
</span>
</td>
</ItemTemplate>
<FooterTemplate>
</tr>
</table>
</FooterTemplate>
</asp:Repeater>
In the code behind of the aspx page, I wanted to append a event (OnSelectedItemChange) to the dropdown control that isn't actually on the ASPX page (yet). However, when I try to do something like the following in the code behind:
foreach(Control eachControl in customID.Controls) {
if (eachControl.HasControls) {
Control DdControl = eachControl.FindControl("optionValues"); //always null
if (DdControl != null && DdControl is typeOf(DropDownList)) {
DdControl = DdControl as DropDownlist; //I might have the syntax wrong
//here, but you get the point.
//add event to control.
}
}
}
But All that eachControl has is a repeater, and it shows its childcontrol count as 0. So when it get to the FindControl method, it always returns null.
QUESTION
So what I am trying to do is add an event (OnSelectedItemChange) to the dropdownlist control inside the repeater (which is a custom control that gets added to the page if criteria is met). When a selection is made in the drop down, I want it to trigger the Event method.
How can I either:
A) Find a way to add the event if the dropdownlist control exists on the page?
OR
B) A way to be able to call a method in the code behind and pass the selections from all the drop down lists (since the custom control could be added more than once creating multiple drop downs?
NOTE I don't have the code in front of me, so if I have something syntactically incorrect, please overlook. I also know that ID's are unique, so this is also assumed to be a problem with my logic. When it renders these, the ID's might be mangled by the asp name convention which I would assume I have to find a way around.
If you create that event on the custom control, you won't need to check if it exists on the page or not. If it does, it will definately trigger the event. Or am I missing something here?
EDIT:
What you'll have to do is declare a public event on your control, then when the SelectedIndexChanged Event fired, you'd fire that event, and receive it on the page.
So on your control you'd have:
public delegate void MyIndexChangedDelegate(string value);
public event MyIndexChangedDelegate MyEvent;
protected void myDropDown_SelectedIndexChanged(object sender, EventArgs e)
{
MyEvent(myDropDown.SelectedValue); // Or whatever you want to work with.
}
Then on your page, on your control declaration you'd have:
<usc:control1 runat="server" OnMyEvent="EventReceiver" />
And on the code behind:
protected void EventReceiver(string value)
{
// Do what you have to do with the selected value, which is our local variable 'value'
ClientScript.RegisterStartupScript(typeof(Page), "Alert", string.Format("<script language='JavaScript'>alert('User selected value {0} on my DropDown!');</script>"), value);
}
That should work.
Assuming that you can't put this logic in the control itself, what you need to do I think is access that repeater and attach an ItemDataBound event to it. Something like this:
Repeater options = customID.FindControl("options") as Repeater;
if (options != null)
{
options.ItemDataBound += new RepeaterItemEventHandler(OptionsItemDataBound);
}
Then in the ItemDataBound event you would do something like this:
void OptionsItemDataBound(object sender, RepeaterItemEventArgs e)
{
DropDownList optionValues = e.Item.FindControl("optionValues") as DropDownList;
if (optionValues != null)
{
//perform logic you need here
}
}
I don't have time to try that out right this minute, but I suspect that should work.
I need to display a control consistently across a set of pages. So I'm using a MasterPage to do that in ASP.NET/C#. However I also need to programmatically access this control, mostly provide options to view/hide depending on whether the controls checkbox is clicked.
Here is the Source for the MasterPage
<div id="verifyInitial" runat="server">
<asp:CheckBox ID="chkInitialVerify" runat="server"
oncheckedchanged="chkInitialVerify_CheckedChanged" />
I agree that my initial data is correct.
</div>
<div id="verifyContinuous" runat="server">
<asp:CheckBox ID="chkContinuousVerify" runat="server"
oncheckedchanged="chkContinuousVerify_CheckedChanged" />
I agree that my continuous data is correct
</div>
Now in the code behind I want to perform the following operations. Basically if a person clicks on the checkbox for the initial div box, then the initial box disappears and the continous box shows up. However it seems that the code behind for the MasterPage does not activate whenver I click on the checkbox. Is that just the way MasterPages are designed? I always thought you could do add some sort of control functionality beyond utilizing the Page Load on the Master Page.
protected void chkInitialVerify_CheckedChanged(object sender, EventArgs e)
{
verifyContinuous.Visible = true;
verifyInitial.Visible = false;
}
protected void chkContinuousVerify_CheckedChanged(object sender, EventArgs e)
{
verifyContinuous.Visible = false;
}
If you're expecting the two controls to trigger a change for that page immediately then you'll need to set the AutoPostBack property to true for both of them:
<div id="verifyInitial" runat="server">
<asp:CheckBox ID="chkInitialVerify" runat="server" oncheckedchanged="chkInitialVerify_CheckedChanged" AutoPostBack="true" />
I agree that my initial data is correct.
</div>
<div id="verifyContinuous" runat="server">
<asp:CheckBox ID="chkContinuousVerify" runat="server" oncheckedchanged="chkContinuousVerify_CheckedChanged" AutoPostBack="true" />
I agree that my continuous data is correct
</div>
Otherwise, you need an <asp:button /> or some other control on the page to trigger a postback and cause your event handlers to run. The button, or other control, can either be on your masterpage or on your content page, the choice is entirely yours.
I am working on an asp.net web application where I have predefined panel in my project with
CSS. Now i want to create another panel with same design and CSS at run time at multiple times. I have a button control when i will click that button it will add another panel.
Please help me to add another panel with same criteria.
If it is something that you plan on reusing, I'd suggest you utilize a user control for this. You can them simply add a new instance of the control on your page.
A few things worth looking into:
https://web.archive.org/web/20210707024005/http://aspnet.4guysfromrolla.com/articles/081402-1.aspx
http://aspalliance.com/565
http://msdn.microsoft.com/en-us/library/c0az2h86.aspx
If you wanted to accomplish this with a postback to the page, add this to your event...
//MyControl = Custom User Control
var myControl = (MyControl) Page.LoadControl("MyControl.ascx");
this.ControlContainer.Controls.Add(myControl);
Like RSolberg said, you could write a User Control and add it multiple times:
<my:UserControl id="MyControl1" runat="server" />
<my:UserControl id="MyControl2" runat="server" />
<my:UserControl id="MyControl3" runat="server" />
Of course, your User Control can be as simple or as complex as you like, thus having repeated functionality on your page.
However, depending on your exact needs you might want to consider something like an ASP.NET Repeater, or ListView, or DataGrid control. With something like a Repeater, you can bind data to it, and have that data be displayed in a list/grid, that has a common look and feel. You can give your Repeater a HTML/CSS template for the header, items, and footer sections too to make it look consistent and professional.
<asp:Repeater id="MyRepeater" runat="server">
<HeaderTemplate>
<h1>Products</h1>
</HeaderTemplate>
<ItemTemplate>
<p>
Product name: <%# Eval("ProductName") %>
</p>
</ItemTemplate>
</asp:Repeater>
and in your code just do this:
MyRepeater.DataSource = products;
MyRepeater.DataBind();
There are many ways of doing what you're asking in ASP.NET - be a bit more specific and we'll be able to give you more specifc help.
Couldn't you just create a new panel in the code behind on the button's "On_Click" event? That would be my suggestion. You may need to have a placeholder to add the panel into something so it appears on the page.