Writing Event Handler for controls in a DataRepeaterItem

In the MSDN Visual Basic Power Packs forum, I had an embarrassing reply on a DataRepeater event handling question, in which the user asked why his event handling code did not work. Here is the original question:

“I want to be able to resize a textbox when a user clicks on a textbox within a datarepeater. So the textbox will start out as a single line, but would expand to be multiline to better allow the user to enter text. When the user's mouse leaves the textbox I want it to return to the original size. I tried changing the textbox.height, but nothing happens. Any help is appreciated.”

By looking at the question, I guessed that the user might forget to set the TextBox.MultiLine to true while trying to change the height. Without hesitation, I replied a sample code as below:

    Private Sub TextBox1_MouseEnter(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.MouseEnter

        Me.TextBox1.Multiline = True

        Me.TextBox1.Height = 50

    End Sub

    Private Sub TextBox1_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.MouseLeave

        Me.TextBox1.Multiline = False

    End Sub 

                                                                  Code Sample 1

The user then replied back that the Multiline was not the problem. My code did not work! What is happening?

The DataRepeater control is very intuitive to use and is close to the WYSIWYG experience. However, we need to remember that at run time, the repeater items are cloned using the repeater item template. At design time, we are working on the repeater item template. At run time, several sets of new controls are created to serve as the display of the visible repeater items. Public properties of a control are copied during the cloning process, so do the event handlers. If Button1 is a button in the data repeater item template, code sample 2 below works perfectly.

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        MessageBox.Show("Hello")

    End Sub

Code Sample 2

Now let’s take a look on code sample 1, the event handler is cloned and so the code will be called when the mouse enters or leaves the TextBox. However, the code does not work as expected because TextBox1 refers to the template version of the TextBox, which is hidden at run time. The underline TextBox that triggers the event is the cloned version of the TextBox and in the context it is the sender object. So I modified code sample 1 to code sample 3, accessing TextBox1 through the sender object. Tested and worked.

    Private Sub TextBox1_MouseEnter(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.MouseEnter

        Dim txtBox As TextBox = TryCast(sender, TextBox)

        If (txtBox IsNot Nothing) Then

            txtBox.Multiline = True

            txtBox.Height = 50

        End If

    End Sub

    Private Sub TextBox1_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.MouseLeave

        Dim txtBox As TextBox = TryCast(sender, TextBox)

        If (txtBox IsNot Nothing) Then

            txtBox.Multiline = False

        End If

    End Sub

Code Sample 3

I hope this example can help you better understand how the DataRepeater works. Keep in mind that the controls in the repeater item area we work on at design time are used for templates and we should not use them at run time unless you want to change the properties for all repeater items (please see Gavin’s blog: How to Change Appearance of all DataRepeater Items at Run Time). If you need to work on an individual control on a repeater item, use the sender object (as in code sample 1) and then get its parent and siblings if necessary. You can also get access to a DataRepeaterItem, which inherits from Panel control, and then you can access to all the controls in the control’s hierarchical structure. To illustrate this, I would like to change the original forum question to resize the TextBox whenever the current repeater item changes. Code sample 4 below is my solution.

    Dim lastModifiedTxtBox As TextBox = Nothing

    Private Sub DataRepeater1_CurrentItemIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DataRepeater1.CurrentItemIndexChanged

        ' Get The TextBox1 from Me.DataRepeater1.CurrentItem

        Dim txtBox As TextBox = Me.DataRepeater1.CurrentItem.Controls("TextBox1")

        If (txtBox IsNot Nothing) Then

            txtBox.Multiline = True

            txtBox.Height = 50

        End If

        If (Not Object.ReferenceEquals(txtBox, lastModifiedTxtBox)) Then

            If (lastModifiedTxtBox IsNot Nothing) Then

                lastModifiedTxtBox.Multiline = False

            End If

            lastModifiedTxtBox = txtBox

        End If

End Sub

Code Sample 4

In code sample 4, I use DataRepeater.CurrentItem property to get the current DataRepeaterItem and then I use Control.Controls property to return the cloned version of the TextBox1 in the current DataRepeaterItem. Notice that the cloned version of the TextBox is named as “TextBox1” as well, this is allowed at run time as long as it is named uniquely under the parent control (the DataRepeaterItem, in the context). Because there is no CurrentItemIndexChanging event, I use lastModifiedTextBox to remember the last modified TextBox and restore its state when necessary.

Lastly, I would like to point out again (as I have in my previous post) that the DataRepeater control applies a virtualization technique to display the data. So at run time, only a few DataRepeaterItems are created for display purpose. These DataRepeaterItems are reused by all data rows when they enter the visible range. So it is important that if you set a property on a control based on a data row. You may want to reset it appropriately when the data row is out of the visible range as the control will be reused by other data rows. Let me change the task again to only resize the TextBox1 for the first data row, and I will use DataRepeater.DrawItem event this time to get the DataRepeaterItem.

    Private Sub DataRepeater1_DrawItem(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.PowerPacks.DataRepeaterItemEventArgs) Handles DataRepeater1.DrawItem

        Dim txtBox As TextBox = e.DataRepeaterItem.Controls("TextBox1")

        If (txtBox IsNot Nothing) Then

            If (e.DataRepeaterItem.ItemIndex = 0) Then

                ' Special treatment for the first data row

                txtBox.Multiline = True

    txtBox.Height = 50

            Else

                ' Reset to its original state..

                ' Without this line, the result will be unexpected.

                txtBox.Multiline = False

            End If

        End If

    End Sub

Code Sample 5

In code sample 5 above, if I omit the line to reset the txtBox.Multiline = False, you will see unexpected result after scrolling the data repeater up and down for a few times. See an illustration in picture 1 below.

Pic2

Picture 1

 

In conclusion, writing event handler for a control in a DataRepeaterItem can be as straightforward as in a plain form like code sample 2. However, when individual control is needed, you need to make sure to get the correct run time control and remember to reset its property after the use.