eXList - FAQ
Exontrol.COM Software - Frequently Asked Questions - ExList Component
1:
The control's release notes can be found on our web site, looking for the Release Notes column in the control's main page. Click here for direct link.
2:
The version 1.0.3.2 includes the built-in HTML format inside the cell. The CaptionFormat property specifies how the cell's caption ( Caption property ) is displayed. If the CaptionFormat property is exText no HTML formatting is applied. Else, if the CaptionFormat property is exHTML the Caption is formatted using the following HTML tags:
  • <b> tag - draws a text bolded until </b> is reached.
  • <br> tag - breaks the line
  • .<i> tag - draws the text using italic font attribute until </i> is reached.
  • <s> tag - draws the text using strikeout font attribute until </s> is reached.
  • <u> tag - draws the text using underline font attribute until </u> is reached.
  • <fgcolor=RRGGBB> tag - draws the text using the RGB(RR,GG,BB) foreground color. </u>, until </fgcolor> is reached. RR, GG and BB should be hexa values.
  • <bgcolor=RRGGBB> tag - draws the text using the RGB(RR,GG,BB) background color. </u>, until </bgcolor> is reached. RR, GG and BB should be hexa values.
  • < dotline > - draws a dotted line. 
  • < solidline > - draws a dotted line
  • < upline > - draws up the line
  • < r > - right aligns the line
For instance: the HTML formatting "<b>Inbox</b> <fgcolor=0000FF>(78)</fgcolor>" draws the Inbox using bold font attributes, and (78) using the blue color, like: Inbox (78)
3:
The control provides a WYSWYG template editor that helps you to create template files. A template file is a collection of instructions that control loads at runtime. In other words the template file holds a collection of properties and their values, methods and objects, into a TEXT file. The template file combines XML style with something close to VBScript. We call it X-Script. It is important to specify that the editor and the X-Script DO NOT USE any external VB script engine, Active script engine, XML parser or DOM. The X-Script was implemented from scratch as lite as possible to let users customize the control in design mode no matter what programming languages they are using. The template files are the same for any programming language, and do the same thing for all! For instance, you can copy and paste the template file from a VFP control to a C++ control!

The editor automatically updates the control's look and feel while you are editing the template file. This way you can learn easy how a property or a method reacts! Also, the editor provides a type library context menu that helps you to find quickly a property ( CTRL + SPACE invokes it ). Here's a screen shot of control's template editor:

To check the following samples open the control's template page and paste them to the editor. The X-Script supports variables using the sequence like Dim v1, v2, v3, supports RGB function like RGB(0,255,255).

The following sample shows how to add 3 columns, and how to change few properties for them:

Columns
{
	"Column 1"
	{
		HeaderBold = True
		DisplayFilterButton = True
	}
	"Column 2"
	"Column 3"
	{
		Position = 1
	}
}

The following sample shows how to add 1 column, and there items

HeaderVisible = False
Columns
{
	"Column 1"
}
Items
{
	Add("Item 1")
	Add("Item 2")
	Add("Item 3")
}
4:
In design mode, select 'Properties' item from object's context menu. Also, clicking the (Custom) property page in your object.
5:
The version 1.0.3.2 includes the built-in HTML format support. This feature allows you to use simple HTML tags like <b>, <fgcolor> into the cell's caption. The CaptionFormat property specifies how the cell's caption ( Caption property ) is displayed. If the CaptionFormat property is exText no HTML formatting is applied. Else, if the CaptionFormat property is exHTML the Caption is formatted using built-in HTML tags.

For instance: the HTML formatting "<b>vb.announcements</b> <fgcolor=0000FF>(580)</fgcolor> <fgcolor=FF0000>(-23)</fgcolor>" draws the 'vb.announcements' using bold font attributes, (580) using the blue foreground color and (-23) using red color, like: 'vb.announcements (580) (-23)'

6:
The control provides a Columns property that helps you to add, remove or changes the columns of the control. By default, the control has no columns. The following code shows you how to add two columns to the control:
With List1
    .BeginUpdate
        With .Columns
            With .Add("Column 1")
                .DisplayFilterButton = True
            End With
            With .Add("Column 2")
                .HeaderImage = 1
            End With
        End With
    .EndUpdate
End With
When many changes are made to the control , you should invoke the BeginUpdate method to temporarily freeze the drawing of the control. This results in less distraction to the user, and a performance gain. After all updates have been made, invoke the EndUpdate method to resume drawing of the control.
7:
The ColumnAutoResize property is what you are looking for. If the control's ColumnAutoResize property is True, the control arranges all visible columns to fit the control's client area. In this case no horizontal scroll bar is displayed. If the ColumnAutoResize property if False, control displays a horizontal scroll bar if the width of visible columns doesn't fit the width of the client area.
8:
The control provides an Items property that helps you to add, remove or changes the items in the control. Before adding any new item to the control make sure that your control has at least one column. There are 4 methods to load items to the control.
  • Using the Add method. 
  • You can call PutItems method if you have an array of elements.
  • Using the DataSource property ( binding the control to an ADO, DAO recordset )
  • Forth is using the IUnboundHandler interface ( unbound mode ).  

Each property that refers a cell requires an index to item and an index to a column. 

By default, the control has no columns, so before adding new items you need to add columns like in the following sample 

With List1.Columns
        .Add "Column 1"
        With .Add("Column 2")
            .HeaderImage = 1
        End With
End With

The following sample uses the first method to add few items to the Items collection.

With List1
    .BeginUpdate
        With .Items
            Dim i As Long
            i = .Add("Item 1")
            .Caption(i, 1) = "SubItem 1"
            i = .Add("Item 1")
            .Caption(i, "Column 2") = "SubItem 2"
        End With
    .EndUpdate
End With
The following VB sample shows you how PutItems method can be used:
Dim v(1, 1) As String
v(0, 0) = "Item 1"
v(1, 0) = "SubItem 1"
v(0, 1) = "Item 2"
v(1, 1) = "SubItem 2"
    
List1.PutItems v
The following C++ sample shows how to use safe arrays and PutItems method:
SAFEARRAY* psa = SafeArrayCreateVector( VT_BSTR, 0, 3 );
BSTR* p = NULL; 
SafeArrayAccessData( psa, (void**)&p );
*p = SysAllocString( L"One" );
p++;
*p = SysAllocString( L"Two" );
p++;
*p = SysAllocString( L"Three" );
SafeArrayUnaccessData( psa );

COleVariant vtArray, vtMissing;
vtMissing.vt = VT_ERROR;
vtArray.vt = VT_ARRAY | VT_BSTR;
V_ARRAY( &vtArray ) = psa;

m_list.PutItems( &vtArray, vtMissing );

The following sample loads an ADO recordset using PutItems method:

Dim rs As Object
Set rs = CreateObject("ADODB.Recordset")
rs.Open "Authors", "Provider=Microsoft.Jet.OLEDB.3.51;Data Source= Biblio.mdb"

With List1
    .BeginUpdate
        .ColumnAutoResize = False
        With .Columns
            For Each f In rs.Fields
                .Add f.Name
            Next
        End With
    .PutItems rs.GetRows()
    .EndUpdate
End With

If you are using MS Access environment ( which is DAO based ), you should be carefully with GetRows property of Recordset object, that retrieves only one record if the 'rows' argument is missing, so you can use a sample like follows:

    Dim rs As Object
    Set rs = CurrentDb.OpenRecordset("Table1")
    With List1
        .BeginUpdate
            .ColumnAutoResize = False
            With .Columns
                For Each f In rs.Fields
                    .Add f.Name
                Next
            End With
        .PutItems rs.GetRows(rs.RecordCount)
        .EndUpdate
    End With

When many changes are made to the control, you should invoke the BeginUpdate method to temporarily freeze the drawing of the control. This results in less distraction to the user, and a performance gain. After all updates have been made, invoke the EndUpdate method to resume drawing of the control.

The following sample uses the VB Array function to insert items to a multiple columns control:

With List1
    .BeginUpdate
    
    .Columns.Add "Column 1"
    .Columns.Add "Column 2"
    .Columns.Add "Column 3"
    
    With .Items
        .Add Array("Item 1", "Item 2", "Item 3")
        .Add Array("Item 4", "Item 5", "Item 6")
        .Add Array("Item 7", "Item 8", "Item 9")
    End With
    
    .EndUpdate
End With 
9:
Once that you have the index of the added item you have to sue the ItemPosition property to specify the item's position.
10:
The control provides a property ShowImageList that shows or hides that images list. By default, the property is True, to let new customers know that they can drag images without using an ImageList control. If you are going to add icons at runtime the control provides Images and ReplaceIcon methods. The Images method takes the handle to an ImageList control. The ReplaceIcon method works like follows:
  • ReplaceIcon( Icon, -1) method. Adds a new icon to control's image list, and retrieves the index of the image. Sample: .ReplaceIcon Image1.Picture.Handle, adds a new icon to the end of the control's image list, .ReplaceIcon LoadPicture("D:\Icons\help.ico").Handle adds a new icon, loads the icon from a file, and adds it to control's image list
  • ReplaceIcon( Icon, n ) ( where n >= 0 ) method. Replaces an icon to control's image list. Sample: .ReplaceIcon Image1.Picture.Handle, 0 replaces the first icon in the control's image list
  • ReplaceIcon( 0, n ) (where n>= 0 ) method. Removes an icon given its index. Sample: .ReplaceIcon 0, 0 removes the first icon in the control's image list 
  • ReplaceIcon( 0, -1) method. Clears the images collection. Sample: .ReplaceIcon, clears the entire image list.
