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
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