Exposing ListItems in Composite Control

L

Lisa

I'm trying to build a composite control that consists of a select
inside a div (a listbox inside a panel). My problem is that when I
compile the control and drag it onto a webform, the Items collection
property that shows up in the Properties window of VS.net doesn't give
me Items for my control. What I wind up with when I put items in is
this:

<cc1:LLList id="LLList2" runat="server">
<asp:ListItem Value="test">test</asp:ListItem>
</cc1:LLList>

What I want to see is this:

<cc1:LLList id="LLList2" runat="server">
<cc1:Item Value="test">test</cc1:ListItem>
</cc1:LLList>

Here is the code for the control. I'm using PersistenceMode and such,
but it doesn't seem to be having any affect.

Imports System
Imports System.IO
Imports System.ComponentModel
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls

<ToolboxData("<{0}:LLList runat=server></{0}:LLList>")> _
Public Class LLList
Inherits System.Web.UI.WebControls.WebControl
Implements INamingContainer

Private _listbox As ListBox
Private _panel As Panel

Protected Overridable Sub Initialize()
Me.Width = WebControls.Unit.Pixel(100)
Me.Height = WebControls.Unit.Pixel(100)
End Sub

Public Overrides ReadOnly Property Controls() As ControlCollection
Get
EnsureChildControls()
Return MyBase.Controls
End Get
End Property

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

Protected Overridable Sub RegisterScript()
If Not Page.IsClientScriptBlockRegistered("LLList_js") Then
Dim reader As New
System.IO.StreamReader(Me.GetType().Assembly.GetManifestResourceStream(Me.GetType(),
"LLList.js"))
Dim script As String = "<script language='javascript'
type='text/javascript' >" + ControlChars.Cr + ControlChars.Lf + "<!--"
+ ControlChars.Cr + ControlChars.Lf + reader.ReadToEnd() +
ControlChars.Cr + ControlChars.Lf + "//-->" + ControlChars.Cr +
ControlChars.Lf + "</script>"
Page.RegisterClientScriptBlock("LLList_js", script)
End If
End Sub

#Region "Properties delegated to child controls"

<Bindable(True), Browsable(True), Category("Misc"),
PersistenceMode(PersistenceMode.InnerProperty),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(True)> _
Public ReadOnly Property Items() As ListItemCollection
Get
Me.EnsureChildControls()
Return _listbox.Items
End Get
End Property

#End Region

Protected Overrides Sub CreateChildControls()

Controls.Clear()

_listbox = New ListBox
_panel = New Panel

_listbox.ID = "ListBox"
_listbox.Attributes("onchange") = "LLShowOption(this,
this.selectedIndex)"
_listbox.Attributes("onkeypress") = "LLAlphaSearch(this)"

_panel.ID = "ParentDiv"
_panel.Controls.Add(_listbox)

_panel.Attributes("style") = "overflow:scroll;
border-width:thin; border-style:inset"
' _panel.Attributes("style") = "overflow:scroll;
width:" & Me.Width.ToString & "; height:" & Me.Height.ToString & ";
border-width:thin; border-style:inset"

Controls.Add(_panel)

End Sub

Protected Overrides Sub Render(ByVal output As
System.Web.UI.HtmlTextWriter)
Me.EnsureChildControls()
RenderContents(output)
End Sub

End Class

Am I doing something obviously wrong? And... if I want to expose
every property from the Listbox, do I have to expose them each
individually, or is there a way I can do them all globally? I would
have been happy to just inherit the Listbox and put the Panel/Div
around it, but apparently you can't do that.

Thanks,
Lisa
 
A

Alvin Bruney [MVP]

have you looked at steve orr's article in the aspnet pro magazine? it shows
you how to build composite controls so it may help. the article is available
on the aspnet pro website as well.
 
L

Lisa

Hi Alvin,

