I've embedded multiple user controls into the same page. The user controls typically have 2 different views, an edit/default view and a completed view. When the user completes an edit then I switch the MultiView to the completed state. This pattern works okay in pages.
The problem is when I go back to the master view/page then the user brings back the control, the view is still in the completed state.
What are some simple methods for resetting/correcting the multiview state? I could have a Reset() function on every control but it feels like I may be able to avoid that.
Update:
This page is designed to show an account/profile view and it has the following parts:
<asp:MultiView>
...read only view
<asp:EmailEditView>
<asp:AccountEditView>
etc..
And then each of those views has a user control. Inside the user control, we have something like this
<asp:multiview>
<asp:view id="edit"/>
<asp:view id="completed"/>
I've found the following problems with this design:
When the user completes an action of user control such as changing an email. The view stays in the finished state even when they go back to it a second time.
When the user goes back to the original form the data is stale. This design really screws with the postback mechanism. If I don't use !IsPostBack on the main form for databding then my dropdownlist on the sub forms will not work. However, once I add the !isPostBack on the main form then the data becomes stale.
I can add a "reset" flag to every user control and add something like this
if ((!IsPostback) || (reset) )
As well as resetting the view. In the other scenario, I can add an event handler to load the reload/bind the data when the user clicks "back", i.e where I change the form back.
I'm trying to recreate the functionality of your problem while taking out the complexity of user controls. Here's my best guess so far (please correct me if I'm wrong)
<asp:MultiView runat="server" ID="MainMultiView" ActiveViewIndex="0">
<asp:View runat="server" ID="DefaultView">
Default View
<asp:Button runat="server" ID="EditEmailButton" Text="Edit Email" OnClick="EditEmailButton_Click" />
<asp:Button runat="server" ID="EditAccountButton" Text="Edit Account" OnClick="EditAccountButton_Click" />
</asp:View>
<asp:View runat="server" ID="EmailView">
<asp:MultiView runat="server" ID="EmailMultiView" ActiveViewIndex="0">
<asp:View runat="server" ID="EmailEditView">
Edit Email
<asp:Button runat="server" ID="SaveEmailButton" Text="Save"
onclick="SaveEmailButton_Click" />
<asp:Button runat="server" ID="CancelEmailButton" Text="Cancel"
onclick="CancelEmailButton_Click" />
</asp:View>
<asp:View runat="server" ID="EmailCompleteView">
Email Edit Complete!
</asp:View>
</asp:MultiView>
</asp:View>
<asp:View runat="server" ID="AccountView">
<asp:MultiView runat="server" ID="AccountMultiView" ActiveViewIndex="0">
<asp:View runat="server" ID="AccountEditView">
Edit Account
<asp:Button runat="server" ID="SaveAccountButton" Text="Save"
onclick="SaveAccountButton_Click" />
<asp:Button runat="server" ID="CancelAccountButton" Text="Cancel"
onclick="CancelAccountButton_Click" />
</asp:View>
<asp:View runat="server" ID="AccountCompleteView">
Account Edit Complete!
</asp:View>
</asp:MultiView>
</asp:View>
</asp:MultiView>
-- code behind
protected void EditEmailButton_Click(object sender, EventArgs e)
{
MainMultiView.SetActiveView(EmailView);
}
protected void EditAccountButton_Click(object sender, EventArgs e)
{
MainMultiView.SetActiveView(AccountView);
}
protected void SaveEmailButton_Click(object sender, EventArgs e)
{
// Save();
EmailMultiView.SetActiveView(EmailCompleteView);
}
protected void CancelEmailButton_Click(object sender, EventArgs e)
{
EmailMultiView.SetActiveView(EmailCompleteView);
}
protected void SaveAccountButton_Click(object sender, EventArgs e)
{
// Save();
AccountMultiView.SetActiveView(AccountCompleteView);
}
protected void CancelAccountButton_Click(object sender, EventArgs e)
{
AccountMultiView.SetActiveView(AccountCompleteView);
}
Related
I have a gridview which displays product information on the index page of my site for a user. I want to extend this to allow the user to tick a checkbox if they have reviewed a product.
However, after adding the check box column into my gridview template, when i try to search multiple times the Page_Load event of my index page stops firing, this causes an issue as some of the events further down the execution tree require the object that is initialised at page load.
The problem seems to be that placing any asp input control inside the gridview is somehow preventing Page_Load from firing before the DataSources OnSelecting and OnSelected and the Grids OnRowDataBound events but i can't see why.
Here is my sample code, I can't see what I'm doing wrong here.
Index.aspx.cs
private ProductSearch productSearch
protected void Page_Load(object sender, EventArgs e)
{
productSearch = new ProductSearch(GetSearchParameters());
productSearch.PageLoad()
}
protected void ProductsSelecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
e.InputParameters["searchParams"] = productSearch.GetSearchParams();
}
protected void ProductsSelected(object sender, ObjectDataSourceStatusEventArgs e)
{
productSearch.SetExportToCsvButton();
}
protected void ProductsPageIndexChanging(object sender, GridViewPageEventArgs e)
{
dgProducts.PageIndex = e.NewPageIndex;
}
protected void ProductsOnRowDataBound(object sender, GridViewRowEventArgs e)
{
productSearch.ProductsRowDataBound(e.Row);
}
Index.aspx
<%# Page Language="C#" MasterPageFile="~/Admin/AdminMaster.master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="Web.Admin.Index" %>
<asp:Content ContentPlaceHolderID="DataFrame" runat="server">
<asp:GridView ID="dgProducts" runat="server" AllowPaging="True" AllowSorting="True" EnableViewState="False"
AutoGenerateColumns="False" DataSourceID="dsProducts" PagerSettings-Position="TopAndBottom" DataKeyNames="ProductNo, KitID"
OnPageIndexChanging="ProductsPageIndexChanging" OnRowDataBound="ProductsOnRowDataBound"
EmptyDataText="There are no products matching your search." meta:resourcekey="dgProducts" onrowcreated="ProductsRowCreated">
<HeaderStyle Font-Size="Small" />
<Columns>
<asp:TemplateField HeaderText="Reviewed">
<ItemTemplate>
<asp:CheckBox runat="server" ID="chkReviewed" class="reviewedCheckbox" Checked="False" />
</ItemTemplate>
</asp:TemplateField>
</Columns
</asp:GridView>
<asp:ObjectDataSource ID="dsProducts" runat="server" EnablePaging="True" SelectMethod="ProductAndKitSearchByParams"
TypeName="ProductSearchController.ProductSearch" onselecting="ProductsSelecting" SortParameterName="SortParameter"
SelectCountMethod="SelectVirtualCount" OnSelected="ProductsSelected">
<SelectParameters>
<asp:Parameter ConvertEmptyStringToNull="true" DefaultValue="" Name="searchParams" Type="Object" />
</SelectParameters>
</asp:ObjectDataSource>
</asp:Content>
Check you master page contentplaceholderid and provide it in content page like this:
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server"></asp:Content>
and also check your content page codebehind file name should be same as mention in page directives.
I want load two user controls on demand.
asp:UpdatePanel ID="UpdatePanel1" runat="server"
ContentTemplate
asp:Button ID="Button1" runat="server" Text="Button" UseSubmitBehavior="false"
OnClick="Button1_Click" /
div id='Div_UserControlPlace' enableviewstate="true" runat="server"
/div
/ContentTemplate
Triggers
asp:PostBackTrigger ControlID="Button1" /
/Triggers
/asp:UpdatePanel
asp:UpdatePanel ID="UpdatePanel2" runat="server"
ContentTemplate
asp:Button ID="Button2" runat="server" Text="Button" UseSubmitBehavior="false"
OnClick="Button2_Click" /
div id='Div_UserControlPlace2' enableviewstate="true" runat="server"
/div
/ContentTemplate
aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
Control FeaturedProductUserControl = new Control();
FeaturedProductUserControl = LoadControl("WebUserControl1.ascx");
FeaturedProductUserControl.EnableViewState = true;
Div_UserControlPlace.Controls.Add(FeaturedProductUserControl);
}
protected void Button2_Click(object sender, EventArgs e)
{
Control FeaturedProductUserControl2 = new Control();
FeaturedProductUserControl2 = LoadControl("WebUserControl2.ascx");
FeaturedProductUserControl2.EnableViewState = true;
Div_UserControlPlace2.Controls.Add(FeaturedProductUserControl2);
}
I load the first user control by clicking on the first button - this works properly but when I click on the other button to load the second UserControl, the first UserControl disappears and the second UserControl loads.
Thanks
IFA_User
You should use the Placeholder control to dynamically add your controls to the form.
Take a look at my last responses about dynamic controls:
OnClick event of dynamically created LinkButtons is not working
Dynamically Added DropDownlists Are Not Firing SelectedIndexChanged Event
Dynamically create an ImageButton
Now I already have some code working for demo purpose, each dynamic user controls keeps its state across post backs
This is the output:
ASPX
<asp:PlaceHolder runat="server" ID="addresses" /><br />
<asp:Button Text="Add Address" runat="server" ID="addAddress" OnClick="addAddress_Click" />
ASPX Code behind
protected void Page_PreLoad(object sender, EventArgs e)
{
for (int i = 0; i < this.DynamicControlsCount; i++)
{
var c = this.LoadControl("~/AddressControl.ascx");
this.addresses.Controls.Add(c);
}
}
protected void addAddress_Click(object sender, EventArgs e)
{
this.DynamicControlsCount++;
var c = this.LoadControl("~/AddressControl.ascx");
this.addresses.Controls.Add(c);
}
protected int DynamicControlsCount
{
get
{
if (this.ViewState["ac"] == null)
{
return 0;
}
return (int)this.ViewState["ac"];
}
set
{
this.ViewState["ac"] = value;
}
}
ASCX
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="AddressControl.ascx.cs" Inherits="WebApplication1.AddressControl" %>
<asp:Panel ID="Panel1" runat="server" GroupingText="Address" DefaultButton="btnSave">
Street: <asp:TextBox runat="server" ID="txtStreet" /><br />
City: <asp:TextBox runat="server" ID="txtCity" /><br />
<asp:Button Text="Save" runat="server" ID="btnSave" OnClick="btnSave_Click" />
</asp:Panel>
<asp:Panel runat="server" GroupingText="Address Summary" Visible="false" ID="summary">
<asp:Label ID="lblStreet" runat="server" /><br />
<asp:Label ID="lblCity" runat="server" />
</asp:Panel>
ASCX Code behind
protected void btnSave_Click(object sender, EventArgs e)
{
this.summary.Visible = true;
this.lblCity.Text = "Selected city: " + this.txtCity.Text;
this.lblStreet.Text = "Selected street: " + this.txtStreet.Text;
}
When a user control is created in the HTML, asp.net will persist across postbacks without any user interaction. But if you are loading them programatically (dynamically), they will not persist accross postbacks. So if you load them programmatically, you have the added task of persisting them programmatically as well. Use the ViewState (or Session I suppose) to store what has been loaded and perhaps any other necessary information that needs to be loaded between postbacks. Every single postback will require you to reload every control or else they will disappear.
There are couple of ways of doing it:
U can load the UserControls using Ajax. Benefit of using Ajax, is ur page does not get post back, thus for example, on click event of Button1, call a ajax(traditional/Jquery) to load UserControl1, and on button click of Button2 User control2.
Put the two button in two different updated panel, by doing this the click event will only refresh a part of ur page.
U have to save somewhere (ViewState/Session),which buttons are clicked, and upon clicking of any button check the value of that variable, and explicit load the control.
Points to note - If u want to get ur data back when ur page made a complete postback, then u have to add the controls keeping in mind the Page load event cycle.
I have a simple problem. I have a dropdown list and a databound repeater. When the user changes the selection in the dropdown list I would like to reload the repeater. I would like to use ASP.NET AJAX so that this does not refresh the entire page. I have read the ASP.NET AJAX documentation over and over but can not find a clear explanation of how to do this. Here is part of my ASPX page
<asp:ScriptManager id="smScriptManager" runat="server" />
<asp:DropDownList id="ddlLocations" runat="server" AutoPostback="true" OnSelectedIndexChanged="ddlLocations_SelectedIndexChanged">
<asp:ListItem Text="Location 1" Value="1"></asp:ListItem>
<asp:ListItem Text="Location 2" Value="2"></asp:ListItem>
<asp:ListItem Text="Location 2" Value="3"></asp:ListItem>
</asp:DropDownList>
<asp:UpdatePanel id="upUpdatePanel" runat="server" ChildrenAsTriggers="false" UpdateMode="Conditional">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="ddlLocations" EventName="SelectedIndexChanged" />
</Triggers>
<ContentTemplate>
<asp:Repeater id="rptItems" runat="server">
<ItemTemplate>
... data bound fields for each location go here ...
</ItemTemplate>
</asp:Repeater>
</ContentTemplate>
</asp:UpdatePanel>
In my code behind I have a separate data binding function that binds the repeater. Currently I call it on Page Load only if Page.IsPostBack = false. So the page loads fine the first time and the Repeater is loaded with all of the data for Location 1 (since it is selected). I also have code in the "ddlLocations_OnSelectedIndexChanged" function that calls the data binding function. In fact, if I strip out all of the AJAX/UpdatePanel stuff the page works fine (although it does a complete page refresh).
With the AJAX stuff, when the user selects Location 2 or Location 3 I get the following JS error:
Message: Sys.WebForms.PageRequestManagerServerErrorException: StartIndex cannot be less than zero.
Parameter name: startIndex
Line: 4723
Char: 21
Code: 0
URI: http://localhost/ScriptResource.axd?d=sTWt9V9Pb7V99ft1kYaE90l3bO7maFff4srx6ham1O6ideYDjPbZU5dv5QJjmzBCPxFQIGrdN2PIuOG7y3evLrZF4ETSZ2de6iwiLo9yZ4w0RvtADl-5JZXm0C_1eyn2F6p6fNX5ZDkxi6yJHF0czKc2lWjUlRrkShaVa4T1l8BPah6A0&t=ffffffff9bdfdd38
Anyone have any ideas why this is happening? Am I doing something wrong? I feel like I don't quite understand how the UpdatePanel works. I get the feeling that I should first set up the page to work without the UpdatePanel/AJAX stuff (and do a complete page refresh). Then add in the UpdatePanel/AJAX stuff and somehow the code magically figures out that I just want the updated Repeater HTML. Or something like that. Any ideas?
UPDATE:
Here is how my back end code works
protected void Page_Load(object sender, EventArgs e)
{
if(!Page.IsPostBack)
{
this.BindData();
}
}
protected void BindData()
{
this.BindDropDownList();
this.BindRepeater();
}
protected void BindDropDownList()
{
...call code for binding drop down list...
(ddlLocations is actually databound - I simplified it above for purposes of this post)
}
protected void BindRepeater()
{
...call code for binding the repeater...
}
protected void ddlLocations_SelectedIndexChanged(object sender, EventArgs e)
{
this.BindRepeater();
}
Thanks,
Corey
I have a gridview that includes dynamically created dropdownlist. When changing the dropdown values and doing a mass update on the grid (btnUpdate.click), I have to create the controls in the page init so they will be available to the viewstate. However, I have several other buttons that also cause a postback and I don't want to create the controls in the page init, but rather later in the button click events.
How can I tell which control fired the postback while in page_init? __EVENTTARGET = "" and request.params("btnUpdate") is nothing
It is possible to determine which control caused a PostBack by looking at Request.Form["__EVENTTARGET"]. The problem with this is that button ids will not show unless you set their UseSubmitBehavior to false. Here's an example:
.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
switch (Request.Form["__EVENTTARGET"].ToString())
{
case "ddlOne":
break;
case "btnOne":
break;
case "btnTwo":
break;
}
}
}
.aspx
<form id="form1" runat="server">
<asp:DropDownList ID="ddlOne" AutoPostBack="true" runat="server">
<asp:ListItem Text="One" Value="One" />
<asp:ListItem Text="Two" Value="Two" />
</asp:DropDownList>
<asp:Button ID="btnOne" Text="One" UseSubmitBehavior="false" runat="server" />
<asp:Button ID="btnTwo" Text="Two" UseSubmitBehavior="false" runat="server" />
</form>
I can't figure out how to pass form data from a Wizard Steps Control to a new page. I posted THIS POST some days ago, but the answer here didn't really help me because i can't even get the value from a TextBox on the new page.
I put tried to put this insted of the hiddenfield but <asp:TextBox ID="amount" runat="server" Text="tester"></asp:TextBox> but the Request.Form["amount"] is still just null.
How do i pass form data from a wizard steps control to a new page? Should this really be that hard?
For information that we collect in a wizard usually translates into a business object, then we just pass that object around in a Session variable. That way we have access to it on any page.
Session variable seems to be easier to work with:
Default.aspx markup:
<asp:Wizard runat="server" ID="wizAwesome" FinishDestinationPageUrl="~/TestPage.aspx" OnFinishButtonClick="wizAwesome_FinishButtonClick">
<WizardSteps>
<asp:WizardStep ID="stepRock" runat="server" Title="Rock!">
This is a wizard step.
<asp:HiddenField runat="server" ID="hiddenName" Value="Juliet" />
</asp:WizardStep>
</WizardSteps>
</asp:Wizard>
Default.aspx.cs
protected void wizAwesome_FinishButtonClick(object sender, WizardNavigationEventArgs e)
{
Session["hiddenName"] = hiddenName.Value;
}
TestPage.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
lblName.Text = Session["hiddenName"].ToString();
}
Your HiddenField needs to be located outside of the Wizard like below and you need to add a FinishNavigationTemplate which posts the data to your new page
<asp:Wizard runat="server" ID="wzd_Amount">
<WizardSteps>
<asp:WizardStep ID="step_Amount" runat="server">
This is a wizard step.
</asp:WizardStep>
</WizardSteps>
<FinishNavigationTemplate>
<asp:Button runat="server" ID="btn_Finish" PostBackUrl="~/Labs/TestPage.aspx" />
</FinishNavigationTemplate>
</asp:Wizard>
<asp:HiddenField runat="server" ID="hdf_Amount" Value="Test" />
On the other page you can just Request the data like so
lbl_Test.Text = Request["hdf_Amount"];