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.
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:
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.
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:
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) _
' Setup the Virtul Mode state for the DataRepeaters
Me.WeekCalendarDataRepeater.VirtualMode = True
Me.DayCalendarDataRepeater.VirtualMode = True
Me.ApptListDataRepeater.VirtualMode = True
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()
Dim row1 As 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
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
e.Value = GetDate(i, 0)
e.Value = GetDate(i, 1)
e.Value = GetDate(i, 2)
e.Value = GetDate(i, 3)
e.Value = GetDate(i, 4)
e.Value = GetDate(i, 5)
e.Value = GetDate(i, 6)
e.Value = i.ToString()
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
UpdateDataSet(i, CType(e.Value, String))
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.
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
Catch er As Exception
If (er.GetType.FullName <> GetType(CancleException).FullName) Then
' Remove the last added row
If (ApptListDataRepeater.ItemCount > oldCount) Then
ApptListDataRepeater.ItemCount = oldCount
Me.TotalApptsLabel.Text = apptDataSet.Appointment.Rows.Count.ToString()
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) _
'Raise a dialog to get a date an appoint
Dim appointmentForm As New NewAppointmentForm
If (appointmentForm.ShowDialog(Me) = Windows.Forms.DialogResult.OK) Then
Throw New CancleException
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
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
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")
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.
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.