I can't find the article you're referring to. Also, I'm don't
subscribe to aspnet pro, and you have to be a subscriber to read most
of the articles on that site.

Is the question I asked that complicated? In a way, that makes me
feel better, knowing that it isn't just something dumb and obvious
that I missed. Are you saying that there isn't a simple way to make
the nested items in my control match the outer part of the control?

Lisa


Alvin Bruney said:
have you looked at steve orr's article in the aspnet pro magazine? it shows
you how to build composite controls so it may help. the article is available
on the aspnet pro website as well.

--
Regards,
Alvin Bruney
[ASP.NET MVP http://mvp.support.microsoft.com/default.aspx]
Got tidbits? Get it here... http://tinyurl.com/27cok
Lisa said:
Can anyone help out with this, please?

Thanks,
Lisa


(e-mail address removed) (Lisa) wrote in message
 
L

Lisa

Hi Alessandro,

Thanks for your help. I have a few questions:

* I put in the tag rendering overrides you suggested, and this is what
was rendered in the IDE on the HTML view after I added some items:

<cc1:LLList id="LLList3" style="Z-INDEX: 102; LEFT: 512px; POSITION:
absolute; TOP: 368px" runat="server">Abel</ASP:LISTITEM>
<ASP:LISTITEM Value="Baker">Baker</ASP:LISTITEM>
<ASP:LISTITEM Value="Charlie">Charlie</ASP:LISTITEM>
<ASP:LISTITEM Value="Delta">Delta</ASP:LISTITEM>
<ASP:LISTITEM Value="Echo">Echo</ASP:LISTITEM>
<ASP:LISTITEM Value="Foxtrot">Foxtrot</ASP:LISTITEM> </cc1:LLList>

The first <asp:listitem> tag is missing. I can't see why.

Also, you wrote that you wouldn't recommend doing it this way, even
though I won't have to expose each property in turn. Why not?

Here's the code that I have now:

Imports System
Imports System.IO
Imports System.ComponentModel
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls

<ToolboxData("<{0}:LLList runat=server></{0}:LLList>")> _
Public Class LLList
Inherits System.Web.UI.WebControls.ListBox
Implements INamingContainer

Protected Overridable Sub Initialize()
Me.Width = WebControls.Unit.Pixel(100)
Me.Height = WebControls.Unit.Pixel(100)
End Sub

Public Overrides ReadOnly Property Controls() As ControlCollection
Get
EnsureChildControls()
Return MyBase.Controls
End Get
End Property

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

Protected Overridable Sub RegisterScript()
If Not Page.IsClientScriptBlockRegistered("LLList_js") Then
Dim reader As New
System.IO.StreamReader(Me.GetType().Assembly.GetManifestResourceStream(Me.GetType(),
"LLList.js"))
Dim script As String = "<script language='javascript'
type='text/javascript' >" + ControlChars.Cr + ControlChars.Lf + "<!--"
+ ControlChars.Cr + ControlChars.Lf + reader.ReadToEnd() +
ControlChars.Cr + ControlChars.Lf + "//-->" + ControlChars.Cr +
ControlChars.Lf + "</script>"
Page.RegisterClientScriptBlock("LLList_js", script)
End If
End Sub

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

Me.AddAttributesToRender(writer)
Dim tag1 As HtmlTextWriterTag = Me.TagKey
writer.AddAttribute("onchange",
"LLShowOption(this,this.selectedIndex)")
writer.AddAttribute("onkeypress", "LLAlphaSearch(this)")
writer.AddStyleAttribute("overflow", "scroll")
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth,
"thin")
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle,
"inset")
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
writer.RenderBeginTag(HtmlTextWriterTag.Div)
MyBase.Style.Remove("TOP")
MyBase.Style.Remove("LEFT")
MyBase.Style.Remove("POSITION")
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

End Class


Thanks again,
Lisa

