While answering questions in the forums, I came across a recurring question about how the Virtual Mode works with the DataRepeater control included in the Visual Basic Power Packs. I realized that not many people use this powerful feature so I’d like to discuss how it works and walk you through a sample that uses it.

The DataRepeater has a VirtualMode property, what is it for?

Let me start with the following diagram illustrating the DataRepeater control’s working mechanism.

pic1

Picture 1

When a program starts, the data is loaded from the database to an in-memory structured data cache (e.g. a dataset). The data cache is bound to the view (the DataRepeater here) through the DataBinding mechanism (e.g. DataRepeater.DataSource = BindingSource). Users interact with the data through the DataRepeater. The user can scroll the view to show different data rows and modify the data at different rows. Original data is pulled from data cache and shown in the view; modified data is pushed back to the data cache. This all happens automatically as long as we setup the DataBinding channel properly (like DataRepeater.DataSource = BindingSource).

Using Virtual Mode means you do not want to use the data binding mechanism and you would like to provide your own data-management operations for the DataRepeater control for various reasons.  For example, you may want to provide data on-the-fly so you do not have a data cache that can be used as normal data source for the DataRepeater.

Performance is one of the main reasons people would choose to use the Virtual Mode, especially when displaying large quantities of data in a DataRepeater control as you are in control of loading data as needed and optimizing the loading speed and memory consumption.

Notice that performance is really a complex issue and it can be affected by many factors. Using Virtual Mode does not guarantee that you have the optimal result. For example, if you already have all data rows loaded into the memory like a DataSet filled by the TableAdapter.Fill method, you will probably not have the performance gain with Virtual Mode.

Motivated by the latest forum question about the usage of the DataRepeater Virtual Mode, in this post, I will present a sample project to focus on how to use the Virtual Mode.

Appointment Book Sample

My sample is called Appointment Book. It allows you to create electronic appointment books which can be set with customized calendar week ranges. This is my New Year resolution to get better organized (just kiddingJ).

The appointment book layout looks like this:

 image

Picture 2

The left hand side is a week calendar with 7 days per row. The right hand side shows the appointments detail in a calendar with 1 day per row. Appointment list can also be shown in a list as below on the right hand side of the book.   

image

Picture 3

To navigate to a given week, I can click on the weeks on the left panel and the associated dates on the right panel will be scrolled into the view, I can then click on a specified date and type in the appointment description TextBox, the associated date will change the color to pink on both calendars. To remove an appointment, you erase the description on the text box. Switching to the appointment list view, you can see the list of all appointments you just work on. 

I use a simple dataset to represent an appointment book. I will assume you are familiar with the DataSet class The DataSet contains two tables: AppointmentBook and Appointment as below:  

image

Picture 4

The AppointmentBook table contains only one record to define the calendar’s start date (must be a Sunday) and the total number of weeks in the calendar. The Appointment table contains the appointment date (represented as days offset from the start date), and the appointment detail. I only allow one appointment for a given date.  I admit this is an over-simplified data model but it lets us focus on the Virtual Mode usage and less on the application details.

The program allows you to create a new appointment book or open an existing one. The last opened file will be remembered thanks to the Application Settings.

DataRepeaters in the sample

In the program, I have used three DataRepeaters, all using VirtualMode.  The first one is called WeekCalendarDataRepeater, which is used to represent the calendar on the left hand side (see Picture 2). I will not do any data input on this DataRepeater, the data values (string values like 12/27, 12/28, etc.) are calculated on the fly, and therefore there is no need to cache the values through a data cache class. This would be a perfect case for using the VirtualMode. In this example, I will demonstrate how to provide data to the DataRepeater.  

The second one is called DayCalendarDataRepeater and it is used for displaying and editing appointments in the calendar view (Picture 2, right hand side). I have set the DataRepeater with fixed date range, each row represents one date, and so it does not allow you to add or remove items (i.e. rows here).  I will demonstrate how we can pull data from the DataRepeater and store in my data cache, the Appointment DataTable. You can see that the DataRepeater’s item and the Appointment DataTable’s row are not in a one to one relationship. The Appointment can have much less items. This is another good example that using VirtualMode is more appropriate compared with non VirtualMode.