11:
You can delete an icon from the images list window in design mode by selecting the icon and pressing the BackSpace key. You can delete the icon using the Delete key but some containers delete the object when Delete key is used.
12:
The MarkSearchColumn specifies whether the searching column is marked or not. Use the MarkSearchColumn to hide that box.
13:
The Items object provides properties like: Items.SelectCount, Items.SelectItem, Items.SelectedItem that helps you to access the selected items. The control fires SelectionChanged event when user changes the selection. The following sample uses the FindItem method to looks for an item that contains in the column "Column 1" the value "Child 2"
List1.Items.SelectItem(List1.Items.FindItem("Item 1", "Column 1")) = True

or

List1.Items.SelectItem(List1.Items.FindItem("Item 1", 0)) = True

The following sample selects the first visible item:

List1.Items.SelectItem(List1.Items.FirstVisibleItem) = True
The following sample displays the selected items. Only the caption on the first column are displayed. If you want to display more columns you have to change the 0 with index of column being displayed. 
Private Sub List1_SelectionChanged()
    With List1.Items
        Dim i As Long
        For i = 0 To .SelectCount - 1
            Debug.Print .Caption(.SelectedItem(i), 0)
        Next
    End With
End Sub
14:
The FindItem method searches for an item. The FindItem method looks for the first item that has in a column the giving value. For instance the following sample gets the index of the item that contains in the first column ( "Column 1"  ) the value "Item 1":
Debug.Print List1.Items.FindItem("Item 1", "Column 1")
If the FindItem method fails to locate the item the -1 is returned. The returned value represents the index of the item found. Once that we have found the searched item all that we need to call the EnsureVisibleItem method in order to ensure that the item is visible.
15:
The control provides multiple ways to do that. If you only need to alternate the background color for items you should use the BackColorAlternate property. If only a particular item needs to be colorized, you have to use properties like: ItemForeColor, ItemBackColor, CellForeColor or CellBackColor. Remember that control fires the AddItem event when a new item is inserted to the Items collection. You can use the AddItem event to apply different colors for the newly added items.  Also, you can use the built-in HTML format.
16:
17:
You can use the Def(exCellBackColor) property to specify the background color for all cells in the column. Another option that you have to color a column is if you are using the CountLockedColumns property. The CountLockedColumn property specifies the number of visible columns that are frozen on the left side. A frozen column is not scrollable. The control provides in that case a property called BackColorLock that specifies the background color for frozen area of the control. The same thing is for ForeColorLock property except that it specifies the foreground color for the frozen area. In case that CountLockedColumn > 0 the BackColor and ForeColor properties are applicable to the scrollable area of the control.
18:
The control provides a plenty of properties like: CellBold, CellItalic, CellUnderline, CellStrikeout, ItemBold, ItemStrikeout,  and so on to help you set font attributes for a cell or for an item. As well as cells the column's header can have its own font attribute using the HeaderBold, HeaderItalic, and so on properties. Also, the control supports built-in HTML format.
19:
By default, the control automatically sorts a column when the user clicks the column's header. If the SortOnClick property is exNoSort, the control doesn't sort the items when user clicks the column's header. There are two methods to get items sorted like follows:
  • Using the SortOrder property of the Column object. The SortOrder property displays the sorting icon in the column's header if the DisplaySortIcon property is True.

    List1.Columns(ColIndex).SortOrder = SortAscending
  • Using the Sort method of Items object. The following sample sort descending the list of root items on the "Column 2"

    List1.Items.Sort "Column 2", False
20:
Yes, you can. The following method uses a for each statement.. In the sample i variable specifies an index to an item.
With List1
        Dim i As Variant
        For Each i In .Items
            Debug.Print .Items.Caption(i, 0)
        Next
End With
The second method to iterate the items can be the following:
With List1
    For i = 0 To .Items.Count - 1
        Debug.Print .Items.Caption(i, 0)
    Next
End With

You can use the GetItems method too like in the following sample:

Dim i As Variant
For Each i In List1.GetItems()
    Debug.Print i
Next
21:
Yes. You can find UNICODE versions here.
22:
Changing the Name property of the Font object doesn't notify the control that the used font has been changed, so calling List1.Font.Name = "Arial Unicode MS" has effect only for the control's drop-down window, but it doesn't change the font for control inside text editors. Remember that Font is a system object, and it is not implemented by the control, so that's the reason why the control is not notified that the user has changed the font's name. The following sample changes the font used by inside text editors as well for the drop-down window:
Dim f As New StdFont
f.Name = "Arial Unicode MS"
List1.Font = f
23:
The control provides properties like CheckImage and RadioImage properties that help you to set your desired icons for check or radio buttons.
24:
When you expect performance you have to be carefully to each line of code in your project. Here's few hints about improving performance when you are using the control:
  • The Items property performs a QueryInterface each time when it is called. It is recommended using a variable that holds the Items  property instead calling the property itself. For instance call set its = List1.Items when form is loaded, and use 'its' variable each time when you need to access the Items collection.
  • Use With .. End With statements each time you can. It avoids calling too many times a QueryInterface by the control. 
  • Holds a column to a variable instead calling Item property. For instance, the Item property of the Columns object looks for a column. The Add method of Columns object retrieves the added Column object. For instance use code like follows to add and initialize a column:
    With List1.Columns
        With .Add("Column 1")
            .Width = 128
            .AllowSizing = False
            .AllowDragging = False
            .DisplaySortIcon = False
        End With
    End With
