Link to home
Start Free TrialLog in
Avatar of ridgeway
ridgewayFlag for United States of America

asked on

VB.Net Event Bubbling with Composite Templated Databound Web Controls

Hi,
I am creating a web control that is comprised of a several controls, one of which includes a databound DataList control.  During the databinding process of the DataList control, additional controls are added to the DataList.  The DataList control utilizes the .ItemTemplate property to assign a template to each row in the DataList.  I am having problems bubbling the events to the parent control.

I have created a simplified version of my scenario to illustrate the issue (below).  
In the example the custom control's class is called "MyCustomControl" and the class that handles the item template is called "TemplateDataListItems".  The "MyEdit" event is never fired.  I'm thinking the issue has something to do with bubbling events from the template class to the control class otherwise the example is very similar to the MSDN developer guide's artical titled "Event Bubbling Control Sample".

Any help on the issue would be greatly appreciated.

Thanks,
Ben


Public Class MyCustomControl
  Inherits System.Web.UI.WebControls.WebControl
  Implements INamingContainer

  Private WithEvents _MyDataList As DataList

  Public Event MyEdit As EventHandler

  Protected Overridable Sub OnMyEdit(ByVal sender As Object, ByVal e As EventArgs)
    RaiseEvent MyEdit(sender, e)
  End Sub

  Protected Overrides Function OnBubbleEvent(ByVal sender As Object, ByVal e As EventArgs) As Boolean
    Dim handled As Boolean = False
    If TypeOf e Is CommandEventArgs Then
      Dim ce As CommandEventArgs = CType(e, CommandEventArgs)
      If (ce.CommandName = "Edit") Then
        OnMyEdit(sender, ce)
        handled = True
      End If
    End If
    Return handled
  End Function


  Protected Overrides Sub CreateChildControls()
    _MyDataList = New DataList
    _MyDataList.ID = Me.UniqueID + "_MyDataList"
    _MyDataList.ItemTemplate = new TemplateDataListItems
    Controls.Add(_MyDataList)
  End Sub


  Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
    'bind to data
    Dim MyDataSet As DataSet = '<Get Data here>
    _MyDataList.DataSource = MyDataSet
    _MyDataList.DataBind()
  End Sub


  Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
    _MyDataList.RenderControl(writer)
  End Sub


  Public Sub ColumnEditClick(ByVal sender As Object, ByVal e As ImageClickEventArgs)
    Page.Trace.write("ColumnEditClick: " + sender.GetType.ToString)
  End Sub


  Public Sub ColumnInfoClick(ByVal sender As Object, ByVal e As ImageClickEventArgs)
    Page.Trace.write("ColumnInfoClick: " + sender.GetType.ToString)
  End Sub


End Class




Public Class TemplateDataListItems
  Implements ITemplate

  Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn
    Dim ibEdit As New ImageButton
    AddHandler ibEdit.DataBinding, AddressOf BindEdit
    container.Controls.Add(ibEdit)
  End Sub


  Private Sub BindEdit(ByVal sender As Object, ByVal e As EventArgs)
    Dim l As ImageButton = sender
    Dim container As DataListItem = l.NamingContainer()
    l.ID = "ibEdit"
    l.CommandName = "Edit"
    '<Column From Bound Dataset>
    l.CommandArgument = container.DataItem("MyColumnName")
    l.ImageUrl = "/images/myeditbutton.gif"
  End Sub

End Class

Avatar of raterus
raterus
Flag of United States of America image

Ahh, finally a "non-newbie" question: (cracks knuckles)

Correct me if I'm missing it, or you just forgot to show it in this code example, but I never see where you are adding an event handler to the imagebutton after it has been created in the datalist.  For example, I would imagine you would need something like this in your custom control:

    Private Sub _MyDataList_ItemCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataListItemEventArgs) Handles _MyDataList.ItemDataBound
        If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem Then
            Dim ib As ImageButton = DirectCast(e.Item.Controls(0), ImageButton)
            AddHandler ib.Command, AddressOf SomeEvent
        End If
    End Sub

--Michael
Avatar of ridgeway

ASKER

Ahh, finally someone that really knows what they are talking about!
I did forget to handle the DataList's ItemDataBound event.

I am however, now having issues with accessing the control.  Since the ibEdit control is not actually the only one I am attempting to access it using the FindControl Method rather than using e.Item.Controls(X) for clarity sake.  When my code executes I get the "Object reference not set to an instance of an object", I did a trace and found that the e.Item.Controls.Count = 0 at this point.  Any ideas?

    If (e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem) Then
      Dim ibEdit As ImageButton = DirectCast(e.Item.FindControl("ibEdit"), ImageButton)
      AddHandler ibEdit.Command, AddressOf OnColumnEdit
    End if

Thanks,
Ben
Yeah most of the "experts" on this site are good at helping the newbies but crack on the advanced discussions.  Whenever I have a real question I post it over on asp.net/forums or the microsoft newsgroups.  The MVP's hang out there, not here...

--

Interesting I don't have that problem with the sample you first posted, I could access the itemtemplate/controls within it without a problem when the DataList fired ItemDataBound/ItemCreated.  Perhaps some inconsistencies between your simplified version and what you really have?  The only thing I can guess is you aren't adding the template to the container in InstantiateIn().