The third DataRepeater is called ApptListDataRepeater, which is used to show the appointment in a list view (Picture 3, right hand side).  For this one, I wouldn’t necessarily have to use VirtualMode to set DataRepeater.DataSource to the Appointment DataTable. However, I still use it with VistualMode to demonstrate the usage of Add and Remove items.

Details usage of the DataRepeaters

To properly use the DataRepeater, you need to set DataRepeater.VirtualMode to true, set DataRepeater.ItemCount and handle the following four events: ItemValueNeeded, ItemValuePushed, NewItemNeeded and ItemsRemoved.   Let’s dig into details with my sample. 

1) To use the DataRepeater VirtualMode, the first thing to do is set the DataRepeater.VirtualMode property to true. You can set it through the property browser or in the code as in my Form_Load event handler below. I set all my three DataRepeter to VitualMode. (Some code removed in the function to improve the readability, same as in other code snippets, I will provide complete sample link at the end.)  

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _

            Handles MyBase.Load

        ' Setup the Virtul Mode state for the DataRepeaters

        Me.WeekCalendarDataRepeater.VirtualMode = True

        Me.DayCalendarDataRepeater.VirtualMode = True

        Me.ApptListDataRepeater.VirtualMode = True

    End Sub  

2) Then you need to tell the DataRepeater how many items the DataRepeater will host initially. In my sample, my three DataRepeater would have total number of weeks, days and appointments respectively in the OpenAppointmentBook function.

    Private Sub OpenAppointmentBook()

        apptDataSet = New AppointmentDataSet()

        apptDataSet.ReadXml(dataFile)

        Dim row1 As AppointmentDataSet.AppointmentBookRow = _

                 CType(apptDataSet.AppointmentBook.Rows(0), AppointmentDataSet.AppointmentBookRow)

        day0 = row1.StartDate

        totalWeeks = row1.TotalWeeks

        totalDays = totalWeeks * 7

        Me.WeekCalendarDataRepeater.ItemCount = totalWeeks

        Me.DayCalendarDataRepeater.ItemCount = totalDays

        Me.ApptListDataRepeater.ItemCount = apptDataSet.Appointment.Rows.Count

    End Sub  

3) You need to provide data to a DataRepeaterItem whenever it needs (e.g. when the item scrolls into view). This is wired by the ItemValueNeeded event. For example, in the WeekCalendarDataRepeater, I provide values for the Label controls by calculating the date and week number from the item index.  

Private Sub WeekCalendarDataRepeater_ItemValueNeeded(ByVal sender As Object, ByVal e As _

  DataRepeaterItemValueEventArgs) Handles WeekCalendarDataRepeater.ItemValueNeeded

        Dim i As Integer = e.ItemIndex

        If (i >= 0 AndAlso i < TotalWeeks) Then

            Select Case e.Control.Name

                Case "Label1"

                    e.Value = GetDate(i, 0)

                Case "Label2"

                    e.Value = GetDate(i, 1)

                Case "Label3"

                    e.Value = GetDate(i, 2)

                Case "Label4"

                    e.Value = GetDate(i, 3)

                Case "Label5"

                    e.Value = GetDate(i, 4)

                Case "Label6"

                    e.Value = GetDate(i, 5)

                Case "Label7"

                    e.Value = GetDate(i, 6)

                Case "WeekLabel"

                    e.Value = i.ToString()

            End Select

        End If

    End Sub

I do not handle ItemValuePushed event in WeekCalendarDataRepeater so it is effectively read-only.  

4) When a DataRepeaterItem is updated in the DataRepeater, you need to handle the ItemValuePushed event to save the data. For example, in DayCalendarDataRepeater, we receive the value from DataRepeater’s ApptTextBox, which stores the appointment description the user just entered or just changed. We store the value in the dataset. 

Private Sub DayCalendarDataRepeater_ItemValuePushed(ByVal sender As Object, ByVal e As _

 DataRepeaterItemValueEventArgs) Handles DayCalendarDataRepeater.ItemValuePushed

        Dim i As Integer = e.ItemIndex

        If (i >= 0) Then

            Select Case e.Control.Name

                Case "ApptTextBox"

                    UpdateDataSet(i, CType(e.Value, String))

            End Select

        End If

    End Sub  

I set both WeekCalendarDataRepeater and DayCalendarDataRepeater with AllowUserToAddItems and AllowUserToDeleteItems to false (see Picture 5) so the item count is fixed and I do not need to handle NewItemNeeded and ItemsRemoved events. 