or
    With List1.Columns
        Dim c As EXListLibCtl.Column
        Set c = .Add("Column 1")
        c.Width = 128
        c.AllowSizing = False
        c.AllowDragging = False
        c.DisplaySortIcon = False
    End With
  • Use BeginUpdate and EndUpdate methods when multiple operations require changing the control.
  • Whenever you want to access an column use its index instead its name. For instance if the "Column 1" is the first column in the control use the .Items.Caption( Index, 0 ) instead .Items.Caption( Index, "Column 1"). or .Columns(0) instead .Columns("Column 1")
  • If you are using the control using the unbound mode make sure that the ReadItem method is light and easy. The ReadItem method is called each time when the control requires an item. Obviously, once that an item was retrieved when control requires the same item, it was already cached so no ReadItem method is called. Also an improvement to ReadItem method could be using a variable its ( that holds the control's Items property ) instead Source.Items. 
  • If you are using the unbound mode, but you still get data from a recordset make sure that you are using an index on the table instead using FindItem method. You can use also hash tables.
25:
The control provides a ScrollPos property that helps you to scroll the vertical or horizontal bar as well. Also, the Items object provides properties like EnsureVisibleItem, EnsureVisibleColumn.
26:
The AllowEdit property enables or disables editing operation in the control. The List1.Items.Edit 0, 0 edits the first cell in the control. The edit operation fires events like BeforeCellEdit and AfterCellEdit. You can disable editing a cell by handling the AfterCellEdit event and setting the Cancel parameter to True.
27:
The CellPicture property of the Items object helps you to attach a picture file ( bmp, gif, whatever ) to a cell. The following sample shows how to attach a picture to the first visible cell of the control:
With List1.Items
	Dim p As IPictureDisp
	Set p = LoadPicture("c:\winnt\Zapotec.bmp")
	.CellPicture(.FirstVisibleItem, 0) = p
End With
or you can use the following way as well:
With List1.Items
	.CellPicture(.FirstVisibleItem, 0) = LoadPicture("c:\winnt\Zapotec.bmp")
End With
If the picture's height is larger than item's height you can use the ItemHeight property to let picture fits the item's client area.
28:
Yes, The version 1.0.3.5 includes the <br> HTML tag that breaks a line. The CellSingleLine property specifies whether the cell uses multiple lines when painting the caption.
29:
Yes. The Picture and PictureDisplay properties help you to load and arrange a picture on the control's background picture. The Picture property takes a IPictureDisp object. The VB environment provides LoadPicture method that retrieves an IPictureDisp object based on a file name.
30:
The control exports the ScrollPos property that helps you to scroll the control's content. The Items object provides also methods like EnsureVisibleItem and EnsureVisibleColumn that ensure that an item or a column fits the visible area of the control.
31:
The control supports auto search feature, or incremental search feature. The auto search feature allows finding and selecting items by typing characters when the control is focused.  The control provides the AutoSearch property that helps you to disable this feature.
32:
First, you have to make sure that the AllowEdit property is True. The following snippet of code shows how to edit a cell when user dbl clicks.
Private Sub List1_DblClick(Shift As Integer, X As Single, Y As Single)

    With List1
        Dim i As Long, c As Long
        i = .ItemFromPoint(-1, -1, c)
        .Items.Edit i, c
    End With

End Sub
33:
Yes, the Exontrol ExPrint component ( exprint.dll ) provides Print and Print Preview capabilities for the exList component. Once that you can have the exPrint component in your Components list, insert a new instance of "ExPrint 1.0 Control Library" to your form and add the following code:
Private Sub Command1_Click()
    With Print1
        Set .PrintExt = List1.Object
        .Preview
    End With
End Sub

The Exontrol Print Preview mainframe looks like follows:

The following VB sample opens the Print Preview frame:

With Print1
    Set .PrintExt = List1.Object
    .Preview
End With

The following C++ sample opens the Print Preview frame:

m_print.SetPrintExt( m_list.GetControlUnknown() );
m_print.Preview();

The following VB.NET sample opens the Print Preview frame:

With AxPrint1
    .PrintExt = AxList1.GetOcx()
    .Preview()
End With

The following C# sample opens the Print Preview frame:

axPrint1.PrintExt = axList1.GetOcx();
axPrint1.Preview();

The following VFP sample opens the Print Preview frame:

with thisform.Print1.Object
    .PrintExt = thisform.List1.Object
    .Preview()
endwith
34:
The Exontrol ExPrint component ( exprint.dll ) provides Print and Print Preview capabilities for the Exontrol ExList component.

The requirements for the FitToPage option:

  • Exontrol.ExPrint version 5.2 ( or greater )

  • Exontrol.ExList version 6.1 ( or greater )

If these are not meet, the Options("FitToPage") property has NO effect.

The FitToPage option could be one of the following:

  • On, (Fit-To-Page) the control's content is printed to a single page ( version 6.1 )
  • p%, (Adjust-To) where p is a positive number that indicates the percent from normal size to adjust to. For instance, the "FitToPage = 50%" adjusts the control's content to 50% from normal size. ( version 8.1 )
  • w x, (Fit-To Wide) where w is a positive number that indicates that the control's content fits w pages wide by how many pages tall are required. For instance, "FitToPage = 3 x" fits the control's content to 3 pages wide by how many pages tall is are required. ( version 8.1 )
  • x t, (Fit-To Tall) where t is a positive number that specifies that the control's content fits t pages tall by how many pages wide are required. For instance, "FitToPage = x 2" fits the control's content to 2 pages tall by how many pages wide are required. ( version 8.1 )
  • w x t, (Fit-To) where w and t are positive numbers that specifies that the control's content fits w pages wide by t pages tall. For instance, "FitToPage = 3 x 2" fits the control's content to 3 pages wide by 2 pages tall. ( version 8.1 )

The following VB6 sample shows how to show the eXList/COM's content to one page when print or print preview the component:

Private Sub Command1_Click()
    With Print1
        .Options = "FitToPage = On"
        Set .PrintExt = List1.Object
        .Preview
    End With
End Sub

The following VB/NET sample shows how to show the eXList/NET's content to one page when print or print preview the component:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    With Exprint1
        .Options = "FitToPage = On"
        .PrintExt = Exlist1
        .Preview()
    End With
End Sub
35:
The control provides the SortOnClick property that helps you to handle how the control sorts the columns when user clicks the column's header. The following sample shows how to sort a column using SortOrder property.
Private Sub Form_Load()
    List1.SortOnClick = exNoSort
End Sub

Private Sub List1_ColumnClick(ByVal Column As EXLISTLibCtl.IColumn)
    Column.SortOrder = (Column.SortOrder + 1) Mod 3 
End Sub

In this case if an user clicks a column, the column gets sorted like follows:

  • if the column was unsorted, it is sorted ascending
  • if the column was sorted ascendent, the control sorts the column descendent
  • if the column is descendent, the column becomes unsorted ( no sorting icon displayed on the column's header bar )   

You can simulate the exDefaultSort by using the following handler:

Private Sub List1_ColumnClick(ByVal Column As EXLISTLibCtl.IColumn)
    Column.SortOrder = (Column.SortOrder + 1) Mod 3 + 1
End Sub
36:
Generally, the user needs to run the control in virtual mode, if a table with large number of records needs to be displayed. In virtual mode, the control displays maximum 2,147,483,647 records. The control is running in virtual mode, only if VirtualMode property is True, and the UnboundHandler property refers an object that implements the IUnboundHandler interface. Implementing the IUnboundHandler interface is easy because it has only two methods. The first one, ItemsCount specifies the number of records that user needs to display in the control. The second method is ReadItem and it provides data for a specific record. When control is running in the virtual mode, the control loads only the items that need to be displayed. If the control is running in the unbound mode ( the VirtualMode property is False ), the control allocates memory for all records that need to be loaded. The data for each record is loaded only when it is required. The virtual mode has few disadvantages like: the sorting is not available ( the user needs to provide sorting data ), the control's filtering items is not available, the user cannot add items manually, and so on. The main advantage of the virtual mode is that the control can display large number of records. The unbound mode requires a lot of memory, depending on number of loaded records, but it allows almost all features of the control, including sorting, filtering and so on. Use the Items.ItemToVirtual property to convert the index of the item in the list to the index of the virtual item. Use the Items.VirtualToItem property to get the index of the item in the list giving the index of the virtual item.  It is important to know, that the Items.VirtualToItem property ensures that the virtual item fits the control's client area.
37:
When you need to display large number of records, you need to provide an object that implements the IUnboundHandler interface. The object provides the number of records that need to be displayed, and data for each record. The VirtualMode property needs to be set on true, and the object you have written needs to be passed to the UnboundHandler property. 

The following sample adds a column, and 100 records. The index of each item in the list is displayed.

  • Create a new project (Project1)
  • Add a control to the form ( List1 )
  • Create a new class module ( Class1 ) and add it to the project
  • Open the code of the class, and type "Implements IUnboundHandler"
  • Add the handler for the IUnboundHandler_ItemsCount property like follows:
Private Property Get IUnboundHandler_ItemsCount(ByVal Source As Object) As Long
    IUnboundHandler_ItemsCount = 100
End Property
The control calls the IUnboundHandler_ItemsCount property when the UnboundHandler property is set, to update the vertical scroll range.
  • Add the handler for the IUnboundHandler_ReadItem method like follows:
 Private Sub IUnboundHandler_ReadItem(ByVal Index As Long, ByVal Source As Object)
    With Source.Items
        .Caption(.VirtualToItem(Index), 0) = Index + 1
    End With
End Sub
The control calls the IUnboundHandler_ReadItem method each time when a virtual item becomes visible.  Important to notice is that the Items.VirtualToItem property is used to convert the index of the virtual item to the index of the item in the list.
  • Open the form's code and add handler for the Form_Load event like follows:
Private Sub Form_Load()
    With List1
        .BeginUpdate
            .Columns.Add "Column 1"
            
            .VirtualMode = True
            Set .UnboundHandler = New Class1
        .EndUpdate
    End With
End Sub
  • Save the project
  • Run the project

The sample runs the control in the virtual mode. The control calls the IUnboundHandler_ItemsCount property when UnboundHandler property is set. The IUnboundHandler_ReadItem method is invoked when a record needs to be displayed.

38:
We assume that you are already familiar with the "Displaying a table, using the Virtual Mode". Let's suppose that we have a table and we need to display its records in the control. 
  • Create a new project (Project1)
  • Add a control to the form ( List1 )
  • Create a new class module ( Class1 ) and add it to the project
  • Add a new variable rs, of Object type like: Public rs as Object. In the following sample, the rs variable holds a reference to an ADO.Recordset object
  • Add a new procedure AttachTable like follows:
Public Sub AttachTable(ByVal strTable As String, ByVal strPath As String, ByVal g As EXLISTLibCtl.List)
    Set rs = CreateObject("ADODB.Recordset")
    rs.Open strTable, "Provider=Microsoft.Jet.OLEDB.4.0;Data Source= " & strPath, 3, 3
    With g
        .BeginUpdate
            With .Columns
                Dim f As Variant
                For Each f In rs.Fields
                    .Add f.Name
                Next
            End With
        .EndUpdate
    End With
End Sub

        The AttachTable subroutine opens a table using ADO, and insert in the control's Columns collection a new column for each field found in the table. 

  • Type "Implements IUnboundHandler" at the beginning of the class
  • Implement the IUnboundHandler_ItemsCount property like follows:
Private Property Get IUnboundHandler_ItemsCount(ByVal Source As Object) As Long
    IUnboundHandler_ItemsCount = rs.RecordCount
End Property

        In this case the IUnboundHandler_ItemsCount property the number of records in the table. 

  • Implement the IUnboundHandler_ReadItem method like follows:
Private Sub IUnboundHandler_ReadItem(ByVal Index As Long, ByVal Source As Object)
    rs.Move Index, 1
    Dim i As Long, l As Long
    With Source.Items
        i = 0
        l = .VirtualToItem(Index)
        Dim f As Variant
        For Each f In rs.Fields
            .Caption(l, i) = f.Value
            i = i + 1
        Next
    End With
End Sub

The  IUnboundHandler_ReadItem method moves the current record using the rs.Move method, at the record with the specified index, and loads values for each cell in the item. If you need to apply colors, font attributes, ... to the items in the control, your handler may change the CellBold, CellForeColor, ... properties. 

  • Open the form's code, and add a new variable n like: Dim n As New Class1
  • Add a handler for the Form_Load event like follows:
Private Sub Form_Load()
    With List1
        .BeginUpdate
        
            n.AttachTable "Select * from Orders", "D:\Exontrol\ExList\sample\sample.mdb", List1
            
            .VirtualMode = True
            Set .UnboundHandler = n
            
        .EndUpdate
    End With
End Sub

The AttachTable method opens the table, and fills the control's Columns collection. The AttachTable method needs to be called before putting the control on virtual mode, because properties of the rs object are called in the ItemsCount and ReadItem methods. 

  • Save the project
  • Run the project
39:
We assume that you are already familiar with the "Displaying a table, using the Virtual Mode", "Editing a table, using the Virtual Mode". Let's suppose that we want to display a column with the current position for each record in the table. In this case, we need to add a new column, and we need to change the ReadItem method like follows:
  • The Form_Load event should look like:
Private Sub Form_Load()
    With List1
        .BeginUpdate
        
            n.AttachTable "Select * from Orders", "D:\Exontrol\ExList\sample\sample.mdb", List1
            
            .VirtualMode = True
            Set .UnboundHandler = n
            
            With .Columns.Add("Position")
                .Position = 0
            End With
            
        .EndUpdate
    End With
End Sub
  • The IUnboundHandler_ReadItem method looks like following:
Private Sub IUnboundHandler_ReadItem(ByVal Index As Long, ByVal Source As Object)
    rs.Move Index, 1
    Dim i As Long, l As Long
    With Source.Items
        i = 0
        l = .VirtualToItem(Index)
        Dim f As Variant
        For Each f In rs.Fields
            .Caption(l, i) = f.Value
            i = i + 1
        Next
        .Caption(l, "Position") = Index + 1
    End With
End Sub

For instance, if you need to have a column that computes its value based on the other columns, it can be done like this:

.Caption(l, "Column") = .Caption(l, "Quantity") * .Caption(l, "UnitPrice")
40:
The following tutorial will show how to run the control in virtual mode. The sample is a simple MFC dialog based application. Anyway, if your application is different than a MFC dialog based, the base things you need are here, so please find that the following information is useful.
  • Create a new project using MFC AppWizard ( exe ) ( ADOVirtual )
  • Select Dialog based, for the type of the application
  • Insert the control to the application's main dialog ( Insert ActiveX Control ) 
  • Save the Project
  • Open the MFC Class Wizard, by pressing CTRL + W
  • Add a new member variable for IDC_LIST1 resource called m_list. In the meanwhile, please notice that the wizard will ask you 'The ActiveX Control "ExList ActiveX Control" has not been inserted into the project. Developer Studio will do this now and generate a C++ wrapper class for it', and you need to click ok, by following the steps that wizard will ask you to do in order to insert the C++ wrapper classes. ( CList1, CItems, CColumns, CColumn, COleFont, CPicture )
  • Save the Project
  • Open the Dialog Properties, and click the "Clip siblings" and "Clip children"
  • Add a new MFC based class, CUnboundHandler derived from the CCmdTarget. We define the CUnboundHandler class to implement the IUnboundHandler interface. 
  • Import the control's definition using the #import directive, to the CUnboundHandler class like follows:
#import "c:\winnt\system32\exlist.dll

The #import directive is used to incorporate information from a type library. The content of the type library is converted into C++ classes, mostly describing the COM interfaces. The path to the file need to be changed if the dll is somewhere else. After building the project, the environment generates a namespace EXLISTLib. The generated namespace includes definition for IUnboundHandler interface. It can be accessed using the declaration EXLISTLib::IUnboundHandler

  • By default, the destructor of the CUnboundHandler class is declared as protected. The  destructor needs to be declared as public ( Remove the protected keyword before ~CUnboundHandler ).
  • Implementing the IUnboundHandler interface using the DECLARE_INTERFACE_MAP, BEGIN_INTERFACE_PART and END_INTERFACE_PART macros. The following snippet needs to be inserted in the class definition like
DECLARE_INTERFACE_MAP()

public:
	BEGIN_INTERFACE_PART(Handler, EXLISTLib::IUnboundHandler)
        	STDMETHOD(get_ItemsCount)(long* pVal);
		STDMETHOD(raw_ReadItem)(long Index, IDispatch * Source);
    	END_INTERFACE_PART(Handler)  

The CUnboundHandler class definition should look like follows ( we have removed the comments added by the wizard ):

#import "c:\winnt\system32\exlist.dll"  

class CUnboundHandler : public CCmdTarget
{
	DECLARE_DYNCREATE(CUnboundHandler)

	CUnboundHandler();           // protected constructor used by dynamic creation

DECLARE_INTERFACE_MAP()

public:
	BEGIN_INTERFACE_PART(Handler, EXLISTLib::IUnboundHandler)
        	STDMETHOD(get_ItemsCount)(long* pVal);
			STDMETHOD(raw_ReadItem)(long Index, IDispatch * Source);
    END_INTERFACE_PART(Handler)

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CUnboundHandler)
	//}}AFX_VIRTUAL

// Implementation
	virtual ~CUnboundHandler();

	// Generated message map functions
	//{{AFX_MSG(CUnboundHandler)
		// NOTE - the ClassWizard will add and remove member functions here.
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()
};
  • Add  INTERFACE_PART definition in the UnboundHandler.cpp file like follows:
BEGIN_INTERFACE_MAP(CUnboundHandler, CCmdTarget)
	INTERFACE_PART(CUnboundHandler, __uuidof(EXLISTLib::IUnboundHandler), Handler)
END_INTERFACE_MAP()
  • Write the get_ItemsCount property like follows:
STDMETHODIMP CUnboundHandler::XHandler::get_ItemsCount(IDispatch * Source,long* pVal)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( pVal )
	{
		*pVal = 25000;
		return S_OK;;
	}
	return E_POINTER;
}
  • Write the raw_ReadItem method like follows:
