property Grid.VirtualMode as Boolean
Specifies a value that indicates whether the control is running in the virtual mode.

TypeDescription
Boolean A boolean expression that indicates whether the control is running 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 handles maximum 2,147,483,647 records. The control is running in virtual mode, only if the 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 data cannot be viewed as a hierarchy, the user cannot add items manually, selection is not available, and so on. The main advantage of the virtual mode is that the control can displays 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 handle the item to the index of the virtual item. Use the VirtualToItem property to get the handle of the item 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, so calling the EnsureVisibleItem method is not required in this case. While running the control in virtual mode, you can use the Selection property to specify/retrieve the control's selection, or the index-es of the virtual selected items.

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 needs 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 VB sample adds a column, and 100 records. The index of each item 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, ByVal ItemHandle As Long)
    With Source.Items
        .CellValue(ItemHandle, 0) = Index + 1
    End With
End Sub
The control calls the IUnboundHandler_ReadItem method each time when a virtual item becomes visible.  
Private Sub Form_Load()
    With Grid1
        .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 EXGRIDLibCtl.Grid)
    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, ByVal ItemHandle As Long)
    rs.Move Index, 1
    Dim i As Long
    i = 0
    With Source.Items
        Dim f As Variant
        For Each f In rs.Fields
            .CellValue(ItemHandle, 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 n the item. If you need to apply colors, font attributes, ... to the items in the control, your handler may change the CellBold, CellForeColor, ... properties like follows:  

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        
            n.AttachTable "Select * from Orders", "D:\Exontrol\ExGrid\sample\sample.mdb", Grid1
            
            .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. 

Editing a table, using the virtual mode

In this case, we assume that you are already familiar with the "displaying a table, using virtual mode". So, beside the steps that need to be followed in "displaying a table, using virtual mode", the following steps need to be follow as well:

With .Columns("OrderDate")
    With .Editor
        .EditType = DateType
    End With
End With
Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        
            n.AttachTable "Select * from Orders", "D:\Exontrol\ExGrid\sample\sample.mdb", Grid1
            
            .VirtualMode = True
            Set .UnboundHandler = n
            
            With .Columns("OrderDate")
                With .Editor
                    .EditType = DateType
                End With
            End With
            
        .EndUpdate
    End With
End Sub

Important to notice is that setting editors is called after setting the UnboundHandler property. Also, the "OrderDate" field needs to be changed if another table or database is used. Until now, the sample is able to display the table, and it provides editors for the columns. Until now,  the user can change the values in the control but the data is not saved to the table so please follow the steps:

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, newValue As Variant)
    With Grid1.Items
        n.Change .ItemToVirtual(Item), ColIndex, newValue
    End With
End Sub

The Change event passes the NewValue to the object that implements the IUnboundHandler interface, so we can make the change to the original place, in our case the recordset.

Private Sub IUnboundHandler_ReadItem(ByVal Index As Long, ByVal Source As Object, ByVal ItemHandle As Long)
    nReading = nReading + 1
    rs.Move Index, 1
    Dim i As Long
    i = 0
    With Source.Items
        Dim f As Variant
        For Each f In rs.Fields
            .CellValue(ItemHandle, i) = f.Value
            i = i + 1
        Next
    End With
    nReading = nReading - 1
End Sub 

Where is the change? The change is that we have added a counter nReading that is increases when the IUnboundHandler_ReadItem method starts and it is decreased when the function ends. Why such of counter? We have added the nReading counter because, during the IUnboundHandler_ReadItem method the user calls CellValue, so the Change event is fired and things get recursively as we do not want...

Public Sub Change(ByVal Index As Long, ByVal ColIndex As Long, ByVal newValue As Variant)
    If nReading = 0 Then
        rs.Move Index, 1
        rs(ColIndex) = newValue
    End If
End Sub

Checking the nReading counter is required because the Change event is called even if the user changes the cell's value using CellValue property. If such of checking is omitted, a recursive call occurs. The nReading counter is increased when the IUnboundHandler_ReadItem method starts, and the nReading counter is decreased when the IUnboundHandler_ReadItem method ends. 

Private Sub Class_Initialize()
    nReading = 0