--Michael
The code will not throw any errors if I access the controls by index:
    e.Item.Controls(3)
however if I try by ID such as:
    e.Item.FindControl("ibEdit")
then I get an error.  For now I have my controls set up to be accessed by index and can deal with that issue later.  

Now that I have added the code in ItemsCreated I still can not get the events to fire.  

Any more ideas?
Oh, wait.  I just realized that I am adding handlers for the Command event and not the Click event.  When I click on the Image Button should it raise both the Click & Command event or just the Click event?  Do I need to figure out a way to bubble the click event to the Command event of the DataList?
I can get the ItemCommand of the DataList to fire for the ImageButton, have you tried handling that?.  One thing I can't seem to do is get my own custom event handler to fire for the ImageButtons (Don't know why, I added a handler correctly)  
OK, I have set aside the actual problem and have created a working example but I still can not get the events to fire.  

Do you see what I am doing wrong.  Does your working example look like this one?

I now have a test.aspx page:

<form id="Form1" method="post" runat="server">
  <cc1:MyCustomControl id="MyCustomControl1" runat="server" TableName="test"></cc1:MyCustomControl>
</form>

And a MyCustomControl.vb:

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

Public Class MyCustomControl
  Inherits System.Web.UI.WebControls.WebControl
  Implements INamingContainer

  Private WithEvents _MyDataList As DataList

  Public Event MyEdit As EventHandler

  Protected Overridable Sub OnMyEdit(ByVal sender As Object, ByVal e As CommandEventArgs) ' EventArgs)
    Page.Trace.Write("OnMyEdit called")
    RaiseEvent MyEdit(sender, e)
  End Sub

  Protected Overrides Function OnBubbleEvent(ByVal sender As Object, ByVal e As EventArgs) As Boolean
    Page.Trace.Write("OnBubbleEvent called")
    Dim handled As Boolean = False
    If TypeOf e Is CommandEventArgs Then
      Dim ce As CommandEventArgs = CType(e, CommandEventArgs)
      If (ce.CommandName = "Edit") Then
        OnMyEdit(sender, ce)
        handled = True
      End If
    End If
    Return handled
  End Function


  Protected Overrides Sub CreateChildControls()
    _MyDataList = New DataList
    _MyDataList.ID = Me.UniqueID + "_MyDataList"
    _MyDataList.ItemTemplate = New TemplateDataListItems
    Controls.Add(_MyDataList)
  End Sub


  Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
    'bind to data
    Dim MyDataSet As DataSet = getSomeData
    _MyDataList.DataSource = MyDataSet
    _MyDataList.DataBind()
  End Sub


  Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
    _MyDataList.RenderControl(writer)
  End Sub


  Private ReadOnly Property getSomeData() As DataSet
    Get
      Dim ds As New DataSet
      ds.Tables.Add("Table1")
      ds.Tables(0).Columns.Add("MyColName")
      For i As Integer = 0 To 4
        Dim dr As DataRow = ds.Tables(0).NewRow
        dr.Item("MyColName") = "Item " + i.ToString
        ds.Tables(0).Rows.Add(dr)
      Next
      Return ds
    End Get
  End Property

  Private Sub _MyDataList_ItemCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataListItemEventArgs) Handles _MyDataList.ItemCreated
    If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem Then
      Dim ib As ImageButton = DirectCast(e.Item.Controls(0), ImageButton)
      AddHandler ib.Command, AddressOf OnMyEdit
    End If
  End Sub

  'Private Sub _MyDataList_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles _MyDataList.ItemCommand

  'End Sub
End Class




Public Class TemplateDataListItems
  Implements ITemplate

  Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn
    Dim ibEdit As New ImageButton
    AddHandler ibEdit.DataBinding, AddressOf BindEdit
    container.Controls.Add(ibEdit)
  End Sub


  Private Sub BindEdit(ByVal sender As Object, ByVal e As EventArgs)
    Dim l As ImageButton = sender
    Dim container As DataListItem = l.NamingContainer()
    l.ID = "ibEdit"
    l.CommandName = "Edit"
    l.CommandArgument = container.DataItem("MyColName")
    l.ImageUrl = "/noimage.gif"
  End Sub

End Class
In the example I am trying to get the custom handler working.  How did you get the ItemCommand of the DataList to fire for the ImageButton?  That may be a more intuitive approach because I can capture and call custom events from there.
ASKER CERTIFIED SOLUTION
Avatar of raterus
raterus
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks for your help!  I learned a lot creating my first databound control.  

I got my own events to fire by raising them from the ItemCommand of the DataList.  I was also able to move all the logic for the CommandName and CommandArgument settings into the databinding statements so that there is no longer a need to use the ItemCreated event or to reference the controls by index.  I added an extra button to the example to better illustrate how to fire custom events based on the CommandName.  The only thing that I need to figure out is how to get the Postback / viewstate working properly so that we don't have to load data every time.