STDMETHODIMP CUnboundHandler::XHandler::raw_ReadItem(long Index, IDispatch * Source)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);

	// gets the source control 
	EXLISTLib::IList* pList = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXLISTLib::IList), (LPVOID*)&pList ) ) )
	{
		// assigns the value for each cell.
		pList->Items->Caption[ pList->Items->VirtualToItem[Index] ][ _variant_t((long)0) ] = _variant_t( Index );
		pList->Release();
	}
	return S_OK;
}
  • Add implementation for QueryInterface, AddRef and Release methods of IUnknown interface like follows:
STDMETHODIMP CUnboundHandler::XHandler::QueryInterface( REFIID riid, void** ppvObject)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( ppvObject )
	{
		if ( IsEqualIID( __uuidof(IUnknown), riid ) )
		{
			*ppvObject = static_cast<IUnknown*>( this );
			AddRef();
			return S_OK;
		}
		if ( IsEqualIID( __uuidof( EXGRIDLib::IUnboundHandler), riid ) )
		{
			*ppvObject = static_cast<EXGRIDLib::IUnboundHandler*>( this );
			AddRef();
			return S_OK;
		}
		return E_NOINTERFACE;
	}
	return E_POINTER;
}

STDMETHODIMP_(ULONG) CUnboundHandler::XHandler::AddRef()
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	return 1;
}

STDMETHODIMP_(ULONG) CUnboundHandler::XHandler::Release()
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	return 0;
}
  • The CUnboundHandler class implementation should look like:
IMPLEMENT_DYNCREATE(CUnboundHandler, CCmdTarget)

BEGIN_INTERFACE_MAP(CUnboundHandler, CCmdTarget)
	INTERFACE_PART(CUnboundHandler, __uuidof(EXLISTLib::IUnboundHandler), Handler)
END_INTERFACE_MAP()

CUnboundHandler::CUnboundHandler()
{
}

CUnboundHandler::~CUnboundHandler()
{
}

STDMETHODIMP CUnboundHandler::XHandler::get_ItemsCount(long* pVal)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( pVal )
	{
		*pVal = 25000;
		return S_OK;;
	}
	return E_POINTER;
}

STDMETHODIMP CUnboundHandler::XHandler::raw_ReadItem(long Index, IDispatch * Source)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);

	// gets the source control 
	EXLISTLib::IList* pList = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXLISTLib::IList), (LPVOID*)&pList ) ) )
	{
		// assigns the value for each cell.
		pList->Items->Caption[ pList->Items->VirtualToItem[Index] ][ _variant_t((long)0) ] = _variant_t( Index );
		pList->Release();
	}
	return S_OK;
}

STDMETHODIMP CUnboundHandler::XHandler::QueryInterface( REFIID riid, void** ppvObject)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( ppvObject )
	{
		if ( IsEqualIID( __uuidof(IUnknown), riid ) )
		{
			*ppvObject = static_cast<IUnknown*>( this );
			AddRef();
			return S_OK;
		}
		if ( IsEqualIID( __uuidof( EXLISTLib::IUnboundHandler), riid ) )
		{
			*ppvObject = static_cast<EXLISTLib::IUnboundHandler*>( this );
			AddRef();
			return S_OK;
		}
		return E_NOINTERFACE;
	}
	return E_POINTER;
}

STDMETHODIMP_(ULONG) CUnboundHandler::XHandler::AddRef()
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	return 1;
}

STDMETHODIMP_(ULONG) CUnboundHandler::XHandler::Release()
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	return 0;
}

BEGIN_MESSAGE_MAP(CUnboundHandler, CCmdTarget)
	//{{AFX_MSG_MAP(CUnboundHandler)
		// NOTE - the ClassWizard will add and remove mapping macros here.
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

After all these steps we have defined the class CUnboundHandler that implements the IUnboundHandler interface. All that we need to do from now, is to add a column to the control, and to set the VirtualMode and UnboundHanlder properties like follows:

  • Open the definition of the application's main dialog ( CADOVirtualDlg )
  • Include the definition of the CUnboundHandler class to CADOVirtualDlg using:
#include "UnboundHandler.h"
  • Add a new member of CUnboundHandler type to the CADOVirtualDlg class like:
CUnboundHandler m_unboundHandler;
  • Open the implementation file for the application's main dialog ( CADOVirtualDlg )
  • Add the definition for CColumns class ( a wrapper class for the control ) at the beginning of the file
 #include "Columns.h"
  • Locate the OnInitDialog() method and add the following code ( after the "// TODO: Add extra initialization here" ):
m_list.BeginUpdate();
	m_list.GetColumns().Add( _T("Column 1") );
	m_list.SetVirtualMode( TRUE );
	m_list.SetUnboundHandler( &m_unboundHandler.m_xHandler );
