property List.VirtualMode as Boolean
Retrieves or sets a value that indicates whether the control runs in the virtual mode.

TypeDescription
Boolean A boolean expression that indicates whether the control runs in the virtual mode.
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 ItemToVirtual property to convert the index of the item in the list to the index of the virtual item. Use the 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 VirtualToItem property ensures that the virtual item fits the control's client area.

Displaying a table, using the Virtual Mode

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.

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.
 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.
Private Sub Form_Load()
    With List1
        .BeginUpdate
            .Columns.Add "Column 1"
            
            .VirtualMode = True
            Set .UnboundHandler = New Class1
        .EndUpdate
    End With
End Sub

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. 

Now, that you got the idea of the virtual mode, let's start to complicate the things. Let's suppose that we have a table and we need to display its records in the control. 

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. 

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. 

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. 

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. 

Adding a custom column, when the control is running in 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:

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
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")

Editing a table using the Virtual Mode 

Private Sub Form_Load()
    With List1
        .BeginUpdate
        
            .AllowEdit = True
        
            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
Private Sub List1_AfterCellEdit(ByVal Index As Long, ByVal ColIndex As Long, ByVal newCaption As String)
    With List1.Items
        n.Change newCaption, .ItemToVirtual(Index), ColIndex
    End With
End Sub

Important to notice is that the Items.ItemToVirtual property is called to convert the index of item in the list to the index of the virtual item being changed. The Change method in the Class1 changes the value in the recordset. 

Public Sub Change(ByVal newCaption As Variant, ByVal Index As Long, ByVal ColIndex As Long)
    rs.Move Index, 1
    rs(ColIndex) = newCaption
    rs.Update
End Sub 

The Change method moves the current position to the Index position in the recordset, and updates the recordset. 

Loading a table using the Virtual Mode in C++

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.

#import "c:\winnt\system32\exlist.dll" rename( "GetItems", "exGetItems" )

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 

DECLARE_INTERFACE_MAP()

public:
	BEGIN_INTERFACE_PART(Handler, EXLISTLib::IUnboundHandler)
        		STDMETHOD(get_ItemsCount)(IDispatch * Source,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"  rename( "GetItems", "exGetItems" )

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)(IDispatch * Source, 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()
};
BEGIN_INTERFACE_MAP(CUnboundHandler, CCmdTarget)
	INTERFACE_PART(CUnboundHandler, __uuidof(EXLISTLib::IUnboundHandler), Handler)
END_INTERFACE_MAP()
STDMETHODIMP CUnboundHandler::XHandler::get_ItemsCount(IDispatch * Source, 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;
}
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(IDispatch * Source, 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:

#include "UnboundHandler.h"
CUnboundHandler m_unboundHandler;
 #include "Columns.h"
m_list.BeginUpdate();
	m_list.GetColumns().Add( _T("Column 1") );
	m_list.SetVirtualMode( TRUE );
	m_list.SetUnboundHandler( &m_unboundHandler.m_xHandler );
m_list.EndUpdate();

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.

#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.

ADODB::_RecordsetPtr m_spRecordset;
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"  rename( "GetItems", "exGetItems" )
#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)(IDispatch * Source, 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;
};
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. 

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

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(IDispatch * Source, 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()
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.

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.

 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();
#include "Items.h"
void CADOVirtualDlg::OnAfterCellEditList1(long Index, long ColIndex, LPCTSTR NewCaption) 
{
	m_unboundHandler.Change( NewCaption, m_list.GetItems().GetItemToVirtual( Index ), ColIndex );
}
virtual void Change( LPCTSTR szCaption, long Index, long ColIndex );

The CUnboundHandler class definition should look like:

#import "c:\winnt\system32\exlist.dll"  rename( "GetItems", "exGetItems" )
#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)(IDispatch * Source, 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;
};
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();
}

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" rename( "GetItems", "exGetItems" )
#import <msado15.dll> rename ( "EOF", "adoEOF" )