Alessandro Zifiglio said:
oops, auto correction. I should have tested my code before posting. To
achieve what you are doing currently from your example by inheriting
from a listbox control use the following code and ignore the code in the
RenderBeginTag() method from the previous post i made and use the
following :

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

Me.AddAttributesToRender(writer)
Dim tag1 As HtmlTextWriterTag = Me.TagKey
writer.AddAttribute("onchange",
"LLShowOption(this,this.selectedIndex)")
writer.AddAttribute("onkeypress", "LLAlphaSearch(this)")
writer.AddStyleAttribute("overflow", "scroll")
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth,
"thin")
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle,
"inset")
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

' panel.Attributes("style") = "overflow:scroll;width:" &
Me.Width.ToString & "; height:" & Me.Height.ToString &
";border-width:thin; border-style:inset"
writer.RenderBeginTag(HtmlTextWriterTag.Div)
MyBase.Style.Remove("TOP")
MyBase.Style.Remove("LEFT")
MyBase.Style.Remove("POSITION")
MyBase.AddAttributesToRender(writer)
If (tag1 <> HtmlTextWriterTag.Unknown) Then
writer.RenderBeginTag(tag1)
Else
writer.RenderBeginTag(Me.TagName)
End If
End Sub


Even if the above code works, I dont recommend going this route, even if
in this manner you have resolved the issue of "how to expose all the
listbox's properties" and now you have all the properties of the
listbox control available to you in the property grid ;p
Somehow I missed something here ? O o
I have also had time to test your code and it seems to work nicely,
except you wont get the properties of the listbox since you are not
inheriting from it.

Also what do you mean by the following --
-----------------------------------------------------------------------
<cc1:LLList id="LLList2" runat="server">
<asp:ListItem Value="test">test</asp:ListItem>
</cc1:LLList>

What I want to see is this:

<cc1:LLList id="LLList2" runat="server">
<cc1:Item Value="test">test</cc1:ListItem>
</cc1:LLList>
-------------------------------------------------------------------------
-

Currently in html view your code produced :

<cc1:WebCustomControl1 id=WebCustomControl1 style="Z-INDEX: 101;
LEFT: 227px; POSITION: absolute; TOP: 307px" runat="server">
<Items>
<asp:ListItem Value="hi">hi</asp:ListItem>
<asp:ListItem Value="hi">hi</asp:ListItem>
</Items>
</cc1:WebCustomControl1>

which is correct. Your items property is a ListItemCollection, and that
is the expected result ;p

Alessandro Zifiglio
http://www.dotnetbox.com

message Lisa, the way you have it currently, your going to have to expose each
property of the listbox exposed individually. For example if you are
bothered with the style attributes, you will have to expose a style
property and copy this style into the listboxes style later in the
render method. Also there are other steps needed here, like overriding
LoadViewState(), SaveViewState() etc, not to mention you are reinventing
the wheel when trying to expose each and every property for a control
that already exposes them, which we wont be getting into since you can
simply and easily inherit from the ListBox control and wrap it up in a
div. This is the correct approach to take because you are building a
composite control.

Now i think the reason you never took this approach initially is
because you were worried about how to wrap the listbox in a div when it
renders a select element in the first place. Well this is simpler than
you think it is :)

All you need to do is override the RenderBeginTag and RenderEndTags
respectively. Here is some sample code :

Imports System.ComponentModel
Imports System.Web.UI

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

Public Overrides Sub RenderBeginTag(ByVal writer As
System.Web.UI.HtmlTextWriter)
Me.AddAttributesToRender(writer)
Dim tag1 As HtmlTextWriterTag = Me.TagKey
If (tag1 <> HtmlTextWriterTag.Unknown) Then
writer.RenderBeginTag(HtmlTextWriterTag.Div)
writer.RenderBeginTag(tag1)
Else
writer.RenderBeginTag(HtmlTextWriterTag.Div)
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
End Class