m_list.EndUpdate();
  • Save, Compile and Run the project

The tutorial shows how to put the control on virtual mode. The sample loads the numbers from 0 to 24999.  

Now, that we got the idea how to implement the IUnboundHandler let's say that we want to change the sample to load an edit an ADO recordset. The following tutorials shows how to display a table and how to add code in order to let user edits the data.

  • Open the definition of the  CUnboundHandler class
  • Import the Microsoft ADO Type Library to the CUnboundHandler class like follows:
#import <msado15.dll> rename ( "EOF", "adoEOF" )

The #import directive generates the ADODB namspace. The ADODB namspace includes all definitions in the Microsoft ADO Type Library.

  • Include a member of ADODB::_RecordsetPtr called m_spRecordset. The m_spRecordset member will handle data in the ADO table. 
ADODB::_RecordsetPtr m_spRecordset;
  • Add definition for AttachTable function like follows:
virtual void AttachTable( EXLISTLib::IList* pList, LPCTSTR szTable, LPCTSTR szDatabase );

Now, the CUnboundHandler class definition should look like follows:

#import "c:\winnt\system32\exlist.dll"  
#import <msado15.dll> rename ( "EOF", "adoEOF" )

class CUnboundHandler : public CCmdTarget
{
	DECLARE_DYNCREATE(CUnboundHandler)

	CUnboundHandler();           // protected constructor used by dynamic creation

DECLARE_INTERFACE_MAP()

public:
	BEGIN_INTERFACE_PART(Handler, EXLISTLib::IUnboundHandler)
        	STDMETHOD(get_ItemsCount)(long* pVal);
			STDMETHOD(raw_ReadItem)(long Index, IDispatch * Source);
    END_INTERFACE_PART(Handler)

	virtual void AttachTable( EXLISTLib::IList* pList, LPCTSTR szTable, LPCTSTR szDatabase );

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CUnboundHandler)
	//}}AFX_VIRTUAL

// Implementation
	virtual ~CUnboundHandler();

	// Generated message map functions
	//{{AFX_MSG(CUnboundHandler)
		// NOTE - the ClassWizard will add and remove member functions here.
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()

	ADODB::_RecordsetPtr m_spRecordset;
};
  • Open the implementation file for CUnboundHandler class (UnboundHandler.cpp file )
  • Add the implementation for AttachTable function like follows:
void CUnboundHandler::AttachTable( EXLISTLib::IList* pList, LPCTSTR szTable, LPCTSTR szDatabase )
{
	if ( SUCCEEDED( m_spRecordset.CreateInstance( "ADODB.Recordset") ) )
	{
		try
		{
			CString strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=";
			strConnection += szDatabase;
			if ( SUCCEEDED( m_spRecordset->Open(_variant_t( szTable ), _variant_t(strConnection), ADODB::adOpenStatic, ADODB::adLockPessimistic, NULL ) ) )
			{
				pList->BeginUpdate();
				for ( long i = 0; i < m_spRecordset->Fields->GetCount(); i++ )
					pList->GetColumns()->Add( m_spRecordset->Fields->GetItem( _variant_t( i ) )->Name );
				pList->EndUpdate();
			}
		}
		catch ( _com_error& e )
		{
			AfxMessageBox( e.Description() );
		}

	}
}

The AttachTable function opens a recordset, and adds a new column to the control's Columns collection for each field found in the recordset. 

  • Change the get_ItemsCount property like follows:
STDMETHODIMP CUnboundHandler::XHandler::get_ItemsCount(IDispatch * Source,long* pVal)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( pVal )
	{
		*pVal = pThis->m_spRecordset->RecordCount;
		return S_OK;;
	}
	return E_POINTER;
}

The ItemsCount property specifies that the control displays all records in the recordset

  • Change the raw_ReadItem method like follows:
STDMETHODIMP CUnboundHandler::XHandler::raw_ReadItem(long Index, IDispatch * Source)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis->m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

	// gets the source control 
	EXLISTLib::IList* pList = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXLISTLib::IList), (LPVOID*)&pList ) ) )
	{
		// assigns the value for each cell.
		long l = pList->Items->VirtualToItem[ Index ];
		for ( long i = 0; i < pThis->m_spRecordset->Fields->GetCount(); i++ )
			pList->Items->Caption[ pList->Items->VirtualToItem[ Index ] ][ _variant_t( i )] = pThis->m_spRecordset->Fields->GetItem( _variant_t( i ) )->Value;
		pList->Release();
	}
	return S_OK;
}

The ReadItem method moves the position of the current record in the recordset, and sets the value for each cell in the item.

The implementation for CUnbundHandler class should look like:

IMPLEMENT_DYNCREATE(CUnboundHandler, CCmdTarget)

BEGIN_INTERFACE_MAP(CUnboundHandler, CCmdTarget)
	INTERFACE_PART(CUnboundHandler, __uuidof(EXLISTLib::IUnboundHandler), Handler)
END_INTERFACE_MAP()

CUnboundHandler::CUnboundHandler()
{
}

CUnboundHandler::~CUnboundHandler()
{
}

STDMETHODIMP CUnboundHandler::XHandler::get_ItemsCount(long* pVal)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( pVal )
	{
		*pVal = pThis->m_spRecordset->RecordCount;
		return S_OK;;
	}
	return E_POINTER;
}

STDMETHODIMP CUnboundHandler::XHandler::raw_ReadItem(long Index, IDispatch * Source)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis->m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

	// gets the source control 
	EXLISTLib::IList* pList = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXLISTLib::IList), (LPVOID*)&pList ) ) )
	{
		// assigns the value for each cell.
		long l = pList->Items->VirtualToItem[ Index ];
		for ( long i = 0; i < pThis->m_spRecordset->Fields->GetCount(); i++ )
			pList->Items->Caption[ l ][ _variant_t( i )] = pThis->m_spRecordset->Fields->GetItem( _variant_t( i ) )->Value;
		pList->Release();
	}
	return S_OK;
}

void CUnboundHandler::AttachTable( EXLISTLib::IList* pList, LPCTSTR szTable, LPCTSTR szDatabase )
{
	if ( SUCCEEDED( m_spRecordset.CreateInstance( "ADODB.Recordset") ) )
	{
		try
		{
			CString strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=";
			strConnection += szDatabase;
			if ( SUCCEEDED( m_spRecordset->Open(_variant_t( szTable ), _variant_t(strConnection), ADODB::adOpenStatic, ADODB::adLockPessimistic, NULL ) ) )
			{
				pList->BeginUpdate();
				for ( long i = 0; i < m_spRecordset->Fields->GetCount(); i++ )
					pList->GetColumns()->Add( m_spRecordset->Fields->GetItem( _variant_t( i ) )->Name );
				pList->EndUpdate();
			}
		}
		catch ( _com_error& e )
		{
			AfxMessageBox( e.Description() );
		}
	}
}

STDMETHODIMP CUnboundHandler::XHandler::QueryInterface( REFIID riid, void** ppvObject)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( ppvObject )
	{
		if ( IsEqualIID( __uuidof(IUnknown), riid ) )
		{
			*ppvObject = static_cast<IUnknown*>( this );
			AddRef();
			return S_OK;
		}
		if ( IsEqualIID( __uuidof( EXLISTLib::IUnboundHandler), riid ) )
		{
			*ppvObject = static_cast<EXLISTLib::IUnboundHandler*>( this );
			AddRef();
			return S_OK;
		}
		return E_NOINTERFACE;
	}
	return E_POINTER;
}

STDMETHODIMP_(ULONG) CUnboundHandler::XHandler::AddRef()
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	return 1;
}

STDMETHODIMP_(ULONG) CUnboundHandler::XHandler::Release()
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	return 0;
}

BEGIN_MESSAGE_MAP(CUnboundHandler, CCmdTarget)
	//{{AFX_MSG_MAP(CUnboundHandler)
		// NOTE - the ClassWizard will add and remove mapping macros here.
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
  • Locate the OnInitDialog method in the implementation file of the application's main dialog ( AdoVirtualDlg.cpp )
  • Add the following code to the OnInitDialog method:
 EXLISTLib::IListPtr spList = NULL;
m_list.GetControlUnknown()->QueryInterface( &spList );
m_list.BeginUpdate();
	m_unboundHandler.AttachTable( spList, _T("Orders"), _T("D:\\Exontrol\\ExList\\sample\\sample.mdb") );
	m_list.SetVirtualMode( TRUE );
	m_list.SetUnboundHandler( &m_unboundHandler.m_xHandler );
m_list.EndUpdate();

The AttachTable function is called before setting the UnboundHandler property. The AttachTable function opens a recordset giving the SQL phrase and the database. The AttachTable function loads also the control's Columns collection from the Fields collection of the recordset.

  • Save, Compile and Run the project

After all these your control will be able to display a table using the virtual mode. Now, we need to add some changes in order to let user edits the data in the control.

  • Change the AllowEdit property of the control like follows:
 m_list.SetAllowEdit( TRUE );

The OnInitDialog looks like:

 EXLISTLib::IListPtr spList = NULL;
m_list.GetControlUnknown()->QueryInterface( &spList );
m_list.BeginUpdate();
	m_unboundHandler.AttachTable( spList, _T("Orders"), _T("D:\\Exontrol\\ExList\\sample\\sample.mdb") );
	m_list.SetAllowEdit( TRUE );
	m_list.SetVirtualMode( TRUE );
	m_list.SetUnboundHandler( &m_unboundHandler.m_xHandler );
