Custom Paging Repeater Control next and previous events happen after databind - c#

I have a Custom Repeater control that inherits from Repeater and has paging functionality, however when I click the next page button the first time it refreshes the control but does not change the page, if I click it again after that it changes page perfectly.
I know what the issue is, when I click the next button it does a postback, then the data is bound to the repeater, and then after that the NextButton Event is handled.
Is there any way I can change the order of the page load events?? Or force the repeater to reload again after the event is handled??
I've included my Custom Repeater class bellow:
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Data;
using System.Collections;
using System;
namespace ASPresentation.Controls
{
[ToolboxData("<cc:PagedRepeater runat=server></cc:PagedRepeater>")]
public class PagedRepeater : Repeater
{
public int PageSize { get; set; }
public int CurrentPageIndex
{
get
{
return Convert.ToInt16(Page.Session["ProjectIndex"]);
}
set
{
Page.Session.Add("ProjectIndex", value);
}
}
public PagedDataSource pagedData = new PagedDataSource();
LinkButton NextBtn = new LinkButton();
LinkButton PrevBtn = new LinkButton();
public bool IsLastPage
{
get
{
return pagedData.IsLastPage;
}
}
public bool IsFirstPage
{
get
{
return pagedData.IsFirstPage;
}
}
public override object DataSource
{
get
{
return base.DataSource;
}
set
{
pagedData.DataSource = (IEnumerable)value;
}
}
protected void NextButtonClick(object sender, EventArgs e)
{
if (!IsLastPage)
{
CurrentPageIndex++;
}
}
protected void PrevButtonClick(object sender, EventArgs e)
{
if (!IsFirstPage)
{
CurrentPageIndex--;
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
NextBtn.Text = "Next";
PrevBtn.Text = "Prev";
NextBtn.Click += new EventHandler(NextButtonClick);
PrevBtn.Click += new EventHandler(PrevButtonClick);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
base.Controls.Add(PrevBtn);
base.Controls.Add(NextBtn);
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
}
public override void DataBind()
{
pagedData.AllowPaging = true;
pagedData.PageSize = PageSize;
pagedData.CurrentPageIndex = CurrentPageIndex;
base.DataSource = pagedData;
base.DataBind();
}
}
}

A couple issues here that jump out at me.
One, why are you dynamically creating the prev/next button? Just put them in the ASCX. Show/hide them if you like based on whether your page index is first/last. If you must create them dynamically, do so in Init...
Two, do not store your page index in the session like that. What happens when you have two of these custom repeaters on one page? Use the ViewState. Key the string name to the control ID if necessary, but I think ViewState does this automatically(?).
Finally, what is triggering the DataBind? What event handler? It must be called from the Page that is hosting this control. If that's the case, then you also need to expose the Next/Prev clicks as events so that DataBind can be called in response to these events. This is how Microsoft's controls that handle paging work, such as the GridView. NextBtn.Click or PrevBtn.Click is guaranteed to be the last postback event handled.
You could handle the next/prev internally, but if you're going to do that you need to also call DataBind() in your code, so that it happens at the correct time.

Call "this.DataBind()" in the Page change functions.
This will mean you databind twice when changing pages but will work :-\

Related

Bypass Page.VerifyRenderingInServerForm

I am trying to render a Wizard control to a HTML string on the click of a Button (using Control.Render). I am already disabling event validation with the following, which works fine and enables me to render the entire Page to the string. I do this within the user control that contains the Wizard:
protected void Page_Init(object sender, EventArgs e)
{
if (Request.Form["__EVENTTARGET"] != null
&& Request.Form["__EVENTTARGET"] == btnPrint.ClientID.Replace("_", "$"))
{
Page.EnableEventValidation = false;
}
}
While this works, I'd like to render the Wizard control on its own. I understand that I can override Page.VerifyRenderingInServerForm in order to prevent the page from throwing an exception when I attempt to render this control on its own (without runat="server" form tags), like so:
public override void VerifyRenderingInServerForm(Control control)
{
// base.VerifyRenderingInServerForm(control);
}
However, I don't want to override this completely. Is there a way I can bypass this dynamically, either:
For the specific PostBack in which the button in question is clicked, or...
Specifically for the Wizard control?
How about something like:
public override void VerifyRenderingInServerForm(Control control)
{
if (!SkipVerifyRenderingInServerForm)
{
base.VerifyRenderingInServerForm(control);
}
}
public bool SkipVerifyRenderingInServerForm
{
get
{
object o = HttpContext.Current.Items["SkipVerifyRenderingInServerForm"];
return (o == null) ? false : (bool) o;
}
set
{
HttpContext.Current.Items["SkipVerifyRenderingInServerForm"] = value;
}
}
You could then set SkipVerifyRenderingInServerForm to true in your button click event handler.

How to bubble an event up multiple levels?

I'm struggling to bubble an event correctly.
I have a master page with a user control, and a page that is a child of the master page.
The user control and the page share common data, so when the user control updates, it updates the apage and vice versa.
The user control exposed an event to the master page. This is the format I use.
outside of class:
public delegate void OfferBookmarkRemoved(int OfferID);
inside class:
public event OfferBookmarkRemoved OfferBookmarkRemoved;
protected void LV_Bookmarks_ItemCommand(object source, ListViewCommandEventArgs e)
{
if (e.CommandName == "RemoveOffer")
{
var offerId = (int)e.CommandArgument;
OnOfferBookmarkRemoved(offerId);
}
}
void OnOfferBookmarkRemoved(int offerId)
{
offerId.ThrowDefault("offerId");
if (OfferBookmarkRemoved != null)
{
OfferBookmarkRemoved(offerId);
}
}
Now this can be used in the master page ok. I don't do anything in the master page and want to expose the event so that the aspx page can use it, like this:
Master.OfferBookmarkRemoved += OnBookmarkRemoved;
void OnBookmarkRemoved(int offerId)
{
offerId.ThrowDefault("offerId");
OfferList1.UpdateBookmark(offerId);
}
So the missing bit is to listen for the event in the master and make it available to the page.
Can anyone help?
You need to define this event in the master page also like that:
public event EventHandler<OfferEventArgs> OfferBookmarkRemoved
{
add
{
userControl.OfferBookmarkRemoved += value;
}
remove
{
userControl.OfferBookmarkRemoved -= value;
}
}
This way any page that registers to the master event will be registered to the usercontrol event.
By the way, you are not following the event pattern. Your event should look like:
public class OfferEventArgs : EventArgs
{
public int OfferID { get; set; }
}
public event EventHandler<OfferEventArgs> OfferBookmarkRemoved;
and when invoked:
OfferBookmarkRemoved(new OfferEventArgs() { OfferID = offerId });

Can I save a postback status and restore it?

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;");
}