As you will note from the above now you have your listbox wrapped
around div tags. If you simply drop this control on your page and set a
listItem "item1" and "item2" you will get a result like this :

<div size="4" name="WebCustomControl11" id="WebCustomControl11"
style="Z-INDEX: 101; LEFT: 223px; POSITION: absolute; TOP: 250px">
<select>
<option value="item1">item1</option>
<option value="Item2">Item2</option>

</select>
</div>

Have a nice day lisa. If your still have problems with the above, let
me know. I'll gladly help you, till you resolve :)

Alessandro Zifiglio
http://www.dotnetbox.com

Lisa said:
Hi Alvin,

I can't find the article you're referring to. Also, I'm don't
subscribe to aspnet pro, and you have to be a subscriber to read most
of the articles on that site.

Is the question I asked that complicated? In a way, that makes me
feel better, knowing that it isn't just something dumb and obvious
that I missed. Are you saying that there isn't a simple way to make
the nested items in my control match the outer part of the control?

Lisa


"Alvin Bruney [MVP]" <vapor at steaming post office> wrote in
message news: said:
have you looked at steve orr's article in the aspnet pro magazine? it shows
you how to build composite controls so it may help. the article is available
on the aspnet pro website as well.

--
Regards,
Alvin Bruney
[ASP.NET MVP http://mvp.support.microsoft.com/default.aspx]
Got tidbits? Get it here... http://tinyurl.com/27cok
Can anyone help out with this, please?

Thanks,
Lisa


(e-mail address removed) (Lisa) wrote in message
I'm trying to build a composite control that consists of a select
inside a div (a listbox inside a panel). My problem is that when I
compile the control and drag it onto a webform, the Items collection
property that shows up in the Properties window of VS.net doesn't give
me Items for my control. What I wind up with when I put items in is
this:

<cc1:LLList id="LLList2" runat="server">
<asp:ListItem Value="test">test</asp:ListItem>
</cc1:LLList>

What I want to see is this:

<cc1:LLList id="LLList2" runat="server">
<cc1:Item Value="test">test</cc1:ListItem>
</cc1:LLList>

Here is the code for the control. I'm using PersistenceMode and such,
but it doesn't seem to be having any affect.

Imports System
Imports System.IO
Imports System.ComponentModel
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls

<ToolboxData("<{0}:LLList runat=server></{0}:LLList>")>
Public Class LLList
Inherits System.Web.UI.WebControls.WebControl
Implements INamingContainer

Private listbox As ListBox
Private panel As Panel

Protected Overridable Sub Initialize()
Me.Width = WebControls.Unit.Pixel(100)
Me.Height = WebControls.Unit.Pixel(100)
End Sub

Public Overrides ReadOnly Property Controls() As ControlCollection
Get
EnsureChildControls()
Return MyBase.Controls
End Get
End Property

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

Protected Overridable Sub RegisterScript()
If Not Page.IsClientScriptBlockRegistered("LLList js") Then
Dim reader As New
System.IO.StreamReader(Me.GetType().Assembly.GetManifestResourceStream(Me
.GetType(),
"LLList.js"))
Dim script As String = "<script language='javascript'
type='text/javascript' >" + ControlChars.Cr + ControlChars.Lf + "<!--"
+ ControlChars.Cr + ControlChars.Lf + reader.ReadToEnd() +
ControlChars.Cr + ControlChars.Lf + "//-->" + ControlChars.Cr +
ControlChars.Lf + "</script>"
Page.RegisterClientScriptBlock("LLList js", script)
End If
End Sub

#Region "Properties delegated to child controls"

<Bindable(True), Browsable(True), Category("Misc"),
PersistenceMode(PersistenceMode.InnerProperty),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(True)>
Public ReadOnly Property Items() As ListItemCollection
Get
Me.EnsureChildControls()
Return listbox.Items
End Get
End Property

#End Region

Protected Overrides Sub CreateChildControls()

Controls.Clear()

listbox = New ListBox
panel = New Panel

listbox.ID = "ListBox"
listbox.Attributes("onchange") = "LLShowOption(this,
this.selectedIndex)"
listbox.Attributes("onkeypress") = "LLAlphaSearch(this)"