m_list.EndUpdate();
  • Add the definition for the CItems class to CAdoVirtualDlg implementation file:
#include "Items.h"
  • Add a new handler for AfterCellEdit event:
void CADOVirtualDlg::OnAfterCellEditList1(long Index, long ColIndex, LPCTSTR NewCaption) 
{
	m_unboundHandler.Change(NewCaption,m_list.GetItems().GetItemToVirtual(Index),ColIndex);
}
  • Add a new function definition ( Change ) to the CUnboundHandler class:
virtual void Change( LPCTSTR szCaption, long Index, long ColIndex );

The CUnboundHandler class definition should look like:

#import "c:\winnt\system32\exlist.dll"  
#import <msado15.dll> rename ( "EOF", "adoEOF" )

class CUnboundHandler : public CCmdTarget
{
	DECLARE_DYNCREATE(CUnboundHandler)

	CUnboundHandler();           // protected constructor used by dynamic creation

DECLARE_INTERFACE_MAP()

public:
	BEGIN_INTERFACE_PART(Handler, EXLISTLib::IUnboundHandler)
        	STDMETHOD(get_ItemsCount)(long* pVal);
			STDMETHOD(raw_ReadItem)(long Index, IDispatch * Source);
    END_INTERFACE_PART(Handler)

	virtual void AttachTable( EXLISTLib::IList* pList, LPCTSTR szTable, LPCTSTR szDatabase );
	virtual void Change( LPCTSTR szCaption, long Index, long ColIndex );

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CUnboundHandler)
	//}}AFX_VIRTUAL

// Implementation
	virtual ~CUnboundHandler();

	// Generated message map functions
	//{{AFX_MSG(CUnboundHandler)
		// NOTE - the ClassWizard will add and remove member functions here.
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()

	ADODB::_RecordsetPtr m_spRecordset;
};
  • Add the Change function's body like follows:
void CUnboundHandler::Change( LPCTSTR szCaption, long Index, long ColIndex )
{
	m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );
	m_spRecordset->Fields->GetItem( _variant_t( ColIndex ) )->Value = szCaption;
	m_spRecordset->Update();
}
  • Save, Compile and Run the project.

If you need to apply colors, font attributes, ... for items or cells while the control is running in the virtual mode, the changes should be done in the raw_ReadItem method like follows:

STDMETHODIMP CUnboundHandler::XHandler::raw_ReadItem(long Index, IDispatch * Source)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis->m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

	// gets the source control 
	EXLISTLib::IList* pList = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXLISTLib::IList), (LPVOID*)&pList ) ) )
	{
		long l = pList->Items->VirtualToItem[ Index ];
		// assigns the value for each cell.
		for ( long i = 0; i < pThis->m_spRecordset->Fields->GetCount(); i++ )
			pList->Items->Caption[ l ][ _variant_t( i )] = pThis->m_spRecordset->Fields->GetItem( _variant_t( i ) )->Value;

		if ( pList->Items->Caption[ _variant_t( l ) ][ _variant_t( _T("ShipRegion") )] == _variant_t( _T("RJ") ) )
			pList->Items->put_ItemForeColor( l , RGB(0,0,255 ) );
		if ( pList->Items->Caption[ _variant_t( l ) ][ _variant_t( _T("ShipRegion") )] == _variant_t( _T("SP") ) )
			pList->Items->put_ItemBold( l , TRUE );

		pList->Release();
	}
	return S_OK;
}

While compiling the project the compiler displays warnings like: "warning C4146: unary minus operator applied to unsigned type, result still unsigned". You have to include the :

#pragma warning( disable : 4146 )

before importing the type libraries.

#pragma warning( disable : 4146 )
#import "c:\winnt\system32\exlist.dll"
#import <msado15.dll> rename ( "EOF", "adoEOF" )
41:
The ItemFromPoint property retrieves the index of the item from the cursor. The ColumnFromPoint property gets the index of the column's header from the cursor. The following sample unselect the last selected item, if the user clicks a section of the control that has no items:
Private Sub List1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim i As Long, c As Long
    Dim b As Boolean
    b = True
    With List1
        i = .ItemFromPoint(-1, -1, c)
        If (i < 0) Then
            If (.HeaderVisible) Then
                c = .ColumnFromPoint(-1, -1)
                b = c < 0
            End If
            If (b) Then
                With .Items
                    If (.SelectCount > 0) Then
                        .SelectItem(.SelectedItem(0)) = False
                    End If
                End With
            End If
        End If
    End With
End Sub
The ShowFocusRect property hides the thin rectangle around the focused item. The FocusItem property of Items object gets the index of the focused item.
42:
The SortOrder property of the Column object specifies the column's sort order. The following function retrieves the index of column that's sorted, or -1 if the control has no sorted columns:
Private Function getSortingColumn(ByVal g As EXLISTLibCtl.List) As Long
    Dim c As EXLISTLibCtl.Column
    For Each c In g.Columns
        If Not c.SortOrder = EXLISTLibCtl.SortNone Then
            getSortingColumn = c.Index
            Exit Function
        End If
    Next
    getSortingColumn = -1
End Function
43:
You can get the client coordinates of the cell using the following VB sample:
Private Sub getCellPos(ByVal l As EXLISTLibCtl.List, ByVal nItem As Long, ByVal nColumn As Long, x As Long, y As Long)
    x = -l.ScrollPos(False)
    With l
        Dim c As EXLISTLibCtl.Column
        For Each c In .Columns
            If (c.Visible) Then
                If (c.Position < .Columns(nColumn).Position) Then
                    x = x + c.Width
                End If
            End If
        Next
        y = 0
        If (.HeaderVisible) Then
            y = y + .HeaderHeight
        End If
        With .Items
            Dim i As Long
            i = .FirstVisibleItem
            While Not (i = nItem) And (i >= 0)
                y = y + .ItemHeight(i)
                i = .NextVisibleItem(i)
            Wend
        End With
    End With
End Sub

The getCellPos method gets the x, y client coordinates of the cell ( nItem, nColumn ). The nItem indicates the index of the item, and the nColumn indicates the index of the column. Use the ClientToScreen API function to convert the client coordinates to screen coordinates like bellow:

Private Type POINTAPI
        x As Long
        y As Long
End Type
Private Declare Function ClientToScreen Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long

In the following MouseDown handler the ItemFromPoint method determines the cell from the cursor. The sample displays an exPopupMenu control at the beginning of the cell, when user right clicks the cell:

Private Sub List1_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
    If Button = 2 Then
        With List1
            Dim i As Long, c As Long
            i = .ItemFromPoint(x / Screen.TwipsPerPixelX, y / Screen.TwipsPerPixelY, c)
            If (i >= 0) Then
                ' Selects the item when user does a right click
                List1.Items.SelectItem(i) = True
                ' Gets the client coordinates of the cell
                Dim xCell As Long, yCell As Long
                getCellPos List1, i, c, xCell, yCell
                ' Converts the client coordinates to the screen coordinates
                Dim p As POINTAPI
                p.x = xCell
                p.y = yCell
                ClientToScreen List1.hwnd, p
                ' Displays the exPopupMenu control at specified position
                PopupMenu1.HAlign = EXPOPUPMENULibCtl.exLeft
                Debug.Print "You have selected " & PopupMenu1.Show(p.x, p.y)
            End If
        End With
    End If
End Sub
44:
Starting with the version 1.0.6.5, you can filter items given numeric rules. If the FilterType property is exNumeric, the Filter property may include operators like <, <=, =, <>, >= or > and numbers to define rules to include numbers in the control's list. The Filter property should be of the following format "operator number [operator number ...]". For instance, the "> 10" indicates all numbers greater than 10. The "<>10 <> 20" filter indicates all numbers except 10 and 20. The "> 10 < 100" filter indicates all numbers greater than 10 and less than 100. The ">= 10 <= 100 <> 50" filter includes all numbers from 10 to 100 excepts 50. The "10" filter includes only 10 in the list. The "=10 =20" includes no items in the list because after control filters only 10 items, the second rule specifies only 20, and so we have no items. The Filter property may include unlimited rules. A rule is composed by an operator and a number. The rules are separated by space characters.
45:
Please check the FilterCriteria property, to specify whether you need to filter items using OR, NOT operators between columns. If the FilterCriteria property is empty, or not valid, the filter uses the AND operator between columns. Use the FilterCriteria property to specify how the items are filtered.
Older versions, does not support FilterCriteria, so you may want to check the following. Lets say you have a database with Firstname, Lastname, Address. When filling the eXList, create an additional field with all other fields concatenated.

For example:
John, Doe, Summer St, JohnDoeSummer St
Make the extra field not visible and use it for fast searching.

Sub cmdSearch_Click ()
     lstContacts.Columns(3).Filter = "*" & txtSearch.Text & "*"
     lstContacts.Columns(3).FilterType = exPattern
     lstContacts.ApplyFilter
End Sub

Thanks to Mats Fälldin, who submitted the note.

46:
The most probably thing you have is items with different height So, if your application is using ItemHeight, CellSingleLine, or other properties or methods that may change the item's height you need to set the ScrollBySingleLine property on True. By default, the ScrollBySingleLine property is False.
47:
You have several options like follows:
  1. EXLISTLib.List
    Private Sub test(ByVal l As EXLISTLib.List)
        With l
        End With
    End Sub
  2. Control
    Private Sub test(ByVal l As Control)
        With l
        End With
    End Sub
  3. Object
    Private Sub test(ByVal l As Object)
        With l
        End With
    End Sub

