Suppose that, after a click on a asp:button, I'd like to store the actual "view", with selected value for each controllers, like asp:checkbox, or input inserted by users on inputbox.
Than, I change page, with a link such as "back to the previous page". I'd like so "restore" the old actual form, before leaving it.
Is it possible with .NET? Or I need to implements all controls in variables and put them in session?
There are several ways how you can persist or pass values between ASP.NET pages.
Have a look here for more informations.
Because you've mentioned that you have "tons" of controls to store and restore, i've tried to find a way to automatize this process.
Here's my approach which uses Session as storage to persist all controls' values of type IPostBackDataHandler. Not really tested but hopefully helpful anyway.
using System;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
public class FormStorage
{
private System.Web.UI.Page _page = null;
public Dictionary<String, Dictionary<String, String>> storage
{
get { return (Dictionary<String, Dictionary<String, String>>)_page.Session["FormStorage"]; }
set { _page.Session["FormStorage"] = value; }
}
public FormStorage(System.Web.UI.Page page)
{
_page = page;
initHandler();
if (this.storage == null)
{
this.storage = new Dictionary<String, Dictionary<String, String>>();
}
if(!this.storage.ContainsKey(_page.Request.Path))
this.storage.Add(_page.Request.Path, new Dictionary<String, String>());
}
private void initHandler()
{
_page.Init += Init;
_page.Load += Load;
}
private void Init(Object sender, EventArgs e)
{
loadForm();
}
private void Load(Object sender, EventArgs e)
{
saveForm();
}
private void loadForm()
{
var pageStorage = storage[_page.Request.Path];
var e = pageStorage.GetEnumerator();
while (e.MoveNext())
{
loadControl(e.Current.Key, e.Current.Value);
}
}
private void loadControl(String ID, String value)
{
Control control = findControlRecursively(_page, ID);
if (control != null)
{
setControlValue(control, value);
}
}
private void setControlValue(Control control, String value)
{
if (control is ITextControl)
{
ITextControl txt = (ITextControl)control;
txt.Text = value == null ? "" : value;
}
else if (control is ICheckBoxControl)
{
ICheckBoxControl chk = (ICheckBoxControl)control;
chk.Checked = value != null;
}
else if (control is ListControl)
{
ListControl ddl = (ListControl)control;
if (value == null)
ddl.SelectedIndex = -1;
else
ddl.SelectedValue = value;
}
}
public void saveForm()
{
saveControlRecursively(this._page);
}
private void saveControlRecursively(Control rootControl)
{
if (rootControl is IPostBackDataHandler)
{
var postBackData = _page.Request.Form[rootControl.ID];
storage[_page.Request.Path][rootControl.ID] = postBackData;
}
if (rootControl.HasControls())
foreach (Control subControl in rootControl.Controls)
saveControlRecursively(subControl);
}
private static Control findControlRecursively(Control rootControl, String idToFind)
{
if (rootControl.ID == idToFind)
return rootControl;
foreach (Control subControl in rootControl.Controls)
{
Control controlToReturn = findControlRecursively(subControl, idToFind);
if (controlToReturn != null)
{
return controlToReturn;
}
}
return null;
}
}
Here's an examplary implementation in a page that redirects to another page:
private FormStorage storage;
protected void Page_PreInit(object sender, EventArgs e)
{
storage = new FormStorage(this);
}
protected void BtnRedirect_Click(object sender, EventArgs e)
{
Response.Redirect("RedirectForm.aspx");
}
Note that it loads and saves implicitely on Page_Load/Page_PreRender. Therefor the FormStorage instance must be created in Page_PreInit.
If you want to change values after Page_Load programmatically, you need to call storage.saveForm() manually (for example in Page_PreRender) to ensure that these values are overridden, because FormStorage will auto-save all postback data in Page_Load.
Edit: The history.go approach of sh4nx0r is probably better since it's more scalable. My approach uses the Session and would not be appropriate for an internet-application with a huge amount of (possible) users.
One (small) advantage of mine is that it would work even when javascript is disabled. One larger advantage is that you can control to what page you want to redirect. You can even restore values across multiple redirects.
You don't need sessions to do this. Just make use of JavaScript.
Consider you have two pages Page1.aspx and Page2.aspx.
You have textboxes , checkboxes , radiobuttons on Page1.aspx and you have a button "Submit". By clicking the button it takes to Page2.aspx.
Just have a button "Back to Previous Page" on Page2.aspx , which on clicking takes you to Page1.aspx with all those values still being there. To do this, Add this line on your Page2.aspx page_load event
protected void Page_Load(object sender, EventArgs e)
{
btnBackButton.Attributes.Add("onClick", "javascript:window.history.go(-1);return false;");
}
Related
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Button click event doesn't work properly
I try to increment an int for each click on a default page. Int=0. It only goes up to 1. What should I do to increment the number for each click?
public partial class _Default : System.Web.UI.Page
{
private int speed = 0;
public int Speed
{
get { return speed; } // Getter
set { speed = value; } // Setter
}
public void accelerate()
{
//speed++;
this.Speed = this.Speed + 1;
}
public void decelerate()
{
// speed--;
this.Speed = this.Speed - 1;
}
public int showspeed()
{
return this.Speed;
}
//car bmw = new car();
public void Page_Load(object sender, EventArgs e)
{
//datatype objectname = new
dashboard.Text = Convert.ToString(this.showspeed());
}
public void acc_Click(object sender, EventArgs e)
{
this.accelerate();
dashboard.Text = Convert.ToString(this.showspeed());
}
public void dec_Click(object sender, EventArgs e)
{
this.decelerate();
this.showspeed();
}
}
You could use the ViewState to maintain the value across postbacks:
private int Speed
{
get
{
if (ViewState["Speed"] == null)
ViewState["Speed"] = 0;
return (int)ViewState["Speed"];
}
set { ViewState["Speed"] = value; }
}
You need to store the result in a way that will persist over postback. I suggest using ViewState, for example:
public int Speed
{
get {
if(ViewState["Speed"] == null) {
ViewState["Speed"] = 1;
}
return Convert.ToInt32(ViewState["Speed"]);
}
set {
ViewState["Speed"] = value;
}
}
Because everytime when you click on the button, it is initializing the value of speed to 0.
HTTP is stateless. That means it wil not retain the values of variable across your postback like you do in Windows Forms programming. So you need to keep the value across your postbacks.
You can use a hidden element in your page to store the value and access the value every time you want to do a function on that:
<asp:HiddenField ID="hdnFileId" runat="server" Value="" />
and in your page load, you can read the value and load it to your variable.
public void Page_Load(object sender, EventArgs e)
{
this.speed = ConvertTo.Int32(hdnFileId.Value);
}
From Adrianftode's comment, the data for the controls like TextBox, Checkbox, Radio button controls values will be posted to the server on the postback because they are rendered as standard HTML form controls in the browser. See Chapter 4. Working with ASP.NET Server Controls.
In my ASP.NET page, I have a generic class that is defined as below:
public partial class log_states : BasePage
{
protected class StatesUsed
{
public int StateCode { get; set; }
public string StateName { get; set; }
}
private List<StatesUsed> _statesUsed;
}
In the Page_Load() event, I initialize _statesUsed like below, and bind it to a grid:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
_statesUsed = new List<StatesUsed>();
BindMyGrid();
}
}
private void BindMyGrid()
{
gvStates.DataSource = _statesUsed;
gvStates.DataBind();
}
I then have a form to add new States. When the user adds a state, I'm trying to add it to the local _statesUsed variable, and rebind the grid. Example:
protected void btnAddState_Click(object sender, EventArgs e)
{
_statesUsed.Add(new StatesUsed { StateCode = 1, StateName = "Test" });
BindMyGrid();
}
This always fails when trying to add the new item saying "Object reference not set to an instance of an object"...
How do I keep _statesUsed persistant? The idea is to add all user input using the generic class and then update the database at one go. If you know of another way to accomplish this, I'd be very grateful.
Thanks in advance!
Instead of
private List<StatesUsed> _statesUsed;
I'm usually using something similar to:
private List<StatesUsed> _statesUsed
{
get
{
var result = ViewState["_stateUsed"] as List<StatesUsed>;
if ( result == null )
{
result = new List<StatesUsed>();
ViewState["_stateUsed"] = result;
}
return result;
}
}
I.e. I am persisting page class variables to the ViewState.
If you want to keep stuff "alive" through multiple postbacks you either have to store stuff to a database, use Session, use the Viewstate, or store it temporarily in shared server memory. Which of these you choose is dependent on your use case,
In your case I would probably add an asp:HiddenField runat="server" ID="HiddenFieldUsedStateIDs" in which I wrote the IDs comma separated whenever there is a change and then read the values into the generic list in Page_Load (on every Page_Load, not just !IsPostBack)
This would utilize the Viewstate mechanism in Asp.Net to write the values to the rendered HTML and read it back into the HiddenField's value on each post
Asuuming that your viewstate in not disabled, you could do,
protected void btnAddMat_Click(object sender, EventArgs e)
{
List<StatesUsed> temp = null;
temp = (List<StatesUsed>)gvStates.DataSource;
if(temp != null)
temp.Add(new StatesUsed { StateCode = 1, StateName = "Test" });
gvStates.DataBind();
}
I once asked for a way to let a linkbutton pass more than one value in the commandArgument and then I reached the approach where I pass a string of multiple values separated by any character and split it into it's original parts...that didn't work out I don't know what was wrong with the splitting!
Now I tried the only solution I got, which is created a user control of the LinkButton and add properties to accept any values nedeed!...could you please tell me what's wrong with my 2 approaches and which is better ?
The first question can be found here : link text
and this is the code for the user control approach >>
MultivaluedLinkButton.ascx :
<asp:LinkButton ID="LnkBtnSort" runat="server" Text="Sort" OnClick="LnkBtnSort_Clicked"/>
MultivaluedLinkButton.ascx.cs :
public partial class MultivaluedLinkButton : System.Web.UI.UserControl
{
public event EventHandler Click;
private int _sortingType;
private string _sortingFactor;
private string _text;
public int SortingType
{
set { _sortingType = value; }
get { return _sortingType; }
}
public string SortingFactor
{
set { _sortingFactor = value; }
get { return _sortingFactor.ToString(); }
}
//public string Text
//{
// set { _text = value; }
// get { return _text.ToString(); }
//}
protected void LnkBtnSort_Clicked(object sender, EventArgs e)
{
if( Click != null )
{
this.Click(this, EventArgs.Empty);
}
}
}
Finally, Here's the implementation of my control inside an aspx page:
protected void MultivaluedLinkButton1_Clicked(object sender, EventArgs e)
{
MultivaluedLinkButton ctrl = (MultivaluedLinkButton)sender;
using (SqlConnection cn1 = new SqlConnection(ConfigurationManager.ConnectionStrings["testConnectionString"].ConnectionString))
{
using (SqlCommand cm1 = new SqlCommand(commandString2, cn1))
{
cm1.Parameters.Add("#arrange_by_id", System.Data.SqlDbType.Int);
cm1.Parameters["#arrange_by_id"].Value = ctrl.SortingType;
cn1.Open();
using (SqlDataReader dr1 = cm1.ExecuteReader())
{
SortBy_rpt.DataSource = dr1;
SortBy_rpt.DataBind();
}
}
}
}
The item template of the repeater in the implementation page :
<ItemTemplate>
<uc1:MultivaluedLinkButton ID="MultivaluedLinkButton1" runat="server" OnClick="MultivaluedLinkButton1_Clicked" SortingType='<%#Eval("arrange_by_id")%>' />
</ItemTemplate>
The problem i see is, you have an eventHandler in your usercontrol which you never really use.
Not 100% sure but, on the Page_Load of your parent page, you need to add MultivaluedLinkButton1_Clicked event to your handler.
MultivaluedLinkButton1.EventHandler_Click = new EventHandler(this.MultivaluedLinkButton1_Clicked);
MultivaluedLinkButton1.LnkBtnSort.Click = MultivaluedLinkButton1.EventHandler_Click;
Basically you are telling that when a user clicks on your linkbutton, MultivaluedLinkButton1_Clicked() on the parent page should be called.
You can remove OnClick="MultivaluedLinkButton1_Clicked" from your UserControl properties on your parent page.
I'm using PropertyGrid to edit an object containing a collection.
Collection is edited using the CollectionEditor.
I have to make sure elements in collection are unique.
How can I add validation to CollectionEditor:
By either overloading CollectionEditor's OnFormClosing
Or adding validation for creating/editing items?
You can create your own collection editor, and hook into events on the default editor's controls. You can use these events to, say, disable the OK button. Something like:
public class MyCollectionEditor : CollectionEditor
{
private static Dictionary<CollectionForm, Button> okayButtons
= new Dictionary<CollectionForm, Button>();
// Inherit the default constructor from CollectionEditor
public MyCollectionEditor(Type type)
: base(type)
{
}
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
collectionForm.FormClosed +=
new FormClosedEventHandler(collectionForm_FormClosed);
collectionForm.Load += new EventHandler(collectionForm_Load);
if (collectionForm.Controls.Count > 0)
{
TableLayoutPanel mainPanel = collectionForm.Controls[0]
as TableLayoutPanel;
if ((mainPanel != null) && (mainPanel.Controls.Count > 7))
{
// Get a reference to the inner PropertyGrid and hook
// an event handler to it.
PropertyGrid propertyGrid = mainPanel.Controls[5]
as PropertyGrid;
if (propertyGrid != null)
{
propertyGrid.PropertyValueChanged +=
new PropertyValueChangedEventHandler(
propertyGrid_PropertyValueChanged);
}
// Also hook to the Add/Remove
TableLayoutPanel buttonPanel = mainPanel.Controls[1]
as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button addButton = buttonPanel.Controls[0] as Button;
if (addButton != null)
{
addButton.Click += new EventHandler(addButton_Click);
}
Button removeButton = buttonPanel.Controls[1] as Button;
if (removeButton != null)
{
removeButton.Click +=
new EventHandler(removeButton_Click);
}
}
// Find the OK button, and hold onto it.
buttonPanel = mainPanel.Controls[6] as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button okayButton = buttonPanel.Controls[0] as Button;
if (okayButton != null)
{
okayButtons[collectionForm] = okayButton;
}
}
}
}
return collectionForm;
}
private static void collectionForm_FormClosed(object sender,
FormClosedEventArgs e)
{
CollectionForm collectionForm = (CollectionForm)sender;
if (okayButtons.ContainsKey(collectionForm))
{
okayButtons.Remove(collectionForm);
}
}
private static void collectionForm_Load(object sender, EventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void propertyGrid_PropertyValueChanged(object sender,
PropertyValueChangedEventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void addButton_Click(object sender, EventArgs e)
{
Button addButton = (Button)sender;
ValidateEditValue((CollectionForm)addButton.Parent.Parent.Parent);
}
private static void removeButton_Click(object sender, EventArgs e)
{
Button removeButton = (Button)sender;
ValidateEditValue((CollectionForm)removeButton.Parent.Parent.Parent);
}
private static void ValidateEditValue(CollectionForm collectionForm)
{
if (okayButtons.ContainsKey(collectionForm))
{
Button okayButton = okayButtons[collectionForm];
IList<MyClass> items = collectionForm.EditValue as IList<MyClass>;
okayButton.Enabled = MyCollectionIsValid(items);
}
}
private static bool MyCollectionIsValid(IList<MyClass> items)
{
// Perform validation here.
return (items.Count == 2);
}
}
You will also need to add an Editor attribute to you collection:
class MyClass
{
[Editor(typeof(MyCollectionEditor),
typeof(System.Drawing.Design.UITypeEditor))]
List<Foo> MyCollection
{
get; set;
}
}
NOTE: I found that the value of items in removeButton_Click was not correct - so some tweaking may need to take place.
Try collectionForm.Context.Instance and typecast it to your data type this should do the trick.
i created Web user control. it's property is included ListDictionary; But i can not generate
in web uset control. if i try wuc in asp.net page, it gives initialize error:
"Object reference not set to an instance of an object."
WEB USER CONTROL:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
FillList1();
}
}
public String OrderClause
{
get { return Session["selecteditem"].ToString(); }
set { Session["selecteditem"] = value; }
}
private ListDictionary items;
public ListDictionary Items
{
get { return items; }
set { items = value; }
}
void FillList1()
{
foreach (string ky in Items.Keys)
{
ListBox1.Items.Add(new ListItem(ky, Items[ky].ToString()));
}
}
BUT!!
if i add my web page this web user control. And write below codes:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
WebUserControl2_1.Items.Add("Yusuf", "1");
WebUserControl2_1.Items.Add("Gulden", "2");
}
}
protected void btn_test_Click(object sender, EventArgs e)
{
lbl_test.Text = WebUserControl2_1.OrderClause.Trim();
}
Object reference not set to an instance of an object.
Error point: WebUserControl2_1.Items.Add("Yusuf", "1");
i need listdictionary new method in web user control but how?
You are referencing the Items property without first instantiating the field. So asking for the item will cause the NullReferenceException. I like the lazy load approach, so when you need it, if it is null, then it will instantiate it.
Inside the getter you could simply put
if(items == null)items = new ListDictionary();
return items;
But then you need to think about state management. You have a few options including ViewState and also ControlState.
Andrew
You're never actually instantiating an instance of the ListDictionary in the items variable
In this bit of code:
private ListDictionary items;
public ListDictionary Items
{
get { return items; }
set { items = value; }
}
Change it to something like this:
private ListDictionary items;
public ListDictionary Items
{
get { if (items == null) {items = new ListDictionary}; return items; }
set { items = value; }
}
You are making a couple o mistakes. In first place, all your properties should have a way to store thier values between postback. an easy way to handle this is through viewstate.
Here is an article that gives more deatailed information about this topic
http://www.codeguru.com/csharp/.net/net_asp/controls/article.php/c11971/
In second place if it is a collection it is more likekely you will only need a getter since you will be manipullating collection instead of replacing the entire collectio.
so Your code should look like this:
public ListDictionary Items
{
get
{
object o = ViewState["Items"];
if (o == null)
{
ViewState["Items"] = new ListDictionary;
o = ViewState["Items"];
}
return (ListDictionary) o;
}
}
Hope this helps,