------------------------------------
-- Test Page (aspx)
<form id="Form1" method="post" runat="server">
  <asp:Label id="Label1" runat="server">Message</asp:Label>
  <cc1:MyCustomControl id="MyCustomControl1" runat="server"
          TableName="test"></cc1:MyCustomControl>
</form>

------------------------------------
-- Test Page (.vb)
Public Class test
    Inherits System.Web.UI.Page

    Private Sub MyCustomControl1_EditClicked(ByVal ArgumentData As String) _
      Handles MyCustomControl1.EditClicked
      Me.Label1.Text = "Edit Clicked: " + ArgumentData
    End Sub

    Private Sub MyCustomControl1_DeleteClicked(ByVal ArgumentData As String) _
      Handles MyCustomControl1.DeleteClicked
      Me.Label1.Text = "Delete Clicked: " + ArgumentData
    End Sub
End Class

------------------------------------
-- Custom Control Class


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

Public Class MyCustomControl
  Inherits System.Web.UI.WebControls.WebControl
  Implements INamingContainer

  Private WithEvents _MyDataList As DataList

  'Custom Events that MyCustomControl Raises
  Public Event EditClicked(ByVal ArgumentData As String)
  Public Event DeleteClicked(ByVal ArgumentData As String)

  Public Enum CommandNames As Integer
    Edit
    Delete
  End Enum

  Public Property DataSource() As Object
    Get
      Return _MyDataList.DataSource
    End Get
    Set(ByVal Value As Object)
      _MyDataList.DataSource = Value
    End Set
  End Property

  Public Overrides Sub DataBind()
    If Not DataSource Is Nothing Then
      _MyDataList.DataBind()
    End If
  End Sub

  Protected Overrides Sub CreateChildControls()
    _MyDataList = New DataList
    _MyDataList.ID = Me.UniqueID + "_MyDataList"
    _MyDataList.ItemTemplate = New TemplateDataListItems
    Controls.Add(_MyDataList)
  End Sub

  Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
    MyBase.OnInit(e)

    EnsureChildControls()

    '(Normally handled in Page - here to simplify)
    'If Not Page.IsPostBack Then
    Me.DataSource = getADataSet
    Me.DataBind()
    'End If

  End Sub


  Private Sub _MyDataList_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles _MyDataList.ItemCommand
    Page.Trace.Write("ItemCommand fired")
    Page.Trace.Write("Source Type=" + source.GetType.ToString)
    Page.Trace.Write("CommandName=" + e.CommandName)
    Select Case e.CommandName
      Case Me.CommandNames.Edit.ToString
        RaiseEvent EditClicked(e.CommandArgument)
      Case Me.CommandNames.Delete.ToString
        RaiseEvent DeleteClicked(e.CommandArgument)
    End Select
  End Sub


  'Used for an example dataset (normally obtained externally)
  Private ReadOnly Property getADataSet() As DataSet
    Get
      Page.Trace.Write("getADataSet called")
      Dim ds As New DataSet
      ds.Tables.Add("Table1")
      ds.Tables(0).Columns.Add("ArgumentData")
      ds.Tables(0).Columns.Add("AlternateTextData")
      For i As Integer = 0 To 2
        Dim dr As DataRow = ds.Tables(0).NewRow
        dr.Item("ArgumentData") = "Item " + i.ToString
        dr.Item("AlternateTextData") = "This is Item " + i.ToString
        ds.Tables(0).Rows.Add(dr)
      Next
      Return ds
    End Get
  End Property

End Class




Public Class TemplateDataListItems
  Implements ITemplate

  Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn
    Dim ibEdit As New ImageButton
    AddHandler ibEdit.DataBinding, AddressOf BindEdit
    container.Controls.Add(ibEdit)

    Dim ibDelete As New ImageButton
    AddHandler ibDelete.DataBinding, AddressOf BindDelete
    container.Controls.Add(ibDelete)

  End Sub

  Private Sub BindEdit(ByVal sender As Object, ByVal e As EventArgs)
    Dim l As ImageButton = sender
    Dim container As DataListItem = l.NamingContainer()
    l.ID = "ibEdit"
    l.CommandName = MyCustomControl.CommandNames.Edit.ToString
    l.AlternateText = "Edit (" + container.DataItem("AlternateTextData") + ")"
    l.CommandArgument = container.DataItem("ArgumentData")
    l.ImageUrl = "/noimage.gif"
  End Sub

  Private Sub BindDelete(ByVal sender As Object, ByVal e As EventArgs)
    Dim l As ImageButton = sender
    Dim container As DataListItem = l.NamingContainer()
    l.ID = "ibDelete"
    l.CommandName = MyCustomControl.CommandNames.Delete.ToString
    l.AlternateText = "Delete (" + container.DataItem("AlternateTextData") + ")"
    l.CommandArgument = container.DataItem("ArgumentData")
    l.ImageUrl = "/noimage.gif"
  End Sub

End Class
"The only thing that I need to figure out is how to get the Postback / viewstate working properly so that we don't have to load data every time"
Why do you post another question on this, be glad to help...
I was thinking it was a little out of scope for this thread.  I'll post one if I need some help.  Again, appreciate the help & learned a lot.
Sure thing, no problem, I always enjoy a good challenge.