LinkButton passing multivalue possible clean solutions

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.

How to enable ajax when deriving from System.Web.UI.WebControls.WebControl on controls that are created inside WebControl

I've built a class that derives from System.Web.UI.WebControl. It basically renders pagination links (same as what you see on top of GridView when enabled) for use above a repeater.
I'm creating some anchor tags explicitly inside my nav control obviously, but they don't perform ajax postbacks. My understanding is that ajax requires POSTS to work right? Well, these would be GETs which I think is the problem.
Is there a way to achieve what I'm trying to do?
Thanks!
To take this advantage, you have to inherit the ICallbackEventHandler and implement its methods as follows.
public class CtlTest : WebControl, ICallbackEventHandler
{
private static readonly object EventClick = new object();
public CtlTest() : base(HtmlTextWriterTag.A) { }
public event EventHandler Click
{
add { base.Events.AddHandler(EventClick, value); }
remove { base.Events.RemoveHandler(EventClick, value); }
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:" + this.Page.ClientScript.GetCallbackEventReference(this, null, "null", null));
}
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
writer.Write("Submit Query");
}
protected virtual void OnClick(EventArgs e)
{
EventHandler handler = this.Events[EventClick] as EventHandler;
if (handler != null)
handler(this, e);
}
#region ICallbackEventHandler Members
string ICallbackEventHandler.GetCallbackResult()
{
return string.Empty;
}
void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
this.OnClick(EventArgs.Empty);
}
#endregion
}
Whereas you are working on a data pager control and it requires to update some portions of the page, it's better to write a non Ajax enabled control and put it and its relative controls within an UpdatePanel.
Ok, I figured it out. I simply made my class implement the IPostBackEventHandler. This makes your control fire an event when the user takes action on something. In my case, it's clicking a nav pagenumber: [1][2][3][4][5][Next >]
Then, inside my render where I create the Anchor tags, I add this to each href (pageStartRow is different for each):
PostBackOptions options = new PostBackOptions(this, pageStartRow.ToString());
writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:" + Page.ClientScript.GetPostBackEventReference(options));
writer.RenderBeginTag(HtmlTextWriterTag.A);
The key is to pass something that uniquely identifies which link they clicked. This is done as the 2nd constructor parameter to the PostBackOptions class.
I then added the following items in my WebControl class:
// Defines the Click event.
public delegate void ClickHandler(object sender, GridPagingNavClickedEventArgs e);
public event ClickHandler Click;
//Invoke delegates registered with the Click event.
protected virtual void OnClick(GridPagingNavClickedEventArgs e)
{
if (Click != null)
{
Click(this, e);
}
}
public void RaisePostBackEvent(string eventArgument)
{
GridPagingNavClickedEventArgs e = new GridPagingNavClickedEventArgs(Convert.ToInt32(eventArgument));
OnClick(e);
}
The GridPagingNavClickedEventArgs contains a single item (pageNumber in my case).
Finally, in my aspx page (where I use the webcontrol), I do this in the Page_OnLoad:
gridNavTop.Click += new GridPagingNavigation.ClickHandler(gridNavTop_Click);
and this is the event code:
private void gridNavTop_Click(object sender, GridPagingNavClickedEventArgs e)
{
StartRow = e.PageStartRow;
}
As long as everything is inside an UpdatePanel, then it works perfectly!

Categories

Resources