End Sub

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 Grid1
        .BeginUpdate
        
            n.AttachTable "Select * from Orders", "D:\Exontrol\ExGrid\sample\sample.mdb", Grid1
            
            .VirtualMode = True
            Set .UnboundHandler = n
            
            With .Columns("OrderDate")
                With .Editor
                    .EditType = DateType
                End With
            End With
            
            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, ByVal ItemHandle As Long)
    nReading = nReading + 1
    rs.Move Index, 1
    Dim i As Long
    i = 0
    With Source.Items
        Dim f As Variant
        For Each f In rs.Fields
            .CellValue(ItemHandle, i) = f.Value
            i = i + 1
        Next
        .CellValue(ItemHandle, "Position") = Index + 1
    End With
    nReading = nReading - 1
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:

.CellValue(ItemHandle, "Column") = .CellValue(ItemHandle, "Quantity") * .CellValue(ItemHandle, "UnitPrice")

Loading and editing a table using 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 are useful.

#import "c:\winnt\system32\exgrid.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 EXGRIDLib. The generated namespace includes definition for IUnboundHandler interface. It can be accessed using the declaration EXGRIDLib::IUnboundHandler 

DECLARE_INTERFACE_MAP()

public:
	BEGIN_INTERFACE_PART(Handler, EXGRIDLib::IUnboundHandler)
        	STDMETHOD(get_ItemsCount)(IDispatch * Source,long* pVal);
		STDMETHOD(raw_ReadItem)(long Index, IDispatch * Source, long ItemHandle);
    	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\exgrid.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, EXGRIDLib::IUnboundHandler)
        STDMETHOD(get_ItemsCount)(IDispatch * Source, long* pVal);
		STDMETHOD(raw_ReadItem)(long Index, IDispatch * Source, long ItemHandle);
    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(EXGRIDLib::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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&pGrid ) ) )
	{
		// assigns the value for each cell.
		pGrid->Items->CellValue[ItemHandle][_variant_t( (long)0 )] = _variant_t( Index );
		pGrid->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( 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;
}
IMPLEMENT_DYNCREATE(CUnboundHandler, CCmdTarget)

BEGIN_INTERFACE_MAP(CUnboundHandler, CCmdTarget)
	INTERFACE_PART(CUnboundHandler, __uuidof(EXGRIDLib::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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&pGrid ) ) )
	{
		// assigns the value for each cell.
		pGrid->Items->CellValue[ItemHandle][_variant_t( (long)0 )] = _variant_t( Index );
		pGrid->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( 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;
}

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_grid.BeginUpdate();
	m_grid.GetColumns().Add( _T("Column 1") );
	m_grid.SetVirtualMode( TRUE );
	m_grid.SetUnboundHandler( &m_unboundHandler.m_xHandler );
m_grid.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( EXGRIDLib::IGrid* pGrid, LPCTSTR szTable, LPCTSTR szDatabase );

Now, the CUnboundHandler class definition should look like follows:

#import "c:\winnt\system32\exgrid.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, EXGRIDLib::IUnboundHandler)
		STDMETHOD(get_ItemsCount)(IDispatch * Source, long* pVal);
		STDMETHOD(raw_ReadItem)(long Index, IDispatch * Source, long ItemHandle);
	END_INTERFACE_PART(Handler)
	virtual void AttachTable( EXGRIDLib::IGrid* pGrid, 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( EXGRIDLib::IGrid* pGrid, 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 ) ) )
			{
				pGrid->BeginUpdate();
				for ( long i = 0; i < m_spRecordset->Fields->GetCount(); i++ )
					pGrid->GetColumns()->Add( m_spRecordset->Fields->GetItem( _variant_t( i ) )->Name );
				pGrid->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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis->m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&pGrid ) ) )
	{
		// assigns the value for each cell.
		for ( long i = 0; i < pThis->m_spRecordset->Fields->GetCount(); i++ )
			pGrid->Items->CellValue[ _variant_t( ItemHandle ) ][ _variant_t( i )] = pThis->m_spRecordset->Fields->GetItem( _variant_t( i ) )->Value;
		pGrid->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(EXGRIDLib::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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis->m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

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

void CUnboundHandler::AttachTable( EXGRIDLib::IGrid* pGrid, 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 ) ) )
			{
				pGrid->BeginUpdate();
				for ( long i = 0; i < m_spRecordset->Fields->GetCount(); i++ )
					pGrid->GetColumns()->Add( m_spRecordset->Fields->GetItem( _variant_t( i ) )->Name );
				pGrid->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( 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;
}

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()
EXGRIDLib::IGridPtr spGrid = NULL;
m_grid.GetControlUnknown()->QueryInterface( &spGrid );
m_grid.BeginUpdate();
	m_unboundHandler.AttachTable( spGrid, _T("Orders"), _T("D:\\Exontrol\\ExGrid\\sample\\sample.mdb") );
	m_grid.SetVirtualMode( TRUE );
	m_grid.SetUnboundHandler( &m_unboundHandler.m_xHandler );
m_grid.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 data in the control using the control's collection of editors.

#include "Columns.h"
#include "Column.h"
#include "Editor.h"
#include "Items.h"
m_grid.GetColumns().GetItem( _variant_t(_T("OrderDate")) ).GetEditor().SetEditType( EXGRIDLib::DateType );
m_grid.GetColumns().GetItem( _variant_t(_T("RequiredDate")) ).GetEditor().SetEditType( EXGRIDLib::DateType );
m_grid.GetColumns().GetItem( _variant_t(_T("ShippedDate")) ).GetEditor().SetEditType( EXGRIDLib::DateType );

The sample includes editors of DateType to "OrderDate", "RequiredDate" and "ShippedDate" columns. If the editor requires adding items or requires more changes, you could save the editor object to a variable like in the following sample:

_variant_t vtMissing; vtMissing.vt = VT_ERROR;
CEditor editor = m_grid.GetColumns().GetItem( _variant_t(_T("EmployeeID")) ).GetEditor();
	editor.SetEditType( EXGRIDLib::DropDownListType );
	editor.AddItem( 1, _T("Nancy Davolio"), vtMissing );
	editor.AddItem( 2, _T("Fuller Andrew"), vtMissing );
	editor.AddItem( 3, _T("Jannet Leverling"), vtMissing );
	editor.AddItem( 4, _T("Margaret Peacock"), vtMissing );
	editor.AddItem( 5, _T("Marius Buchanan"), vtMissing );
	editor.AddItem( 6, _T("Michael Suyama"), vtMissing );
	editor.AddItem( 7, _T("Robert King"), vtMissing );
	editor.AddItem( 8, _T("Laura Callahan"), vtMissing );
	editor.AddItem( 9, _T("Anne Dodsworth"), vtMissing );
void CADOVirtualDlg::OnChangeGrid1(long Item, long ColIndex, VARIANT FAR* NewValue) 
{
	m_unboundHandler.Change( NewValue, m_grid.GetItems().GetItemToVirtual( Item ), ColIndex );
}
 long m_nReading;

The Change event is called even if the user changes the cell's value using the Cellvalue property. Because in the ReadItem method we are using the Cellvalue, we need to increase the m_nReading counter when ReadItem method starts, and decreases it when the function ends. So, we will be able to avoid recursive calls. 

The CUnboundHandler class definition should look like:

#import "c:\winnt\system32\exgrid.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, EXGRIDLib::IUnboundHandler)
		STDMETHOD(get_ItemsCount)(IDispatch * Source, long* pVal);
		STDMETHOD(raw_ReadItem)(long Index, IDispatch * Source, long ItemHandle);
    END_INTERFACE_PART(Handler)

	virtual void AttachTable( EXGRIDLib::IGrid* pGrid, LPCTSTR szTable, LPCTSTR szDatabase );
	virtual void Change( VARIANT* pvtNewValue, 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;
	long m_nReading;
};
STDMETHODIMP CUnboundHandler::XHandler::raw_ReadItem(long Index, IDispatch * Source, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis->m_nReading++;
	pThis->m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&pGrid ) ) )
	{
		// assigns the value for each cell.
		for ( long i = 0; i < pThis->m_spRecordset->Fields->GetCount(); i++ )
			pGrid->Items->CellValue[ _variant_t( ItemHandle ) ][ _variant_t( i )] = pThis->m_spRecordset->Fields->GetItem( _variant_t( i ) )->Value;
		pGrid->Release();
	}
	pThis->m_nReading--;
	return S_OK;
}
void CUnboundHandler::Change( VARIANT* pvtNewValue, long Index, long ColIndex )
{
	if ( m_nReading == 0 )
	{
		m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );
		m_spRecordset->Fields->GetItem( _variant_t( ColIndex ) )->Value = *pvtNewValue;
		m_spRecordset->Update();
	}
}