panel.ID = "ParentDiv"
panel.Controls.Add( listbox)

panel.Attributes("style") = "overflow:scroll;
border-width:thin; border-style:inset"
' panel.Attributes("style") = "overflow:scroll;
width:" & Me.Width.ToString & "; height:" & Me.Height.ToString & ";
border-width:thin; border-style:inset"

Controls.Add( panel)

End Sub

Protected Overrides Sub Render(ByVal output As
System.Web.UI.HtmlTextWriter)
Me.EnsureChildControls()
RenderContents(output)
End Sub

End Class

Am I doing something obviously wrong? And... if I want to expose
every property from the Listbox, do I have to expose them each
individually, or is there a way I can do them all globally? I would
have been happy to just inherit the Listbox and put the Panel/Div
around it, but apparently you can't do that.

Thanks,
Lisa
--
 
A

Alessandro Zifiglio

Lisa, can you try compiling again. I ran the code again and nothing seems to
be missing. Sometimes you might need to remove all references to the
component and add it again to the toolbox. I'm reposting the complete code I
have with additional changes since you had asked why I did not recommend
going this route.

The reason is because when you override the renderbegintag and
renderendtag, it is there for you to change the begining and closing tags
obviously but since we are inheriting from the listbox control, and our
calling the base (listbox) classes renderbeingtag and renderendtag
respectively, our outer div wont get the style attributes, which is why i
had to work around adding the aboslute positioning to the outerdiv. In
effect our outer div does not get any of the style attributes, and we dont
want these anyway since most of the attributes are specific to the
<select.../> element.

I put in a few minutes and I have added a new style property to the control
and supplied that to the outer div, which is working out nicely, so I guess
its not as bad as I had thought initially, even though I had to override
some additional methods like LoadViewState, SaveViewState and TrackViewState
methods. This is necessary since the style property i had exposed is a
complex type and you have to manually Load/Save/Track viewstate for this
property. Now you can supply css style, including the height, width, border,
background color etc from the propertygrid in vs.net and it will all get
rendered in the outer div ;p

Also do not forget that there is a bug in the listbox control, and that if
you tried to add attributes to the listbox contros items collection it wont
output it. I have added this fix as well. Since you are already creating a
custom control and deriving from the listbox, you might as well fix this bug
:)

Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web

<DefaultProperty("Text"), _
ToolboxData("<{0}:WebCustomControl1 runat=server></{0}:WebCustomControl1>")>
_
Public Class WebCustomControl1
Inherits System.Web.UI.WebControls.ListBox
Private _containerDivStyle As Style
Public Overrides Sub RenderBeginTag(ByVal writer As
System.Web.UI.HtmlTextWriter)

Me.AddAttributesToRender(writer)
Dim tag1 As HtmlTextWriterTag = Me.TagKey
writer.AddAttribute("onchange",
"LLShowOption(this,this.selectedIndex)")
writer.AddAttribute("onkeypress", "LLAlphaSearch(this)")
writer.AddStyleAttribute("overflow", "scroll")
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.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


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

End Class

Regards,
Alessandro Zifiglio
http://www.dotnetbox.com (The custom web control that lets you drag and
resize content on your pages like as if you were in Visual Studio.NET
designer.)




Lisa said:
Hi Alessandro,

Thanks for your help. I have a few questions:

* I put in the tag rendering overrides you suggested, and this is what
was rendered in the IDE on the HTML view after I added some items:

