?
Solved

VB.Net Event Bubbling with Composite Templated Databound Web Controls

Posted on 2005-03-31
13
Medium Priority
?
668 Views
Last Modified: 2008-02-26
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

0
Comment
Question by:ridgeway
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 7
  • 6
13 Comments
 
LVL 33

Expert Comment

by:raterus
ID: 13673601
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
0
 
LVL 1

Author Comment

by:ridgeway
ID: 13674430
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
0
 
LVL 33

Expert Comment

by:raterus
ID: 13674615
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
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 1

Author Comment

by:ridgeway
ID: 13675101
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?
0
 
LVL 1

Author Comment

by:ridgeway
ID: 13675363
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?
0
 
LVL 33

Expert Comment

by:raterus
ID: 13675489
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)  
0
 
LVL 1

Author Comment

by:ridgeway
ID: 13676840
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
0
 
LVL 1

Author Comment

by:ridgeway
ID: 13677006
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.
0
 
LVL 33

Accepted Solution

by:
raterus earned 2000 total points
ID: 13680735
Here's what I have, and ItemCommand is fired in the DataList.  I can't get my own custom event handler to fire, but I almost think that may be by design in the DataList, it routes all events of subcontrols through ItemCommand.  As you'll see, I made some changes, namely adding a "DataSource" property, and moving the actual Databinding process to DataBind, not pre_render.  I also made a fake datasource in OnInit  This should work though...

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

    Private WithEvents _MyDataList As DataList

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

    Protected Overrides Sub CreateChildControls()
        Page.Trace.Write("Loading ChildControls")

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

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

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

        EnsureChildControls()

        'this would be handled in the aspx page, but i'm doing it here for simplicity
        If Not Page.IsPostBack Then
            Dim str As String() = {"1", "2", "3"}
            Me.DataSource = str
        End If
    End Sub

    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 s As String = CStr(e.Item.DataItem)
            Dim ib As ImageButton = DirectCast(e.Item.Controls(0), ImageButton)
            ib.CommandArgument = s
            ib.CommandName = s
        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("Fired 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
        ibEdit.ID = "ibEdit"
        ibEdit.CommandName = "Edit"
        ibEdit.CommandArgument = "Something"
        ibEdit.ImageUrl = "/images/delete.gif"
        container.Controls.Add(ibEdit)
    End Sub
End Class
0
 
LVL 1

Author Comment

by:ridgeway
ID: 13682480
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
0
 
LVL 33

Expert Comment

by:raterus
ID: 13682533
"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...
0
 
LVL 1

Author Comment

by:ridgeway
ID: 13682597
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.
0
 
LVL 33

Expert Comment

by:raterus
ID: 13682625
Sure thing, no problem, I always enjoy a good challenge.
0

Featured Post

Enroll in August's Course of the Month

August's CompTIA IT Fundamentals course includes 19 hours of basic computer principle modules and prepares you for the certification exam. It's free for Premium Members, Team Accounts, and Qualified Experts!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Just a quick little trick I learned recently.  Now that I'm using jQuery with abandon in my asp.net applications, I have grown tired of the following syntax:      (CODE) I suppose it just offends my sense of decency to put inline VBScript on a…
Introduction This article shows how to use the open source plupload control to upload multiple images. The images are resized on the client side before uploading and the upload is done in chunks. Background I had to provide a way for user…
NetCrunch network monitor is a highly extensive platform for network monitoring and alert generation. In this video you'll see a live demo of NetCrunch with most notable features explained in a walk-through manner. You'll also get to know the philos…
In this video, Percona Director of Solution Engineering Jon Tobin discusses the function and features of Percona Server for MongoDB. How Percona can help Percona can help you determine if Percona Server for MongoDB is the right solution for …
Suggested Courses

770 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question