The Change function moves the  position of the current record in the recordset, and updates the table. The control automatically will reread the record in order to update the date in the cells of the item, after Change event is processed.

Finally, the implementation of the CUnboundHandler class looks like:

IMPLEMENT_DYNCREATE(CUnboundHandler, CCmdTarget)

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

CUnboundHandler::CUnboundHandler()
{
	m_nReading = 0;
}

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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis->m_nReading++;
	pThis->m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&pGrid ) ) )
	{
		// assigns the value for each cell.
		for ( long i = 0; i < pThis->m_spRecordset->Fields->GetCount(); i++ )
			pGrid->Items->CellValue[ _variant_t( ItemHandle ) ][ _variant_t( i )] = pThis->m_spRecordset->Fields->GetItem( _variant_t( i ) )->Value;
		pGrid->Release();
	}
	pThis->m_nReading--;
	return S_OK;
}

void CUnboundHandler::AttachTable( EXGRIDLib::IGrid* pGrid, 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 ) ) )
			{
				pGrid->BeginUpdate();
				for ( long i = 0; i < m_spRecordset->Fields->GetCount(); i++ )
					pGrid->GetColumns()->Add( m_spRecordset->Fields->GetItem( _variant_t( i ) )->Name );
				pGrid->EndUpdate();
			}
		}
		catch ( _com_error& e )
		{
			AfxMessageBox( e.Description() );
		}

	}
}