<cc1:LLList id="LLList3" style="Z-INDEX: 102; LEFT: 512px; POSITION:
absolute; TOP: 368px" runat="server">Abel</ASP:LISTITEM>
<ASP:LISTITEM Value="Baker">Baker</ASP:LISTITEM>
<ASP:LISTITEM Value="Charlie">Charlie</ASP:LISTITEM>
<ASP:LISTITEM Value="Delta">Delta</ASP:LISTITEM>
<ASP:LISTITEM Value="Echo">Echo</ASP:LISTITEM>
<ASP:LISTITEM Value="Foxtrot">Foxtrot</ASP:LISTITEM> </cc1:LLList>

The first <asp:listitem> tag is missing. I can't see why.

Also, you wrote that you wouldn't recommend doing it this way, even
though I won't have to expose each property in turn. Why not?

Here's the code that I have now:

Imports System
Imports System.IO
Imports System.ComponentModel
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls

<ToolboxData("<{0}:LLList runat=server></{0}:LLList>")> _
Public Class LLList
Inherits System.Web.UI.WebControls.ListBox
Implements INamingContainer

Protected Overridable Sub Initialize()
Me.Width = WebControls.Unit.Pixel(100)
Me.Height = WebControls.Unit.Pixel(100)
End Sub

Public Overrides ReadOnly Property Controls() As ControlCollection
Get
EnsureChildControls()
Return MyBase.Controls
End Get
End Property

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

Protected Overridable Sub RegisterScript()
If Not Page.IsClientScriptBlockRegistered("LLList_js") Then
Dim reader As New
System.IO.StreamReader(Me.GetType().Assembly.GetManifestResourceStream(Me.Ge
tType(),
"LLList.js"))
Dim script As String = "<script language='javascript'
type='text/javascript' >" + ControlChars.Cr + ControlChars.Lf + "<!--"
+ ControlChars.Cr + ControlChars.Lf + reader.ReadToEnd() +
ControlChars.Cr + ControlChars.Lf + "//-->" + ControlChars.Cr +
ControlChars.Lf + "</script>"
Page.RegisterClientScriptBlock("LLList_js", script)
End If
End Sub

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

Me.AddAttributesToRender(writer)
Dim tag1 As HtmlTextWriterTag = Me.TagKey
writer.AddAttribute("onchange",
"LLShowOption(this,this.selectedIndex)")
writer.AddAttribute("onkeypress", "LLAlphaSearch(this)")
writer.AddStyleAttribute("overflow", "scroll")
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth,
"thin")
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle,
"inset")
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
writer.RenderBeginTag(HtmlTextWriterTag.Div)
MyBase.Style.Remove("TOP")
MyBase.Style.Remove("LEFT")
MyBase.Style.Remove("POSITION")
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

End Class


Thanks again,
Lisa

"Alessandro Zifiglio" <[email protected]> wrote in
message news: said:
oops, auto correction. I should have tested my code before posting. To
achieve what you are doing currently from your example by inheriting
from a listbox control use the following code and ignore the code in the
RenderBeginTag() method from the previous post i made and use the
following :

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

Me.AddAttributesToRender(writer)
Dim tag1 As HtmlTextWriterTag = Me.TagKey
writer.AddAttribute("onchange",
"LLShowOption(this,this.selectedIndex)")
writer.AddAttribute("onkeypress", "LLAlphaSearch(this)")
writer.AddStyleAttribute("overflow", "scroll")
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth,
"thin")
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle,
"inset")
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

' panel.Attributes("style") = "overflow:scroll;width:" &
Me.Width.ToString & "; height:" & Me.Height.ToString &
";border-width:thin; border-style:inset"
writer.RenderBeginTag(HtmlTextWriterTag.Div)
MyBase.Style.Remove("TOP")
MyBase.Style.Remove("LEFT")
MyBase.Style.Remove("POSITION")
MyBase.AddAttributesToRender(writer)
If (tag1 <> HtmlTextWriterTag.Unknown) Then
writer.RenderBeginTag(tag1)
Else
writer.RenderBeginTag(Me.TagName)
End If
End Sub