image

Picture 5  

5) In ApptListDataRepeater, I need to add and remove the appointments. I use AddButton and RemoveButton to start the Add/Remove action. The AddButton click code looks like the following. The main statement is ApptListDataRepeater.AddNew().  I add error handling code so that it won’t add a new row if any error happens.   

Private Sub ApptListDataRepeater_AddButton_Click(ByVal sender As System.Object, ByVal e As _

  System.EventArgs) Handles AddButton.Click

        Dim oldCount = ApptListDataRepeater.ItemCount

        Try

            ApptListDataRepeater.AddNew()

        Catch er As Exception

            If (er.GetType.FullName <> GetType(CancleException).FullName) Then

                MessageBox.Show(er.Message)

            End If

            ' Remove the last added row

            If (ApptListDataRepeater.ItemCount > oldCount) Then

                ApptListDataRepeater.ItemCount = oldCount

                Me.TotalApptsLabel.Text = apptDataSet.Appointment.Rows.Count.ToString()

            End If

        End Try

    End Sub

The above code will trigger the DataRepeater to raise NewItemNeeded event. And we handle it as following:    

  Private Sub ApptListDataRepeater_NewItemNeeded(ByVal sender As Object, ByVal e As System.EventArgs) _

    Handles ApptListDataRepeater.NewItemNeeded

 

        'Raise a dialog to get a date an appoint

        Dim appointmentForm As New NewAppointmentForm

        If (appointmentForm.ShowDialog(Me) = Windows.Forms.DialogResult.OK) Then

            Me.apptDataSet.Appointment.AddAppointmentRow(appointmentForm.AppointmentDate.Subtract(day0).Days, _

                                                         appointmentForm.Description)

            RefreshDataRepeater(Me.DayCalendarDataRepeater)

            RefreshDataRepeater(Me.WeekCalendarDataRepeater)

        Else

            Throw New CancleException

        End If

  End Sub

I use a new form to enter the date and detail to compose a new appointment data item.

6) To remove an item, you can use DataRepeter.RemoveAt(index) as following:

Private Sub ApptListDataRepeater_DeleteButton_Click(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles DeleteButton.Click

        ApptListDataRepeater.RemoveAt(ApptListDataRepeater.CurrentItemIndex)

    End Sub

You can also use the delete key to achieve the same effect. At the same time, you need to handle ItemsRemoved event like below:  

Private Sub ApptListDataRepeater_ItemsRemoved(ByVal sender As Object, ByVal e As _

 DataRepeaterAddRemoveItemsEventArgs) Handles ApptListDataRepeater.ItemsRemoved

   

        If (e.ItemIndex >= 0 AndAlso e.ItemIndex < apptDataSet.Appointment.Rows.Count) Then

            apptDataSet.Appointment.Rows.RemoveAt(e.ItemIndex)

        End If

    End Sub

7) Finally, you may see in my sample I have this function to refresh a DataRepeater.

    Private Sub RefreshDataRepeater(ByRef repeater As DataRepeater)

        Debug.Assert(repeater IsNot Nothing, "repeater is not set")

        repeater.BeginResetItemTemplate()

        repeater.EndResetItemTemplate()

End Sub

This is needed when you want the DataRepeater to refresh the data from your data source, the DataSet in this example. Notice that DataRepeater.Invalidate is used to force the control to repaint and it will not update the data.

Summary

Here is a summary of the DataRepeater VirtualMode usage:

1)      Set DataRepeater.VirtualMode = true

2)      Set DataRepeater.ItemCount.

3)      Handle DataRepeater.ItemValueNeeded event to populate the DataRepeater

4)      Call DataRepeater.BeginResetItemTemplate and EndResetItemTemplete to refresh the DataRepeater if data changed

5)      Handle ItemValuePushed to save changes happened in the DataRepeater

6)      Call DataRepwater.AddNew to add a new item and handle NewItemNeeded event to provide the new value

7)      Call DataRepeater.RemoveAt(index) to remove a DataRepeaterItem and handle ItemsRemoved event to remove value from outside data storage.

For the complete sample source code, please check out in MSDN Code Gallery http://code.msdn.microsoft.com/AppintmentBook

For more information about the DataRepeater Virtual Mode, please refer to MSDN document here.

Cheers!