void CUnboundHandler::Change( VARIANT* pvtNewValue, long Index, long ColIndex )
{
	if ( m_nReading == 0 )
	{
		m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );
		m_spRecordset->Fields->GetItem( _variant_t( ColIndex ) )->Value = *pvtNewValue;
		m_spRecordset->Update();
	}
}

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;
}

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

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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis->m_nReading++;
	pThis->m_spRecordset->Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source->QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&pGrid ) ) )
	{
		// assigns the value for each cell.
		for ( long i = 0; i < pThis->m_spRecordset->Fields->GetCount(); i++ )
			pGrid->Items->CellValue[ _variant_t( ItemHandle ) ][ _variant_t( i )] = pThis->m_spRecordset->Fields->GetItem( _variant_t( i ) )->Value;

		if ( pGrid->Items->CellValue[ _variant_t( ItemHandle ) ][ _variant_t( _T("ShipRegion") )] == _variant_t( _T("RJ") ) )
			pGrid->Items->put_ItemForeColor( ItemHandle , RGB(0,0,255 ) );
		if ( pGrid->Items->CellValue[ _variant_t( ItemHandle ) ][ _variant_t( _T("ShipRegion") )] == _variant_t( _T("SP") ) )
			pGrid->Items->put_ItemBold( ItemHandle , TRUE );

		pGrid->Release();
	}
	pThis->m_nReading--;
	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\exgrid.dll" rename( "GetItems", "exGetItems" )
#import <msado15.dll> rename ( "EOF", "adoEOF" ) 

Running the VirtualMode in VFP 7.0 or greater

  1. Create a PRG file, say, CLASS1.PRG to define a class to implement the IUnboundHandler interface:
    define class UnboundHandler as custom
    	implements IUnboundHandler in "ExGrid.dll"
    	
    	function IUnboundHandler_get_ItemsCount(Source)	
    		return 10000000	&& we are going to have that many virtual items
    	endfunc
    
    	function IUnboundHandler_ReadItem(Index, Source, ItemHandle)
    		With Source.Items
    		.DefaultItem = ItemHandle
            		.CellValue(0, 0) = 'Virtual Item ' + transform(Index+1)	&& in this example, just set text in Column 0 
    		EndWith
    	endfunc
    
    enddefine
  2. Set up ExGrid's properties as follows (in the example, it is done by Form1.Init)
    with thisform.Grid1
    	.Columns.Add("Column 0")
    	.VirtualMode = .t.
    	.UnboundHandler = newobject('UnboundHandler', 'class1.prg')
    endwith	
  3. You can also use Virtual Mode to scan a table, say, MyCursor, in natural order.
function IUnboundHandler_get_ItemsCount(Source)	
	return reccount('MyCursor')
endfunc

function IUnboundHandler_ReadItem(Index, Source, ItemHandle)
	select MyCursor
	go Index+1
	With Source.Items
		.DefaultItem = ItemHandle
        	.CellValue(0, 0) = 'Virtual Item ' + MyCursor.Field1
	EndWith
endfunc
enddefine