Even if the above code works, I dont recommend going this route, even if
in this manner you have resolved the issue of "how to expose all the
listbox's properties" and now you have all the properties of the
listbox control available to you in the property grid ;p
Somehow I missed something here ? O o
I have also had time to test your code and it seems to work nicely,
except you wont get the properties of the listbox since you are not
inheriting from it.

Also what do you mean by the following --
-----------------------------------------------------------------------
<cc1:LLList id="LLList2" runat="server">
<asp:ListItem Value="test">test</asp:ListItem>
</cc1:LLList>

What I want to see is this:

<cc1:LLList id="LLList2" runat="server">
<cc1:Item Value="test">test</cc1:ListItem>
</cc1:LLList>
-------------------------------------------------------------------------
-

Currently in html view your code produced :

<cc1:WebCustomControl1 id=WebCustomControl1 style="Z-INDEX: 101;
LEFT: 227px; POSITION: absolute; TOP: 307px" runat="server">
<Items>
<asp:ListItem Value="hi">hi</asp:ListItem>
<asp:ListItem Value="hi">hi</asp:ListItem>
</Items>
</cc1:WebCustomControl1>

which is correct. Your items property is a ListItemCollection, and that
is the expected result ;p

Alessandro Zifiglio
http://www.dotnetbox.com

message Lisa, the way you have it currently, your going to have to expose each
property of the listbox exposed individually. For example if you are
bothered with the style attributes, you will have to expose a style
property and copy this style into the listboxes style later in the
render method. Also there are other steps needed here, like overriding
LoadViewState(), SaveViewState() etc, not to mention you are reinventing
the wheel when trying to expose each and every property for a control
that already exposes them, which we wont be getting into since you can
simply and easily inherit from the ListBox control and wrap it up in a
div. This is the correct approach to take because you are building a
composite control.

Now i think the reason you never took this approach initially is
because you were worried about how to wrap the listbox in a div when it
renders a select element in the first place. Well this is simpler than
you think it is :)

All you need to do is override the RenderBeginTag and RenderEndTags
respectively. Here is some sample code :

Imports System.ComponentModel
Imports System.Web.UI

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

Public Overrides Sub RenderBeginTag(ByVal writer As
System.Web.UI.HtmlTextWriter)
Me.AddAttributesToRender(writer)
Dim tag1 As HtmlTextWriterTag = Me.TagKey
If (tag1 <> HtmlTextWriterTag.Unknown) Then
writer.RenderBeginTag(HtmlTextWriterTag.Div)
writer.RenderBeginTag(tag1)
Else
writer.RenderBeginTag(HtmlTextWriterTag.Div)
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
End Class


As you will note from the above now you have your listbox wrapped
around div tags. If you simply drop this control on your page and set a
listItem "item1" and "item2" you will get a result like this :

<div size="4" name="WebCustomControl11" id="WebCustomControl11"
style="Z-INDEX: 101; LEFT: 223px; POSITION: absolute; TOP: 250px">
<select>
<option value="item1">item1</option>
<option value="Item2">Item2</option>

</select>
</div>

Have a nice day lisa. If your still have problems with the above, let
me know. I'll gladly help you, till you resolve :)

Alessandro Zifiglio
http://www.dotnetbox.com

Lisa said:
Hi Alvin,

I can't find the article you're referring to. Also, I'm don't
subscribe to aspnet pro, and you have to be a subscriber to read most
of the articles on that site.

Is the question I asked that complicated? In a way, that makes me
feel better, knowing that it isn't just something dumb and obvious
that I missed. Are you saying that there isn't a simple way to make
the nested items in my control match the outer part of the control?

Lisa


"Alvin Bruney [MVP]" <vapor at steaming post office> wrote in
message news: said:
have you looked at steve orr's article in the aspnet pro magazine? it shows
you how to build composite controls so it may help. the article is available
on the aspnet pro website as well.

