Maintaining scroll position in .NET 2.0 ListBox on PostBack
Warning: this is only a partially working solution due to some Javascript issues described (and solved) here.
A requirement I had was to maintain the scroll position of ListBoxes on PostBack. The only solution I could find was to get the scroll through Javascript (the scrollTop property of the select) and restore it on page load, however, that would have meant a lot of custom controls, not to mention lots of work, to which I am usually against.
So, I used a ControlAdapter! The ControlAdapter is something new to the NET 2.0 framework. The Control in 2.0 looks for a ControlAdapter and delegates the usual methods (like OnLoad,OnInit,Render,etc) to the adapter. You tell the site to use an adapter for a specific type of control and possibly a specific browser type (by using a browser file), and it uses that adapter for all of the controls of the selected type and also the ones inherited from them. To disallow the "adaptation" of your control, override ResolveAdapter to always return null.
Ok, the code!
Of course, you will ask me What is that ScriptManagerHelper? It's a little something that tries to get the ScriptManager class without having to reference the System.Web.Extensions library for Ajax. That means that if there is Ajax around, it will use ScriptManager.[method] and if it is not it will use ClientScript.[method]. To.Int(object) is obviously something that gets the integer value from a string.
There is another thing, at the beginning I've inherited this adapter from a WebControlAdapter, but it resulted in showing all the options in the select (all the items in the ListBox) with empty text. The value was set as well as the number of options. It might be because in WebControlAdapter the Render method looks like this:
instead of just calling the control Render method.
A requirement I had was to maintain the scroll position of ListBoxes on PostBack. The only solution I could find was to get the scroll through Javascript (the scrollTop property of the select) and restore it on page load, however, that would have meant a lot of custom controls, not to mention lots of work, to which I am usually against.
So, I used a ControlAdapter! The ControlAdapter is something new to the NET 2.0 framework. The Control in 2.0 looks for a ControlAdapter and delegates the usual methods (like OnLoad,OnInit,Render,etc) to the adapter. You tell the site to use an adapter for a specific type of control and possibly a specific browser type (by using a browser file), and it uses that adapter for all of the controls of the selected type and also the ones inherited from them. To disallow the "adaptation" of your control, override ResolveAdapter to always return null.
Ok, the code!
Of course, you will ask me What is that ScriptManagerHelper? It's a little something that tries to get the ScriptManager class without having to reference the System.Web.Extensions library for Ajax. That means that if there is Ajax around, it will use ScriptManager.[method] and if it is not it will use ClientScript.[method]. To.Int(object) is obviously something that gets the integer value from a string.
There is another thing, at the beginning I've inherited this adapter from a WebControlAdapter, but it resulted in showing all the options in the select (all the items in the ListBox) with empty text. The value was set as well as the number of options. It might be because in WebControlAdapter the Render method looks like this:
protected internal override void Render(HtmlTextWriter writer)
{
this.RenderBeginTag(writer);
this.RenderContents(writer);
this.RenderEndTag(writer);
}
instead of just calling the control Render method.
hi,
ReplyDeletegeat solution to this problem, i'm going to use it.
Let me know how it works out... If there is any problem , I will fix it.
ReplyDeleteI just spent all afternoon working on a similiar solution and when I finished, I had it at 1 line of code. Try this also (it's in VB, so replace the &'s and put a semi-colon in, you know the story), this needs to be run everytime so don't nest it in a IsPostBack block. Also, the if statement is required because on the initial load those objects don't exist yet... they will on subsequent postbacks.
ReplyDeleteScriptManager.RegisterClientScriptBlock(Page, btnAdd.GetType, "Reposition", "if (document.getElementById('lstPrograms')) { document.getElementById('lstPrograms').selectedIndex=" & lstPrograms.SelectedIndex & "; }", True)
Your solution works only for single selection listboxes. Also, it doesn't preserve the scroll, it only scrolls to the selected item.
ReplyDeleteYou are correct on both of your statements. Our end goals were different, in my brain fog I thought they were closer, my apologies.
ReplyDeleteIn my case, they've already moved the contents from a left hand box to a right hand box (with a multi-select) on a post back and the only spec was to keep the scroll back on the first selected (I would prefer the last selected though at which point we could just loop through to find it's index).
You are right though, my example does not preserve the multiple selections, just the first selected scroll position.
I forgot to mention also, yours is a great solution, many thanks for sharing it. :)
this seems to work great in .net 3.5
ReplyDeletewith this slight modification
add declaration /assignment
ClientScriptManager cs = Page.ClientScript;
change references to scriptmanagerhelper to cs and remove page as parameter in the three cs. method calls
that takes care of nonajax
also to take care of items in update panals repeat the codeblock in another if statement
(Page != null) && (Control is WebControl) && ScriptManager.GetCurrent(Page) != null)
and in this codeblock replace scriptmanagerhelper with ScriptManager
Thanks, Vernard!
ReplyDeleteThanks very much!!!!
ReplyDeletesolution to a big dilema...
First easily implemented solution I've found after A LOT of looking, many thanks!
ReplyDelete