In the first case you need to call test List1.Object, in the second or third option you may call test List1. For instance, the test procedure may look like follows:

Private Sub test(ByVal l As Control)
    With l
        .BeginUpdate
            With .Items
                .Add .Count
                .Add .Count
                .Add .Count
            End With
        .EndUpdate
    End With
End Sub
48:
The component supports skinning parts of the control, including the selected item. Please check the control's help file for the Add method of the Appearance object. There you will find almost everything you need to change the visual appearance for most of the UI parts of the control. Shortly, the idea is that identifier of the skin being added to the Appearance collection is stored in the first significant byte of property of the color type. In our case, we know that the SelBackColor property changes the background color for the selected item. This is what we need to change. In other words, we need to change the visual appearance for the selected item, and that means changing the background color of the selected item. So, the following code ( blue code ) changes the appearance for the selected item:
With List1
    .VisualAppearance.Add &H34, App.Path + "\aqua.ebn"
    .SelBackColor = &H34000000
End With

Please notice that the 34 hexa value is arbitrary chosen, it is not a predefined value. Shortly, we have added a skin with the identifier 34, and we specified that the SelBackColor property should use that skin, in order to change the visual appearance for the selected item. Also, please notice that the 34 value is stored in the first significant byte, not in other position. For instance, the following sample doesn't use any skin when displaying the selected item:

With List1
    .VisualAppearance.Add &H34, App.Path + "\aqua.ebn"
    .SelBackColor = &H34
End With  

This code ( red code ) DOESN'T use any skin, because the 34 value is not stored in the higher byte of the color value. The sample just changes the background color for the selected item to some black color ( RGB(0,0,34 ) ). So, please pay attention when you want to use a skin and when to use a color. Simple, if you are calling &H34000000, you have 34 followed by 6 ( six ) zeros, and that means the first significant byte of the color expression. Now, back to the problem. The next step is how we are creating skins? or EBN files? The Exontrol's exbutton component includes a builder tool that saves skins to EBN files. So, if you want to create new skin files, you need to download and install the exbutton component from our web site. Once that the exbutton component is installed, please follow the steps.

Let's say that we have a BMP file, that we want to stretch on the selected item's background.

  1. Open the VB\Builder or VC\Builder sample
  2. Click the New File button ( on the left side in the toolbar ), an empty skin is created. 
  3. Locate the Background tool window and select the Picture\Add New item in the menu, the Open file dialog is opened.
  4. Select the picture file ( GIF, BMP, JPG, JPEG ). You will notice that the visual appearance of the focused object in the skin is changed, actually the picture you have selected is tiled on the object's background.
  5. Select the None item, in the Background tool window, so the focused object in the skin is not displaying anymore the picture being added.
  6. Select the Root item in the skin builder window ( in the left side you can find the hierarchy of the objects that composes the skin ), so the Root item is selected, and so focused.
  7. Select the picture file you have added at the step 4, so the Root object is filled with the picture you have chosen.
  8. Resize the picture in the Background tool window, until you reach the view you want to have, no black area, or change the CX and CY fields in the Background tool window, so no black area is displayed.
  9. Select Stretch button in the Background tool window, so the Root object stretches the picture you have selected.
  10. Click the Save a file button, and select a name for the new skin, click the Save button after you typed the name of the skin file. Add the .ebn extension.
  11. Close the builder

You can always open the skin with the builder and change it later, in case you want to change it.

Now, create a new project, and insert the component where you want to use the skin, and add the skin file to the Appearance collection of the object, using blue code, by changing the name of the file or the path where you have selected the skin. Once that you have added the skin file to the Appearance collection, you can change the visual appearance for parts of the controls that supports skinning. Usually the properties that changes the background color for a part of the control supports skinning as well.
49:
The HitTestInfoEnum.exHTBetween value indicates whether the cursor is between two items. For instance, you can provide a visual effect for the item while performing OLE drag and drop operations, when the cursor is in the top half of the item, using the exDragDropListTop, or in the second half using the exDragDropListBottom value. In the same way you can provide a visual effect when the cursor is over or between two items, using the exDragDropListOver and exDragDropListBetween values. The ItemFromPoint property retrieves the handle of the item from the cursor, and retrieves also a code (HitTestInfo parameter), to indicate the part in the item where the cursor is. So, the exHTBetween value indicates whether the cursor is between items. The exHTBetween is an OR combination with other predefined values, so you must call HitTestInfo AND 0x1000 to check if the cursor is between rows/items as in the following samples:

The following VB sample displays a message when the cursor is between two items:

Private Sub List1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim i As Long, c As Long, h As HitTestInfoEnum
    i = List1.ItemFromPoint(-1, -1, c, h)
    If Not (i = 0) Then
        If (h And exHTBetween) Then
            Debug.Print "The cursor is between two items."
        Else
            Debug.Print "The cursor is over the item."
        End If
    End If
End Sub

The following VB.NET sample displays a message when the cursor is between two items:

Private Sub AxList1_MouseMoveEvent(ByVal sender As System.Object, ByVal e As AxEXLISTLib._IListEvents_MouseMoveEvent) Handles AxList1.MouseMoveEvent
    With AxList1
        Dim c As Integer, h As EXLISTLib.HitTestInfoEnum
        Dim i As Integer = .get_ItemFromPoint(-1, -1, c, h)
        If Not i = 0 Then
            If (h And EXLISTLib.HitTestInfoEnum.exHTBetween) Then
                Debug.Print("The cursor is between items.")
            Else
                Debug.Print("The cursor is over the item.")
            End If
        End If
    End With
End Sub

The following C# sample displays a message when the cursor is between two items:

private void axList1_MouseMoveEvent(object sender, AxEXLISTLib._IListEvents_MouseMoveEvent e)
{
    int c = 0;
    EXLISTLib.HitTestInfoEnum h;
    int i = axList1.get_ItemFromPoint(-1, -1, out c, out h);
    if (i != 0)
        if ( (h & EXLISTLib.HitTestInfoEnum.exHTBetween) == EXLISTLib.HitTestInfoEnum.exHTBetween )
            System.Diagnostics.Debug.Print("The cursor is between items.");
        else
            System.Diagnostics.Debug.Print("The cursor is over the item.");
}

The following C++ sample displays a message when the cursor is between two items:

void OnMouseMoveList1(short Button, short Shift, long X, long Y) 
{
	long c = 0, h = 0;
	long i = m_tree.GetItemFromPoint( -1, -1, &c, &h );
	if ( i != 0 )
		if ( h & 0x1000 /*exHTBetween*/ )
			OutputDebugString( "The cursor is between items.\n" );
		else
			OutputDebugString( "The cursor is over the item.\n" );
}

The following VFP sample displays a message when the cursor is between two items:

*** ActiveX Control Event ***
LPARAMETERS button, shift, x, y

local c, hit
c = 0
hit = 0
with thisform.List1
	.Items.DefaultItem = .ItemFromPoint( x, y, @c, @hit )
	if ( .Items.DefaultItem <> 0 )
		if bitand(hit,0x1000) = 0x1000
			wait window nowait "The cursor is between items."
		else
			wait window nowait "The cursor is over the item."
		endif
	endif
endwith
50:
There are several options in order to display a different content for the column. By default, the Items.Caption property indicates the value being shown in the cell.
  1. Column.FormatColumn property specifies a formula to display the column's new content, using predefined functions for numbers, strings, dates and so on.
  2. Change the Value parameter of the FormatColumn event which is fired if the Column.FireFormatColumn property is True. For instance the following sample displays  the second column using current currency format with 2 decimals. The Item parameter of the FormatColumn event indicates the item where the cell is hosted, the ColIndex indicates the column where the cell belongs, while the Value parameter indicates the cell's value before formatting and after. In case you need formatting multiple columns, you can distingue them using the ColIndex parameter.
    Private Sub Form_Load()
        With List1
            .BeginUpdate
            .Columns.Add "A"
            .Columns.Add("B").FireFormatColumn = True ' Index of it is 1
            With .Items
                .Add Array("One", 1)
                .Add Array("Two", 2)
            End With
            .EndUpdate
        End With
    End Sub
    
    Private Sub List1_FormatColumn(ByVal ItemIndex As Long, ByVal ColIndex As Long, Value As Variant)
        Value = FormatCurrency(Value, 2, vbUseDefault)
    End Sub
51:
The OLEDropMode property of the control must be set on exOLEDropManual (1). If this property is set, the control fires the OLEDragDrop event which notifies that the user drags data to the control. The Files collection holds a collection of files being dragged. 

The  following VB sample copies the original icon being displayed in Windows Explorer and displays it on the control:

Private Declare Function SHGetFileInfo Lib "shell32.dll" Alias "SHGetFileInfoA" (ByVal pszPath As String, ByVal dwFileAttributes As Long, psfi As SHFILEINFO, ByVal cbFileInfo As Long, ByVal uFlags As Long) As Long
Private Const SHGFI_OPENICON = &H2                       '  get open icon
Private Const SHGFI_SMALLICON = &H1                      '  get small icon
Private Const SHGFI_SYSICONINDEX = &H4000
Private Const SHGFI_ICON = &H100                         '  get icon
Private Const MAX_PATH = 260
Private Type SHFILEINFO
        hIcon As Long                      '  out: icon
        iIcon As Long          '  out: icon index
        dwAttributes As Long               '  out: SFGAO_ flags
        szDisplayName As String * MAX_PATH '  out: display name (or path)
        szTypeName As String * 80         '  out: type name
End Type

