Results 1 to 2 of 2

Thread: VB.NET List bound to DataRepeater in single- and multi-threading

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2007
    Location
    cobwebbed to PC
    Posts
    311

    VB.NET List bound to DataRepeater in single- and multi-threading

    For this tutorial you will need to have the Visual Basic PowerPack installed – You can check if you have this be looking in the toolbox and checking for the section named Visual Basic PowerPacks. If you do not have it, follow the instructions in this YouTube video http://youtu.be/Ox9w_LLyw38 which also provides a good idea of the normal setup and operation of the DataRepeater with a database.

    I am using .NET framework 4 in Visual Studio 2010. I believe this tutorial should work with Studios 2008 and 2012 and .NET down to 2.0 as well, but outside of that you may have to resort to trial and error.

    When using data bound controls we don’t always want to bind them to the usual database sourced DataTables. In this tutorial we will see how to bind a list of objects to a DataRepeater so that changes to the list will be reflected in the DataRepeater.

    Following on from this we will see how this needs to be altered when changing the list from a non-UI thread in a multi-threaded application.

    Setup the project and form
    To begin with you should start a new WindowsFormsApplication project. When your project is opened, add a Button control and a DataRepeater control to the form. Next add three Labels to the DataRepeater’s ItemTemplate.

    The form should now look something like this:
    Name:  Form1.png
Views: 4260
Size:  106.6 KB

    Open the code view of the form (press F7). Then, at the top of the code view we should see the empty Form1 class already there as normal.

    Create the List
    Inside the Form1 class add the following sub class:
    VB Code:
    1. Public Class TestListItem
    2.         Public Property TestKey As UInteger
    3.         Public Property TestString As String
    4.         Public Property TestVar As UInteger
    5.     End Class
    6.     Dim TestItem As New TestListItem
    This will create the class of the objects placed on the list and when you use this for your own application this should be changed to whatever type of object you need. The last line creates an instance of the class called TestItem.

    The next line to add will create the list itself, called TestList, that lists objects of the same type of class that we just created above.
    VB Code:
    1. Dim TestList As New List(Of TestListItem)
    2. Dim TestVarInt As New System.Random()
    You can see that we also created another variable, called TestVarInt that holds some random number; we will come back to this soon.

    We have now created a class and a list of objects of that class, but it has no objects currently listed. So time to add some data!

    Add some items to the List
    Continue by adding the Form.Load event handler:
    VB Code:
    1. Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    2.         ' Create and populate 2 items and add them to the List.
    3.         TestItem = New TestListItem
    4.         TestItem.TestKey = 0
    5.         TestItem.TestString = "TestItem1"
    6.         TestItem.TestVar = TestVarCount.Next(1, 10)
    7.         TestList.Add(TestItem)
    8.  
    9.         TestItem = New TestListItem
    10.         TestItem.TestKey = 1
    11.         TestItem.TestString = "TestItem2"
    12.         TestItem.TestVar = TestVarCount.Next(1, 10)
    13.         TestList.Add(TestItem)
    14.  
    15.         ' Show the Form.
    16.         Me.Show()
    17.     End Sub
    Here we start by assigning a new object to TestItem (ln 3) and then setting each of its members to some data (lns 4-6). You can see that used the random number variable to set one of the members to some random number between 1 and 10. Once populated the item is added to the list (ln 7). This is then done a second time with different data to create and add a second list item (lns 9-13). Finally we show the form (ln 16).

    Bind the List to the DataRepeater
    This has populated the list with items which are objects that have some data assigned to their members. Now we need to bind the list to the DataRepeater. Do this by inserting the following code before the form is shown.
    VB Code:
    1. ' Bind list item properties to the DataRepeaterItem controls.
    2.         Label1.DataBindings.Add("Text", TestList, "TestKey")
    3.         Label2.DataBindings.Add("Text", TestList, "TestString")
    4.         Label3.DataBindings.Add("Text", TestList, "TestVar")
    5.         DataRepeater1.DataSource = TestList
    Label1, 2 and 3 are the three labels we added to the DataRepeater ItemTemplate. The first three lines (lns 2-4) of this new code tells the labels what parts of the list items they should take notice of.
    I.E we add a binding to the label controls to the list item members.

    The first argument to the Label.Databindings.Add() method sets the property of the control that is to be bound, the second argument specifies the DataSource, which in our case is our list, and the third argument specifies the data member of the DataSource that is to be bound. So in the case of Label1 we would like its .Text property to be bound to the TestKey member of the items on the TestList.

    The final line (ln 5) of the new code sets the DataSource of the DataRepeater itself, our TestList.

    If we run our program now we should see the DataRepeater shows the two items we added to the list, YAY!
    Name:  TestRun1.png
