Losing SelectedIndex on Custom Listbox postback

L

lisa

This is a strange problem. I have built a custom ListBox control that
inherits from the regular WebControls.ListBox. It has two
modifications: it overrides RenderContents to force rendering of any
attributes added to Items in the ListBox (fixing a bug that Microsoft
says is "by design"), and it wraps the ListBox in a div and adds some
client script so that it works like a Windows Forms ListBox (horizontal
scroll).

But when anything on the page triggers a postback, I lose the
SelectedIndex property. I tried putting one of my ListBoxes and a
regular one on a WebForm, populated them exactly the same way, and
selected the same Item. Then I hit a button to postback, and my
ListBox lost the selection, while the regular one retained it.

I put a breakpoint in on the Page_Load of the WebForm, and checked to
see what the SelectedIndex was at that point. But the custom version
was already at SelectedIndex = -1 by then.

I put another breakpoint on the OnSelectedIndexChanged event in the
control, and that event never fires.

If anyone can solve this for me, I'd be grateful.

Thanks,
Lisa

Here's the code:

Option Strict On
Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web

<DefaultProperty("SelectedIndex"), ToolboxData("<{0}:BetterListBox
runat=server></{0}:BetterListBox>")> _
Public Class BetterListBox
Inherits System.Web.UI.WebControls.ListBox

Private _containerDivStyle As Style
Private _width As Unit
Private _height As Unit

Public Sub New()
If Me.Width.IsEmpty Then Me.Width = Unit.Pixel(100)
If Me.Height.IsEmpty Then Me.Height = Unit.Pixel(100)
End Sub

Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
RegisterScript()
End Sub

Protected Overridable Sub RegisterScript()
'this script is startup script, and needs to be called for each
instance of the control
Dim SUreader As New
System.IO.StreamReader(Me.GetType().Assembly.GetManifestResourceStream(Me.GetType(),
"BetterListBoxStartup.js"))
Dim SUscript As String = "<script language='javascript'
type='text/javascript' >" _
+ ControlChars.CrLf _
+ "<!--" _
+ ControlChars.CrLf _
+ "objSelect =
document.getElementById('" + Me.ID + "');" _
+ ControlChars.CrLf _
+ SUreader.ReadToEnd() _
+ ControlChars.CrLf _
+ "LLShowOption(objSelect, " +
Me.SelectedIndex.ToString + ");" _
+ ControlChars.CrLf _
+ "//-->" _
+ ControlChars.CrLf _
+ "</script>"
Page.RegisterStartupScript(Me.ID, SUscript) 'use the ID for the
key so that it isn't unique

'this script is client script and should appear only once
If Not
Page.IsClientScriptBlockRegistered("BetterListBoxClient_js") Then
Dim reader As New
System.IO.StreamReader(Me.GetType().Assembly.GetManifestResourceStream(Me.GetType(),
"BetterListBoxClient.js"))
Dim script As String = "<script language='javascript'
type='text/javascript' >" _
+ ControlChars.CrLf _
+ "<!--" _
+ ControlChars.CrLf _
+ reader.ReadToEnd() _
+ ControlChars.CrLf _
+ "//-->" _
+ ControlChars.CrLf _
+ "</script>"
Page.RegisterClientScriptBlock("BetterListBoxClient_js",
script)
End If
End Sub

Public Overrides Sub RenderBeginTag(ByVal writer As
System.Web.UI.HtmlTextWriter)

Dim tag1 As HtmlTextWriterTag = Me.TagKey
writer.AddAttribute("ID", "div_" & Me.ID)
writer.AddAttribute("name", "div_" & Me.ID)
writer.AddStyleAttribute("overflow", "scroll")
writer.AddStyleAttribute("border-width", "thin")
writer.AddStyleAttribute("border-style", "inset")
writer.AddStyleAttribute("width", Me.Width.ToString)
writer.AddStyleAttribute("height", Me.Height.ToString)
If Not MyBase.Style("TOP") Is Nothing Then
writer.AddStyleAttribute("TOP", MyBase.Style("TOP"))
End If
If Not MyBase.Style("LEFT") Is Nothing Then
writer.AddStyleAttribute("LEFT", MyBase.Style("LEFT"))
End If
If Not MyBase.Style("POSITION") Is Nothing Then
writer.AddStyleAttribute("POSITION",
MyBase.Style("POSITION"))
End If
If Not (Me.ContainerDivStyle Is Nothing) Then
Me.ContainerDivStyle.AddAttributesToRender(writer, Me)
End If
writer.RenderBeginTag(HtmlTextWriterTag.Div)
MyBase.Style.Remove("TOP")
MyBase.Style.Remove("LEFT")
MyBase.Style.Remove("POSITION")

MyBase.Attributes.Add("onchange", "LLShowOption(this,
this.selectedIndex)")
MyBase.Attributes.Add("onkeypress", "LLAlphaSearch(this)")

MyBase.AddAttributesToRender(writer)
If (tag1 <> HtmlTextWriterTag.Unknown) Then
writer.RenderBeginTag(tag1)
Else
writer.RenderBeginTag(Me.TagName)
End If

End Sub

Public Overrides Sub RenderEndTag(ByVal writer As
System.Web.UI.HtmlTextWriter)
writer.RenderEndTag()
writer.RenderEndTag()
End Sub

'Override the RenderContents method to fix the bug :)
Protected Overrides Sub RenderContents(ByVal writer As
System.Web.UI.HtmlTextWriter)

Dim myFlag1 As Boolean = False
Dim myFlag2 As Boolean = (Me.SelectionMode =
ListSelectionMode.Single)
Dim collection1 As ListItemCollection = Me.Items
Dim listItemsCount As Integer = collection1.Count
If (listItemsCount > 0) Then
For num2 As Integer = 0 To listItemsCount - 1
Dim item1 As ListItem = collection1.Item(num2)
writer.WriteBeginTag("option")
If item1.Selected Then
If myFlag2 Then
If myFlag1 Then
Throw New HttpException("A ListBox cannot
have multiple items selected when the SelectionMode is Single")
End If
myFlag1 = True
End If
writer.WriteAttribute("selected", "selected")
End If
writer.WriteAttribute("value", item1.Value, True)
'The line below is why the listbox never
'rendered any attributes you set for list items.
item1.Attributes.Render(writer) '<-- Missing line
writer.Write(">")
HttpUtility.HtmlEncode(item1.Text, writer)
writer.WriteEndTag("option")
writer.WriteLine()
Next num2
End If
End Sub

<Browsable(True),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
_
Public ReadOnly Property ContainerDivStyle() As Style
Get
If (Me._containerDivStyle Is Nothing) Then
Me._containerDivStyle = New Style
If IsTrackingViewState Then
CType(Me._containerDivStyle,
IStateManager).TrackViewState()
End If
End If
Return _containerDivStyle
End Get
End Property

<Browsable(True),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
_
Public Overrides Property Width() As Unit
Get
If _width.IsEmpty Then
_width = Unit.Pixel(100)
End If
Return CType(ViewState("Width"), Unit)
End Get
Set(ByVal Value As Unit)
ViewState("Width") = Value
End Set
End Property

<Browsable(True),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
_
Public Overrides Property Height() As Unit
Get
If _height.IsEmpty Then
_height = Unit.Pixel(100)
End If
Return CType(ViewState("Height"), Unit)
End Get
Set(ByVal Value As Unit)
ViewState("Height") = Value
End Set
End Property

Protected Overrides Sub LoadViewState(ByVal savedState As Object)
' Customized state management to handle saving state of
contained objects.
If Not (savedState Is Nothing) Then
Dim myState As Object() = CType(savedState, Object())
If Not (myState(0) Is Nothing) Then
MyBase.LoadViewState(myState(0))
End If
If Not (myState(1) Is Nothing) Then
CType(ContainerDivStyle,
IStateManager).LoadViewState(myState(1))
End If
End If
End Sub

Protected Overrides Function SaveViewState() As Object
' Customize state management to handle saving state of
contained objects such as styles.
Dim baseState As Object = MyBase.SaveViewState()
Dim ContainerDivStyleState As Object

If Not (Me._containerDivStyle Is Nothing) Then
ContainerDivStyleState = CType(Me._containerDivStyle,
IStateManager).SaveViewState()
Else
ContainerDivStyleState = Nothing
End If
Dim myState(1) As Object
myState(0) = baseState
myState(1) = ContainerDivStyleState
End Function

Protected Overrides Sub TrackViewState()
If Not (Me._containerDivStyle Is Nothing) Then
CType(Me._containerDivStyle,
IStateManager).TrackViewState()
End If
End Sub

Protected Overrides Sub OnSelectedIndexChanged(ByVal e As
System.EventArgs)
MyBase.SelectedIndex = Me.SelectedIndex
End Sub
End Class
 
L

lisa

I just wanted to add that I've checked the SaveViewState and
LoadViewState, and at every stage, the SelectedIndex of the custom
ListBox is -1, regardless of how it was set on the page. All the
properties are identical in the two controls, except for height and
width:

AutoPostBack = False
EnableViewState = True

Everything is the same, but the regular one maintains its state and the
custom one loses it. I know this must be something obvious that I'm
missing. I've looked at other custom List controls online
(http://www.eggheadcafe.com/articles/20030910.asp, for example), and I
can't see what they're doing that I'm not.

Thanks,
Lisa
 
L

lisa

Okay... I'm getting somewhere. I tried commenting out the overrides of
SaveViewState, LoadViewState and TrackViewState. Now when I post back,
I keep the contents of the custom ListBox. Which is good; that wasn't
happening before. I was losing everything.

But I'm still not retaining the SelectedIndex. Could this be (maybe)
because in the HTML, it's "selected" and not "SelectedIndex = n"?

Lisa
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,733
Messages
2,569,439
Members
44,829
Latest member
PIXThurman

Latest Threads

Top