Private iIcon As Long
Private Sub Form_Load()
    iIcon = 1
    With List1
        .BeginUpdate
            .FullRowSelect = False
            .DefaultItemHeight = 18
            .Columns.Add "Icons"
        .EndUpdate
    End With
End Sub

Private Sub List1_OLEDragDrop(ByVal Data As EXLISTLibCtl.IExDataObject, Effect As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    With Data.Files
        If (.Count > 0) Then
            For i = 0 To .Count - 1
                With List1
                    Dim g As SHFILEINFO
                    .BeginUpdate
                    SHGetFileInfo Data.Files.Item(i), 0, g, Len(g), SHGFI_ICON Or SHGFI_SMALLICON
                    .ReplaceIcon g.hIcon
                    .Items.CellImage(List1.Items.Add(Data.Files.Item(i)), 0) = iIcon
                    iIcon = iIcon + 1
                    .EndUpdate
                End With
            Next
        End If
    End With
End Sub

Private Sub List1_OLEDragOver(ByVal Data As EXLISTLibCtl.IExDataObject, Effect As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single, ByVal State As Integer)
    If (Data.Files.Count = 0) Then
        Effect = 0
    End If
End Sub

The sample uses the SHGetFileInfo API function to retrieve the handle of the icon ( HICON ) to be copied and displayed in the control.

52:
The following VB sample removes the selected items:
Private Sub removeSelection2()
    
    List1.BeginUpdate
    With List1.Items
        While .SelectCount > 0
            .Remove .SelectedItem(0)
        Wend
    End With
    List1.EndUpdate

End Sub
53:
You should call the DoEvents method before calling the EnsureVisibleItem method. For /NET you should use the Application.DoEvents method. For Delphi, you should use the Application.ProcessMessages.

A C++ replica for DoEvents could be:

static void DoEvents()
{
	MSG m = {0};
	while ( PeekMessage( &m, NULL, NULL, NULL, PM_REMOVE ) )
	{
		TranslateMessage( &m );
		DispatchMessage( &m );
	}
}
54:
The control provides the Print and Print Preview using the Exontrol's ExPrint component. Please check the printing FAQ for adding Print and Print Preview support in your programming language. 

In order to prevent updating the control during Print and PrintPreview you need to call the BeginUpdate of the control during the Refreshing event of the eXPrint,  and call the EndUpdate once the Refresh event of the eXPrint occurs, like in the following sample.

Private Sub Print1_Refreshing()
    List1.BeginUpdate
End Sub

Private Sub Print1_Refresh()
    List1.EndUpdate
End Sub
55:
The control's ClearFilter method ( or clicking the X button in the filter bar ) does the following:
  • set the Column.Filter property on empty, IF the Column.FilterType property is exNumeric, exCheck or exImage, else
  • set the Column.FilterType property on exAll. IF the Column.FilterOnType property is True, the Column.Filter is set on empty too, else the Column.Filter property remains.

The FilterType property of the Column object indicates the type of the filter to be applied on the column. Generally, you can check for exAll on FiterType unless you are not using the exNumeric, exCheck or exImage type of column's filters. 

The following VB function returns False, if no filter is applied, or True, if any filter is applied. This sample works ok, if no using any of exNumeric, exCheck or exImage types

Private Function hasFilter(ByVal g As Object) As Boolean
    Dim c As Object
    For Each c In g.Columns
        If Not (c.FilterType = 0) Then
            hasFilter = True
            Exit Function
        End If
    Next
    hasFilter = False
End Function

The following VB function returns False, if no filter is applied, or True, if any filter is applied. This sample works for all type of filters:

Private Function hasFilter(ByVal g As Object) As Boolean
    Dim c As Object
    For Each c In g.Columns
        Select Case c.FilterType
            Case 5, 6, 10                           ' exNumeric, exCheck, exImage
                hasFilter = Not (c.Filter.Length = 0)
            Case Else
                hasFilter = Not (c.FilterType = 0)  ' exAll
        End Select
        If (hasFilter) Then
            Exit Function
        End If
    Next
    hasFilter = False
End Function
56:
When loading data from a DataSource and you pass the rst![field] to a cell, you actually send the reference to a field, instead the field's value. The correct way of loading the field's value to the cell's content is rst![field].Value or using the CStr(rst![field]). The Value returns the field's value as specified by the type of the field, while the CStr will returns a copy of the field's value as string.
57:
The ScrollPos property changes the control's scroll position ( horizontal or vertical scroll position ). The OffsetChanged event occurs when the control's scroll horizontal or vertical position is changed, in other words all it is required is calling the ScrollPos during the OffsetChanged like in the following sample. Because the ScrollPos property invokes the OffsetChanged, you must use a member flag ( iSyncing ) to prevent recursive calls:
Private iSyncing As Long

Private Sub List1_OffsetChanged(ByVal Horizontal As Boolean, ByVal NewVal As Long)
    If (iSyncing = 0) Then
        iSyncing = iSyncing + 1
            List2.ScrollPos(Not Horizontal) = NewVal
        iSyncing = iSyncing - 1
    End If
End Sub

Private Sub List2_OffsetChanged(ByVal Horizontal As Boolean, ByVal NewVal As Long)
    If (iSyncing = 0) Then
        iSyncing = iSyncing + 1
            List1.ScrollPos(Not Horizontal) = NewVal
        iSyncing = iSyncing - 1
    End If
End Sub

This sample synchronizes the vertical  / horizontal scroll bars of both controls, so when the user scrolls one of the control's content, the other component is syncing as well.

58:
The KeyPress event notifies your application once the user presses the SPACE key, or any other character. In other words, you can disable handing the space key by setting the KeyAscii parameter on 0 as in the following sample:
Private Sub List1_KeyPress(KeyAscii As Integer)
    With List1
        If (KeyAscii = vbKeySpace) Then ' vbKeySpace is 32
            KeyAscii = 0
        End If
    End With
End Sub
59:
First, you can disable the control, using the Enabled property, but it means that all parts of the control are disabled. Second, you can handle the SelectionChanged event, and call the SelectItem method during it, as explained bellow. The control fires the SelectionChanged event, when the control's selection is changed. You need to prevent any recursive calls of the SelectionChanged event, by using a member that's increased when event starts, and decreased when event ends as in the following sample:
Dim iSelectionChanged As Long

Private Sub List1_SelectionChanged()
    If (iSelectionChanged = 0) Then
        iSelectionChanged = iSelectionChanged + 1
        With List1
            With .Items
                .SelectItem(0) = True
            End With
        End With
        iSelectionChanged = iSelectionChanged - 1
    End If
End Sub

Private Sub Form_Load()
    iSelectionChanged = 0
    With List1
        .BeginUpdate
        .Columns.Add "Default"
        With .Items
            .CellImages(.Add(0), 0) = "1"
            .CellHasCheckBox(.Add(1), 0) = True
            .CellImages(.Add(2), 0) = "1"
            .SelectItem(0) = True
        End With
        .EndUpdate
    End With
End Sub
This sample keeps selected the item with the index 0 ( the first item in the list control ).
60:
The AllowAutoDrag event triggers contiguously while the user drags / hovers the focus/selection of items over the control. The GetAsyncKeyState API method can be used to detect whether the mouse button has been released, and so the drop action occurs. 

The following VB sample displays "Drag" while user dragging the items, and displays "Drop", when drop operation starts.
Private Sub List1_AllowAutoDrag(ByVal Item As Long, ByVal InsertA As Long, ByVal InsertB As Long, Cancel As Boolean)
    With List1
        Debug.Print "Drag"
        If (GetAsyncKeyState(VK_LBUTTON) = 0) Then
            Debug.Print "Drop"
        End If
    End With
End Sub

where declarations for GetAsyncKeyState API used is:

Private Const VK_LBUTTON = &H1
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer

Once you run the code, you will notice that the AllowAutoDrag event "Drop" may be fired multiple times, so we suggest to postpone any of your actions ( like displaying a message box ), by posting a window message or use a timer event, to let the control handles / completes the event as in the following sample:

Private Sub List1_AllowAutoDrag(ByVal Item As Long,ByVal InsertA As Long,ByVal InsertB As Long,Cancel As Boolean)
    With List1
        Debug.Print "Drag"
        If (GetAsyncKeyState(VK_LBUTTON) = 0) Then
            mctlTimerDrop.Enabled = True
        End If
    End With
End Sub

where mctlTimerDrop is defined as follows:

Dim WithEvents mctlTimerDrop As VB.Timer

Private Sub mctlTimerDrop_Timer()
    mctlTimerDrop.Enabled = False
    MsgBox "Drop."
End Sub


Private Sub Form_Load()
    Set mctlTimerDrop = Me.Controls.Add("VB.Timer", "DropTimer1")
    With mctlTimerDrop
        .Enabled = False
        .Interval = 100
    End With
End Sub
61:
Usually it is happen when you load data from a record set. When you call Caption() = rs("Field") the Caption property holds a reference to a Field object not to the field's value. In order to fix that you have to pass the rs("Field").Value to the Caption property as shown in the following sample:

The following code enumerates the records within a recordset, and adds a new item for each record found:

rs.MoveFirst
While Not rs.EOF()
    .Add rs(0)
rs.MoveNext
Wend

The list shows nothing, so you need to use a code as follows:

rs.MoveFirst
While Not rs.EOF()
    .Add rs(0).Value
rs.MoveNext
Wend
In conclusion, the rs("Field") returns a reference to an object of Field type, while rs("Field").Value returns the value of the field itself.
How-To Questions
General Questions