Views: 2682
Size:  9.0 KB

    Changing the List
    Well and good, but what if we would like to add, change or remove any of the items on the list? This is where our Button comes in.

    Back in the design view of our Form, double-click on the Button control. This should switch us back to the code view with a new empty Button1.Click even handler added for us. Before we add anything to the handler we need to add a few new things to the top of our Form class members at the top.

    As shown below, add:
    • The enum definition, named ListAction, with the three possible actions (lns 3-7).
    • The ListAction type variable, named Action and initialised to ADD (ln 18).
    • The unsigned integer, named TestKeyCount and initialised to 0 (ln 19).


    The top of our Form1 class should now look like the below
    VB Code:
    1. Public Class Form1
    2.  
    3.     Enum ListAction
    4.         ADD
    5.         REMOVE
    6.         CHANGE
    7.     End Enum
    8.  
    9.     Public Class TestListItem
    10.         Public Property TestKey As UInteger
    11.         Public Property TestString As String
    12.         Public Property TestVar As UInteger
    13.     End Class
    14.  
    15.     Dim TestItem As New TestListItem
    16.     Dim TestList As New BindingList(Of TestListItem)
    17.     Dim TestVarInt As New System.Random()
    18.     Dim Action As ListAction = ListAction.ADD
    19.     Dim TestKeyCount As UInteger = 0
    Now head back to our empty Button1.Click event handler and add code to create (ln 3) and populate a new object (lns 6-11); nearly the same as we did in the Form1.Load event handler:
    VB Code:
    1. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    2.         ' Create new item.
    3.         TestItem = New TestListItem()
    4.  
    5.         ' Populate New item.
    6.         If Action = ListAction.ADD Then
    7.             TestKeyCount += 1
    8.         End If
    9.         TestItem.TestKey = TestKeyCount
    10.         TestItem.TestString = "ThreadItem"
    11.         TestItem.TestVar = TestVarInt.Next(1, 10)
    12.  
    13.         ' Call sub to do perform some action on the list.
    14.         DoListAction(TestItem)
    15.     End Sub
    In this case however we have set the TestKey member of the object (ln 9) to the new unsigned integer we just created at the top. You can also see that we are incrementing this count value every time the button is clicked if the Action variable is set to the ADD action (lns 6-8).

    Instead of calling the List.Add method, the last line (ln 14) instead calls a new sub, named DoListAction and passes our new object to it as an argument.

    This is because there are multiple operations that could be performed on our list. As you may have guessed we will be looking at adding, changing and removing items. In this case we will separate this work from the event handler into the DoListAction sub.

    We will create this sub outside of the two current event handlers (Form1.Load and Button1.Click), but still inside the Form1 class; I have put it at the bottom of the class as follows:
    VB Code:
    1. Private Sub DoListAction(ByVal listItem As Object)
    2.         ' Check which action to do.
    3.         If Action = ListAction.ADD Then
    4.             ' Add the item to the end of the list.
    5.             TestList.Add(listItem)
    6.             ' Change the action flag
    7.             Action = ListAction.CHANGE
    8.  
    9.         ElseIf Action = ListAction.CHANGE Then
    10.             ' Change the last item on the list.
    11.             TestList.Item(TestList.IndexOf(TestList.Last)) = listItem
    12.             ' Change the action flag.
    13.             Action = ListAction.REMOVE
    14.  
    15.         ElseIf Action = ListAction.REMOVE Then
    16.             ' Remove the last item on the list.
    17.             ' We dont actually need the item argument in this case.
    18.             TestList.Remove(TestList.Last)
    19.             ' Change the action flag
    20.             Action = ListAction.ADD
    21.         End If
    22.     End Sub
    23. End Class
    Inside the sub we check the Action variable to see which action is required (lns 3, 9 & 15) and then use the argument passed as the listItem parameter (our newly created object) to perform this action (lns 5, 11 & 18). After the action is performed we then make sure the action flag is changed (lns 7, 13 & 20) ready for a different action the next time the Button is clicked.

    If we try running our application now, we would hope that clicking the Button would add a third item to the list, change it and then remove it. So go ahead and run it.

    OH NOES! It doesn't work. So what’s going wrong? Well if we checked the TestList in the watch window while we were running our application we would see that the changes are being made to our list when we click the Button, the changes just aren't propagating to the DataRepater.

    Fixing the binding
    This is because the binding is only “one-way”, that is, if we had used text boxes instead of labels in the DataRepeater ItemTemplate and allowed the user to edit the content of those text boxes, the binding would ensure that those changes propagated from the DataRepeater to the list. What we want at the moment though, is for the binding to work the other way around for us.

    It isn’t currently working like this for us because there is no way for the DataRepeater to “know” when the list has been changed.

    What we need to do is change the type of collection we are using to one specifically suited for alerting controls bound to its data to any changes in that data, i.e. one that surfaces events that notify when the list has been changed.

    Currently the collection we are using is a list:
    VB Code:
    1. Dim TestList As New List(Of TestListItem)
    It turns out all we need to do in this case is change our List(Of T) to a BindingList(Of T).

    For this we will need to import System.Componentmodel at the top of the file, then change the list declaration like so:
    VB Code:
    1. Dim TestList As New BindingList(Of TestListItem)
    Run the application and, bada-bing, bada-boom, the DataRepeater updates with respect to changes to the bound List when we press the Button.

    The first click of the Button adds a third item, the second click changes it (look at Label3, this is our random number at work) and a third click removes it again.

    In the next post we will look at what changes we may need to make if we were using this in a multi-threaded application.

    Below is the final code for this post:
    VB Code:
    1. Imports System.ComponentModel
    2.  
    3. Public Class Form1
    4.  
    5.     Enum ListAction
    6.         ADD
    7.         REMOVE
    8.         CHANGE
    9.     End Enum
    10.  
    11.     Public Class TestListItem
    12.         Public Property TestKey As UInteger
    13.         Public Property TestString As String
    14.         Public Property TestVar As UInteger
    15.     End Class
    16.  
    17.     Dim TestItem As New TestListItem
    18.     Dim TestList As New BindingList(Of TestListItem)
    19.     Dim TestVarInt As New System.Random()
    20.     Dim Action As ListAction = ListAction.ADD
    21.     Dim TestKeyCount As UInteger = 0
    22.  
    23.     Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    24.         ' Create and populate 2 items and add them to the List.
    25.         TestItem = New TestListItem
    26.         TestItem.TestKey = 0
    27.         TestItem.TestString = "TestItem1"
    28.         TestItem.TestVar = TestVarInt.Next(1, 10)
    29.         TestList.Add(TestItem)
    30.  
    31.         TestItem = New TestListItem
    32.         TestItem.TestKey = 1
    33.         TestItem.TestString = "TestItem2"
    34.         TestItem.TestVar = TestVarInt.Next(1, 10)
    35.         TestList.Add(TestItem)
    36.  
    37.         ' Bind list item properties to the DataRepeaterItem controls.
    38.         Label1.DataBindings.Add("Text", TestList, "TestKey")
    39.         Label2.DataBindings.Add("Text", TestList, "TestString")
    40.         Label3.DataBindings.Add("Text", TestList, "TestVar")
    41.         DataRepeater1.DataSource = TestList
    42.  
    43.         ' Show the Form.
    44.         Me.Show()
    45.     End Sub
    46.  
    47.     Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    48.         ' Create new item.
    49.         TestItem = New TestListItem()
    50.  
    51.         ' Populate New item.
    52.         If Action = ListAction.ADD Then
    53.             TestKeyCount += 1
    54.         End If
    55.         TestItem.TestKey = TestKeyCount
    56.         TestItem.TestString = "ThreadItem"
    57.         TestItem.TestVar = TestVarInt.Next(1, 10)
    58.  
    59.         DoListAction(TestItem)
    60.     End Sub
    61.  
    62.     Private Sub DoListAction(ByVal listItem As Object)
    63.         ' Check which action to do.
    64.         If Action = ListAction.ADD Then
    65.             ' Add the item to the end of the list.
    66.             TestList.Add(listItem)
    67.             ' Change the action flag
    68.             Action = ListAction.CHANGE
    69.  
    70.         ElseIf Action = ListAction.CHANGE Then
    71.             ' Change the last item on the list.
    72.             TestList.Item(TestList.IndexOf(TestList.Last)) = listItem
    73.             ' Change the action flag.
    74.             Action = ListAction.REMOVE
    75.  
    76.         ElseIf Action = ListAction.REMOVE Then
    77.             ' Remove the last item on the list.
    78.             ' We dont actually need the item argument in this case.
    79.             TestList.Remove(TestList.Last)
    80.             ' Change the action flag
    81.             Action = ListAction.ADD
    82.         End If
    83.  
    84.     End Sub
    85.  
    86. End Class
    Last edited by wolf99; Aug 25th, 2013 at 03:13 PM.
    Thanks

  2. #2

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2007
    Location
    cobwebbed to PC
    Posts
    311

    Re: VB.NET List bound to DataRepeater in single- and multi-threading

    Adding threading
    As you may know, when you are using multi-threading in an application and a non-UI thread needs to act with the UI thread (the one running the UI controls) it is necessary to request the control to invoke the required action rather than performing it directly.

    These same considerations apply to the BindingList(Of T). This is because, as we mentioned earlier, the BindingList “notifies” the control that it is bound to (the DataRepeater in our case), that the list has changed in some way. This means that execution starts of from the call to change the list, passes to actually changing the list, to handling the whatever change event the list raises with the bound control to updating that control, before it finally returns back again.

    The only point we are directly involved in in this process is the call to change the list. Everything else happens as a result of that call and is, for the purposes of our tutorial, beyond our control. Thus just changing the list requires a change to the UI.

    If we are changing the list from a non-UI thread we must then use the invocation pattern to ask the UI-thread to execute the change for us.

    Starting the thread
    Continuing with the same project from before, we need to import System.Threading and then add two items at the top of the form to declare the thread and a delegate of the same type as the sub we will need to call, the DoListAction sub.
    VB Code:
    1. Dim Worker As Thread
    2.     Private Delegate Sub ListActionDelegate(ByVal listItem As Object)
    We can change the Button1.Click event handler to start a new thread (lns 14 & 17) instead of calling the DoListActionSub:
    VB Code:
    1. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    2.         ' Create new item.
    3.         TestItem = New TestListItem()
    4.  
    5.         ' Populate New item.
    6.         If Action = ListAction.ADD Then
    7.             TestKeyCount += 1
    8.         End If
    9.         TestItem.TestKey = TestKeyCount
    10.         TestItem.TestString = "ThreadItem"
    11.         TestItem.TestVar = TestVarInt.Next(1, 10)
    12.  
    13.         ' Set thread to add item to list.
    14.         Worker = New Thread(AddressOf DoSomeWork)
    15.  
    16.         ' Start the thread.
    17.         Worker.Start(TestItem)
    18.     End Sub
    Here, for some hypothetical reason, we need to start a thread to go do some work whenever the button is clicked. As part of that work it may need to perform some action on the bound list.

    The work the thread will be performing for us will be in the sub DoSomeWork and when we start it we will pass it our new TestItem object.

    The thread
    We will add this DoSomeWork sub as follows, in which, aside from our hypothetical actions we will perform some action on our list:
    VB Code:
    1. Private Sub DoSomework(ByVal listItem As Object)
    2.         ' We do some hypothetical thread work here...
    3.  
    4.         ' Then for some reason we need to do something with the list here,
    5.         ' so we’ll pass the listItem argument on to the DoListAction Sub here.
    6.         DoListAction(listItem)
    7.  
    8.         ' Then maybe some more hypothetical thread work.
    9.     End Sub

    Switching threads
    Now, we need to alter our old DoListAction sub:
    VB Code:
    1. Private Sub DoListAction(ByVal listItem As Object)
    2.         ' Check if the repeater needs to invoke a delegate
    3.         If DataRepeater1.InvokeRequired Then
    4.             ' Invoke the delegate and pass the item as an argument.
    5.             DataRepeater1.Invoke(New ListActionDelegate(AddressOf DoListAction), listItem)
    6.  
    7.         Else
    8.  
    9.             ' Check which action to do.
    10.             If Action = ListAction.ADD Then
    11.                 ' Add the item to the end of the list.
    12.                 TestList.Add(listItem)
    13.                 ' Change the action flag
    14.                 Action = ListAction.CHANGE
    15.  
    16.             ElseIf Action = ListAction.CHANGE Then
    17.                 ' Change the last item on the list.
    18.                 TestList.Item(TestList.IndexOf(TestList.Last)) = listItem
    19.                 ' Change the action flag.
    20.                 Action = ListAction.REMOVE
    21.  
    22.             ElseIf Action = ListAction.REMOVE Then
    23.                 ' Remove the last item on the list.
    24.                 ' We dont actually need the item argument in this case.
    25.                 TestList.Remove(TestList.Last)
    26.                 ' Change the action flag
    27.                 Action = ListAction.ADD
    28.             End If
    29.  
    30.         End If
    31.     End Sub
    Some of this code is the same as our old DoListAction sub, except that first we check if our DataRepeater needs to be invoked (ln 3). This will be true if we are calling this sub from a thread different to the one the control is on – I.E a non-UI thread.

    In this case we have called this sub from another thread, InvokeRequired will return true and so we ask the DataRepeater to invoke this function for us (using the delegate) instead of calling it directly (ln 5).

    Now when the DataRepater calls this function it will of course be executing on the same thread as the DataRepeater itself – the UI-thread.

    This time when the function is called InvokeRequired should return false and so execution passes into the Else branch (ln 7) of the If and into our old code that performs whatever change it was we wanted on the list (lns 10-28).

    Run the application and hit the button a couple times, providing everything went OK, BOOM list items changing and the changes show in the DataRepeater!

    Our final code should hopefully look like the following:

    VB Code:
    1. Imports System.ComponentModel
    2. Imports System.Threading
    3.  
    4. Public Class Form1
    5.  
    6.     Enum ListAction
    7.         ADD
    8.         REMOVE
    9.         CHANGE
    10.     End Enum
    11.  
    12.     Public Class TestListItem
    13.         Public Property TestKey As UInteger
    14.         Public Property TestString As String
    15.         Public Property TestVar As UInteger
    16.     End Class
    17.  
    18.     Dim TestItem As New TestListItem
    19.     Dim TestList As New BindingList(Of TestListItem)
    20.     Dim TestVarInt As New System.Random()
    21.     Dim Action As ListAction = ListAction.ADD
    22.     Dim TestKeyCount As UInteger = 0
    23.  
    24.     Dim Worker As Thread
    25.     Private Delegate Sub ListActionDelegate(ByVal listItem As Object)
    26.  
    27.     Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    28.         ' Create and populate 2 items and add them to the List.
    29.         TestItem = New TestListItem
    30.         TestItem.TestKey = 0
    31.         TestItem.TestString = "TestItem1"
    32.         TestItem.TestVar = TestVarInt.Next(1, 10)
    33.         TestList.Add(TestItem)
    34.  
    35.         TestItem = New TestListItem
    36.         TestItem.TestKey = 1
    37.         TestItem.TestString = "TestItem2"
    38.         TestItem.TestVar = TestVarInt.Next(1, 10)
    39.         TestList.Add(TestItem)
    40.  
    41.         ' Bind list item properties to the DataRepeaterItem controls.
    42.         Label1.DataBindings.Add("Text", TestList, "TestKey")
    43.         Label2.DataBindings.Add("Text", TestList, "TestString")
    44.         Label3.DataBindings.Add("Text", TestList, "TestVar")
    45.         DataRepeater1.DataSource = TestList
    46.  
    47.         ' Show the Form.
    48.         Me.Show()
    49.     End Sub
    50.  
    51.     Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    52.         ' Create new item.
    53.         TestItem = New TestListItem()
    54.  
    55.         ' Populate New item.
    56.         If Action = ListAction.ADD Then
    57.             TestKeyCount += 1
    58.         End If
    59.         TestItem.TestKey = TestKeyCount
    60.         TestItem.TestString = "ThreadItem"
    61.         TestItem.TestVar = TestVarInt.Next(1, 10)
    62.  
    63.         ' Set thread to add item to list.
    64.         Worker = New Thread(AddressOf DoSomeWork)
    65.  
    66.         ' Start the thread.
    67.         Worker.Start(TestItem)
    68.     End Sub
    69.  
    70.     Private Sub DoSomework(ByVal listItem As Object)
    71.         ' We do some hypothetical thread work here...
    72.  
    73.         ' Then for some reason we need to do something with the list here.
    74.         DoListAction(listItem)
    75.  
    76.         ' Then maybe some more hypothetical thread work.
    77.     End Sub
    78.  
    79.     Private Sub DoListAction(ByVal listItem As Object)
    80.         ' Check if the repeater needs to invoke a delegate
    81.         If DataRepeater1.InvokeRequired Then
    82.             ' Invoke the delegate and pass the item as an argument.
    83.             DataRepeater1.Invoke(New ListActionDelegate(AddressOf DoListAction), listItem)
    84.  
    85.         Else
    86.  
    87.             ' Check which action to do.
    88.             If Action = ListAction.ADD Then
    89.                 ' Add the item to the end of the list.
    90.                 TestList.Add(listItem)
    91.                 ' Change the action flag
    92.                 Action = ListAction.CHANGE
    93.  
    94.             ElseIf Action = ListAction.CHANGE Then
    95.                 ' Change the last item on the list.
    96.                 TestList.Item(TestList.IndexOf(TestList.Last)) = listItem
    97.                 ' Change the action flag.
    98.                 Action = ListAction.REMOVE
    99.  
    100.             ElseIf Action = ListAction.REMOVE Then
    101.                 ' Remove the last item on the list.
    102.                 ' We dont actually need the item argument in this case.
    103.                 TestList.Remove(TestList.Last)
    104.                 ' Change the action flag
    105.                 Action = ListAction.ADD
    106.             End If
    107.  
    108.         End If
    109.     End Sub
    110.  
    111. End Class
    For more info on threading and the invoke pattern see the following tutorials:
    http://www.vbforums.com/showthread.p...ding-in-VB-Net
    http://www.vbforums.com/showthread.p...Worker-Threads
    Last edited by wolf99; Aug 25th, 2013 at 03:08 PM.
    Thanks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width