--
Regards,
Alvin Bruney
[ASP.NET MVP http://mvp.support.microsoft.com/default.aspx]
Got tidbits? Get it here... http://tinyurl.com/27cok
Can anyone help out with this, please?

Thanks,
Lisa


(e-mail address removed) (Lisa) wrote in message
I'm trying to build a composite control that consists of a select
inside a div (a listbox inside a panel). My problem is that when I
compile the control and drag it onto a webform, the Items collection
property that shows up in the Properties window of VS.net doesn't give
me Items for my control. What I wind up with when I put items in is
this:

<cc1:LLList id="LLList2" runat="server">
<asp:ListItem Value="test">test</asp:ListItem>
</cc1:LLList>

What I want to see is this:

<cc1:LLList id="LLList2" runat="server">
<cc1:Item Value="test">test</cc1:ListItem>
</cc1:LLList>

Here is the code for the control. I'm using PersistenceMode and such,
but it doesn't seem to be having any affect.

Imports System
Imports System.IO
Imports System.ComponentModel
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls

<ToolboxData("<{0}:LLList runat=server></{0}:LLList>")>
Public Class LLList
Inherits System.Web.UI.WebControls.WebControl
Implements INamingContainer

Private listbox As ListBox
Private panel As Panel

Protected Overridable Sub Initialize()
Me.Width = WebControls.Unit.Pixel(100)
Me.Height = WebControls.Unit.Pixel(100)
End Sub

Public Overrides ReadOnly Property Controls() As ControlCollection
Get
EnsureChildControls()
Return MyBase.Controls
End Get
End Property

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

Protected Overridable Sub RegisterScript()
If Not Page.IsClientScriptBlockRegistered("LLList js") Then
Dim reader As New
System.IO.StreamReader(Me.GetType().Assembly.GetManifestResourceStream(Me
.GetType(),
"LLList.js"))
Dim script As String = "<script language='javascript'
type='text/javascript' >" + ControlChars.Cr + ControlChars.Lf + "<!--"
+ ControlChars.Cr + ControlChars.Lf + reader.ReadToEnd() +
ControlChars.Cr + ControlChars.Lf + "//-->" + ControlChars.Cr +
ControlChars.Lf + "</script>"
Page.RegisterClientScriptBlock("LLList js", script)
End If
End Sub

#Region "Properties delegated to child controls"

<Bindable(True), Browsable(True), Category("Misc"),
PersistenceMode(PersistenceMode.InnerProperty),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(True)>
Public ReadOnly Property Items() As ListItemCollection
Get
Me.EnsureChildControls()
Return listbox.Items
End Get
End Property

#End Region

Protected Overrides Sub CreateChildControls()

Controls.Clear()

listbox = New ListBox
panel = New Panel

listbox.ID = "ListBox"
listbox.Attributes("onchange") = "LLShowOption(this,
this.selectedIndex)"
listbox.Attributes("onkeypress") = "LLAlphaSearch(this)"

panel.ID = "ParentDiv"
panel.Controls.Add( listbox)

panel.Attributes("style") = "overflow:scroll;
border-width:thin; border-style:inset"
' panel.Attributes("style") = "overflow:scroll;
width:" & Me.Width.ToString & "; height:" & Me.Height.ToString & ";
border-width:thin; border-style:inset"

Controls.Add( panel)

End Sub

Protected Overrides Sub Render(ByVal output As
System.Web.UI.HtmlTextWriter)
Me.EnsureChildControls()
RenderContents(output)
End Sub

End Class

Am I doing something obviously wrong? And... if I want to expose
every property from the Listbox, do I have to expose them each
individually, or is there a way I can do them all globally? I would
have been happy to just inherit the Listbox and put the Panel/Div
around it, but apparently you can't do that.

Thanks,
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
474,039
Messages
2,570,376
Members
47,029
Latest member
EmiliaSton

Latest Threads

Top