featured products
exg2antt excalendar exmenu

How do you find the control's help / documentation on your computer:

Frequently Asked Questions - General

Click the programming language you use for general questions:

How-To Questions

Click the programming language you use for how-to questions:

Exontrol Software - ExGrid FAQ page

Frequently Asked Questions - ExGrid Component

Listed below are the questions that we are asked quite often. Before you write us, be sure to check here. 

Are you looking for something?

Just press CTRL+F ( or select Edit\Find menu item ) and you have a dialog that will help you to locate the string that are you looking for.

Where can I find the control's release notes?

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.

How can I add columns at design mode? ( WYSWYG Template Editor )

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 an editable column, and few items

HeaderVisible = False
Columns
{
	"Column 1"
	{
		Editor
		{
			EditType = 1
		}
	}
}
Items
{
	AddItem("Item 1")
	AddItem("Item 2")
	AddItem("Item 3")
}

The following sample shows how to add 1 column, 1 root item and two 2 child items:

HeaderVisible = False
FullRowSelect = False
LinesAtRoot = -1
Columns 
{
	"Column 1"
}
Items
{
	Dim h
	h = AddItem("Root")
	InsertItem(h,,"Child1")
	InsertItem(h,,"Child2")
	ExpandItem(h) = True
}

I've been reading about DAO v ADO and wonder if exGrid will take either?

The control handles ADO recordsets as well as DAO recordsets. The DataSource property loads an ADO as a DAO recordset as well.  

I have an exGrid into a property page ( I am using Visual C++ ) and when I use OleCreatePropertyFrame method the property page is not opened. What I am doing wrong?

By default, your property page is derived from CDialogImpl class. The Activate method of the IPropertyPageImpl class calls CreateDialogParam method that's not able to handle CONTROL tags in the resource file. In order to get a fix for that your property page object should derive from the CAxDialogImpl class instead CDialogImpl. The AtlAdviseSinkMap method should be called in OnInitDialog method, if you want to handle events using BEGIN_SINK_MAP, SINK_ENTRY and END_SINK_MAP macros.

 Any plans to introduce simple HTML formatting inside cell?

The version 1.0.2.1 includes built-in HTML format inside cell. The CellValueFormat property specifies how the cell's value ( CellValue property ) is displayed. If the CellValueFormat property is exText no HTML formatting is applied. Else, if the CellValueFormat property is exHTML the CellValue is formatted using HTML tags. The list of valid tags are:

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

The problem that I am having is that some data disappears after I load the control. Do you have any insight on the problem?

Usually it is happen when you are loading data from a record set. When you are calling CellValue() = rs("Field") the CellValue 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 CellValue property.

How can I add new columns to the control?

In design mode, the control provides a WYSWYG Template feature, that helps the user to initialize the control in design mode. For instance, the following x-script adds three columns to your control.

Columns
{
	"Column 1"
	"Column 2".DisplayFilterButton = True
	"Column 3"
}

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 VB code shows you how to add two columns to the control, at runtime:

With Grid1
    .BeginUpdate
        With .Columns
            With .Add("Column 1")
                .Width = 164
                .HTMLCaption = "<b>Column<b> <fgcolor=0000FF>1</fgcolor>"
            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.

How can I add new items to the control?

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. 

Because control can load a list as well as a hierarchy each item is specified by a handle HITEM not by index. Each property that refers a cell requires a handle 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 Grid1.Columns
        .Add "Column 1"
        With .Add("Column 2")
            .HTMLCaption = "Column <b>2</b>"
        End With
End With

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

With Grid1.Items
    Dim h As HITEM, hChild As HITEM
    h = .AddItem("Group 1")
    .CellValue(h, 1) = "Information about Group 1"
    hChild = .InsertItem(h, , "Child 1")
    .CellValue(hChild, 1) = "Information about Child 1"
    hChild = .InsertItem(h, , "Child 2")
    .CellValue(hChild, 1) = "Information about Child 2"
    
    h = .AddItem("Group 2")
    .CellValue(h, 1) = "Information about Group 2"
    hChild = .InsertItem(h, , "Child 1")
    .CellValue(hChild, 1) = "Information about Child 1"
    hChild = .InsertItem(h, , "Child 2")
    .CellValue(hChild, 1) = "Information about Child 2"    
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 like in the following sample:

With Grid1
        .BeginUpdate
            With .Columns
                .Add "Column 1"
            End With
            With .Items
                For i = 0 To 20000
                    .AddItem i
                Next
            End With
        .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 Grid1
        .BeginUpdate
            With .Columns
                For Each f In rs.Fields
                    .Add f.Name
                Next
            End With
	    .PutItems rs.GetRows(rs.RecordCount)
        .EndUpdate
    End With

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

With Grid1
    .BeginUpdate
    
    .LinesAtRoot = exLinesAtRoot
    .HasLines = exNoLine
    .HasButtons = exArrow
    
    .Columns.Add "Column 1"
    .Columns.Add "Column 2"
    
    With .Items
        Dim h As HITEM
        h = .AddItem(Array("Cell 1", "Cell 2"))
        .InsertItem h, , Array("Sub Cell 1.1", "Sub Cell 2.1")
        .InsertItem h, , Array("Sub Cell 1.2", "Sub Cell 2.2")
        
    End With
    
    .EndUpdate
End With 

I was able to add a new item, but now how can I insert it at the top of the list?

Once that you have the handle of the added item you have to sue the ItemPosition property to specify the item's position. 

How can I get ride of the images dialog at design time?

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:

  • ( 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.

How can I delete an icon from the images list window, shown at design time?

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. You can insert new icon files to the control's images list by pressing INSERT key.

I have few columns and a rectangle is shown around the first column, Is there any way to get ride of that?  

The MarkSearchColumn specifies whether the searching column is marked or not. Use the MarkSearchColumn to hide that box.

 How can I select an item?

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"

Grid1.Items.SelectItem(Grid1.Items.FindItem("Child 2", "Column 1")) = True

or

Grid1.Items.SelectItem(Grid1.Items.FindItem("Child 2", 0)) = True

The following sample selects the first visible item:

Grid1.Items.SelectItem(Grid1.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.
With Grid1.Items
        Dim i As Long
        For i = 0 To .SelectCount - 1
            Debug.Print .CellValue(.SelectedItem(i), 0)
        Next
End With

How can I find an item and how can I make sure that's visible?

There Items object provides few methods like FindItem, FindPath in order to find 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 handle of the item that contains in the first column ( "Column 1"  ) the value "Child 2":

Debug.Print Grid1.Items.FindItem("Child 2", "Column 1")

If the FindItem method fails to locate the item the 0 is returned. If a non 0 value is returned that means that the control was able to locate the item. 

The FindPath method looks for a path in the control's hierarchy using the SearchColumnIndex property that indicates the searched column. The method requires the full path separated by the "/".

Debug.Print Grid1.Items.FindPath("Group 2\Child 2")

Once that we have found the searched item all that we need to call EnsureVisibleItem method in order to ensure that the item is visible. If the item was a child of an item that was collapsed the EnsureVisibleItem method expands that item too.

How do I color an item or a cell?

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. Also HTML tags like <fgcolor> or <bgcolor> can be used in the CellValue property. 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.

How do I color a column?

The Def(exCellBackColor) property of Column object specifies the background color for all cells in the column. The following sample changes the background color for all cells in the first column:
With Grid1.Columns(0)
    .Def(exCellBackColor) = RGB(&HF0, &HF0, &HF0)
End With
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.

How do I bold a cell?

The control provides properties like: CellBold, CellItalic, CellUnderline, CellStrikeout, ItemBold, ItemStrikeout, ItemUnderline and ItemItalic  to set the 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 properties like HeaderBold, HeaderItalic, HeaderUnderline or HeaderStrikeOut. Also, the CellValue property accepts HTML tags like: <b>, <i>, <u> or <s> to apply different font attributes for parts in the cell's caption. Use the HTMLCaption property to display the column's header using built-in HTML format.

The CellFont property specifies the cell's font. The following sample assigns a new font for a cell:

With Grid1
        .BeginUpdate
        With .Items
            Dim f As New StdFont
            f.Name = "Tahoma"
            Set .CellFont(.ItemByIndex(0), 0) = f
        End With
        .EndUpdate
End With

The following C++ sample shows how to change the font for the cell:

IFontDisp* pFontDisp = NULL;
	static FONTDESC _NewFont = { sizeof(FONTDESC), OLESTR("Tahoma"), FONTSIZE(9), FW_NORMAL, DEFAULT_CHARSET, FALSE, FALSE, FALSE };
	if ( SUCCEEDED( OleCreateFontIndirect( &amp;_NewFont, IID_IFontDisp, (void**)&amp;pFontDisp ) ) )
	{
		m_grid.BeginUpdate();
		CItems items = m_grid.GetItems();
		items.SetCellFont( COleVariant( items.GetItemByIndex( 0 ) ), COleVariant( long( 0 ) ), pFontDisp );
		m_grid.EndUpdate();
		pFontDisp-&gt;Release();
	}

How do I sort the list of items at runtime?

By default, the control automatically sort a column when the user clicks the column's header. If the SortOnClick property is exNoSort, the control doesn't sort the items if the 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:

    Grid1.Column(ColIndex).SortOrder = SortAscending
  • Using the SortChildren method of t the Items object. The SortChildren sorts the items. The SortChildren method sorts the child items of the given parent item in the control. SortChildren will not recurse through the tree, only the immediate children of Item will be sorted. The following sample sort descending the list of root items on the "Column 1"( if your control displays a list, all items are considered being root items ).

    Grid1.Items.SortChildren 0, "Column 1", False

The SortType property of the Column object specifies the way how a column gets sorted. By default, a column gets sorted as string. If you need to sort your dates, the following snippet of code should be used:

With Grid1
        With .Columns(0)
            .SortType = SortDate
        End With
End With

If you need to sort a column using your special way you may want to use the SortType = SortUserData that sorts the column using CellData property for each cell in the column. In this case, the CellData property holds numeric values only.

What is the best way to loop or enumerate through all the items in the grid and extract cell data?

There are the several ways of enumerating the items/cells in the control. The following samples are in VB, but they can be easily converted to any other programming language. This samples shows you an idea how easily you can enumerate through the items.

A). Using the GetItems method of the control. The GetItems method gets the items as they are displayed, sorted and filtered to an array or vector. Also, the GetItems method collect the child items as well, no matter if the parent item is collapsed. The GetItems method returns an array. For instance, if your control contains 1 column, the GetItems will retrieves a one-dimensional array. A 2 columns will get a two-dimensional array, an so on. You can use the PutItems method to insert the array to the control.

B). Using the for each statement for Items property of the control. The Items property gets a collection of items as they were added. This method lists the items by index not by their positions. The items is represented by handles, so the handle can be used in the Cell properties to refer the cell. For instance, Items.CellCaption(Handle,Column) gets the cell from the Item with the specified handle on specified column. The following sample displays the cells in the first column as they were added:

With Grid1
        Dim h As Variant
        For Each h In .Items
            Debug.Print .Items.CellCaption(h, 0)
        Next
End With

If you need to access multiple columns add the Debug.Print .Items.CellCaption(h, 1), Debug.Print .Items.CellCaption(h, 2) ... for each column you require.

C). A similar approach to B is using the Items.ItemCount and Items.ItemByIndex properties. This method lists the items by index not by their positions.

With Grid1
    Dim i As Long
    With .Items
        For i = 0 To .ItemCount - 1
            Debug.Print .CellCaption(.ItemByIndex(i), 0)
        Next
    End With
End With

The Items.ItemByIndex retrieves the handle of the item giving its index. For instance, the first added item has the index 0, the second added item has the index 1, and so on.

D). Using the Items.NextVisibleItem property. This method gets the items as they are displayed, sorted and filtered.

With Grid1
    With .Items
        Dim h As Long
        h = .RootItem(0)
        While Not h = 0
            Debug.Print .CellCaption(h, 0)
            h = .NextVisibleItem(h)
        Wend
    End With
End With

E). Using the Items.ItemChild and Items.NextSiblingItem property. This method enumerates recursively the items and its children. This method gets the items as they are displayed, sorted and filtered, including the children items that are not visible aka parent item is collapsed.

With Grid1
    With .Items
        For i = 0 To .RootCount - 1
            RecItem Grid1, .RootItem(i)
        Next
    End With
End With
Sub RecItem(ByVal c As Object, ByVal h As Long)
    If Not (h = 0) Then
        Dim hChild As Long
        With c.Items
            Debug.Print .CellCaption(h, 0)
            hChild = .ItemChild(h)
            While Not (hChild = 0)
                RecItem c, hChild
                hChild = .NextSiblingItem(hChild)
            Wend
        End With
    End If
End Sub

Can I loop through the items collection recursively?

Yes. The following function displays all child items ( recursively )

Sub RecItem(ByVal c As EXGridLibCtl.Grid, ByVal h As HITEM)
    If Not (h = 0) Then
        Dim hChild As HITEM
        With c.Items
            Debug.Print .CellValue(h, 0)
            hChild = .ItemChild(h)
            While Not (hChild = 0)
                RecItem c, hChild
                hChild = .NextSiblingItem(hChild)
            Wend
        End With
    End If
end sub

My control displays multiple columns but no horizontal scroll is displayed. How can I show the horizontal scroll bar ?

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. 

Is there a special download for the UNICODE version?

Yes. You can find UNICODE versions here.

I want to change the font at runtime, and it seems that doesn't working?

Changing the Name property of the Font object doesn't notify the control that the used font has been changed, so calling Grid1.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"
Grid1.Font = f

Does the exGrid control allow for checkboxes that look different from the default one shown in the demo?

The control provides two properties, CheckImage and RadioImage that you can use to set desired icons for checkboxes or radio buttons.

I have problems with the performances of the control.

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 = Grid1.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 searches 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 new column:
With Grid1.Columns
    With .Add("Column 1")
        .Width = 128
        .AllowSizing = False
        .AllowDragging = False
        .DisplaySortIcon = False
    End With
End With
or
With Grid1.Columns
    Dim c As EXGridLibCtl.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.CellValue( Handle, 0 ) instead .Items.CellValue( Handle, "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. The Select property uses the FindItem method that does a linear search.

Why cannot I run Access example (sample.mdb) in Access'97 "Unrecognised databased format?

The sample cannot be run because you have installed Access 97, and it is not able to recognize the database. There is a VB\Acceess97 folder where you can find  a sample for Access 97.

 Does control support DAO recordset?

Yes. The control DataSource property supports ADO and DAO recordsets as well.

Will it be possible to visually divide the control using horizontal dividers using the eXGrid control?

The exGrid supports divider items. A divider item is an item that's fixed and cannot be scrolled. Using divider items you can create application looks like Windows Explorer XP. The ItemDivider and ItemDividerLine properties help you to add divider items. The VB\Divider sample explains better how divider items work. 

 I've seen that the editors are for column, but in my project I need different editors for cells in the same column. Is this possible using your control?  

The control supports editors for the entire column as well for a specific cell. The Column.Editor property specifies the editor for the entire column. The Items.CellEditor specifies an editor for a particular item. The following sample shows how to specify a different editor for a cell ( the handle specifies the handle of the item, and col specifies the column's index or column's name ):

With .Items
	With .CellEditor( handle, col )
		.EditType = DropDownListType
		.AddItem 0, "Zero"
		.AddItem 1, "One"
		.AddItem 2, "Two"
	End With
End With

I have a tree in my exGrid and I want to hide editors for parent items. Is this possible? 

Yes. The Items object provides a property Items.CellEditorVisible that helps you to hide the default editor. If the cell's has no its own editor ( CellEditor ), the column's editor is used. 

 Is your product exGrid able run either on a win98, win nt 4, win 2000 or win XP platform?

Yes. It runs just fine on all Windows platforms.

Have take a look at your exGrid and I missing the feature to add a new row by the user?

The Items object exposes methods like AddItem, InsertItem or InsertControlItem. These help you to add new items to the control. Also, please check the entry "How can I add new items to the control?"

In German we use some special chars like , These chars are supressed if I use Editor.Mask = "?*"?

The '?' character in the mask, supplies the characters a-z, A-Z. In order to accept special characters use instead '?' the sequence [a-zA-Z] 

Do the controls operate correctly within form based .NET applications ?

Yes. We have customers that already use them on .NET forms.

I need a multiple columns drop down editor. Is there any way to handle it? (Editor.UserEditor property)

The control provides ability to use any third part control as an user editor. In other words, you can show/hide a custom editor when the control's cell gets or loses the focus. The control provides a new editor type EditTypeEnum.UserEditorType. The UserEditor method of the Editor object specifies the control's identifier ( like Exontrol.ComboBox, MSCAL.Calendar, and so on ), and the control's runtime license key. The runtime license key is not the same with your development license key. If the control uses a runtime-license key you have to request for its vendor. Once the UserEditor method is invoked, the Editor object creates the editor based on the control's identifier. In that case, you can access the newly created object using the UserEditorObject property. The property UserEditorObject allows you to initialize the user editor before using it as a built-in editor.

There are three steps to follow when using an user editor as listed:

  • building and filling the user editor with values (UserEditorObject property)
  • preparing the user editor to be shown when the cell get the focus (UserEditorOpen event)
  • updating the cell's value when the user editor loses the focus (UserEditorClose event)

The control fires the UserEditorOpen event when the UserEditorType editor is about to be displayed ( the control's cell gets the focus or Edit method has been called ) . Using this event, you have the ability to prepare the user editor before showing it. When the UserEditorType is about to be closed ( the control's cell loses the focus, the user clicks outside of the user editor ), the control fires the UserEditorClose event, so you have to handle this event to specify the new value to be updated on the control. The user editor fires its events through the UserEditorOleEvent event. The control fires the UserEditorOleEvent event when an user editor fires an event. The event has an argument CloseEditor that helps you to close the editor when a certain action occurs. 

Having these said, let's show how you can insert the Exontrol.ComboBox as an user editor, to provide a multiple-columns drop down within the control:

  • building and filling the user editor with values ( UserEditorObject property)
    With Grid1.Columns.Add("Exontrol.ComboBox").Editor
        .EditType = UserEditorType
        .UserEditor "Exontrol.ComboBox", ""
        With .UserEditorObject
            .BeginUpdate
            .Style = 2
            .ColumnAutoResize = False
            Set rs = CreateObject("ADOR.Recordset")
            With rs
                .Open "Orders", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB", 3, 3
            End With
            .DataSource = rs
            .MinHeightList = 128
            .EndUpdate
        End With
    End With

    The code adds a new column to the control, assigns an Exontrol.ComboBox user editor, and fills the combobox with the values ( this sample loads the values from a ADO table )

    Let's have some items to be shown on the control:

    With Grid1
        .DefaultItemHeight = 21
        .DrawGridLines = exRowLines
        With .Items
            .CellEditorVisible(.AddItem(10248), 0) = exEditorVisible
            .CellEditorVisible(.AddItem(10249), 0) = exEditorVisible
            .CellEditorVisible(.AddItem(10250), 0) = exEditorVisible
        End With
    End With 
  • preparing the user editor to be shown when the cell get the focus ( UserEditorOpen event)
    Private Sub Grid1_UserEditorOpen(ByVal Object As Object, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
        With Object
            .SearchColumnIndex = 0
            .Value = Grid1.Items.CellValue(Item, ColIndex)
            .Items.EnsureVisibleItem .Items.FocusItem
        End With
    End Sub

    The Object parameter indicates refers the Exontrol.ComboBox editor that has been assigned to the cell/column. The code selects the value in the combobox to be the one that the control's displays.

     

  • updating the cell's value when the user editor loses the focus ( UserEditorClose event)
    Private Sub Grid1_UserEditorClose(ByVal Object As Object, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
        Grid1.Items.CellValue(Item, ColIndex) = Object.Value
    End Sub

    The Object parameter indicates refers the Exontrol.ComboBox editor that has been assigned to the cell/column. The updates the control cell's value with the selected value in the combobox control.

Now, let's say we want to ( this samples can be combined in any way you desire, it shows how you can do something, but does not mean that's all you can do ) :

  • navigate the items of the control up or down while pressing UP or DOWN keys and the drop down portion of the Exontrol.ComboBox is not shown, and navigate through the items of the  Exontrol.ComboBox, when it is opened.

  • update the cell's value once the SelectionChanged event of the Exontrol.ComboBox user editor is fired.

  • close the editor once the user hits the ENTER key

In this case, we must handle the UserEditorOleEvent event, which notifies the application when an event is fired by the inside user editor.

Before all, you must see for a but how you can display information about firing events, so just add the following handler:

Private Sub Grid1_UserEditorOleEvent(ByVal Object As Object, ByVal Ev As EXGRIDLibCtl.IOleEvent, CloseEditor As Boolean, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
    Debug.Print Ev.ToString
End Sub

This handler displays information about the events that inside user editor fires, like in the following output:

KeyDown[-602](KeyCode/Short* = 38,Shift/Short = 0)
SelectionChanged[6]()
KeyUp[-604](KeyCode/Short* = 38,Shift/Short = 0)
KeyDown[-602](KeyCode/Short* = 27,Shift/Short = 0)
DropUp[21]()
KeyPress[-603](KeyAscii/Short* = 27)
KeyUp[-604](KeyCode/Short* = 27,Shift/Short = 0)

This is how the information about firing events is shown, but the content is different when using a different user editor. 

Let's show step by step how we can do the:

  •  navigate the items of the control up or down while pressing UP or DOWN keys and the drop down portion of the Exontrol.ComboBox is not shown, and navigate through the items of the  Exontrol.ComboBox, when it is opened (F4 key).

    Dim iPreventChangeCellValue As Long
    
    Private Sub Grid1_UserEditorClose(ByVal Object As Object, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
        If (iPreventChangeCellValue = 0) Then
            Grid1.Items.CellValue(Item, ColIndex) = Object.Value
        End If
    End Sub
    
    Private Sub Grid1_UserEditorOleEvent(ByVal Object As Object, ByVal Ev As EXGRIDLibCtl.IOleEvent, CloseEditor As Boolean, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
        Debug.Print Ev.ToString
        Dim nKeyCode As Long
        If (Ev.ID = -602) Then                                              ' KeyDown
            nKeyCode = CLng(Ev(0).Value)                                    ' The code of the key being pressed
            If (nKeyCode = 38) Or (nKeyCode = 40) Then                      ' Up or Down
                If (Not Object.DropDown(Nothing)) Then                      ' Is the ExComboBox's DropDown portion closed?
                    Ev(0).Value = 0                                         ' Eats the key, so cancels any further operation on ExComboBox
                    
                    '
                    ' Selects the next/prev visible item in the control, makes the current editor to be close, so
                    ' the Grid1_UserEditorClose, and so prevents changing the cell's value, by using the iPreventChangeCellValue internal counter
                    '
                    
                    iPreventChangeCellValue = iPreventChangeCellValue + 1
                    With Grid1.Items
                        Select Case nKeyCode
                            Case 38
                                If (Not .PrevVisibleItem(.FocusItem) = 0) Then
                                    .SelectItem(.PrevVisibleItem(.FocusItem)) = True
                                End If
                            Case 40
                                If (Not .NextVisibleItem(.FocusItem) = 0) Then
                                    .SelectItem(.NextVisibleItem(.FocusItem)) = True
                                End If
                        End Select
                    End With
                    iPreventChangeCellValue = iPreventChangeCellValue - 1
                End If
            End If
        Else
            If (Ev.ID = 6) Then ' SelectionChanged
                If (Object.DropDown(Nothing)) Then                          ' Is the ExComboBox's DropDown portion opened?
                    Grid1.Items.CellValue(Item, ColIndex) = Object.Value    ' Updates the cell'value with the new selected value in the combobox
                End If
            End If
        End If
    End Sub

    The sample selects the next/prev items in the control, when user presses the Up/Down key and the Exontrol.ComboBox is closed, updates the cell's value when the SelectionChanged event is fired while the Exontrol.ComboBox is opened.  The UserEditorClose event has been changed, so we can prevent when the cell's value is updated. For instance, selecting a new item in the control makes the current editor to be closed, so the UserEditorClose is fired, but we do not want to update the cell's value as we need just to advance to a next field

  • update the cell's value once the SelectionChanged event of the Exontrol.ComboBox user editor is fired.

    Private Sub Grid1_UserEditorOleEvent(ByVal Object As Object, ByVal Ev As EXGRIDLibCtl.IOleEvent, CloseEditor As Boolean, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
        Debug.Print Ev.ToString
        If (Ev.ID = 6) Then ' SelectionChanged
            Grid1.Items.CellValue(Item, ColIndex) = Object.Value    ' Updates the cell'value with the new selected value in the combobox
        End If
    End Sub

    The sample updates the cell's value when an item gets selected in the Exontrol.ComboBox user editor.

  •  close the editor once the user hits the ENTER key, 

    Private Sub Grid1_UserEditorOleEvent(ByVal Object As Object, ByVal Ev As EXGRIDLibCtl.IOleEvent, CloseEditor As Boolean, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
        Debug.Print Ev.ToString
        If (Ev.ID = -603) Then   ' KeyPress event
            If (CLng(Ev(0).Value) = 13) Then    ' 13 is the code for ENTER key
                CloseEditor = True
            End If
        End If
    End Sub

    The sample changes the CloseEditor parameter to True, when the user hits the ENTER key.

In case you have an alternative COM object that can be used, you need to use its identifier when using the UserEditor method. Please consult the object's documentation for its properties,  methods or events. Unfortunately, we can't provide information or specifications for third parties ActiveX controls. We can provide technical support only for controls that we own. For instance, the following sample uses the Microsoft ComboBox control as an user editor:

With Grid1.Items
    With .CellEditor(.AddItem(0), 0)
        .EditType = UserEditorType
        .UserEditor "Forms.ComboBox.1", ""
        With .UserEditorObject
            .BackColor = vbBlue
            .ForeColor = vbWhite
            .AddItem "One"
            .AddItem "Two"
        End With
    End With
End With

Before running the sample, the control needs to have at least a column.

Does control support merging cells?

The Items object provides properties like ItemDivider, ItemDividerLine helps you to merge cells of the row into a single cell. The setup installs a sample VB\Divider that helps you to understand how ItemDivider works. Also, the ItemDivider property helps you to group items in the control. The divider items are not scrolled when the user drags the horizontal scroll bar so the groups titles will be visible most of the time. The ItemDivider property specifies the index of cell being displayed instead displaying the entire item.

Can I add a picture to a cell?

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 Grid1.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. The CellPicture property accepts objects of IPictureDisp type. The LoadPicture function retrieves an IPictureDisp object. The following sample can be used too:

With Grid1.Items
    .CellPicture(.FirstVisibleItem, 0) = "c:\winnt\Zapotec.bmp"
End With

If the CellPicture property points to a string value, the control loads the picture file. If the CellPicture refers a Picture object the picture object is loaded.

Could I add buttons with captions to simulate a header?

The CellHasButton property of Items object specifies whether the control uses the cell's caption to paint a button or a simple caption.

I am using Add method of Controls object to insert the control at runtime. I get the the VB run-time error '429', License information for this component not found. You do not have an appropriate license to use this functionality in the design environment. What am I doing wrong?

The control requires a runtime license key before calling Add method of Controls collection. The VB environment provides a Licenses collection that holds runtime license keys. So, your sample should look like following:

Private Sub Form_Load()
    Dim obj As Object
    Licenses.Add "Exontrol.Grid", "xxxxxxxx"
    Set obj = Controls.Add("Exontrol.Grid", "grid", Me)
    obj.Visible = True
End Sub

The value "xxxxxxxx" is NOT a valid runtime license key. Please contact us if you require the control's runtime license key. The site version of the control doesn't require a runtime license key. Your development key machine is not the control's runtime key. 

I am using the InsertControlItem method to add a new grid inside, but it doesn't show up on the client machine. What should I do?

The 'License' optional parameter of the InsertControlItem method needs to provide the control's runtime license key. For instance, if you have something like:

.InsertControlItem(,"Exontrol.Grid")

it should look like:

.InsertControlItem(,"Exontrol.Grid", "xxxxxxxx")

Please note that the "xxxxxxxx" is NOT a valid runtime license key. Please contact us if you require the runtime license key for any of our components.

Is it possible to have different values in a CheckListType for each cells in the same column

Yes, that's possible. You can have different editors for each cells in the same column, by using CellEditor property like in the following sample:

With Grid1.Items
	With .CellEditor(.ItemByIndex(0), 0)
		.EditType = CheckListType
		.AddItem 1, "Item1"
		.AddItem 2, "Item2"
		.AddItem 4, "Item3"
	End With
	With .CellEditor(.ItemByIndex(1), 0)
		.EditType = CheckListType
		.AddItem 1, "NewItem1"
		.AddItem 2, "NewItem2"
		.AddItem 4, "NewItem3"
	End With
End With

How can I display a shortcut menu when the user right clicks a cell?

If you want to provide different shortcut menus for control depending on clicked cell, the MouseUp or MouseDown events should be used. Else, if you want to provide a general shortcut menu, the RClick event can be used too. The following sample handles the MouseUp event and displays a shortcut menu when user right clicks a cell:

Private Sub Grid1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
    ' Checks whether the user right clicks the mouse
    If (Button = 2) Then
        ' Gets the cell from point
        Dim h As HITEM, c As Long, i As Long, hit as Long
        h = Grid1.ItemFromPoint(X / Screen.TwipsPerPixelX, Y / Screen.TwipsPerPixelY, c, hit)
        ' Displays a popup menu if the right clicks a cell
        If (h <> 0) Then
            i = PopupMenu1.ShowAtCursor()
            If (i > 0) Then
                ' Displays the identifier of the context menu item  selected
                Debug.Print "You have selected the item " & i
            End If
        End If
    End If
End Sub

The sample uses the exPopupMenu that can be downloaded here.

How can I use an icon from Windows Explorer in my control?

The SHGetFileInfo API function gets the icon associated to a file. The following sample associates a Windows Explorer icon to a cell. 

The sample requires the following declarations:

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_ICON = &amp;H100
Private Const SHGFI_OPENICON = &amp;H2
Private Const SHGFI_SMALLICON = &amp;H1
Private Type SHFILEINFO
	hIcon As Long
	iIcon As Long
	dwAttributes As Long
	szDisplayName As String * 1024
	szTypeName As String * 80
End Type

The following sample shows how to associate a Windows Explorer icon to a cell. 

Dim s As SHFILEINFO
SHGetFileInfo "c:\winnt\system32\sndrec32.exe", 0, s, 0, SHGFI_SMALLICON Or SHGFI_ICON Or SHGFI_OPENICON
Grid1.ReplaceIcon s.hIcon, 0
Grid1.Items.CellImage(Grid1.Items.FirstVisibleItem, 0) = 1

How can I make the auto search feature case sensitive?

The control provides the ASCIILower and ASCIIUpper properties that helps you to specify the set of characters that are converted by the auto search feature. If you want to make the auto search feature case sensitive you have to use ASCIIUpper = ""  

The AutoSearch behavior is case sensitive by special characters?

The control provides the ASCIILower and ASCIIUpper properties that helps you to specify the set of characters that are converted by the auto search feature. For instance if you have the set / / Ā/ā Ă/ă Ą/ą Ć/ć Ĉ/ĉ Ċ/ċ Č/č Ď/ď Ē/ē Ĕ/ĕ Ė/ė Ę/ę Ě/ě Ĝ/ĝ Ğ/ğ Ġ/ġ Ģ/ģ Ĥ/ĥ Ō/ō Ŏ/ŏ Ŕ/ŕ Ŗ/ŗ Ř/ř Ś/ś, you have to call something like: ASCIILower like "abcdefghijklmnopqrstuvwxyzā....", and ASCIIUpper like  "ABCDEFGHIJKLMNOPQRSTUVWXYZA...."

I want to use an ExComboBox control as an user editor. How can I do that ?

If you are using VB, the setup installs the VB\UserEdit sample that will help you to add an ExComboBox control as an user editor. Shortly, in order to add a new user editor to one of your grid columns, or cells, you have to know that EditType property of Editor object should be EditTypeEnum.UserEditorType. Once that you have set this property, the user has to specify the type of the user control using the UserEditor method of Editor object.  For instance, UserEditor "Exontrol.ComboBox", "" initializes an user editor of ExComboBox type. If the grid creates the user editor successfully, the UserEditorObject property of Editor object points to the newly created user control. So, in this case it references an ExComboBox control. Use the UserEditorObject property any time when you need to access the user editor control. The events like UserEditorOleEvent, UserEditorOpen or UserEditorClose are fired anytime when grid control has to deal with an user editor control. 

The UserEditorOpen event is fired when the grid control needs to display the user editor control. The user has to handle this event to prepare the user editor control before showing. For instance, in VB your handler should look like:

Private Sub Grid1_UserEditorOpen(ByVal Object As Object, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
    With Object
        .Select(0) = Grid1.Items.CellValue(Item, ColIndex)
    End With
End Sub

If you are using C++ the handler should look like:

VOID __stdcall OnUserEditorOpenGrid1(IDispatch * Object, EXGRIDLib::HITEM Item, LONG ColIndex)
{
	// Selects the associated item into the user editor control. ( In this case the user editor is an ExComboBox control )
	EXCOMBOBOXLib::IComboBoxPtr spCombo = Object;
	spCombo-&gt;PutSelect( _variant_t((long)0), m_spGrid-&gt;Items-&gt;CellValue[Item][_variant_t(ColIndex)] );
}

The UserEditorOleEvent event occurs when an user editor control fires an event. In VB, the handler should look like follows:  

Private Sub Grid1_UserEditorOleEvent(ByVal Object As Object, ByVal Ev As EXGRIDLibCtl.IOleEvent, CloseEditor As Boolean, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
    CloseEditor = Ev.Name = "Change"
    If (CloseEditor) Then
        Grid1.Items.CellValue(Item, ColIndex) = Object.Select(0)
    End If
End Sub

In C++ the handler should look like follows:

VOID __stdcall OnUserEditorOleEventGrid1(IDispatch * Object, EXGRIDLib::IOleEvent * Ev, VARIANT_BOOL * CloseEditor, EXGRIDLib::HITEM Item, LONG ColIndex)
{
	// Closes the user editor when it fires the "Change" event. In this case the user editor is an ExComboBox control
	if ( *CloseEditor = (Ev-&gt;Name == _bstr_t("Change")) )
	{
		// Changes the grid's value based on the user selection.
		EXCOMBOBOXLib::IComboBoxPtr spCombo = Object;
		m_spGrid-&gt;Items-&gt;CellValue[ Item ][ _variant_t((long)ColIndex) ] = spCombo-&gt;GetSelect( _variant_t( (long)0 ) );
	}
}

The UserEditorClose event occurs when the control hides the user editor control.

I use the ExComboBox control as an user editor. How can I advance / focus to the next column, when user presses the TAB key?

The user editor (inner control) acts independently when hosting by the control (master control), in other words, the inner control handles the keys / mouse the same way as it were hosted by a form, window, or dialog. So, in order to overwrite the behavior of tab TAB key inside the inner control you have two options: 1) sending the TAB key to the master control, 2) changes the FocusColumnIndex property, during the master's UserEditorOleEvent event, when the KeyDown inner event occurs, and the KeyAscii parameter is 9 ( TAB character code).

1) The following sample handles the UserEditorOleEvent event, so when the user presses the TAB key inside the inner control, sends the TAB key to the master control:

Private Sub Grid1_UserEditorOleEvent(ByVal Object As Object, ByVal Ev As EXGRIDLibCtl.IOleEvent, CloseEditor As Boolean, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
    Debug.Print Ev.ToString()                                       ' Print brief information of the inner event
    If (Ev.ID = -602) Then                                          ' Is KeyDown event of the inner control?
        If (CInt(Ev.Param(0).Value) = 9) Then                       ' Is the TAB key pressed?
            With Grid1
                .SetFocus                                           ' Focuses the master control
                CreateObject("WScript.Shell").SendKeys ("{TAB}")    ' Simulates pressing the TAB key, so focuses the next visible column, and edits it if case
            End With
        End If
    End If
End Sub

This method has the advantage that it uses the control's TAB mechanism to move the focus to the next / previously visible column, as they are displayed. For instance, if you have hidden columns, they are ignored

2) The following sample handles the UserEditorOleEvent event, so when the user presses the TAB key inside the inner control, changes the control's FocusColumnIndex property, and so edits the next column/cell.

Private Sub Grid1_UserEditorOleEvent(ByVal Object As Object, ByVal Ev As EXGRIDLibCtl.IOleEvent, CloseEditor As Boolean, ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
    Debug.Print Ev.ToString()                       ' Print brief information of the inner event
    If (Ev.ID = -602) Then                          ' Is KeyDown event of the inner control?
        If (CInt(Ev.Param(0).Value) = 9) Then       ' Is the TAB key pressed ?
            Ev.Param(0).Value = 0                   ' Prevents any further action of the inner control
            With Grid1
                .EditClose                          ' Closes the current user editor
                .FocusColumnIndex = ColIndex + 1    ' Focus the next column
                .Edit                               ' Opens the editor of the focused column ( Has effect only AutoEdit property is False )
            End With
        End If
    End If
End Sub
This method has the disadvantage that you have to collect the visible columns as they are displayed, and provide the next / previously valid index for FocusColumnIndex, based on the ColIndex parameter..

I've added some new items at runtime to a drop down editor and all that I want is to get them sorted. Is there any way to do that?

By default, the drop down portion of an editor is loaded with the predefined values as they were added. In order to sort them, the Editor object provides a method called SortItems. The SortItems method sorts the list of items into an Editor object.   

Is there any way to specify the default sort order?

By default, the column gets sorted descendent when user clicks the column's header. The DefaultSortOrder property of the Column object specifies whether the default sort order for a column is ascending or descending.

Is there any way to replace the default sort method?

By default, the SortOnClick property is exDefaultSort. If the Grid1.SortOnClick = exUserSort the control displays the sort icons on the column's header but it doesn't sort the items. The ColumnClick event is fired when user clicks the column's header.

How can I use an icon from Windows Explorer in my control?

The SHGetFileInfo API function gets the icon associated to a file. The following sample associates a Windows Explorer icon to a cell. 

The sample requires the following declarations:

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_ICON = &amp;H100
Private Const SHGFI_OPENICON = &amp;H2
Private Const SHGFI_SMALLICON = &amp;H1
Private Type SHFILEINFO
	hIcon As Long
	iIcon As Long
	dwAttributes As Long
	szDisplayName As String * 1024
	szTypeName As String * 80
End Type

The following sample shows how to associate a Windows Explorer icon to a cell. 

Dim s As SHFILEINFO
SHGetFileInfo "c:\winnt\system32\sndrec32.exe", 0, s, 0, SHGFI_SMALLICON Or SHGFI_ICON Or SHGFI_OPENICON
Grid1.ReplaceIcon s.hIcon, 0
Grid1.Items.CellImage(Grid1.Items.FirstVisibleItem, 0) = 1

Is it possible to apply your built-in HTML tags to column's caption?

Yes. The Column object provides the HTMLCaption property that allows you display the column's caption using built-in HTML tags. If the HTMLCaption property is empty ( by default it is empty ), the Caption property is displayed on the column's header. If the HTMLCaption property is not empty, the control displays the HTMLCaption in the column's header using built-in HTML  tags.

 How can I display a multi line header?

The HeaderHeight property helps you to specify the height for the control's header. The <br> built-in HTML tag can be used to break a line, so you can use it in the HTMLCaption property of the Column object like in the following sample: Column1.HTMLCaption = "Line <b>1</b><br>Line <b>2</b>"

Is there any way to add scroll bars to a memo editor?

The Option property of Editor object provides the ability to add scroll bars to a memo editor using the exMemoHScrollBar and exMemoVScrollBar options. For instance, the following sample adds both scroll bar to the editor of the first column:

With Grid1.Columns(0).Editor
        .Option(exMemoAutoSize) = False     ' Disables auto resizing when user alters the text
        .Option(exMemoVScrollBar) = True    ' Adds the vertical scroll bar
        .Option(exMemoHScrollBar) = True    ' Adds the horizontal scroll bar
End With

I want to display a simple text to a cell while its column has an editor associated. Is it possible?

If the column has associated an editor all cells in the column display the cell's value depending on the type of the column's editor. For instance if you have an editor of DropDown type, the cell's value should be the value of the item in the predefined list. In this case, if you want to display another caption to the cell you have to use the CellEditorVisible property to hide the cell's editor, and to set the newly caption using CellValue property like in the following sample:

With Grid1
	Dim h As HITEM
	h = .Items.ItemByIndex(1)		' Takes the second item
	.Items.CellEditorVisible(h, 1) = False	' Hides the default cell's editor ( that the column's editor instead )
	.Items.CellValue(h, 1) = "Just a text"	' Displays a new caption
End With

How do I find the value for an item into an drop down editor?

The Editor object exports the FindItem property that helps you to get the value of an item giving its caption, or finding the item's caption giving its value. If the FindItem property retrieves an empty value ( vt = VT_EMPTY ), if it cannot find the item's value or item's caption. If the value passed to FindItem is of string type, the control looks for the item's value with the giving caption, else it looks for the item's caption giving its value.

I am linking the grid to an ADO data source. The table has 10 fields (columns). I want to be able to choose 3 or 4 columns from the 10 fields. How can do that?

  1. You can have a recordset that shows only the fields you are interested in, like "Select Field1, Field2, ... From Table". 
  2. You can load the entire table, and you can use the Visible property of the Column object to hide columns that you are not interested in.

Is there any way to display a tooltip for a column?

The Tooltip property of Column object specifies the tooltip's description that shows up when the cursor is over the column's caption. If the Tooltip property is empty, no tooltip is displayed. By default, the title for the tooltip window is the column's caption. 

How do I get the column from cursor?

The ColumnFromPoint property gets the index of the column from the cursor. The ColumnFromPoint property retrieves -1, if no column was found over the cursor. The following sample displays the column's caption using the MouseMove event:

Private Sub Grid1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    With Grid1
        Dim i As Long
        i = .ColumnFromPoint(X / Screen.TwipsPerPixelX, Y / Screen.TwipsPerPixelY)
        If (i >= 0) Then
            Debug.Print .Columns(i).Caption
        End If
    End With
End Sub  

Is there any way to access a column using a key not by its caption?

The Key property of the Column object helps you to assign a key to a column. Using this key you can access the column using the Item property of the Columns object. 

I need to handle the DblClick event without expanding the items. Is it possible?

The control's ExpandOnDblClick property whether the item is expanded or collapsed when the user dbl clicks an item. By default, the ExpandOnDblClick is True, so you need to set the ExpandOnDblClick property on False, and to use a handler like follows:

Private Sub Grid1_DblClick(Shift As Integer, X As Single, Y As Single)
    With Grid1
    Dim c As Long, h As HITEM, hit as Long
    h = .ItemFromPoint(X / Screen.TwipsPerPixelX, Y / Screen.TwipsPerPixelY, c, hit)
    If Not (h = 0) Then
        With .Items
            Debug.Print .CellValue(h, c)
        End With
    End If
    End With
End Sub

Is there any way to change the caption in the control's filter bar?

The following sample displays a new caption in the control's filter bar when user changes the filter.

Private Sub Grid1_FilterChange()
    With Grid1
        .FilterBarCaption = "Click the left button to remove the current filter."
    End With
End Sub

If you need to change the function names like IsBlank, not IsBlank used by the default filter bar caption, you have to use the Description property like follows:

Grid1.Description(exFilterBarIsBlank) = "blank"

Is it possible to assign multiple icons to the same cell?

The CellImage property assign a single icon to the cell. Instead if multiple icons need to be assigned to a single cell you have to use the CellImages property. The CellImages property takes a list of additional icons and display them in the cell. The list is separated by ','  and should contain numbers that represent indexes to Images list collection. The following sample assign first and third icon to the cell:

With Tree1.Items
        .CellImages(.ItemByIndex(0), 1) = "1,3"
End With

Is there any print and print preview support?

Yes, the Exontrol ExPrint component ( exprint.dll ) provides Print and Print Preview capabilities for the exGrid 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 VB code:

Private Sub Command1_Click()
    With Print1
        Set .PrintExt = Grid1.Object
        .Preview
    End With
End Sub

The following sample shows how to call Preview method in C#:

private void button1_Click(object sender, System.EventArgs e)
{
	axPrint1.PrintExt = axGrid1.GetOcx();
	axPrint1.Preview();
} 

The following sample shows how to call Preview method in C++:

void CTemplate2Dlg::OnPreview() 
{
	m_print.SetPrintExt( m_grid.GetControlUnknown() );
	m_print.Preview();
	
}

where the m_print is a member that wraps the Exontrol.Print component, and m_grid is the wrapper class for Exontrol.Grid control.

The Exontrol Print Preview mainframe looks like follows:

The exPrint component is free of charge, if you are registered user of the exGrid component.

The following VB sample opens the Print Preview frame:

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

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

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

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

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

The following C# sample opens the Print Preview frame:

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

The following VFP sample opens the Print Preview frame:

with thisform.Print1.Object
    .PrintExt = thisform.Grid1.Object
    .Preview()
endwith

Is there any Fit-To-Page option for print and print preview?

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.ExGrid version 6.3 ( 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.3 )
  • 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 10.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 10.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 10.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 10.1 )

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

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

The following VB/NET sample shows how to show the eXGrid/NET or /WPF'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 = Exgrid1
        .Preview()
    End With
End Sub

Only a record is loaded when I use PutItems rs.GetRows(). Am I doing something wrong?

The most probably thing is that your recordset ( rs ) is a DAO recordset, and the GetRows with no argument retrieves only a single record. Instead you can use the rs.GetRows( rs.RecordCount).

Can I set the background color of the column headers?

The BackColorHeader property changes the background color of columns header. The BackColor property changes the background color for the entire control. If you need to change the background color for a particular column, you can use the HTMLCaption property of the Column object like in the following code:

With Grid1.Columns(0)
	.HTMLCaption = "<bgcolor=FF0000>new caption</bgcolor>"
End With

How do I assign an editor to a column or to a cell?

The editor can be applied to a column or to a cell. It depends on how do you indent to use the control. 

So, for instance if you need an editor for all cells in the column, the Editor property of the Column should be used like in the following sample:

With Grid1.Columns(0)
        With .Editor
            .EditType = DropDownListType
            .AddItem 1, "1. One", 1
            .AddItem 2, "2. Two", 2
            .AddItem 3, "3. Three", 3
        End With
End With

 The sample assigns to the first column an editor of drop down type with three values. 

Now if you need a particular editor for a particular cell you need to use the CellEditor property of Items object like in the following sample:

With Grid1.Items.CellEditor(Grid1.Items.ItemByIndex(0), 0)
        .EditType = DropDownListType
        .AddItem 1, "1. One", 1
        .AddItem 2, "2. Two", 2
        .AddItem 3, "3. Three", 3
        .AddItem 4, "4. Four", 4
End With

If you need to hide the cell's editor you have to use the CellEditorVisible property like follows ( if the cell's editor is hidden the default cell's value is shown ).:

Grid1.Items.CellEditorVisible(Grid1.Items.ItemByIndex(0), 0) = False 

If you need to remove the cell's editor, you can use the DeleteCellEditor method like in the following sample:

Grid1.Items.DeleteCellEditor Grid1.Items.ItemByIndex(0), 0

If you deleted the cell's editor using DeleteCellEditor method, the cell's editor is the column's editor in case it was set.

Can I have a predefined list of colors in your color picker?

Yes. The ColorListType value of EditTypeEnum specifies a color picker editor, that allows you to specify a predefined list of colors. By default, an editor of ColorListType editor adds the following colors: Black, White, Dark Red, Dark Green, Dark Yellow, Dark Blue, Dark Magenta, Dark Cyan, Light Grey, Dark Grey, Red, Green, Yellow, Blue, Magenta, Cyan. 

The following sample declares your own colors for a single cell:

With Grid1
        .Columns.Add ("Caption")
        With .Items
            Dim h As HITEM
            h = .AddItem(vbWhite)
            With .CellEditor(h, 0)
                .EditType = ColorListType
                .DropDownAutoWidth = False
                .ClearItems
                .AddItem vbBlack, "Black"
                .AddItem vbWhite, "White"
            End With
        End With
End With

The following sample declares your own colors for a column:

With Grid1
        Dim c As Column
        Set c = .Columns.Add("Colors")
        With c.Editor
            .EditType = ColorListType
            .DropDownAutoWidth = False
            .ClearItems
            .AddItem vbBlack, "Black"
            .AddItem vbWhite, "White"
        End With
        With .Items
            .AddItem vbBlack
            .AddItem vbWhite
        End With
        
End With

I am wondering if the control can display the color name when I use ColorListType editor?

If the Editor.Option( exColorListShowName ) is True, the control displays the color's name. By default, the  Editor.Option( exColorListShowName ) is False, and the control doesn't display the color's name. The following sample shows how to add a list of predefined colors:

With Grid1
        .MarkSearchColumn = False
        With .Columns.Add("ColorList").Editor
            .EditType = ColorListType
            .Option(exColorListShowName) = True
            .ClearItems
            .AddItem vbBlack, "Black"
            .AddItem vbWhite, "White"
        End With
        
        With .Items
            .AddItem vbBlack
            .AddItem vbWhite
        End With
End With

Is there any way to hide the system colors list into a ColorList editor? 

The system colors list into a ColorList editor is visible only if the Editor.Option( exColorShowSystem ) is True. Use the Editor.Option( exColorShowSystem ) = False to hide the system colors list into a ColorList editor. The following sample displays only the palette colors list:

With Grid1
        .MarkSearchColumn = False
        With .Columns.Add("Color").Editor
            .EditType = ColorType
            .Option(exColorShowSystem) = False
        End With
        
        With .Items
            .AddItem vbBlack
            .AddItem vbWhite
        End With
End With

Use the Editor.Option( exColorShowPalette ) = False to hide the palette colors list into a ColorType editor.

I have an item that hosts a Word document. How can I make the document read only?

The following sample adds an item that hosts a read-only Word Document:

With Grid1
        .BeginUpdate
        If .Columns.Count &gt; 0 Then
            Dim hx As HITEM
            hx = .Items.InsertControlItem(, "D:\Program Files\Microsoft Visual Studio .NET\Vc7\migration_guide.doc")
            With .Items.ItemObject(hx)
                .Document.Protect 2
            End With
        End If
        .EndUpdate
End With

The whole idea is to call Protect method of object returned by the Document property. 

Here's few hints that should be followed in order to get information about returned object ( ItemObject property ).

We would suggest using the following snippet of code ( the sample requires an Exontrol ExPropertiesList control on the form ).

MsgBox PropertiesList1.Interfaces(Grid1.Items.ItemObject(XXX))

where XXX is the handle of the item that hosts an ActiveX control. PropertiesList1 is the name of the ExPropertiesList control into your form. The above snippet displays the list of interfaces implemented by the object passed to Interfaces property of the ExPropertiesList control. Once that we got the interfaces list, we should look for any interface that object implements. For instance, if we are using a WebBrowser control the result of Interfaces property will include interfaces like: IWebBrowser and IWebBrowser2. Of course, you need to read more about each implemented interface depends on what are you trying to do with the hosted object. In our case, we have a Microsoft Web Browser control that hosts a Word document. Calling any property of IWebBrowser2 will affect only the WebBrowser control without affecting the inside document, so we need to go forward by looking at what Document property exposes using the following snippet of code:

MsgBox PropertiesList1.Interfaces(Grid1.Items.ItemObject(XXX).Document)

In this case the result is the list of interfaces exported by Document object. We will observe that it includes the _Document interface ( the main interface for Word automation ). Now how can I see the properties and methods that _Document interface exposes? There are plenty of tools that can browses the COM objects type libraries, we prefer using the  OLE/COM Object Viewer ( OLEVIEW.EXE ) tools. Usually it is located in the C:\Program Files\Microsoft Visual Studio\Common\Tools folder, it depends how you installed the MSDEV. So, in order to find out properties and methods that an IDispatch interface exposes you have to open the "Interfaces" item, and to look for the interface name. Once that we locate the interface we have to display its type library ( right click\View\View Type Info).

How do I enable the partial check feature? 

The control provides partial check feature for each column. The Column object exports the PartialCheck property that enables or disables the partial check feature on the column. The CellHasCheckBox property assigns a checkbox to a cell. Use the Def property to assign check boxes to all cells in the column. 

The following sample assigns check boxes to all cells in the first column: 

With Grid1
    .Columns(0).Def(exCellHasCheckBox) = True
End With

The following sample shows how to add checkboxes to the first column by enumerating the cells in the column:

With Grid1
        Dim i As Variant
        For Each i In .Items
            .Items.CellHasCheckBox(i, 0) = True
        Next
End With

Another option to turn on the cell's check box is using the AddItem event like in the following sample:

Private Sub Grid1_AddItem(ByVal Item As EXGRIDLibCtl.HITEM)
    Grid1.Items.CellHasCheckBox(Item, 0) = True
End Sub

If a cell is checked, the CellState property gets 1, if the cell is unchecked, the CellState property gets 0. If a cell is partially checked, the CellState gets 2. The CellStateChanged event is fired when user clicks the cell's checkbox.

How do I enumerate all visible items as they are displayed?

The following sample enumerates all visible items as they are displayed:

Private Sub enumVisibleItems(ByVal grid As EXGRIDLibCtl.grid)
    With grid.Items
        Dim h As HITEM
        h = .RootItem(0)
        While h <> 0
            Debug.Print .CellValue(h, 0)
            h = .NextVisibleItem(h)
        Wend
    End With
End Sub

I have an ActiveX control as a child item. How can I handle events that are fired by inside ActiveX control?

The exgrid control fires the ItemOleEvent event when an inside ActiveX control fires an event. The following sample shows how to handle events from contained components:

Private Sub Grid1_ItemOleEvent(ByVal Item As EXGRIDLibCtl.HITEM, ByVal Ev As EXGRIDLibCtl.IOleEvent)
On Error Resume Next
    With Ev
        Debug.Print .Name
        Dim i As Long
        For i = 0 To .CountParam - 1
            Debug.Print .Param(i).Name &amp; " = " &amp; .Param(i).Value
        Next
    End With
End Sub

If you need to retrieve the object that fires the event you can use the following statement:

Grid1.Items.ItemObject( Item )

Is there any way to set the default so that all items appear initially expanded, or do you have to set it each time for each item?

There is no public property to set the default so that all items appear initially expanded, but we would suggest you an alternative using the AddItem event like follows:

Private Sub Grid1_AddItem(ByVal Item As EXGRIDLibCtl.HITEM)
    With Grid1
        .BeginUpdate
        With .Items
            Dim h As HITEM
            h = .ItemParent(Item)
            While h <> 0
                .ExpandItem(h) = True
                h = .ItemParent(h)
            Wend
        End With
        .EndUpdate
    End With
End Sub

How can I determine if an item has child items?

You can use Items.ChildCount or Items.ItemChild property like follows:

If (Grid1.Items.ChildCount(h) <> 0) Then
End If

or

If (Grid1.Items.ItemChild(h) <> 0) Then
End If

The Items.ItemHasChildren property adds an expand button to the left side of the cell, no matter if the item contains child items. It is useful to build your virtual tree. 

How can I use the unbound mode?

The component exports the IUnboundHandler interface that provides unbound mode support. The IUnboundHandler.ItemsCount method specifies the number of items that will be loaded to the control. The IUnboundHandler.ReadItem method is called each time when component requires data for an item. The control requires data only for visible items. The ReadItem method is called only once for a single item.  The UnboundMode property specifies an object that implements the IUnboundHandler interface. If large number of records must be loaded in the control, we will recommend using the control in virtual mode

 The following sample uses the unbound mode to read data from an array. In the following sample the Form is the object that implements IUnboundHandler interface. Of course you can create your own class that implements IUnboundHandler in case you have multiple controls in the same form.

Dim a(100) As String
Implements IUnboundHandler

Private Sub Form_Load()
    a(0) = "First"
    a(100) = "Last"
    With Grid1
        .BeginUpdate
            .HeaderVisible = False
            .MarkSearchColumn = False
            With .Columns
                .Add "Index"
                .Add "Value"
            End With
            
            Set .UnboundHandler = Me
        .EndUpdate
    End With
End Sub

Private Property Get IUnboundHandler_ItemsCount() As Long
    IUnboundHandler_ItemsCount = UBound(a) - LBound(a) + 1
End Property

Private Sub IUnboundHandler_ReadItem(ByVal Index As Long, ByVal Source As Object, ByVal ItemHandle As Long)
    With Source.Items
        .CellValue(ItemHandle, 0) = Index
        .CellValue(ItemHandle, 1) = a(Index)
        .ItemBold(ItemHandle) = Index Mod 2 = 0
    End With
End Sub

How can I get the count of filtered items without full enumerating?

The following sample shows how to get the count of visible items when a filter was applied:

Private Declare Function GetScrollRange Lib "user32" (ByVal hwnd As Long, ByVal nBar As Long, lpMinPos As Long, lpMaxPos As Long) As Long
Private Const SB_VERT = 1

Private Sub Command1_Click()
    Dim l As Long, r As Long
    GetScrollRange Grid1.hwnd, SB_VERT, l, r
    Debug.Print r - l + 1
End Sub

I have inserted a "Shell.Explorer" control using Items.InsertControlItem method. The Items.ItemAppearance seems to have no effect. What can I do? 

The Items.InsertControlItem method inserts and hosts an ActiveX control. The Items.ItemAppearance doesn't change the border style of the ActiveX window. For instance, the following sample removes the WS_EX_CLIENTEDGE style of a "Shell.Explorer" control. 

The sample requires the following declarations:

Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Private Const GW_CHILD = 5
Private Const GWL_EXSTYLE = (-20)
Private Const WS_EX_CLIENTEDGE = &amp;H200

Here's the sample:

With Grid1.Items
        Dim h As Long, hx As HITEM
        
        hx = .InsertControlItem(, "Shell.Explorer")
        With .ItemObject(hx)
            .Navigate2 "c:\"
        End With
        h = .ItemWindowHost(hx)
        h = GetWindow(GetWindow(GetWindow(GetWindow(GetWindow(h, GW_CHILD), GW_CHILD), GW_CHILD), GW_CHILD), GW_CHILD)
        SetWindowLong h, GWL_EXSTYLE, GetWindowLong(h, GWL_EXSTYLE) And Not WS_EX_CLIENTEDGE
End With

Another alternative is like follows:

With Grid1.Items
            Dim hx As HITEM, h As Long
            hx = .InsertControlItem(, "C:\")
            h = getLastChild(getLastChild(.ItemWindowHost(hx)))
            SetWindowLong h, GWL_EXSTYLE, GetWindowLong(h, GWL_EXSTYLE) And Not WS_EX_CLIENTEDGE
End With
Private Function getLastChild(ByVal h As Long) As Long
    While GetWindow(h, GW_CHILD) <> 0
        h = GetWindow(h, GW_CHILD)
    Wend
    getLastChild = h
End Function

Both samples require a Column being added before adding new items, so you can use the control's template editor to add a column or the following snippet as well:

With Grid1.Columns
        .Add "Column 1"
End With

I am using the control in a C++ dialog. How can I add images to the control at design mode?

The control provides an Images panel that holds the icons of the control. The ShowImageList property specifies whether the Images panel is visible or hidden at design mode. In C++, the Images panel is behind the environment window. In order to insert new icons to the control, you need to locate the Images panel by minimizing the top windows. By dragging icon, dll, or exe files to the Images panel, you can insert new icons to the control. After you add icons to Images panel, you have to resize the control and to save the project.

Can I load the images from project's resources (C++)?

Actually there are two options to load icons from the project's resources like follows: 

  1. Passing a HIMAGELIST variable to the Images method of the exGrid control.
  2. Inserting new icons using the ReplaceIcon method of the exGrid control .

1. Save the Images list to a file using the ImageList_Write API. Use the CreateStreamOnHGlobal API to create a stream. Insert the file to the project's resources. Use the FindResource and LoadResource APIs to find and load a resource. Use the CreateStreamOnHGlobal API to create a stream. Use the ImageList_Create API to create a new Images list, use the ImageList_Read API to load images from a stream. Pass the HIMAGELIST to the Images method of the control.

2.  Use the LoadIcon or LoadImage APIs to load an icon from project's resources. 

The following sample shows how to load new icons from the project's resources

m_spGrid->ReplaceIcon( COleVariant(long(LoadIcon( _Module.GetResourceInstance(), MAKEINTRESOURCE(IDI_ICON1) ))), COleVariant((long)-1) );

I thought that Column.Alignment applies to the whole column , but it is only to a header. What can I do?

The Column.Alignment property aligns the whole column. The problem you have encountered is that the column that paints the hierarchy ( the TreeColumnIndex property specifies the index of the column where the hierarchy is painted ) can't be centered, so you need to call:

With Grid1
        .TreeColumnIndex = -1
End With

in case you are using the control to load a flat table. By default, the TreeColumnIndex is 0, and it points to the first column of the control. Use the Column.HeaderAlignment property to align the column's header.

Is it possible in tooltip not to show the column name?

By default, the Column.Caption property describes the title of the cell's tooltip. If you don't need to show the column's name in the cell's tooltip, you have to set the Caption property to an empty string, and to use the HTMLCaption property to assign the column's caption. This way, the cell's tooltip will not include the column's name.

Is there any way to suppress the cell's tooltip or should we call CellToolTip = ""?

Use the ToolTipDelay property = 0 or ToolTipPopDelay property = 0 to suppress the tooltips.

Is it possible to allow the user to enter multiple lines of text into a drop down window?

Yes, the exGrid control supports such of feature ( starting with the version 1.0.4.9 ). The EditTypeEnum.MemoDropDownType (18 ) specifies a multiple lines drop down edit control. The following sample shows how to assign to a cell a multiple lines drop down editor:

With Grid1.Items
        Dim h As HITEM
        h = .AddItem("This is a bit of text that should appear on a MemoDropDownType editor.")
        With .CellEditor(h, 0)
            .EditType = MemoDropDownType
            .Option(exMemoDropDownWidth) = 196
            .Option(exMemoDropDownHeight) = 64
            .Option(exMemoVScrollBar) = True
        End With
End With

Important notes about MemoDropDownType. 

  • The Editor.Option( exMemoDropDownWidth ) specifies the width ( in pixels ) of the MemoDropDownType editor when it is dropped. 
  • The Editor.Option( exMemoDropDownHeight ) specifies the height ( in pixels ) of the MemoDropDownType editor when it is dropped. 
  • The Editor.Option( exMemoDropDownAcceptReturn ) specifies whether the user closes the MemoDropDownType editor by pressing the ENTER key. If the Editor.Option( exMemoDropDownAcceptReturn ) is True, the user inserts new lines by pressing the ENTER key. The user can close the editor by pressing the CTRL + ENTER key. If the Editor.Option( exMemoDropDownAcceptReturn ) is False, the user inserts new lines by pressing the CTRL + ENTER key. The user can close the editor by pressing the ENTER key.  
  • The Editor.Option( exMemoHScrollBar ) adds the horizontal scroll bar to a MemoType or MemoDropDownType editor.
  • The Editor.Option( exMemoVScrollBar ) adds the vertical scroll bar to a MemoType or MemoDropDownType editor
  • Use the Items.CellSingleLine property to specify whether the cell displays multiple lines.

 How do I expand all items as a result of interactive demand from an user ( pressing button and etc... )?

The ExpandItem property of the Items collection expands or collapses an item. In order to expand all items you can use the following snippet of code:

With Grid1
        .BeginUpdate
        With .Items
            Dim i As Long
            For i = 0 To .ItemCount - 1
                .ExpandItem(.ItemByIndex(i)) = True
            Next
        End With
        .EndUpdate
End With

The following sample collapses all items:

With Grid1
        .BeginUpdate
        With .Items
            Dim i As Long
            For i = 0 To .ItemCount - 1
                .ExpandItem(.ItemByIndex(i)) = False
            Next
        End With
        .EndUpdate
End With

How can I define an editor, which allows only numeric values in cells, but also those numeric values with colons (or commas, depending on regional settings) like 10.5 (or 10,5 in Germany)?

You can use the EditType editor and FormatColumn event like in following sample:

Private Sub Form_Load()
    ' Specifies an EditType editor for the "Numeric" column,
    ' and let FormatColumn event being fired for it.
    With Grid1.Columns("Numeric")
        .Alignment = RightAlignment
        .FireFormatColumn = True
        With .Editor
            .EditType = EditType
        End With
    End With
End Sub

Private Sub Grid1_FormatColumn(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, Value As Variant)
On Error GoTo Error
    ' If the FormatNumber VB function fails to convert the value, resets the Value parameter
    Value = FormatNumber(Value, 2)
    Exit Sub
Error:
    Grid1.Items.CellValue(Item, ColIndex) = "0"
End Sub

Private Sub Grid1_KeyPress(KeyAscii As Integer)
    ' Moves the selected line to next visible line, when user presses the ENTER key
    If Grid1.FocusColumnIndex = Grid1.Columns("Numeric").Index Then
        If KeyAscii = vbKeyReturn Then
            With Grid1
                With .Items
                    If .SelectCount() &gt; 0 Then
                        Dim h As HITEM
                        h = .NextVisibleItem(.SelectedItem(h))
                        If h &lt;&gt; 0 Then
                            .SelectItem(h) = True
                            .EnsureVisibleItem h
                        End If
                    End If
                End With
            End With
            Else
                ' Allows only numeric characters
                Dim strAllowChars As String
                strAllowChars = "0123456789." &amp; Chr(8)
                If Not (InStr(1, strAllowChars, Chr(KeyAscii), vbTextCompare) &gt; 0) Then
                    KeyAscii = 0
                End If
        End If
    End If
End Sub

Also, the sample shows how to move the selected line to next visible line when user presses the ENTER key.

In VFP, it seems that if I call Edit method during DblClick event has no effect if I am using the AutoEdit = .F. Do you have any suggestion?

Call the Edit method like in the following sample:

thisform.Olecontrol1.Object.Edit()

The Object property provides access to the Automation server properties and methods for an OLE object. ( The Object property is provided by wrapper object in VFP, and it is not provided by the exGrid control ).

What are the keys combinations that control can handle?

Shortcut Keys Description
DOWN ARROW Selects the next visible item.
CTRL + DOWN ARROW Moves the focused item to next visible item if the SingleSel property is False.
UP ARROW Selects the previously visible item.
CTRL + UP ARROW Moves the focused item to the previously visible item, if SingleSel property is False.
RIGHT ARROW Expands the item if it is collapsed. Moves the focused cell to next visible column. Selects the next visible item if the item is expanded or item has no editor inside. 
CTRL + RIGHT ARROW Moves the focused cell to next visible column.
LEFT ARROW Collapses the item if it is expanded. Moves the focused cell to previously visible column. Selects the item to previously visible item if the item is collapsed or item has no editor inside.
CTRL + LEFT ARROW Moves the focused cell to previously visible column.
PAGE DOWN Scrolls down one screen in the current view, and selects the last visible item in the view.
CTRL + PAGE DOWN Scrolls down one screen in the current view, and moves the focused item to the last visible item in the view, if the SingleSel property is FALSE
PAGE UP Scrolls up one screen in the current view, and selects the first visible item in the view.
CTRL + PAGE UP Scrolls up one screen in the current view, and moves the focused item to the first visible item in the view, if the SingleSel property is False.
HOME Scrolls the control's content to the top of the control and selects the first visible item in the control.
CTRL + HOME Scrolls the control's content to the top of the control and moves the focused item to the first visible item in the control.
END Scrolls the control's content to the end of the control and selects the last visible item in the control.
CTRL + END Scrolls the control's content to the end of the control and moves the focused item to the last visible item in the control.
ADD Expands the item.
SUBTRACT Collapses the item.
MULTIPLY Expands recursively the focused ( selected ) item.
CTRL + MULTIPLY Expands recursively all items.
CTRL + SPACE Selects or unselects an item if control support multiple selection. 
SPACE Checks the next cell in the same radio group.
BACKSPACE Removes the last character when user searches for an item. The BACKSPACE key is handled only if the AutoSearch property is TRUE.
F2 Unselects the text inside an edit control if the text is selected. Selects the text inside an edit control if no text is selected.
F3 Selects the next matching item. The F3 key is handled only if the AutoSearch property is TRUE.
F4 Opens the editor of the cell. The F4 key has no effect if the cell has no editor attached.
TAB Moves the searching column to next visible column. The TAB key is handled only if the UseTabKey property is TRUE. 
SHIFT + TAB Moves the searching column to previously visible column. The SHIFT + TAB key is handled only if the UseTabKey property is TRUE.

Is there any property that changes the default paper size when using exPrint component?

The exPrint component uses the settings for default printer. Use your Control Panel/Printers to configure the settings for your default printer. 
  • From the Start menu, point to Settings, and then click Control Panel
  • Locate the Printers item and look for your default printer
  • Right click on your default printer, and select the Printing Preferences...
  • Select the orientation of the page and the paper size.
  • Click the Apply button

Is there any way to prevent the recordset from being automatically updated so that I can display a confirmation request message to make sure the user wants to go ahead with the change?

The Change event notifies your application that a change occurs. You can handle the Change event like in the following sample:

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    If Not MsgBox("Change", vbYesNo) = vbYes Then
        NewValue = Grid1.Items.CellValue(Item, ColIndex)
    End If
End Sub

If your application changes a value in the grid and there is no necessary user confirmation you can have the Change event like follows:

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    If m_nConfirmation = 0 Then
        If Not MsgBox("Change", vbYesNo) = vbYes Then
            NewValue = Grid1.Items.CellValue(Item, ColIndex)
        End If
    End If
End Sub

The code that changes a value in the grid should look like:

m_nConfirmation = m_nConfirmation + 1
    With Grid1
    ' DO the change here
    End With
m_nConfirmation = m_nConfirmation - 1
        

The tooltip for the cells in the list are always displayed to the right of that cell. If the cell is at the right hand edge of the screen, the tooltip is displayed off the screen. Is it a bug?

The control fires the ToolTip event before a tooltip shows up. The following samples moves the tooltip window if it is displayed off the screen:

Private Sub Grid1_ToolTip(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, Visible As Boolean, _ 
			  X As Long, Y As Long, ByVal CX As Long, ByVal CY As Long)
    With Screen
        If (X + CX > .Width / .TwipsPerPixelX) Then
            X = .Width / .TwipsPerPixelX - CX
        End If
        If (Y + CY > .Height / .TwipsPerPixelY) Then
            Y = .Height / .TwipsPerPixelY - CY
        End If
    End With
End Sub
        

How do I center an item on the control's client area in a user-sort multi-level hierarchy?

The EnsureVisibleItem method of the Items collection ensures that an item is in the control's client area. By default, the EnsureVisibleItem method cannot specify the position where the item should be displayed. Instead, the following EnsureVisibleItem procedure ensures that an item is displayed at a given position. The function requires declarations for the SendMessage API like follows:

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Const WM_VSCROLL = &H115
Private Const SB_VERT = 1
Private Const SB_LINEDOWN = 1
Private Const SB_LINEUP = 0

The EnsureVisibleItem procedure looks like following:

Private Sub EnsureVisibleItem(ByVal g As EXGRIDLibCtl.Grid, ByVal h As HITEM, ByVal p As Long)
    With g
        .BeginUpdate
            With .Items
                .SelectItem(h) = True
                .EnsureVisibleItem h
                
                Dim i As HITEM, pi As Long
                pi = 0
                i = .FirstVisibleItem
                While Not (i = h)
                    i = .NextVisibleItem(i)
                    pi = pi + 1
                Wend
                
                Dim b As Boolean
                b = pi < p
                For i = IIf(b, pi, p) To IIf(b, p, pi) - 1
                    SendMessage g.hwnd, WM_VSCROLL, IIf(Not b, SB_LINEDOWN, SB_LINEUP), 0
                Next
            End With
        .EndUpdate
    End With
End Sub

You can call the EnsureVisibleItem function like following:

EnsureVisibleItem Grid1, hItem, nPosition

where hItem is the handle of the item being positioned, and the nPosition is the position where the hItem should be displayed. If nPosition is 0, the hItem should be displayed as first visible item. 

Is there any workaround to display the item's index into a column

You can display the item's index using the FormatColumn event like in the following sample:

Private Sub Grid1_FormatColumn(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, Value As Variant)
    Value = Grid1.Items.ItemToIndex(Item)
End Sub     

Is there any option to assign check boxes to a boolean column?

The EditType.CheckValueType value specifies whether the editor displays a check box to represent the cell's value. For instance, if you have a column of boolean values, you can use the following code to assign check boxes to each cell in the column:

With Grid1.Columns("Check").Editor
        .EditType = CheckValueType
End With

In this case, the Items.CellValue property for each cell in the column specifies the state of the check box as follows:

  • 0 ( unchecked state ). The control displays the unchecked state check box.
  • 1 ( checked state ). The control displays the checked state check box.
  • 2 ( partial checked state ). The control displays the partial checked state check box.

The CheckImage property specifies the icon being displayed for different check box states.

If the values in the column differs than 0, 1, or 2 you can call the Editor.Option property to specify the check box states being displayed. For instance, if your column contains boolean values, True ( -1 ) and False ( 0 ), you can use the following sample to get displayed the checked states instead partial checked states.

With Grid1.Columns("Check").Editor
        .EditType = CheckValueType
        .Option(exCheckValue2) = 1
End With

For instance, use the CellEditorVisible property to hide the cell's editor, in case you need to represent a null check box state.

Unbound Mode vs Virtual Mode

Generally, the user needs to run the control in virtual mode, if a table with large number of records needs to be loaded. In virtual mode, the control handles 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 data cannot be viewed as a hierarchy, the user cannot add items manually, and so on. The main advantage of the virtual mode is that the control can handle 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 handle the item to the index of the virtual item. Use the Items.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 Items.EnsureVisibleItem method is not required in this case.        

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 sample adds a column, and 100 records. The index of each item is displayed.

  • Create a new project (Project1)
  • Add a control to the form ( Grid1 )
  • 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, 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.  
  • Open the form's code and add handler for the Form_Load event like follows:
Private Sub Form_Load()
    With Grid1
        .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. 

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. 

  • Create a new project (Project1)
  • Add a control to the form ( Grid1 )
  • 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 EXGRIDLibCtl.Grid)
    Set rs = CreateObject("ADODB.Recordset")
    rs.Open strTable, "Provider=Microsoft.Jet.OLEDB.4.0;Data Source= " &amp; 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, 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:  

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

  • Save the project
  • Run the project

Editing a table, using the VirtualMode

In this case, we assume that you are already familiar with the "Displaying a table, using the 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:
  • Add editors for each column that require being editable like follows:
With .Columns("OrderDate")
    With .Editor
        .EditType = DateType
    End With
End With
  • The Form_Load event should look 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
            
        .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:

  • Handle the control's Change event like follows:
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.

  • The Change event is fired when user changes a value in the control. The Change event is called even is the user changes the cell's value using the CellValue property, so the IUnboundHandler_ReadItem needs a change like follows:
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...

  • Add a new method to the Class1 object like follows ( Change ):
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. 

  • The last thing that we need to add is to declare the variable (counter ) nReading as Long: Dim nReading As Long, and to initialize it in the Class1 constructor like follows:
Private Sub Class_Initialize()
    nReading = 0
End Sub
  • Save and run the project

Adding a custom column, when the control is running in the Virtual Mode

We assume that you are familiar with: "Displaying a table, using the VirtualMode", "Editing a table, using the VirtualMode", 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 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
  • The IUnboundHandler_ReadItem method looks like following:
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 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 are 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_GRID1 resource called m_grid. In the meanwhile, please notice that the wizard will ask you 'The ActiveX Control "ExGrid 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 classese. ( CGrid, CItems, CColumn, CEditor, COleFont, CPicture, CColumns )
  • 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 like follows:
#import "c:\winnt\system32\exgrid.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 EXGRIDLib. The generated namespace includes definition for IUnboundHandler interface. It can be accessed using the declaration EXGRIDLib::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, 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"
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)(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()
};
  • Add  INTERFACE_PART definition in the UnboundHandler.cpp file like follows:
BEGIN_INTERFACE_MAP(CUnboundHandler, CCmdTarget)
	INTERFACE_PART(CUnboundHandler, __uuidof(EXGRIDLib::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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source-&gt;QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&amp;pGrid ) ) )
	{
		// assigns the value for each cell.
		pGrid-&gt;Items-&gt;CellValue[ItemHandle][_variant_t( (long)0 )] = _variant_t( Index );
		pGrid-&gt;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&lt;IUnknown*&gt;( this );
			AddRef();
			return S_OK;
		}
		if ( IsEqualIID( __uuidof( EXGRIDLib::IUnboundHandler), riid ) )
		{
			*ppvObject = static_cast&lt;EXGRIDLib::IUnboundHandler*&gt;( 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(EXGRIDLib::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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source-&gt;QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&amp;pGrid ) ) )
	{
		// assigns the value for each cell.
		pGrid-&gt;Items-&gt;CellValue[ItemHandle][_variant_t( (long)0 )] = _variant_t( Index );
		pGrid-&gt;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&lt;IUnknown*&gt;( this );
			AddRef();
			return S_OK;
		}
		if ( IsEqualIID( __uuidof( EXGRIDLib::IUnboundHandler), riid ) )
		{
			*ppvObject = static_cast&lt;EXGRIDLib::IUnboundHandler*&gt;( 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_grid.BeginUpdate();
	m_grid.GetColumns().Add( _T("Column 1") );
	m_grid.SetVirtualMode( TRUE );
	m_grid.SetUnboundHandler( &m_unboundHandler.m_xHandler );
m_grid.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( EXGRIDLib::IGrid* pGrid, LPCTSTR szTable, LPCTSTR szDatabase );

Now, the CUnboundHandler class definition should look like follows:

#import "c:\winnt\system32\exgrid.dll"
#import &lt;msado15.dll&gt; 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)(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;
};
  • Open the implementation file for CUnboundHandler class (UnboundHandler.cpp file )
  • Add the implementation for AttachTable function like follows:
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-&gt;Open(_variant_t( szTable ), _variant_t(strConnection), ADODB::adOpenStatic, ADODB::adLockPessimistic, NULL ) ) )
			{
				pGrid-&gt;BeginUpdate();
				for ( long i = 0; i &lt; m_spRecordset-&gt;Fields-&gt;GetCount(); i++ )
					pGrid-&gt;GetColumns()-&gt;Add( m_spRecordset-&gt;Fields-&gt;GetItem( _variant_t( i ) )-&gt;Name );
				pGrid-&gt;EndUpdate();
			}
		}
		catch ( _com_error&amp; 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-&gt;m_spRecordset-&gt;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, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis-&gt;m_spRecordset-&gt;Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

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

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

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source-&gt;QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&amp;pGrid ) ) )
	{
		// assigns the value for each cell.
		for ( long i = 0; i &lt; pThis-&gt;m_spRecordset-&gt;Fields-&gt;GetCount(); i++ )
			pGrid-&gt;Items-&gt;CellValue[ _variant_t( ItemHandle ) ][ _variant_t( i )] = pThis-&gt;m_spRecordset-&gt;Fields-&gt;GetItem( _variant_t( i ) )-&gt;Value;
		pGrid-&gt;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-&gt;Open(_variant_t( szTable ), _variant_t(strConnection), ADODB::adOpenStatic, ADODB::adLockPessimistic, NULL ) ) )
			{
				pGrid-&gt;BeginUpdate();
				for ( long i = 0; i &lt; m_spRecordset-&gt;Fields-&gt;GetCount(); i++ )
					pGrid-&gt;GetColumns()-&gt;Add( m_spRecordset-&gt;Fields-&gt;GetItem( _variant_t( i ) )-&gt;Name );
				pGrid-&gt;EndUpdate();
			}
		}
		catch ( _com_error&amp; 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&lt;IUnknown*&gt;( this );
			AddRef();
			return S_OK;
		}
		if ( IsEqualIID( __uuidof( EXGRIDLib::IUnboundHandler), riid ) )
		{
			*ppvObject = static_cast&lt;EXGRIDLib::IUnboundHandler*&gt;( 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:
EXGRIDLib::IGridPtr spGrid = NULL;
m_grid.GetControlUnknown()-&gt;QueryInterface( &amp;spGrid );
m_grid.BeginUpdate();
	m_unboundHandler.AttachTable( spGrid, _T("Orders"), _T("D:\\Exontrol\\ExGrid\\sample\\sample.mdb") );
	m_grid.SetVirtualMode( TRUE );
	m_grid.SetUnboundHandler( &amp;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.

  • 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 data in the control using the control's collection of editors.

  • Include the definition for the CColumns, CColumn, CEditor, CItems classes in the application's main dialog ( CAdoVirtualDlg )
#include "Columns.h"
#include "Column.h"
#include "Editor.h"
#include "Items.h"
  • Locate the OnInitDialog method in the implementation file of the application's main dialog ( AdoVirtualDlg.cpp )
  • Add editors for the columns like following:
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 );
  • Add a handler for the Change event of the control. 
void CADOVirtualDlg::OnChangeGrid1(long Item, long ColIndex, VARIANT FAR* NewValue) 
{
	m_unboundHandler.Change( NewValue, m_grid.GetItems().GetItemToVirtual( Item ), ColIndex );
}
  • Open the definition file of the CUnboundHandler class ( UnboundHandler.h file )
  • Add a new member variable of long type as:
 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. 

  • Add the definition of the Change function  in the CUnboundHandler class:
    virtual void Change( VARIANT* pvtNewValue, long Index, long ColIndex );

The CUnboundHandler class definition should look like:

#import "c:\winnt\system32\exgrid.dll"
#import &lt;msado15.dll&gt; 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)(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;
};
  • Init the counter in the CUnboundHandler class constructor like follows:
    CUnboundHandler::CUnboundHandler()
    {
    	m_nReading = 0;
    }
  • Change the raw_ReadItem method like follows:
STDMETHODIMP CUnboundHandler::XHandler::raw_ReadItem(long Index, IDispatch * Source, long ItemHandle)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	pThis-&gt;m_nReading++;
	pThis-&gt;m_spRecordset-&gt;Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source-&gt;QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&amp;pGrid ) ) )
	{
		// assigns the value for each cell.
		for ( long i = 0; i &lt; pThis-&gt;m_spRecordset-&gt;Fields-&gt;GetCount(); i++ )
			pGrid-&gt;Items-&gt;CellValue[ _variant_t( ItemHandle ) ][ _variant_t( i )] = pThis-&gt;m_spRecordset-&gt;Fields-&gt;GetItem( _variant_t( i ) )-&gt;Value;
		pGrid-&gt;Release();
	}
	pThis-&gt;m_nReading--;
	return S_OK;
}
  • Add implementation for the Change function like:
void CUnboundHandler::Change( VARIANT* pvtNewValue, long Index, long ColIndex )
{
	if ( m_nReading == 0 )
	{
		m_spRecordset-&gt;Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );
		m_spRecordset-&gt;Fields-&gt;GetItem( _variant_t( ColIndex ) )-&gt;Value = *pvtNewValue;
		m_spRecordset-&gt;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(long* pVal)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( pVal )
	{
		*pVal = pThis-&gt;m_spRecordset-&gt;RecordCount;
		return S_OK;;
	}
	return E_POINTER;
}

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

	// gets the source control 
	EXGRIDLib::IGrid* pGrid = NULL;
	if ( SUCCEEDED( Source-&gt;QueryInterface( __uuidof(EXGRIDLib::IGrid), (LPVOID*)&amp;pGrid ) ) )
	{
		// assigns the value for each cell.
		for ( long i = 0; i &lt; pThis-&gt;m_spRecordset-&gt;Fields-&gt;GetCount(); i++ )
			pGrid-&gt;Items-&gt;CellValue[ _variant_t( ItemHandle ) ][ _variant_t( i )] = pThis-&gt;m_spRecordset-&gt;Fields-&gt;GetItem( _variant_t( i ) )-&gt;Value;
		pGrid-&gt;Release();
	}
	pThis-&gt;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-&gt;Open(_variant_t( szTable ), _variant_t(strConnection), ADODB::adOpenStatic, ADODB::adLockPessimistic, NULL ) ) )
			{
				pGrid-&gt;BeginUpdate();
				for ( long i = 0; i &lt; m_spRecordset-&gt;Fields-&gt;GetCount(); i++ )
					pGrid-&gt;GetColumns()-&gt;Add( m_spRecordset-&gt;Fields-&gt;GetItem( _variant_t( i ) )-&gt;Name );
				pGrid-&gt;EndUpdate();
			}
		}
		catch ( _com_error&amp; e )
		{
			AfxMessageBox( e.Description() );
		}

	}
}

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

STDMETHODIMP CUnboundHandler::XHandler::QueryInterface( REFIID riid, void** ppvObject)
{
	METHOD_PROLOGUE(CUnboundHandler, Handler);
	if ( ppvObject )
	{
		if ( IsEqualIID( __uuidof(IUnknown), riid ) )
		{
			*ppvObject = static_cast&lt;IUnknown*&gt;( this );
			AddRef();
			return S_OK;
		}
		if ( IsEqualIID( __uuidof( EXGRIDLib::IUnboundHandler), riid ) )
		{
			*ppvObject = static_cast&lt;EXGRIDLib::IUnboundHandler*&gt;( 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-&gt;m_nReading++;
	pThis-&gt;m_spRecordset-&gt;Move( Index, _variant_t( (long)ADODB::adBookmarkFirst ) );

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

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

		pGrid-&gt;Release();
	}
	pThis-&gt;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"
#import <msado15.dll> rename ( "EOF", "adoEOF" )

Is there any option to change the look for expanding/collapsing buttons?

Use the HasButtons property of the control to change the appearance for expanding/collapsing buttons. 

How can I edit a password?

The Editor.Option( exEditPassword ) property indicates whether an edit control displays all characters as an asterisk (*) as they are typed ( passwords ). Use the Editor.Option( exEditPasswordChar ) property specifies the password character. The following sample adds a cell with a password editor:
With Grid1.Items
    Dim h As HITEM
    h = .InsertItem(, , "password")
    With .CellEditor(h, 0)
        .EditType = EXGRIDLibCtl.EditType
        .Option(EXGRIDLibCtl.EditorOptionEnum.exEditPassword) = True
    End With
End With

How can I get the column that's sorted?

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 there is no sorted column:
Private Function getSortingColumn(ByVal g As EXGRIDLibCtl.Grid) As Long
    Dim c As EXGRIDLibCtl.Column
    For Each c In g.Columns
        If Not c.SortOrder = EXGRIDLibCtl.SortNone Then
            getSortingColumn = c.Index
            Exit Function
        End If
    Next
    getSortingColumn = -1
End Function 

How can I select all filtered items?

The RootCount property specifies the number of root items in the control's Items collection. The RootItem property gives the root item by its position. The RootItem(0) gives the first item in the control's Items collection. The NextVisibleItem property gets the next visible item. In the sample we have not used the property FirstVisibleItem because it gives the first visible item, as it is displayed in the control's client area. The RootItem(0) gives the first visible item in the control no matter if the control contains a horizontal scroll bar or not.

The following function selects all visible items:

Private Sub selVisibleItems(ByVal g As EXGRIDLibCtl.Grid)
    With g
        .BeginUpdate
            With .Items
                Dim h As HITEM
                If .RootCount > 0 Then
                    h = .RootItem(0)
                    While Not h = 0
                        .SelectItem(h) = True
                        h = .NextVisibleItem(h)
                    Wend
                End If
            End With
        .EndUpdate
    End With
End Sub

The above sample selects only visible items, so items that are not expanded will not be selected. The following sample selects all items, even if they are collapsed:

Private Sub selAllItems(ByVal g As EXGRIDLibCtl.Grid)
    With g
        .BeginUpdate
            With .Items
                Dim h As HITEM
                If .RootCount > 0 Then
                    h = .RootItem(0)
                    While Not h = 0
                        selRecItem g, h
                        h = .NextSiblingItem(h)
                    Wend
                End If
            End With
        .EndUpdate
    End With
End Sub

Sub selRecItem(ByVal g As EXGRIDLibCtl.Grid, ByVal h As HITEM)
    If Not (h = 0) Then
        With g.Items
            .SelectItem(h) = True
            Dim hChild As HITEM
            hChild = .ItemChild(h)
            While Not (hChild = 0)
                .SelectItem(hChild) = True
                selRecItem g, hChild
                hChild = .NextSiblingItem(hChild)
            Wend
        End With
    End If
End Sub

The ItemChild property gives the first child of the item, if the item contains child items. The NextSiblingItem property retrieves the next sibling of the item in the parent's child list.

The control ensures that a column fits the visible client area when I click a cell. How can I avoid that?

The control automatically calls the EnsureVisibleColumn method when the user clicks a cell. The following sample shows how to avoid calling the EnsureVisibleColumn method when user clicks a cell:
 Private Sub Grid1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    With Grid1
        Dim c As Long, hit as Long
        Dim h As HITEM
        h = .ItemFromPoint(X / Screen.TwipsPerPixelX, Y / Screen.TwipsPerPixelY, c, hit)
        If Not (h = 0) Then
            .SearchColumnIndex = c
        End If
    End With
End Sub

I have a DateType editor. The NewValue parameter of the Change event gets the today date instead the value entered by the user. Is there any option to find out the value that user types?

The control converts the value that user types into a valid value accepted by the type of the editor. For instance, if you have a DateType editor, and user types the string "qws" the NewValue parameter of the Change event will not contain the string "qws", it will contain a valid date, in this case the today date.

If your application still requires the string that user typed in the text box, you can use the following:

  • Starting with the version 8.0.0.1 the EditingText property retrieves the caption of the editor while the control is in edit mode.
  • Starting with the version 1.0.6.8 the Editing property retrieves the window's handle for the built-in editor that's visible and focused, so all you need is to use the GetWindowText API to retrieve the window's text as follows:

The following VB code prints the text being typed: 

 Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    ' Finds the text inside the text box, in case that NewValue parameter is changed to a valid data
    Debug.Print getWndText(Grid1.Editing)
End Sub

Private Function getWndText(ByVal h As Long) As String
    Dim s As String
    s = Space(1024)
    GetWindowText h, s, 1024
    getWndText = To0(s)
End Function

Private Function To0(ByVal s As String) As String
    To0 = Left$(s, InStr(s, Chr$(0)) - 1)
End Function

The following C++ code prints the text being typed:

 void OnChangeGrid1(long Item, long ColIndex, VARIANT FAR* NewValue) 
{
	if ( m_grid.GetControlUnknown() != NULL )
		if ( long hEditing = m_grid.GetEditing() )
		{
			TCHAR szText[1024] = _T("");
			::GetWindowText( HWND(hEditing), szText, 1024 );
			OutputDebugString( szText );
		}
}

note The Editing property is called during the Change event.

  • Prior to version 1.0.6.8, you can use the following trick:
Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    ' Finds the text inside the text box, in case that NewValue parameter is changed to a valid data
    Debug.Print getWndText(getEditWnd(Grid1))
End Sub

Private Function getEditWnd(ByVal g As EXGRIDLibCtl.Grid) As Long
    Dim h As Long
    h = GetWindow(g.hwnd, GW_CHILD)
    While Not (h = 0)
        If (getWndClass(h) = "HolderBuiltIn") Then
                getEditWnd = GetWindow(h, GW_CHILD)
            Exit Function
        End If
        h = GetWindow(h, GW_HWNDNEXT)
    Wend
    getEditWnd = 0
End Function

Private Function getWndText(ByVal h As Long) As String
    Dim s As String
    s = Space(1024)
    GetWindowText h, s, 1024
    getWndText = To0(s)
End Function

Private Function getWndClass(ByVal h As Long) As String
    Dim s As String
    s = Space(1024)
    GetClassName h, s, 1024
    getWndClass = To0(s)
End Function

Private Function To0(ByVal s As String) As String
    To0 = Left$(s, InStr(s, Chr$(0)) - 1)
End Function

The sample requires the following API declarations:

Private Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Const GW_CHILD = 5
Private Const GW_HWNDNEXT = 2

The following C++ sample displays a message box with the caption that user types inside the text box of an editor:

HWND getEditWnd( HWND h )
{
	TCHAR szName[1024] = _T("");
	h = GetWindow( h, GW_CHILD );
	while ( !( h == 0 ) )
	{
		GetClassName( h, szName, 1024 );
		if ( _tcscmp( _T("HolderBuiltIn"), szName ) == 0 )
			return GetWindow( h, GW_CHILD );
		h = GetWindow( h, GW_HWNDNEXT );
	}
	return 0;
}

void OnChangeGrid1(long Item, long ColIndex, VARIANT FAR* NewValue) 
{
	HWND h = getEditWnd( m_grid.m_hWnd );
	if ( h )
	{
		TCHAR szText[1024] = _T("");
		::GetWindowText( h, szText, 1024 );
		::MessageBox( NULL, szText,NULL, NULL );
	}
}

See also: How can I unselect the cell's text when the user edits the cells?

How do I close the current editor after user hits the ENTER key?

The EditClose method closes the current editor, if the control is running in the edit mode. Use the Editing property to determine whether the control runs in the edit mode. The following sample closes the current editor if the user presses the ENTER key:
Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    With Grid1
        If not (.Editing = 0) Then
            If (KeyCode = 13) Then
                .EditClose
            End If
        End If
    End With
End Sub

The Form_Load event can look like follows

Private Sub Form_Load()
    nChanging = 0
    nChanging = nChanging + 1
    With Grid1
        .BeginUpdate
        With .Columns.Add("Column 1").Editor
            .EditType = EXGRIDLibCtl.EditType
        End With
        With .Items
            .AddItem 1
            .AddItem 2
        End With
        .EndUpdate
    End With
    nChanging = nChanging - 1
End Sub

In C++ the KeyDown handler looks like follows:

void OnKeyDownGrid1(short KeyCode, short Shift) 
{
	if ( m_grid.GetEditing() )
		if ( KeyCode == VK_RETURN )
		{
			m_grid.EditClose();
		}
}

I am using the ColumnAutoResize property to let control resizes the visible columns to fit the control's client area, but the proportions are lost when user resizes the control as small as possible. Is there any workaround?

The ColumnAutoResize property resizes the visible columns to fit the control's client area. There are two options to avoid losing the columns  proportions:
  • Avoiding resizing the control under a specified width, like in the sample:
Private Sub Form_Resize()
On Error Resume Next
    If ScaleWidth / Screen.TwipsPerPixelX > 64 Then
        With Grid1
            .Left = 0
            .Top = 0
            .Width = ScaleWidth
            .Height = ScaleHeight
        End With
    End If
End Sub
  • Using the LayoutChanged event to store the columns proportions manually, like in the following sample:
Option Explicit
Dim nFit As Long
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As MSG, ByVal hwnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Private Declare Function TranslateMessage Lib "user32" (lpMsg As MSG) As Long
Private Declare Function DispatchMessage Lib "user32" Alias "DispatchMessageA" (lpMsg As MSG) As Long
Private Const PM_REMOVE = &amp;H1
Private Type POINTAPI
        x As Long
        y As Long
End Type
Private Type MSG
    hwnd As Long
    message As Long
    wParam As Long
    lParam As Long
    time As Long
    pt As POINTAPI
End Type

Private Sub Form_Load()
    nFit = 0
    
    onGridResize Grid1
End Sub

Private Sub Form_Resize()
On Error Resume Next
    nFit = nFit + 1
    With Grid1
        .Left = 0
        .Top = 0
        .Width = ScaleWidth
        .Height = ScaleHeight
    End With
    fit Grid1
   
    nFit = nFit - 1
End Sub

Private Sub Grid1_LayoutChanged()
    If (nFit = 0) Then
        onGridResize Grid1
    End If
End Sub

Private Sub fit(ByVal g As EXGRIDLibCtl.Grid)
    nFit = nFit + 1
    With g
        If (.ColumnAutoResize) Then
            .BeginUpdate
            .ColumnAutoResize = False
            Dim c As EXGRIDLibCtl.Column
            For Each c In .Columns
                c.Width = c.Data
            Next
            .ColumnAutoResize = True
            .EndUpdate
        End If
    End With
    waitToProcessMessages
    nFit = nFit - 1
End Sub

Private Sub onGridResize(ByVal g As EXGRIDLibCtl.Grid)
    Dim c As Object
    With g
        If (.ColumnAutoResize) Then
            For Each c In .Columns
                c.Data = c.Width
            Next
        End If
    End With
End Sub

Private Sub waitToProcessMessages()
    Dim m As MSG
    While PeekMessage(m, 0, 0, 0, PM_REMOVE)
        TranslateMessage m
        DispatchMessage m
    Wend
End Sub

The sample holds the columns proportions when LayoutChanged event is fired. The sample ensures that the proportions are saved only when the user resizes on of the control's columns, not when the user resizes the entire control. The proportions are kept by the Data property of the Column object. The sample can be changed smoothly by using a simple collection to hold the columns proportions instead using the Data property of the Column object. By default, the control keeps the columns proportions in the Width property of the Column object. This way the width of the column is known every moment. 

How can I change the foreground color for the column's header?

There are two options to change the foreground color of the column's header:
  • Using the ForeColorHeader property to change the foreground color of the control's header bar.
  • Using the HTMLCaption property of the Column object to color parts of the column's caption, like in the following sample:
With Grid1.Columns(0)
	.HTMLCaption = "<fgcolor=008000>Column <b>1</b></fgcolor>"
End With

How can I use the Ev parameter of the UserEditorOleEvent event in C++?

The Ev parameter of the UserEditorOleEvent event holds a reference to an object that implements the IOleEvent interface. If the class wizard didn't generate any wrapper class to handle the IOleEvent interface you can use the #import directive to load the type library for the exGrid control like follows:

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

In this case the EXGridLib namespace is generated, so you will be able to locate the IOleEventPtr class declared as a smart pointer, so your handler could look like:

void OnUserEditorOleEventGrid1(LPDISPATCH Object, LPDISPATCH Ev, BOOL FAR* CloseEditor, long Item, long ColIndex) 
{
	EXGRIDLib::IOleEventPtr spEvent = Ev;
	TCHAR szText[1024] = _T("");
	wsprintf( szText, _T("Event: %s"), (char*)( spEvent-&gt;Name ) );
	SetWindowText( szText );
}

The Object parameter holds a reference to the ActiveX control created by UserEditor method. The type the object depends on the identifier used. The UserEditorObject property gets the same value as Object parameter.

The item is not selected when user clicks the +/- buttons. How can I get selected that item?

When user clicks the + or - buttons to expand or collapse an item, the control doesn't select the item. 

For instance, you can use the BeforeExpandItem event to select the item being expanded or collapsed.

Private Sub Grid1_BeforeExpandItem(ByVal Item As EXGRIDLibCtl.HITEM, Cancel As Variant)
    Grid1.Items.SelectItem(Item) = True
End Sub 

The SelectionChanged event is not fired if the user clicks the cell's check box. Is it by design?

Yes. Clicking the cell's check box doesn't select the item.

For instance, you can use the CellStateChanged event to select the item when the user clicks its check box:.

Private Sub Grid1_CellStateChanged(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long)
    Grid1.Items.SelectItem(Item) = True
End Sub

or you can the MouseDown event this way:

Private Sub Grid1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    With Grid1
        Dim c As Long, hit As HitTestInfoEnum
        i = .ItemFromPoint(-1, -1, c, hit)
        If (i <> 0) Then
            .Items.SelectItem(i) = True
        End If
    End With
End Sub

I have to support drag & drop from other applications to ExGrid-control, but don't know what to do. Can you point me on the right direction?

Change the OleDropMode property to exOleDropManual and handle the OLEDragDrop event. For instance, the following sample displays the files that user drags to the control.
Private Sub Grid1_OLEDragDrop(ByVal Data As EXGRIDLibCtl.IExDataObject, Effect As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    If (Data.GetFormat(exCFFiles)) Then
        With Data
            For Each f In .Files
                Debug.Print f
            Next
        End With
    End If
End Sub

In case the user drags anything else than files, you have to use the GetFormat and GetData properties. For instance, if you the user drags a text you can get the text by using the following sample:

Private Sub Grid1_OLEDragDrop(ByVal Data As EXGRIDLibCtl.IExDataObject, Effect As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    If (Data.GetFormat(exCFText)) Then
        With Data
            Debug.Print .GetData(exCFText)
        End With
    End If
End Sub

In VB, if you need to access directly the IDataObject interface you can use the following sample:

Private Sub Grid1_OLEDragDrop(ByVal Data As EXGRIDLibCtl.IExDataObject, Effect As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    Dim i As IDataObject
    Set i = Data
    ' Call here IDataObject members
End Sub

In C++, if you need to access directly the IDataObject interface you can use the following sample:

void CPPDlg::OnOLEDragDropGrid1(LPDISPATCH Data, long FAR* Effect, short Button, short Shift, long X, long Y) 
{
	IDataObject* pDataObject = NULL;
	if ( SUCCEEDED( Data-&gt;QueryInterface( IID_IDataObject, (LPVOID*)&amp;pDataObject ) ) )
	{
		// Call here the pDataObject memebers.
		pDataObject-&gt;Release();
	}
	
}

Is there any way to let user filter items on a given interval of dates?

Yes. Set the DisplayFilterPattern and DisplayFilterDate properties on True. The drop down filter window displays a date selector button right to the "Date" field that helps users to include dates in the interval.

Having an internal ExGrid ( using the InsertControlItem method ) there is no way to navigate out of the internal grid, using the keyboard. Is there something we can do in this direction?

The following sample uses the ItemOleEvent event, to focus the external control when the internal component fires KeyDown events. Also, the sample changes the focus to the internal component as soon as the selection is changed, using the SelectionChanged event. In order to run the following sample, we would suggest to set the Template property for your component ( using Properties\Template page, in design mode ), to have some columns loaded.
Private Declare Function PutFocus Lib "user32" Alias "SetFocus" (ByVal hwnd As Long) As Long

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        .HideSelection = True
        With .Items
            With .ItemObject(.InsertControlItem(, "Exontrol.Grid"))
                .Template = Grid1.Template
                .HideSelection = True
            End With
        End With
        .EndUpdate
    End With
End Sub

Private Sub Grid1_ItemOleEvent(ByVal Item As EXGRIDLibCtl.HITEM, ByVal Ev As EXGRIDLibCtl.IOleEvent)
    If (Ev.Name = "KeyDown") Then
        Dim bOut As Boolean
        bOut = False
        Dim i As Long
        i = Ev.Param("KeyCode").Value
        Select Case i
            Case vbKeyUp
                If (Ev.Param("Shift").Value = 2) Then
                    bOut = True
                End If
            Case vbKeyDown
                If (Ev.Param("Shift").Value = 2) Then
                    bOut = True
                End If
        End Select
        If bOut Then
            Ev.Param("KeyCode").Value = 0
            PutFocus Grid1.hwnd
        End If
    End If
End Sub

Private Sub Grid1_SelectionChanged()
    With Grid1.Items
        If (.SelectCount = 1) Then
            Dim o As Object
            Set o = .ItemObject(.SelectedItem(0))
            If Not o Is Nothing Then
                PutFocus o.hwnd
            End If
        End If
    End With
End Sub

The user can change the focus to the external control when internal component is focused by pressing the CTRL + KeyUp or CTRL+KeyDown keys. The idea is like follows. The sample changes the focus to the main window of an ActiveX control when the selected item hosts an ActiveX control, and changes the focus to the outside component when inside ActiveX control fires KeyDown event.

How do I advance to the next line when user hits ENTER key?

The following sample uses the KeyDown event to send Key Down when the user hits ENTER key:.
Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = 13) Then
        KeyCode = vbKeyDown
    End If
End Sub 

or:

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = 13) Then
        KeyCode = 40    ' vbKeyDown
    End If
End Sub 

or:

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = 13) Then
        Grid1.EventParam(0) = 40    ' KeyCode = vbKeyDown
    End If
End Sub

or:

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = 13) Then
        Grid1.ExecuteTemplate ("EventParam(0) = 40")
    End If
End Sub

Another option is to advance to the next line when user hits the enter key like follows:

Private Sub Grid1_KeyDown(KeyCode As Integer, ByVal Shift As Integer)
    If (KeyCode = 13) Then
        With Grid1.Items
            Dim h As HITEM
            h = .NextVisibleItem(.FocusItem)
            If Not (h = 0) Then
                .SelectItem(h) = True
                .EnsureVisibleItem h
                Grid1.Edit
            End If
        End With
    End If
End Sub

Automatically, if the AutoEdit property is True, the next visible line is edited.

A) The following sample shows how you can advance to the next column/first column /next line, once the user presses the Enter key. The sample defines the RETURN key to behave as TAB key, and sends DOWN key if reaches the last column.

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
            .UseTabKey = True
            .FullRowSelect = exColumnSel
            .Columns.Add("A").Editor.EditType = EditType
            .Columns.Add ("B")
            .Columns.Add("C").Editor.EditType = EditType
            .Items.SelectItem(.Items.AddItem(Array(1, 2, 3))) = True
            .Items.AddItem Array(4, 5, 6)
            .Items.AddItem Array(7, 8, 9)
        .EndUpdate
    End With
End Sub

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If KeyCode = 13 Then    ' Enter
        KeyCode = 9         ' Tab
        With Grid1
            If (.FocusColumnIndex = .Columns.Count - 1) Then
                CreateObject("WScript.Shell").SendKeys "{DOWN}" ' Down
            End If
        End With
    End If
End Sub

B) The following sample shows how you can advance to the next column/first column /next line, once the user presses the Enter key, if columns are read-only ( no editor is assigned to any column ). The sample defines the RETURN key to behave as TAB key.

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
            .UseTabKey = True
            .FullRowSelect = exColumnSel
            .Columns.Add "A"
            .Columns.Add "B"
            .Columns.Add "C"
            .Items.SelectItem(.Items.AddItem(Array(1, 2, 3))) = True
            .Items.AddItem Array(4, 5, 6)
            .Items.AddItem Array(7, 8, 9)
        .EndUpdate
    End With
End Sub

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If KeyCode = 13 Then    ' Enter
        KeyCode = 9         ' Tab
        With Grid1
            If (.FocusColumnIndex = .Columns.Count - 1) Then
                With .Items
                    Dim hNext As Long
                    hNext = .NextVisibleItem(.FocusItem) ' Advances to the next line, in case we reach the last column
                    If (hNext = 0) Then
                        hNext = .RootItem(0)
                    End If
                    If Not (hNext = 0) Then
                        .EnsureVisibleItem hNext
                        .SelectItem(hNext) = True
                    End If
                End With
            End If
        End With
    End If
End Sub

C) The following sample shows how you can advance to the next editable column/first editable column /next line, once the user presses the Enter key, if columns contains visible editors. The sample defines the RETURN key to behave as RIGHT key.

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
            .UseTabKey = True
            .FullRowSelect = exColumnSel
            .Columns.Add("A").Editor.EditType = EditType
            .Columns.Add("B").Editor.EditType = EditType
            .Columns.Add("C").Editor.EditType = EditType
            .Items.SelectItem(.Items.AddItem(Array(1, 2, 3))) = True
            .Items.AddItem Array(4, 5, 6)
            .Items.AddItem Array(7, 8, 9)
        .EndUpdate
    End With
End Sub

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If KeyCode = 13 Then        ' Enter
        KeyCode = vbKeyRight    ' Right
    End If
End Sub 

Is there any way to allow incremental searching into a DropDownList cell, without opening the drop down portion of the cell's editor?

Yes. The Editor.Option(exAutoDropDownList) property should be 1. By default, the Editor.Option(exAutoDropDownList) property is 0. The following sample adds such of a drop down list editor to the focused cell:
With Grid1.Items
    With .CellEditor(.FocusItem, Grid1.FocusColumnIndex)
        .EditType = DropDownListType
        .Option(exAutoDropDownList) = 1
        .DropDownAutoWidth = False
        .AddItem 1, "Root", 1
        .InsertItem 2, "Child 1", 2, 1
        .InsertItem 3, "Child 2", 3, 1
        .ExpandAll
    End With
End With

Is there any option to expand items while using the incremental searching feature? 

Yes. The version 1.0.7.4 adds a new property called ExpandOnSearch property. By default, the ExpandOnSearch property is False. If the ExpandOnSearch property is True the control expands items while user types characters in the control. Use the AutoSearch property to enable incremental searching feature.

How do I let my users change the positions of items by drag and drop?

The control supports OLE drag and drop, by setting the OLEDropMode property on exOLEDropManual. The ItemPosition property specifies the position of the item in the sibling items collection. In order to let user moves the items in your control by drag and drop please follow the steps:

  • Change the control's OLEDropMode property to exOLEDropMode type

  • Implementing the OLEStartDrag event like follows

    Private Sub Grid1_OLEStartDrag(ByVal Data As EXGRIDLibCtl.IExDataObject, AllowedEffects As Long)
        With Grid1.Items
            If Not (.FocusItem = 0) Then
                AllowedEffects = 2
                Data.SetData .FocusItem, exCFText
            End If
        End With
    End Sub
  • Implementing the OLEDragDrop event like follows:

    Private Sub Grid1_OLEDragDrop(ByVal Data As EXGRIDLibCtl.IExDataObject, Effect As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
        If (Data.GetFormat(exCFText)) Then
            With Grid1
                Dim hNew As HITEM, c As Long, h As HITEM
                hNew = .ItemFromPoint(X / Screen.TwipsPerPixelX, Y / Screen.TwipsPerPixelY, c)
                If Not hNew = 0 Then
                    h = Data.GetData(exCFText)
                    With .Items
                        If .ItemParent(h) = .ItemParent(hNew) Then
                            ' Moves the item in the sibling items collection only
                            .ItemPosition(h) = .ItemPosition(hNew)
                        End If
                    End With
                End If
            End With
        End If
    End Sub

If you need to change the cursor during drag and drop operation you have to handle the OLEGiveFeedback event and OLECompleteDrag event like follows:

Private Sub Grid1_OLECompleteDrag(ByVal Effect As Long)
    Screen.MousePointer = 0
End Sub

Private Sub Grid1_OLEGiveFeedback(ByVal Effect As Long, DefaultCursors As Boolean)
    DefaultCursors = False
    Screen.MousePointer = vbSizeNS
End Sub

How can I determine if the user clicks the +/- button of the cell?

Use the ItemFromPoint property to determine the item/cell over the cursor. Also, the ItemFromPoint property returns the hit test code within the cell. The following sample displays the cell's caption only when the mouse hovers the +/- buttons:

Private Sub Grid1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    ' Prints the cell over the cursor
    With Grid1
        Dim c As Long
        Dim h As HITEM
        Dim hit As EXGRIDLibCtl.HitTestInfoEnum
        h = .ItemFromPoint(X / Screen.TwipsPerPixelX, Y / Screen.TwipsPerPixelY, c, hit)
        If Not (h = 0) Then
            If (hit = exHTExpandButton) Then
                Debug.Print .Items.CellCaption(h, c)
            End If
        End If
        End With
End Sub

I want to use a SpinType editor for user input of numbers between 1 and 200. How can I do that? 

The SliderType editor includes a spin control and filters numbers between a range, so the idea is to get a slider, and to hide the spin control inside. The sample uses also the Numeric property to let users enter only integer numbers. Use the Option property of the Editor object to enable or disable a specific option.

You can have a sample like follows ( the sample assumes that the control has already a column )

With Grid1.Items
    Dim h As EXGRIDLibCtl.HITEM
    h = .AddItem(10)
    With .CellEditor(h, 0)
        .EditType = EXGRIDLibCtl.EditTypeEnum.SliderType
        .Numeric = exInteger
        .Option(EXGRIDLibCtl.EditorOptionEnum.exSliderWidth) = 0
        .Option(EXGRIDLibCtl.EditorOptionEnum.exSliderMin) = 1
        .Option(EXGRIDLibCtl.EditorOptionEnum.exSliderMax) = 200
    End With
End With

In case you require a simple edit box to filter numbers between a range, you can hide the spin control too using the exSpinStep option like in the following sample:

With Grid1.Items
    Dim h As EXGRIDLibCtl.HITEM
    h = .AddItem(10)
    With .CellEditor(h, 0)
        .EditType = EXGRIDLibCtl.EditTypeEnum.SliderType
        .Numeric = exInteger
        .Option(EXGRIDLibCtl.EditorOptionEnum.exSliderWidth) = 0
        .Option(EXGRIDLibCtl.EditorOptionEnum.exSliderMin) = 1
        .Option(EXGRIDLibCtl.EditorOptionEnum.exSliderMax) = 200
        .Option(EXGRIDLibCtl.EditorOptionEnum.exSpinStep) = 0
    End With
End With

Is there a way that we can programmatically clear the search text that is highlighted?

The following sample does the trick:

Grid1.ExpandOnSearch = Grid1.ExpandOnSearch

The control resets the searching text by refreshing the ExpandOnSearch property.

How do I change the background color for a particular item that contains a progressbar?

You need to assign an editor to the cell using the CellEditor property, and to use the option exProgressBarBackColor like in the following sample:
void setProgressStatusColor(long item, COLORREF rgb)
{
  CEditor itemProgress = m_items.GetCellEditor( V(item), V(long(0)) );

  itemProgress.SetEditType( 13 );				// progressBarType
  itemProgress.SetOption(12,  V(long(1)) );		// progress bar alignment
  itemProgress.SetOption(13,  V(long(1)) );		// progress bar mark ticker
  itemProgress.SetOption(11,  V(long( rgb )) ); // progress bar background
}

Is there any way to draw myself a cell ( owner draw support )?

The CellOwnerDraw property specifies an object that implements the IOwnerDrawHandler interface ( that's provided by the control ). The IOwnerDrawHandler interface exports a single method that needs to be implemented like in the following sample.

Implements IOwnerDrawHandler

Private Declare Function MoveToEx Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, lpPoint As POINTAPI) As Long
Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long

Private Type POINTAPI
        x As Long
        y As Long
End Type

Private Sub Form_Load()
    With Grid1.Items
        Set .CellOwnerDraw(.FindItem("Root 2"), 0) = Me
    End With
End Sub

Private Sub IOwnerDrawHandler_DrawCell(ByVal hdc As Long, ByVal left As Long, ByVal top As Long, ByVal right As Long, ByVal bottom As Long, ByVal Item As Long, ByVal Column As Long, ByVal Source As Object)
    Dim p As POINTAPI
    MoveToEx hdc, left, top, p
    LineTo hdc, right, bottom
End Sub

The sample draws a line from left-top corner to right-bottom corner of a single cell ( "Root 2" ), using few Windows API functions. The sample uses the Form itself to implement the IOwnerDrawHandler interface, but you can create multiple objects ( classes) that implement the IOwnerDrawHandler interface.

How can I access the inside ActiveX control, in C++?

Use the InsertControlItem method to insert items that host ActiveX controls. Use the ItemObject property to access the object being created by the InsertControlItem method.

The following C++ sample inserts a new exGrid control inside, and access the inside object: 

#include "items.h"
#import "c:\winnt\system32\exgrid.dll"
void init( CGrid& grid )
{
	CItems items = grid.GetItems();
	long hActiveX = items.GetItemByIndex( 1 );
	EXGRIDLib::IGridPtr spGrid = items.GetItemObject( hActiveX );
	spGrid->BeginUpdate();
		spGrid->Columns->Add("Column 1");
		spGrid->Columns->Add("Column 2");
	spGrid->EndUpdate();
}

The sample uses a template like follows:

BeginUpdate
LinesAtRoot = 1
Columns
{
	"Column 1"
}
Items
{
	Dim h, hx
	h = AddItem("Item 1")
	hx = InsertControlItem(h,"Exontrol.Grid")
	InsertItem(h,,"Total:")
	ExpandItem(h) = True
}
EndUpdate

The sample uses the #import directive to include the definitions of the exGrid component inside your project. It generates the EXGRIDLib namespace where you will be able to find all the types that control defines. The ItemObject property gets an IDispatch object, and you can get the grid inside by simple passing its value to a variable of IGridPtr type ( smart pointer ).

How do I check if the editor's drop down portion is visible or hidden?

You can use a sample like follows:

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Private Function isDropped()
    ' Specifies whether the control's drop down portion is visible or not
    isDropped = Not FindWindow("HostPopupWindow", "") = 0
End Function

The following sample advance to the next line when the ENTER key is pressed, and does the default action when an editor of drop down type is opened:

 Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = 13) Then
        If Not isDropped() Then
            KeyCode = vbKeyDown
        End If
    End If
End Sub

Is there any option to hide the column's name when showing the cell's tooltip?

By default, the cell's tooltip displays the column's caption on its title. Use the HTMLCaption property to specify the column's caption and set the Caption property on empty string. This way the cell's tooltip will not display the column's caption.

I want the standard Windows behavior when user does a right click. Is that possible with your control?

In the standard Windows behavior we have:
  • User can left-click to select, and can use shift and ctrl-click to select multiple items
  • A single right-click on an unselected item deselect all, selects the clicked item and displays a pop-up menu
  • A right-click on a selected item does not deselect all, moves the focus to the clicked item and  displays a pop-up m

Use the SingleSel property to allow multiple selection in the control. The following VB sample uses the ItemFromPoint property to determine the item from point, and MouseDown event to notify the application that the user right clicks the control (  the sample uses the exPopupMenu control to display a context menu ):

Private Sub Grid1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If Button = vbRightButton Then
        With Grid1
            Dim i As HITEM, c As Long, h As HitTestInfoEnum
            i = .ItemFromPoint(X / Screen.TwipsPerPixelX, Y / Screen.TwipsPerPixelY, c, h)
            If Not (i = 0) Then
                ' Unselect the rest of items if the user clicks a non selected item, and select the item
                .BeginUpdate
                With .Items
                    If .SelectItem(i) Then
                    Else
                        Dim j As Long
                        While .SelectCount > 0
                            .SelectItem(.SelectedItem(0)) = False
                        Wend
                    End If
                    .SelectItem(i) = True
                End With
                .EndUpdate
                ' Displays a context menu
                With PopupMenu1
                    .HAlign = EXPOPUPMENULibCtl.exLeft
                    With .Items
                        .Clear
                        .Add Grid1.Items.CellValue(i, c)
                        .Add "..."
                    End With
                    Debug.Print "You have selected " & .ShowAtCursor()
                End With
            End If
        End With
    End If
End Sub

I'm impressed by the control's filtering capabilities, and I am wondering if I can filter items that are in a given interval?

Starting with the version 1.0.8.3, 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.

Is there any function to get the cell's coordinates, in order to align nicely my context menu?

You can get the client coordinates of the cell using the following VB sample:
Private Sub getCellPos(ByVal g As EXGRIDLibCtl.Grid, ByVal hItem As EXGRIDLibCtl.hItem, ByVal nColumn As Long, X As Long, Y As Long)
    X = -g.ScrollPos(False)
    With g
        Dim c As EXGRIDLibCtl.Column
        For Each c In .Columns
            If (c.Visible) Then
                If (c.Position &lt; .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 EXGRIDLibCtl.hItem
            i = .FirstVisibleItem()
            While Not (i = hItem) And Not (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 ( hItem, nColumn ). The hItem indicates the handle 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 Grid1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If Button = 2 Then
        With Grid1
            Dim h As EXGRIDLibCtl.hItem, c As Long, hit As EXGRIDLibCtl.HitTestInfoEnum
            h = .ItemFromPoint(X / Screen.TwipsPerPixelX, Y / Screen.TwipsPerPixelY, c, hit)
            If Not (h = 0) Then
                ' Selects the item when user does a right click
                Grid1.Items.SelectItem(h) = True
                ' Gets the client coordinates of the cell
                Dim xCell As Long, yCell As Long
                getCellPos Grid1, h, c, xCell, yCell
                ' Converts the client coordinates to the screen coordinates
                Dim p As POINTAPI
                p.X = xCell
                p.Y = yCell
                ClientToScreen Grid1.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

In MS Access, the EXGRIDLibCtl should be renamed to EXGRIDCtl. The getCellPos function should look like:

Private Sub getCellPos(ByVal g As Grid, ByVal hItem As EXGRIDLib.hItem, ByVal nColumn As Long, X As Long, Y As Long)

instead 

Private Sub getCellPos(ByVal g As EXGRIDLibCtl.Grid, ByVal hItem As EXGRIDLibCtl.hItem, ByVal nColumn As Long, X As Long, Y As Long)

Thanks to Aileen Cariaso, IHG, who submitted the MS Access note.

How can I stop expanding the nodes when user presses the '*' on the numeric keypad?

There are at least two options to do it:
  • Using the ExpandOnKeys property. Specifies a value that indicates whether the control expands or collapses a node when user presses arrow keys. If the ExpandOnKeys property is False, the '*' on numeric keypad has no effect. In the same time, left arrow key, '-' on the numeric keypad, right arrow key, '+' on the numeric keypad will not expand or collapse the focused item. 
  • Using the KeyDown event handler like follows:
    Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
        If KeyCode = vbKeyMultiply Then
            If (Shift = 0) Then
                KeyCode = 0
            End If
        End If
    End Sub

    The code still allows you to use the CTRL + '*' on the numeric keypad to expand nodes. In order to disable completely the '*' feature you have to comment the 'Shift = 0' statement.

Does your control support merging cells?

Yes. Starting with the version 2.0.0.2 the control supports merging cells. Use the MergeCells method to combine two or more cells in a single cell. Use the UnmergeCells method to unmerge the merged cells. Use the CellMerge property to merge two or more cells in a single cell. The current implementation supports only horizontal merging. Use the SplitCell property to split a cell in two cells.

Can I split a cell using your control?

Yes. Starting with the version 2.0.0.3 the control supports splitting cells. Use the SplitCell property to split a cell in two cells. The SplitCell property retrieves the handle of the inner cell that's created. Use the UnsplitCell method  to remove the inner cell if it exists.Use the MergeCells method to combine two or more cells in a single cell. Use the CellMerge property to merge two or more cells in a single cell. Use the UnmergeCells method to unmerge the merged cells.

How can I unselect the cell's text when the user edits the cells?

By default, the text gets selected when the user edits a cell. The following sample sends an EM_SETSEL message to the cell's edit control when the EditOpen event is fired.
Private Function getEditWnd(ByVal g As EXGRIDLibCtl.Grid) As Long
    Dim h As Long
    h = GetWindow(g.hwnd, GW_CHILD)
    While Not (h = 0)
        If (getWndClass(h) = "HolderBuiltIn") Then
                getEditWnd = GetWindow(h, GW_CHILD)
            Exit Function
        End If
        h = GetWindow(h, GW_HWNDNEXT)
    Wend
    getEditWnd = 0
End Function

Private Function getWndClass(ByVal h As Long) As String
    Dim s As String
    s = Space(1024)
    GetClassName h, s, 1024
    getWndClass = To0(s)
End Function

Private Function To0(ByVal s As String) As String
    To0 = Left$(s, InStr(s, Chr$(0)) - 1)
End Function

Private Sub Grid1_EditOpen()
    PostMessage getEditWnd(Grid1), EM_SETSEL, 0, 0
End Sub

The sample requires the following declarations:

Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Const GW_CHILD = 5
Private Const GW_HWNDNEXT = 2
Private Const EM_SETSEL = &amp;HB1

See also: I have a DateType editor. The NewValue parameter of the Change event gets the today date instead the value entered by the user. Is there any option to find out the value that user types?

The DataSource property says that my recordset needs bookmarks. How can I find if my ADO recordset supports bookmarks?

The DataSource takes an ADO recordset and fetches data from the recordset to the control. The DataSource property may fail if the recordset doesn't support bookmarks and notifiers. In order to check if your recordset supports bookmarks and notifiers use the Supports property of the recordset like in the following VB sample:
Debug.Print "Bookmark support: " & rs.Supports(adBookmark)
Debug.Print "Notify support: " & rs.Supports(adNotify)

where the rs is an ADO object. If one of the Supports(adBookmark) or Supports(adNotify) value is False, the DataSource property fails. The control requires a bookmarkable recordset to identify a record when the user changes a value in the grid. The control requires the Notify support to update the control once that an ouside source changes the recordset. Additional, if the control's property DetectDelete or DetectNew is True, the CursorLocation property of the recordset must be adUseClient.

The following sample creates an ADO recordset in memory and load it to the control:

Dim rs As New ADODB.Recordset

Private Sub Form_Load()
    
    rs.CursorLocation = adUseClient
    rs.Fields.Append "String", adBSTR
    rs.Fields.Append "Boolean", adBoolean
    rs.Open
    
    rs.AddNew
    rs(0) = "item 1"
    rs(1) = True
    rs.Update
    
    rs.AddNew
    rs(0) = "item 2"
    rs(1) = False
    rs.Update
    
    Set Grid1.DataSource = rs
    
End Sub 

Is there any option to display a string that includes [m] in my cell?

Yes. You need to type 0178 from the numeric keypad while keeping the ALT key down. You may include strings like [m], [m], [180], ml, or m, m, and so on. If you can't find the ALT combination, just copy the symbol from this page, and paste to your cell. Open a symbol viewer and inspect the characters that you may insert. Each font contains symbols.

How do I assign a new look for the selected item?

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 Grid1
    .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 Grid1
    .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. Generally the properties that changes the background color for a part of the control supports skinning as well.

How fast is your control?

Please run the following sample and find out on your machine how fast it can be. The sample adds 1000 columns, and displays 2,000,000,000 items. On the machine we tested ( Pentium III, 1.8 GHz, 512 Mb ), it run in 94 mili seconds.
Option Explicit
Implements IUnboundHandler
Private Declare Function GetTickCount Lib "kernel32" () As Long
Dim its As EXGRIDLibCtl.Items

Private Sub Form_Load()
    Dim h As Long
    h = GetTickCount()
    With Grid1
        Dim i As Long
        .BeginUpdate
        .ColumnAutoResize = False
        With .Columns
            For i = 0 To 1000
                .Add i
            Next
        End With
        .VirtualMode = True
        Set its = .Items
        Set .UnboundHandler = Me
        .EndUpdate
    End With
    MsgBox GetTickCount - h
End Sub

Private Property Get IUnboundHandler_ItemsCount(ByVal Source As Object) As Long
    IUnboundHandler_ItemsCount = 2000000000
End Property

Private Sub IUnboundHandler_ReadItem(ByVal Index As Long, ByVal Source As Object, ByVal ItemHandle As Long)
    With its
        Dim i As Long
        For i = 0 To Source.Columns.Count - 1
            .CellValue(ItemHandle, i) = Index & " " & i
        Next
    End With
End Sub

The sample virtually loads 2,000,000,000 items.

Is there a setting where I can replace the current filter string with the one selected from the FilterList?

When I select an item in the filterlist on a column, it applies that filter to the grid - magic. When I select another value, it adds that
value to the first one selected and my column is now filtered for both those values. By default, the filter strings are cumulative. You can replace the selection in the drop down filter window, by resetting the FilterType property of the Column object, during the FilterChange event, like in the following VB sample:

Private Sub Grid1_FilterChange()
    Dim c As EXGRIDLibCtl.Column
    With Grid1
        For Each c In .Columns
            c.FilterType = exAll
        Next
        .FilterBarCaption = "new filter"
    End With
End Sub 

The sample changes the FilterType property to exAll, so next time when the user opens the drop down filter window, the 'All Items' filter option is selected, so user can select a new items from the drop down filter window, without filtering with two or more values. The code doesn't change the filter, because the ApplyFilter method is not called. Use the FilterBarCaption property to change the caption of the filterbar.

How do I get my list sorted after user changes data?

The Change event is called when the user changes data in the control.

The following sample sorts items when the user changes data ( The sample sorts the child items only ):

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    If (iLoading = 0) Then
        iLoading = iLoading + 1
        With Grid1.Items
            .CellValue(Item, ColIndex) = NewValue
            .SortChildren .ItemParent(Item), ColIndex, True
            Grid1.EditClose
        End With
        iLoading = iLoading - 1
    End If
End Sub  

The iLoading counter ensures that the Change event is not called recursively. The .CellValue(Item, ColIndex) = NewValue assignment is very important because it ensures that the CellValue property is filled before calling the SortChildren method. The Change event is called just before putting the NewValue parameter to the CellValue property.

The following sample sorts the entire column, when a cell on this column is changed ( The sample sorts all items ):

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    If (iLoading = 0) Then
        iLoading = iLoading + 1
        With Grid1.Items
            .CellValue(Item, ColIndex) = NewValue
            Grid1.Columns(ColIndex).SortOrder = SortAscending
            Grid1.EditClose
        End With
        iLoading = iLoading - 1
    End If
End Sub

The .CellValue(Item, ColIndex) = NewValue assignment is very important because it ensures that the CellValue property is filled before calling the SortOrder method. The EditClose method closes the editor.

Check also How do I sort the list of items at runtime?

Does your control supports conditional format?

The conditional formatting feature allows you to apply formats to a cell or range of cells, and have that formatting change depending on the value of the cell or the value of a formula. Yes, the control supports conditional format. The ConditionalFormats property gets the control's collection of ConditionalFormat objects.

For instance, the following sample bolds the items where the sum between first two columns is greater than 0:

Grid1.ConditionalFormats.Add("%0+%1>0").Bold = True

I'd like to filter for a specific value across any of the control's fields. How can I do that?

The control supports filtering items using AND, OR, NOT operators between columns. The FilterCriteria property specifies the filter criteria. In your case, if you have three columns, the control's FilterCriteria property should be "%0 or %1 or %2". The "not %1" specifies that the second column ( column's index is 1 ) excludes the values selected in the drop down filter window.

I have a column of images. How can I filter the records based on the type of image using the filter?

Set the Column.FilterType property on exImage. The Items.CellImage property indicates the cell's icon.

I use the UseTabKey property to navigate through my columns. Is there any possibility to jump to a specified column?

Use the SearchColumnIndex property to change the column where the user is able to search for items by typing characters. The trick is to disable the default implementation of the Tab key, and to provide a new one using the KeyDown event like in the following handler:
Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = vbKeyTab) Then
        KeyCode = 0
        Grid1.SearchColumnIndex = 1
    End If
End Sub

Is it possible to write the contents of a grid to a .TXT file or to a .CSV file?

The control provides the GetItems method to put grid's data to a safe array. The following sample enumerates the data in a safe array and put in a TAB separated file:
Dim r As String
With Grid1
    Dim j As Long, n As Long
    j = 0
    n = .Columns.Count
    For Each i In .GetItems()
        r = r & IIf(j <> 0, vbTab, "") & i
        j = (j + 1) Mod n
        If j = 0 Then
            r = r + vbCrLf
        End If
    Next
End With
Debug.Print r

 Or an alternative sample is:

Dim r As String, v As Variant, i As Long, j As Long
v = Grid1.GetItems()
For j = 0 To UBound(v, 2)
    For i = 0 To UBound(v, 1)
        r = r & IIf(i = 0, "", vbTab) & v(i, j)
    Next i
    r = r + vbCrLf
Next j
Debug.Print r

Is there any option to display a larger picture in the control's header?

Use the <img> built-in HTML tag in the HTMLCaption property, like: HTMLCaption = "<img>pic1</pic>", where pic1 was added using the HTMLPicture property.

Can I change the header when the cursor hovers the column?

The Background(exCursorHoverColumn) property specifies the visual appearance of the column's header when cursor hovers it. By default, the Background(exCursorHoverColumn) property is zero, and in this case it has no effect. Use the Add method to assign new skins to the control. Use the Background(exCursorHoverColumn) property to display a skin on the header, when the cursor hovers the column.

Is there any simple way to connect my grid to a MDB table?

You can use the Template feature of the control using the following x-script:
Dim rs
ColumnAutoResize = False
rs = CreateObject("ADOR.Recordset").Open("Orders","Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB", 3, 3 )
DataSource = rs

The sample loads the Orders table from the sample.mdb file. If loading the table takes too much time, you can run the control in virtual mode, by calling VirtualMode property on True, before setting the DataSource property like follows:

Dim rs
ColumnAutoResize = False
rs = CreateObject("ADOR.Recordset").Open("Orders","Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB", 3, 3 )
VirtualMode = True
DataSource = rs

Is there any way to connect my grid to a XML file?

You can use the Template feature of the control using the following x-script:
ColumnAutoResize = False
Dim rs
rs = CreateObject("ADODB.Recordset").Open("C:\Program Files\Exontrol\ExComboBox\Sample\VB\XML\Deals.xml")
DataSource = rs

The sample creates an ADODB recordset and loads the XML file. The DataSource property connects the grid to a recordset.

Is there any option to define some visual effect when drag and drop my items?

The control provides the following options to define the visual effect when drag and drop items:
  •  Background(exDragDropBefore), Specifies the visual appearance for the drag and drop cursor before showing the items. This option can be used to apply a background to the dragging items, before painting the items. By default, the control doesn't draw any background for the items being dragged. For instance, use the Background(exDragDropBefore) = SelBackColor property to specify the same background color/skin for items being dragged as they are selected.
  •  Background(exDragDropAfter), Specifies the visual appearance for the drag and drop cursor after showing the items. This option can be used to apply a semi-transparent/opaque background to the dragging items, after painting the items. Use this option to apply a transparent/opaque skin, after the items are painted. For instance, using an color or an opaque skin you can show something else when dragging the items.
  • Background(exDragDropListTop), Specifies the graphic feedback of the item from the drag and drop cursor if the cursor is in the top half of the row. Use this option to indicate the graphic to be displayed on the item, when the cursor is in the top half row. By default, nothing is displayed.
  • Background(exDragDropListBottom), Specifies the graphic feedback of the item from the drag and drop cursor if the cursor is in the bottom half of the row. Use this option to indicate the graphic to be displayed on the item, when the cursor is in the bottom half row. By default, nothing is displayed. Use the HitTestInfoEnum.exHTBottomHalf flag to check whether the user drags the items in the top half or bottom half of the row.
  • Background(exDragDropForeColor), Specifies the foreground color for the items being dragged. By default, the foreground color is black. 

All options, excepts the exDragDropForeColor option accept skins. Use the Appearance.Add method to define new skins in the control.

Do you have any plans to add cell spacing and cell padding to the grid?

  • The Column.Def(exCellPaddingLeft), Column.Def(exCellPaddingRight) defines the cell's horizontal padding, while the Column.Def(exCellPaddingTop), Column.Def(exCellPaddingBottom) defines the cell's vertical padding. If the Items.CellSingleLine property is False ( so the cell displays its content on multiple lines ), the Column.Def(exCellPaddingTop), Column.Def(exCellPaddingBottom) are added to the computed item's height. The exHeaderPaddingLeft, exHeaderPaddingRight, exHeaderPaddingTop, exHeaderPaddingBottom defines the padding for captions in the control's header.  
  • The control already supports cell padding, using the exCRD feature. The Exontrol's Custom Row Designer is a WYSWYG tool to build new layouts for cells/nodes, items/rows or columns/fields. The exCRD tool generates CRD strings from the layout you built. The syntax of CRD strings is designed to be easy to build, change and read. Using CRD strings is powerful than preformatted card view, group view formats, nested bands, and so on, since you are free to define the full layout of the cell/node, item/row or a column/field. The CellFormatLevel property or Column.Def(exCellFormatLevel) property define the layout of the cell/column using CRD strings. 

    For instance, this layout [dgl=1]""[b=0]:4,(4;""[b=4]/0/4;""[b=1]),""[b=0]:4 adds a 4 pixel borders around to the object its applies, like in the following picture:

Notes on VirtualMode, for VFP9

The original problem was that it was not possible to use the eXGrid control in VFP9 when using Virtual mode. The moment the UnboundHandler ReadItem method is called, a GPF occurs. When I run the 'VirtualMode' application under VFP8, it runs perfectly. When the need arose to use the virtual mode for real (number of lines were unpredictably high), we went for a solution I should have thought of before: we created a wrapper for the Unboundhandler in .Net.  This allowed us to use the eXgrid control in VFP9 in a virtual mode. Here you can download the wrapper.

Thanks to Timo Zuidema, AccountView B.V., Netherlands, who submitted this note.

How do I check if the cursor is between two items?

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 Grid1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim i As HITEM, c As Long, h As HitTestInfoEnum
    i = Grid1.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 AxGrid1_MouseMoveEvent(ByVal sender As System.Object, ByVal e As AxEXGRIDLib._IGridEvents_MouseMoveEvent) Handles AxGrid1.MouseMoveEvent
    With AxGrid1
        Dim c As Integer, h As EXGRIDLib.HitTestInfoEnum
        Dim i As Integer = .get_ItemFromPoint(-1, -1, c, h)
        If Not i = 0 Then
            If (h And EXGRIDLib.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 axGrid1_MouseMoveEvent(object sender, AxEXGRIDLib._IGridEvents_MouseMoveEvent e)
{
    int c = 0;
    EXGRIDLib.HitTestInfoEnum h;
    int i = axGrid1.get_ItemFromPoint(-1, -1, out c, out h);
    if (i != 0)
        if ( (h & EXGRIDLib.HitTestInfoEnum.exHTBetween) == EXGRIDLib.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 OnMouseMoveGrid1(short Button, short Shift, long X, long Y) 
{
	long c = 0, h = 0;
	long i = m_Grid.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.Grid1
	.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

How can display the sum for values within a column?

The following samples add the values from a column with 20000 rows/cells, and get the sum displayed on a locked item on the bottom side:

1. The following sample adds the locked item before adding rows, with initial value 0, and computes the sum progressively:

Dim iChange As Long

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    If (iChange = 0) Then
        iChange = iChange + 1
        With Grid1.Items
            h = .LockedItem(exBottom, 0)
            ' Removes the OldValue, and adds the NewValue
            .CellValue(h, 0) = .CellValue(h, 0) - .CellValue(Item, ColIndex) + NewValue
        End With
        iChange = iChange - 1
    End If
End Sub

Private Sub Form_Load()
    iChange = 0
    With Grid1
        .BeginUpdate
        .FullRowSelect = exColumnSel
        .Columns.Add("Value").Editor.EditType = 1
        With .Items

            ' Adds the divider item that displays the sum
            .LockedItemCount(exBottom) = 1
            h = .LockedItem(exBottom, 0)
            .ItemDivider(h) = 0
            .ItemDividerLineAlignment(h) = DividerTop
            .CellHAlignment(h) = RightAlignment
            .CellValue(h, 0) = 0

            ' Adds 20.000 rows
            For i = 1 To 20000
            .AddItem i
            Next

        End With
        .EndUpdate
    End With
End Sub

2. The following sample adds the locked item after adding the rows, and computes the entire sum each time a value is changed:

Dim iChange As Long

Private Function Sum() As Double
    Dim s As Double
    With Grid1.Items
        For Each i In Grid1.Items
            s = s + .CellValue(i, 0)
        Next
    End With
    Sum = s
End Function

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    If (iChange = 0) Then
        iChange = iChange + 1
        With Grid1.Items
            ' Updates the value of the cell before calling the Sum
            .CellValue(Item, ColIndex) = NewValue
            ' Call the Sum to update the sum cell
            .CellValue(.LockedItem(exBottom, 0), 0) = Sum()
        End With
        iChange = iChange - 1
    End If
End Sub

Private Sub Form_Load()
    iChange = 0
    With Grid1
        iChange = iChange + 1
        .BeginUpdate
        .FullRowSelect = exColumnSel
        .Columns.Add("Value").Editor.EditType = 1
        With .Items

            ' Adds 20.000 rows
            For i = 1 To 20000
            .AddItem i
            Next
            
            ' Adds the divider item that displays the sum
            .LockedItemCount(exBottom) = 1
            h = .LockedItem(exBottom, 0)
            .ItemDivider(h) = 0
            .ItemDividerLineAlignment(h) = DividerTop
            .CellHAlignment(h) = RightAlignment
            .CellValue(h, 0) = Sum()

        End With
        .EndUpdate
        iChange = iChange - 1
    End With
End Sub

The computed field displays decimals 0.4166666 while I enter 1.00 in the formula's cell. What is happen?

The computed field is trying to convert the value to double, but if it can be converted to a date, ( 1:00:00 AM ), the date value is used instead. You can convert the type using dbl operator in the ComputedField formula as ( dbl(%0) + dbl(%1) means adding the values converted to double  ) or convert the NewValue parameter of the Change event as follows:
Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)

    NewValue = CDbl(NewValue)

End Sub

How can I display currency format within the column?

There are several options in order to display a different content for the column. By default, the Items.CellValue 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 Grid1
            .BeginUpdate
            .Columns.Add "A"
            .Columns.Add("B").FireFormatColumn = True ' Index of it is 1
            With .Items
                .AddItem Array("One", 1)
                .AddItem Array("Two", 2)
            End With
            .EndUpdate
        End With
    End Sub
    
    Private Sub Grid1_FormatColumn(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, Value As Variant)
        Value = FormatCurrency(Value, 2, vbUseDefault)
    End Sub
  3. Items.CellOwnerDraw property indicates an object that perform owner draw on cells.
  4. Assigns an editor to a cell or column using the Items.CellEditor or Column.Editor. For instance, you have a drop down list editor ( DropDownListType(3) ), which lists predefined values including HTML format, and so, the cell/column will display the associated string to the cell's value.

How can I drag icons from Windows Explorer and display in the control?

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 Grid1
        .BeginUpdate
            .OLEDropMode = exOLEDropManual
            .FullRowSelect = False
            .DefaultItemHeight = 18
            .Columns.Add "Icons"
        .EndUpdate
    End With
End Sub

Private Sub Grid1_OLEDragDrop(ByVal Data As EXGRIDLibCtl.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 Grid1
                    Dim g As SHFILEINFO
                    .BeginUpdate
                    SHGetFileInfo Data.Files.Item(i), 0, g, Len(g), SHGFI_ICON Or SHGFI_SMALLICON
                    .ReplaceIcon g.hIcon
                    .Items.CellImage(Grid1.Items.AddItem(Data.Files.Item(i)), 0) = iIcon
                    iIcon = iIcon + 1
                    .EndUpdate
                End With
            Next
        End If
    End With
End Sub

Private Sub Grid1_OLEDragOver(ByVal Data As EXGRIDLibCtl.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.

How can I advance to the next field once the user presses the ENTER key?

 The following sample moves the focus to a new cell / field once the user presses the ENTER key:
Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = 13) Then
        KeyCode = 9
    End If
End Sub

The sample check if the user presses the ENTER key, and sends back to the control the TAB key. By default, the TAB key moves the focused cell or column to the next available psotion.

You can use the FocusColumnIndex property to change programmatically the focused column, and so the focused field or cell. Use the FocusItem property to determine the focused item.

I am using multiple selection. How can I remove all selected items?

Here is 2 options to remove the selected items in the control:
  1. Collects the items to be removed, using the Items.SelectCount and Items.SelectedItem properties. Once the collection is completed, you can call the Items.RemoveItem for each element being found.
  2. While the Items.SelectCount property is greater than 0, call the Items.RemoveItem( Items.SelectedItem(0) ), so removes the first selected item until all all released.

The following VB sample shows the method 1:

Private Sub removeSelection1()

    Dim i As Long, h As Variant
    Dim cItems As New Collection
    
    Grid1.BeginUpdate
    With Grid1.Items
        For i = 0 To .SelectCount - 1
            cItems.Add .SelectedItem(i)
        Next
        For Each h In cItems
            .RemoveItem h
        Next
    End With
    Grid1.EndUpdate
    
End Sub

The following VB sample shows the method 2:

Private Sub removeSelection2()
    
    Grid1.BeginUpdate
    With Grid1.Items
        While .SelectCount > 0
            .RemoveItem .SelectedItem(0)
        Wend
    End With
    Grid1.EndUpdate

End Sub

How can I find the index of the focused item?

The SelectionChanged event is fired once the user changes the selection/focused item. The Items.FocusItem property retrieves the handle of the focused item, while the Items.ItemToIndex property gives the index of the item within the Items collection.

The following VB sample displays the index of the focused item/row:

Private Sub Grid1_SelectionChanged()
    With Grid1.Items
        Dim nFocusIndex As Long
        nFocusIndex = .ItemToIndex(.FocusItem)
        Debug.Print "Focus Index is " & nFocusIndex
    End With
End Sub

I am using the EnsureVisibleItem/ScrollPos method but seems that it is not working?

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

How can I prevent highlighting the column from the cursor?

The Background(exCursorHoverColumn) property specifies the column's visual appearance when the cursor hovers the column's header bar. The idea is to provide an empty or transparent EBN to be displayed when the cursor hovers the column as in the following VB sample:
With Grid1
	.VisualAppearance.Add 1,"gBFLBCJwBAEHhEJAEGg4BI0IQAAYAQGKIYBkAKBQAGaAoDDUOQzQwAAxDKKUEwsACEIrjKCYVgOHYYRrIMYgBCMJhLEoaZLhEZRQiqDYtRDFQBSDDcPw/EaRZohGaYJgEgI="
	.Background(exCursorHoverColumn) = &H1000000
End With

Is it possible to copy the selected row/item from my application to MS Excel?

Whenever you need to copy data from your application to another application you can use the Clipboard mechanism, using the CTRL + C (Copy), CTRL + V (Paste). The idea is building a string with the information you want to paste to another application, and set it as the clipboard's data. The following samples use the Items.FocusItem, which returns the handle of the focused item, the Items.CellValue that gives the value of the specified cell and the Columns.Count that gets the number of the columns. Also, you can use the Items.SelectCount,  Items.SelectedItem to retrieve the set of selected items.

The following VB sample copies the content of the focused row to the clipboard so it can be pasted to any OLE application, including MS Excel.

Private Sub CopyFocusItem(ByVal g As Object)
    On Error Resume Next
    Dim sCopy As String
    With g.Items
        For i = 0 To g.Columns.Count - 1
            sCopy = sCopy + .CellValue(.FocusItem, i) + vbTab
        Next
    End With
    Clipboard.Clear
    Clipboard.SetText sCopy
End Sub

So, call the CopyFocusItem method whenever you need to copy the focused item ( clicking of a button, pressing the CTRL + C key, and so on ), and paste the clipboard's content to your MS Excel. 

  • Call the CopyFocusItem method.
  • Open MS Excel
  • Click a cell where you need to paste the content
  • Press the CTRL + V, so the content of the clipboard is pasted.

The sample can be extended so it will include the column's captions as:

Private Sub CopyFocusItem(ByVal g As Object)
    On Error Resume Next
    Dim sCopy As String
    With g.Columns
        For i = 0 To g.Columns.Count - 1
            sCopy = sCopy + g.Columns(i).Caption + vbTab
        Next
    End With
    sCopy = sCopy + vbCrLf
    With g.Items
        For i = 0 To g.Columns.Count - 1
            sCopy = sCopy + .CellValue(.FocusItem, i) + vbTab
        Next
    End With
    Clipboard.Clear
    Clipboard.SetText sCopy
End Sub

The following VB sample copies the selected items to the clipboard ( in case your application lets user selects multiple items, SingleSel property ):

Private Sub CopySelectedItems(ByVal g As Object)
    On Error Resume Next
    Dim sCopy As String
    With g.Items
        For j = 0 To .SelectCount - 1
            For i = 0 To g.Columns.Count - 1
                sCopy = sCopy + .CellValue(.SelectedItem(j), i) + vbTab
            Next
        sCopy = sCopy + vbCrLf
        Next
    End With
    Clipboard.Clear
    Clipboard.SetText sCopy
End Sub

The following VB sample copies the selected items to the clipboard, including the column's captions ( in case your application lets user selects multiple items, SingleSel property ):

Private Sub CopySelectedItems(ByVal g As Object)
    On Error Resume Next
    Dim sCopy As String
    With g.Columns
        For i = 0 To g.Columns.Count - 1
            sCopy = sCopy + g.Columns(i).Caption + vbTab
        Next
    End With
    sCopy = sCopy + vbCrLf
    With g.Items
        For j = 0 To .SelectCount - 1
            For i = 0 To g.Columns.Count - 1
                sCopy = sCopy + .CellValue(.SelectedItem(j), i) + vbTab
            Next
        sCopy = sCopy + vbCrLf
        Next
    End With
    Clipboard.Clear
    Clipboard.SetText sCopy
End Sub

Can I use arrays to load to your control?

Here's some ideas on how you can use arrays with the control.

A). Using the GetItems/PutItems to get or put the items/cells using arrays. The GetItems method gets the items/cells of the control to a safe array. The PutItems inserts the array of values to the control. For instance the following sample adds 3 columns and 1001 items from an array:

/COM version

With Grid1
    .Columns.Add "C1"
    .Columns.Add "C2"
    .Columns.Add "C3"
    Dim v(2, 1000) As String
    v(1, 10) = "zece"
    .PutItems v
End With

In VB the arrays is zero-based, so 2 indicates actually 0, 1 and 2 ( 3 columns ).

/NET or /WPF version

With Exgrid1
    .Columns.Add("C1")
    .Columns.Add("C2")
    .Columns.Add("C3")
    Dim v(2, 1000) As String
    v(1, 10) = "zece"
    .PutItems(v)
End With

B1). You can use the PutItems method to insert a hierarchy, for single-column control. The following sample adds a hierarchy as Root\Child 1, Child 2\SubChild 1, SubChild 2

/COM version

With Grid1
    .LinesAtRoot = exLinesAtRoot
    .Columns.Add "Nodes"
    .PutItems Array("Root", Array("Child 1", "Child 2", Array("SubChild 1", "SubChild 2")))
End With

/NET or /WPF version

With Exgrid1
    .LinesAtRoot = exontrol.EXGRIDLib.LinesAtRootEnum.exLinesAtRoot
    .Columns.Add("Nodes")
    .PutItems(New Object() {"Root", New Object() {"Child 1", "Child 2", New Object() {"SubChild 1", "SubChild 2"}}})
End With

B2). You can use the PutItems method to insert a hierarchy, for single-column control, as child items. The following sample adds an item New, and a sub-hierarchy Root\Child 1, Child 2\SubChild 1, SubChild 2

/COM version

With Grid1
    .LinesAtRoot = exLinesAtRoot
    .Columns.Add "Nodes"
    .PutItems Array("Root", Array("Child 1", "Child 2", Array("SubChild 1", "SubChild 2"))), .Items.AddItem("new")
End With

/NET or /WPF version

With Exgrid1
    .LinesAtRoot = exontrol.EXGRIDLib.LinesAtRootEnum.exLinesAtRoot
    .Columns.Add("Nodes")
    .PutItems(New Object() {"Root", New Object() {"Child 1", "Child 2", New Object() {"SubChild 1", "SubChild 2"}}}, .Items.AddItem("new"))
End With

C). You can use the arrays to fill a multiple-columns control in Items.AddItem/Items.InsertItem methods as in the following sample:

/COM version

With Grid1
    .Columns.Add "C1"
    .Columns.Add "C2"
    .Columns.Add "C3"
    With .Items
        .AddItem Array("Cell 1.1", "Cell 1.2", "Cell 1.3")
        .AddItem Array("Cell 2.1", "Cell 2.2", "Cell 2.3")
    End With
End With

/NET or /WPF version

With Exgrid1
    .Columns.Add("C1")
    .Columns.Add("C2")
    .Columns.Add("C3")
    With .Items
        .AddItem(New Object() {"Cell 1.1", "Cell 1.2", "Cell 1.3"})
        .AddItem(New Object() {"Cell 2.1", "Cell 2.2", "Cell 2.3"})
    End With
End With

I am calling the Edit method during the DblClick event and the newly editor is locked. What I can do?

For some events, calling the Edit method programmatically requires be calling outside of the event, or when the event ends. You can use the following tricks:
Private Declare Function RegisterWindowMessage Lib "user32" Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Private Sub Grid1_DblClick(Shift As Integer, X As Single, Y As Single)
   MsgBox "DblClick event"
   PostMessage Grid1.hwnd, RegisterWindowMessage("WM_EDITOPEN"), 0, 0
End Sub
Private Sub Grid1_DblClick(Shift As Integer, X As Single, Y As Single)
    MsgBox "Edit is gone"
    Timer1.Enabled = True
End Sub

Private Sub Timer1_Timer()
    Timer1.Enabled = False
    Grid1.Edit
End Sub

The both samples forces calling the Edit method once the event is processed and returned to the control. The first sample uses the PostMessage to invokes the Edit method once the control is available to process new messages, while the second sample uses a timer to call the Edit method later.

How do I update ( add, remove, update records ) a DAO recordset, using your /COM control?

The /COM version of the component provides the DataSource property, which can be used to bound the control's view to a DAO recordset. By default, once you set the DataSource property to a recordset, all changes you do on the control will be updated in the associated recordset. Because the DAO object does not provide any notifications or events the control is not able to detect any AddNew or Delete method that has been called. Instead, the control provides the AddItem and RemoveItem events that notifies your application once a new item is added to the control, or when an item is deleted. Based on these events, you will be able to manipulate the DAO recordset appropriate as in the following samples. In addition, the control fires the Error event in case any error occurs when handling the ADO or DAO recordsets, For instance, trying to update a read-only field.  In conclusion, if user changes a cell/value in the control, the associated field in the recordset is automatically updated. If any error occurs on updating the associated record, the Error event is fired which describes the error.

Handling the AddNew method in the control, using the DAO recordset on MS Access

  • Insert a Button and the Control to a form, and name them as cmdAddNew and Grid1
  • Add the Form_Load event of the form with the following code:
    Private Sub Form_Load()
        With Grid1
            .BeginUpdate
                .DataSource = CurrentDb.OpenRecordset("Employees")
                .DetectAddNew = True
            .EndUpdate
        End With
    End Sub 

The code binds the control to a DAO recordset. Please notice, that the DetectAddNew property is set after calling the DataSource method. Setting the DetectAddNew property on True, makes the control associate new items with new records added during the AddItem event as shown bellow.

  • Add the Click event of the cmdAddNew button with the following code
Private Sub cmdAddNew_Click()
    With Grid1.Items
        .EnsureVisibleItem .AddItem
    End With
End Sub 

The code adds a new item to the control and ensures that the new item fits the control's client area. The Items.AddItem call makes the control to fire the AddItem event, which will actually add the new record to the database, as in the following code

  • Add the AddItem event of the Control with the following code:
    Private Sub Grid1_AddItem(ByVal Item As Long)
        With Grid1
            If .DetectAddNew Then
                With .DataSource
                    .AddNew
                    !Lastname = "new"
                    !FirstName = "new"
                    .Update
                End With
            End If
        End With
    End Sub

The code adds a new record to the bounded recordset. Here you need to insert or update the required fields so the new record is added to the DAO recordset. Once the event is finished, the new item is associated with the new record in the database, so from now on, any change to the item will be reflected in the recordset.

Handling the Delete method in the control, using the DAO recordset on MS Access

  • Insert a Button and the Control to a form, and name them as cmdRemove and Grid1
  • Add the Form_Load event of the form with the following code:
    Private Sub Form_Load()
        With Grid1
            .BeginUpdate
                .DataSource = CurrentDb.OpenRecordset("Employees")
                .DetectDelete = True
            .EndUpdate
        End With
    End Sub 

The code binds the control to a DAO recordset. The DetectDelete property on True, makes the control to move the current record on the item to be deleted, and to remove any reference to the record to be deleted.

  • Add the Click event of the cmdRemove button with the following code
Private Sub cmdRemove_Click()
    With Grid1.Items
        .RemoveItem .FocusItem
    End With
End Sub

The code removes the focused item. The Items.RemoveItem call makes the control to fire the RemoveItem event, which will actually delete the associated record in the database, as in the following code

  • Add the RemoveItem event of the Control with the following code:
    Private Sub Grid1_RemoveItem(ByVal Item As Long)
        With Grid1
            If .DetectDelete Then
                With .DataSource
                    .Delete
                End With
            End If
        End With
    End Sub 

The code deletes the current record.

This sample just gives the basic idea of handling the AddNew/Delete methods of the DAO recordset. You can customize the sample, so you can add or remove new items by selecting items on a context menu, and so on.

How do I update ( add, remove, update records ) an ADO recordset, using your /COM control?

The /COM version of the component provides the DataSource property, which can be used to bound the control's view to an ADO recordset. By default, once you set the DataSource property to a recordset, all changes you do on the control will be updated in the associated recordset. 

Handling the AddNew method in the control, using the ADO recordset in VB

  • Insert a Button and the Control to a form, and name them as cmdAddNew and Grid1
  • Add the Form_Load event of the form with the following code:
    Private Sub Form_Load()
        With Grid1
            Set rs = CreateObject("ADOR.Recordset")
            With rs
                .Open "Employees", "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\sample.accdb", 3, 3
            End With
            .DataSource = rs
            .DetectAddNew = True
        End With
    End Sub

The code binds the control to an ADO recordset.

  • Add the Click event of the cmdAddNew button with the following code
Private Sub cmdAddNew_Click()
    With Grid1.DataSource
        .AddNew Array("FirstName", "LastName"), Array("new", "new")
        .Update
    End With
End Sub 

The code adds a new record to the attached recordset, and the control will add a new associated item, because the DetectAddNew method is True. 

Handling the Delete method in the control, using the ADO recordset in VB

  • Insert a Button and the Control to a form, and name them as cmdRemove and Grid1
  • Add the Form_Load event of the form with the following code:
    Private Sub Form_Load()
        With Grid1
            Set rs = CreateObject("ADOR.Recordset")
            With rs
                .Open "Employees", "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\sample.accdb", 3, 3
            End With
            .DataSource = rs
            .DetectDelete = True
        End With
    End Sub 

The code binds the control to an ADO recordset.

  • Add the Click event of the cmdRemove button with the following code
Private Sub cmdRemove_Click()
    With Grid1.DataSource
        .Delete
    End With
End Sub

The Delete method of the recordset removes the current record ( select a new item to the control, and the current record is changed ), and due DetectDelete the associated item is removed from the view. 

This sample just gives the basic idea of handling the AddNew/Delete methods of the ADO recordset. You can customize the sample, so you can add or remove new items by selecting items on a context menu, and so on.

When using the control tied to a ADO datasource, is there a way to associate an icon file to the contents of a column/cell (method 1)?

The /COM version of the component provides the DataSource property, which can be used to bound the control's view to an ADO or DAO recordset. Once you assign the control's DataSource property, the control's AddItem event is fired, and so you can change the cell's icon / image once the control is tied to your data source. The Images method of the control should be used to load the icons that your view should display. The Items.CellImage or Items.CellImages property should be used to assign a single or multiple icons to specified cell. The Items.CellValue property specifies the cell's value. 

The following VB6 sample loads 9 icons using the Images method, and change the Items.CellImage property according to Items.CellValue during the AddItem event:

Private Sub Grid1_AddItem(ByVal Item As EXGRIDLibCtl.HITEM)
    With Grid1.Items
        .CellImage(Item, 1) = .CellValue(Item, 1)
    End With
End Sub

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        Dim s As String
        s = "gBJJgBAIJAAOAAEAAQhYAf8Pf4hh0QihCJo2AEZjQAjEZFEaIEgjQBAAgjcZkMnlUrlktl0vmExmUzmk"
        s = s + "1m03nE5nU7nk9n0/oFBoVDolFo1HpFJpVLplNp1PqFRqVTqlVq1XrFZrVbrldr1fsFhsVjslls1ntFpt"
        s = s + "Vrtltt1vuFxuVzul1u13vF5vV7vl9v1/wGBwWDwmFw2HxGJxWLxmNx0wiETf+PylCyMsy+VzVEzObz03"
        s = s + "yOhh+f0kyzsn0+l1Wo0eY1ur2Guyep2O12233G53W73m932/4HB4XD4nF42l0WTrnJru0rXMrHOyVV6X"
        s = s + "SqHWqfV19e7FK7XKq/dqPf6nb1ngqXM8VU9dP6Hh0fvq3t5/m9n2jX0ov66/4jL+Pc/ymvI+7ZvjA70P"
        s = s + "LA0EwK6b5QU6bjwlCcKQrC0LwxDMNQ3DkOw9D8QM5AUGvVEcAwWsMAO9Ez0xZCDZObFz+wYrcVRfE8HQ"
        s = s + "RCL4Ro7kZOzH6jQJGsgqzGymSOn7TySy0dOXAUmJ7Ib5ydIkeyNKr6xQsEoxDL0vzBEKLB8lcyI8kSUJ"
        s = s + "GACSJOASdS6xZ/nAlR/nhOh8JUeA6JUYCEJOQAHpVQCVgPQlDJVQtEUFQM/0YjZgA/PI/zocE5UtSp4T"
        s = s + "rTVMnwf9O0/PVQjpPU+1KB0+0BVIH1VVlA0LV4AVhWVY1pWdbVrWNW11VYH0hXwP0geA/2FYg/0vY9M2"
        s = s + "TTVP09T1RVFU1TV3XdcVva1q2xWVp15VVf1/YtioCA=="
        .Images (s)
       
        Set rs = CreateObject("ADOR.Recordset")
        With rs
            .Open "Orders", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\SAMPLE.MDB", 3, 3
        End With
        .DataSource = rs
        .EndUpdate
    End With
End Sub

The sample just gives a basic idea on how you can assign/change the cell's icon based on the cell's value. Please change the \SAMPLE.MDB with the path of your database. For instance: C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB

The following VBA/Access sample loads 9 icons using the Images method, and change the Items.CellImage property according to Items.CellValue during the AddItem event:

Private Sub Grid1_AddItem(ByVal Item As Long)
    With Grid1.Items
        .CellImage(Item, 1) = .CellValue(Item, 1)
    End With
End Sub

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        Dim s As String
        s = "gBJJgBAIJAAOAAEAAQhYAf8Pf4hh0QihCJo2AEZjQAjEZFEaIEgjQBAAgjcZkMnlUrlktl0vmExmUzmk"
        s = s + "1m03nE5nU7nk9n0/oFBoVDolFo1HpFJpVLplNp1PqFRqVTqlVq1XrFZrVbrldr1fsFhsVjslls1ntFpt"
        s = s + "Vrtltt1vuFxuVzul1u13vF5vV7vl9v1/wGBwWDwmFw2HxGJxWLxmNx0wiETf+PylCyMsy+VzVEzObz03"
        s = s + "yOhh+f0kyzsn0+l1Wo0eY1ur2Guyep2O12233G53W73m932/4HB4XD4nF42l0WTrnJru0rXMrHOyVV6X"
        s = s + "SqHWqfV19e7FK7XKq/dqPf6nb1ngqXM8VU9dP6Hh0fvq3t5/m9n2jX0ov66/4jL+Pc/ymvI+7ZvjA70P"
        s = s + "LA0EwK6b5QU6bjwlCcKQrC0LwxDMNQ3DkOw9D8QM5AUGvVEcAwWsMAO9Ez0xZCDZObFz+wYrcVRfE8HQ"
        s = s + "RCL4Ro7kZOzH6jQJGsgqzGymSOn7TySy0dOXAUmJ7Ib5ydIkeyNKr6xQsEoxDL0vzBEKLB8lcyI8kSUJ"
        s = s + "GACSJOASdS6xZ/nAlR/nhOh8JUeA6JUYCEJOQAHpVQCVgPQlDJVQtEUFQM/0YjZgA/PI/zocE5UtSp4T"
        s = s + "rTVMnwf9O0/PVQjpPU+1KB0+0BVIH1VVlA0LV4AVhWVY1pWdbVrWNW11VYH0hXwP0geA/2FYg/0vY9M2"
        s = s + "TTVP09T1RVFU1TV3XdcVva1q2xWVp15VVf1/YtioCA=="
        .Images (s)
        Set .DataSource = CurrentDb.OpenRecordset("Orders")
        .EndUpdate
    End With
End Sub
  • Open the C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB database
  • create a new form,
  • add the control to the form with the name Grid1 
  • paste the above code. 

When using the control tied to a ADO datasource, is there a way to associate an icon file to the contents of a column/cell (method 2)?

The /COM version of the component provides the DataSource property, which can be used to bound the control's view to an ADO or DAO recordset. The Images method of the control should be used to load the icons that your view should display. Instead of Items.CellImage or Items.CellImages property you can use the <img> HTML built-in tags to display one or more icons to each cell based on the value. The method uses the Column.FormatColumn property to specify the format to be displayed on the column such as <img>1</img> which means displaying the icon with the index 1.

The following VB6 sample loads 9 icons using the Images method, and displays an icon based on the cell's value. 

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        Dim s As String
        s = "gBJJgBAIJAAOAAEAAQhYAf8Pf4hh0QihCJo2AEZjQAjEZFEaIEgjQBAAgjcZkMnlUrlktl0vmExmUzmk"
        s = s + "1m03nE5nU7nk9n0/oFBoVDolFo1HpFJpVLplNp1PqFRqVTqlVq1XrFZrVbrldr1fsFhsVjslls1ntFpt"
        s = s + "Vrtltt1vuFxuVzul1u13vF5vV7vl9v1/wGBwWDwmFw2HxGJxWLxmNx0wiETf+PylCyMsy+VzVEzObz03"
        s = s + "yOhh+f0kyzsn0+l1Wo0eY1ur2Guyep2O12233G53W73m932/4HB4XD4nF42l0WTrnJru0rXMrHOyVV6X"
        s = s + "SqHWqfV19e7FK7XKq/dqPf6nb1ngqXM8VU9dP6Hh0fvq3t5/m9n2jX0ov66/4jL+Pc/ymvI+7ZvjA70P"
        s = s + "LA0EwK6b5QU6bjwlCcKQrC0LwxDMNQ3DkOw9D8QM5AUGvVEcAwWsMAO9Ez0xZCDZObFz+wYrcVRfE8HQ"
        s = s + "RCL4Ro7kZOzH6jQJGsgqzGymSOn7TySy0dOXAUmJ7Ib5ydIkeyNKr6xQsEoxDL0vzBEKLB8lcyI8kSUJ"
        s = s + "GACSJOASdS6xZ/nAlR/nhOh8JUeA6JUYCEJOQAHpVQCVgPQlDJVQtEUFQM/0YjZgA/PI/zocE5UtSp4T"
        s = s + "rTVMnwf9O0/PVQjpPU+1KB0+0BVIH1VVlA0LV4AVhWVY1pWdbVrWNW11VYH0hXwP0geA/2FYg/0vY9M2"
        s = s + "TTVP09T1RVFU1TV3XdcVva1q2xWVp15VVf1/YtioCA=="
        .Images (s)
       
        Set rs = CreateObject("ADOR.Recordset")
        With rs
            .Open "Orders", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\SAMPLE.MDB", 3, 3
        End With
        .DataSource = rs
        With .Columns(1)
            .Def(exCellValueFormat) = exHTML
            .FormatColumn = "`<img>` + value + `</img>`"
        End With
        .EndUpdate
    End With
End Sub

This sample does not use the AddItem event, instead the cell's icon is automatically updated once the cell's value. Please change the \SAMPLE.MDB with the path of your database. For instance: C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB

The following VBA/Access sample loads 9 icons using the Images method, and displays an icon based on the cell's value. 

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        Dim s As String
        s = "gBJJgBAIJAAOAAEAAQhYAf8Pf4hh0QihCJo2AEZjQAjEZFEaIEgjQBAAgjcZkMnlUrlktl0vmExmUzmk"
        s = s + "1m03nE5nU7nk9n0/oFBoVDolFo1HpFJpVLplNp1PqFRqVTqlVq1XrFZrVbrldr1fsFhsVjslls1ntFpt"
        s = s + "Vrtltt1vuFxuVzul1u13vF5vV7vl9v1/wGBwWDwmFw2HxGJxWLxmNx0wiETf+PylCyMsy+VzVEzObz03"
        s = s + "yOhh+f0kyzsn0+l1Wo0eY1ur2Guyep2O12233G53W73m932/4HB4XD4nF42l0WTrnJru0rXMrHOyVV6X"
        s = s + "SqHWqfV19e7FK7XKq/dqPf6nb1ngqXM8VU9dP6Hh0fvq3t5/m9n2jX0ov66/4jL+Pc/ymvI+7ZvjA70P"
        s = s + "LA0EwK6b5QU6bjwlCcKQrC0LwxDMNQ3DkOw9D8QM5AUGvVEcAwWsMAO9Ez0xZCDZObFz+wYrcVRfE8HQ"
        s = s + "RCL4Ro7kZOzH6jQJGsgqzGymSOn7TySy0dOXAUmJ7Ib5ydIkeyNKr6xQsEoxDL0vzBEKLB8lcyI8kSUJ"
        s = s + "GACSJOASdS6xZ/nAlR/nhOh8JUeA6JUYCEJOQAHpVQCVgPQlDJVQtEUFQM/0YjZgA/PI/zocE5UtSp4T"
        s = s + "rTVMnwf9O0/PVQjpPU+1KB0+0BVIH1VVlA0LV4AVhWVY1pWdbVrWNW11VYH0hXwP0geA/2FYg/0vY9M2"
        s = s + "TTVP09T1RVFU1TV3XdcVva1q2xWVp15VVf1/YtioCA=="
        .Images (s)
        Set .DataSource = CurrentDb.OpenRecordset("Orders")
        With .Columns(1)
            .Def(EXGRIDLib.DefColumnEnum.exCellValueFormat) = EXGRIDLib.ValueFormatEnum.exHTML
            .FormatColumn = "`<img>` + value + `</img>`"
        End With
        .EndUpdate
    End With
End Sub
  • Open the C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB database
  • create a new form,
  • add the control to the form with the name Grid1 
  • paste the above code. 

When using the control tied to a ADO datasource, is there a way to associate an icon file to the contents of a column/cell (method 1)?

The /COM version of the component provides the DataSource property, which can be used to bound the control's view to an ADO or DAO recordset. Once you assign the control's DataSource property, the control's AddItem event is fired, and so you can change the cell's picture once the control is tied to your data source. The Items.CellPicture property can be used to assign a custom-size picture to a cell. The following sample uses the HTMLPicture property just to hold a collection of pictures, so we do not need to load a new picture for each cell, in other words we assign the same reference to a picture for all cells with the same picture, instead loading the same picture for different cells with the same picture.

The following VB6 sample loads 9 pictures using the HTMLPicture property, and change the Items.CellPicture property according to Items.CellValue during the AddItem event:

Private Sub Grid1_AddItem(ByVal Item As EXGRIDLibCtl.HITEM)
    With Grid1.Items
        .CellPicture(Item, 1) = Grid1.HTMLPicture(.CellValue(Item, 1))
    End With
End Sub

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        For i = 1 To 9
            .HTMLPicture(i) = "\PICTURES\" & i & ".jpg"
        Next
        Set rs = CreateObject("ADOR.Recordset")
        With rs
            .Open "Orders", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\SAMPLE.MDB", 3, 3
        End With
        .DataSource = rs
        .EndUpdate
    End With
End Sub

The sample just gives a basic idea on how you can assign/change the cell's picture based on the cell's value. Please change the \SAMPLE.MDB with the path of your database. For instance: C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB. Also, please change the \PICTURES with the path where you can locate the 1.jpg, 2.jpg, ..., 9.jpg

In addition, you can use the following properties:

  • DefaultItemHeight property to specify the default height for items to be added.
  • Items.CellPictureWidth property to specify the width of the picture to be displayed on the cell.
  • Items.CellPictureHeight property to specify the height of the picture to be displayed on the cell.

The following VBA/Access sample loads 9 pictures using the HTMLPicture property, and change the Items.CellPicture property according to Items.CellValue during the AddItem event:

Private Sub Grid1_AddItem(ByVal Item As Long)
    With Grid1.Items
        .CellPicture(Item, 1) = Grid1.HTMLPicture(.CellValue(Item, 1))
    End With
End Sub

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        Dim i As Long
        For i = 1 To 9
            .HTMLPicture(i) = "\PICTURES\" & i & ".jpg"
        Next
        Set .DataSource = CurrentDb.OpenRecordset("Orders")
        .EndUpdate
    End With
End Sub
  • Open the C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB database
  • create a new form,
  • add the control to the form with the name Grid1 
  • paste the above code
  • change the \PICTURES with the path where you can locate the 1.jpg, 2.jpg, ..., 9.jpg

When using the control tied to a ADO datasource, is there a way to associate an icon file to the contents of a column/cell (method 2)?

The /COM version of the component provides the DataSource property, which can be used to bound the control's view to an ADO or DAO recordset. Instead of Items.CellPicture property you can use the <img> HTML built-in tags to display one or more custom-size pictures to each cell based on the value. The method uses the Column.FormatColumn property to specify the format to be displayed on the column such as <img>P1</img> which means displaying the picture with the key P1. The HTMLPicture property should be used to assign the pictures to be used in the control.

The following VB6 sample loads 9 pictures using the HTMLPicture property, and specify the Column.FormatColumn to display them based on the cell's value:

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        For i = 1 To 9
            .HTMLPicture("P" & i) = "\PICTURES\" & i & ".jpg"
        Next
        Set rs = CreateObject("ADOR.Recordset")
        With rs
            .Open "Orders", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\SAMPLE.MDB", 3, 3
        End With
        .DataSource = rs
        With .Columns(1)
            .Def(exCellValueFormat) = exHTML
            .FormatColumn = "`<img>P` + value + `</img>`"
        End With
        .EndUpdate
    End With
End Sub

This sample does not use the AddItem event, instead the cell's picture is automatically updated once the cell's value. Please change the \SAMPLE.MDB with the path of your database. For instance: C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB. Also, please change the \PICTURES with the path where you can locate the 1.jpg, 2.jpg, ..., 9.jpg

The following VBA/Access sample loads 9 pictures using the HTMLPicture property, and specify the Column.FormatColumn to display them based on the cell's value:

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        Dim i As Long
        For i = 1 To 9
            .HTMLPicture("P" & i) = "\PICTURES\" & i & ".jpg"
        Next
        Set .DataSource = CurrentDb.OpenRecordset("Orders")
        With .Columns(1)
            .Def(exCellValueFormat) = exHTML
            .FormatColumn = "`<img>P` + value + `</img>`"
        End With
        .EndUpdate
    End With
End Sub
  • Open the C:\Program Files\Exontrol\ExGrid\Sample\SAMPLE.MDB database
  • create a new form,
  • add the control to the form with the name Grid1 
  • paste the above code
  • change the \PICTURES with the path where you can locate the 1.jpg, 2.jpg, ..., 9.jpg

 

How can I prevent updating the control while I do a Print and Print Preview?

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()
    Grid.BeginUpdate
End Sub

Private Sub Print1_Refresh()
    Grid.EndUpdate
End Sub

Is it possible to have the ExPrint control only show the columns that are currently visible on the control display?

The control provides the Print and Print Preview using the Exontrol's ExPrint component. By default, the Print and Print Preview displays all visible columns of the control. The Visible property of the Column object specifies whether the column is visible or hidden. In conclusion, all you need is to specify the columns to be shown on the print and print preview, and restore the Visible property once the preview is done. For that, all you need is to handle the Refreshing and Refresh events of the eXPrint component. Please check the printing FAQ for adding Print and Print Preview support in your programming language.

The following VB sample sets the visible columns to be the first column only, and restore the visibility once the previewing is done:

  • Create a new form
  • Add the ExPrint and ExGrid components to the same form, named Print1 and Grid1
  • Copy/Translate the following code.
Private Sub Print1_Refreshing()
    Dim c As Variant
    With Grid1
        For Each c In .Columns
            c.Data = c.Visible
            c.Visible = False
        Next
        .Columns(0).Visible = True
    End With
End Sub

Private Sub Print1_Refresh()
    Dim c As Variant
    For Each c In Grid1.Columns
        c.Visible = c.Data
    Next
End Sub

The sample enumerates all columns and stores the Visible property of the Column to Data property ( you can hold any value to Data property ), hides the column, and set the Visible property for the first column to be visible. This way the Print and Print Preview will display only the columns you need, not all Visible columns. The Refresh event just restores the Visible properties with saved data. Also, You can use the Item and Count properties of the Columns to enumerate the Column objects in the Columns collection. 

The Change event is fired when the cell's value is changed. Is there any event to know if the change occurs due UI action? 

The Change event of the control notifies your application once the cell's value is changed. The EditOpen and EditClose events are fired before and after the user edits a cell, so you can use them to know when user changes a value in a cell.

The edit events are fired in the following order:

  1. Edit event. Prevents editing cells, before showing the cell's editor.
  2. EditOpen event. The edit operation started, the cell's editor is shown. The Editing property gives the window's handle of the built-in editor being started.
  3. Change event. The Change event is fired if the cell's value is changed
  4. EditClose event. The cell's editor is hidden and closed. 

In conclusion, there are 2 ways of finding when the user changes a value using the control's UI elements 

  • check the Editing property during the Change event, and if it returns a non-zero value, the cell's value has been changed using the control's UI.
  • use an internal member initialized with zero, increases the member value when the EditOpen event, and decreases the member value if the EditClose event occurs.During the Change event you can check the member if it is zero or not, so you know if there were a change using the control's UI.

How do I enumerate all visible columns as they are displayed?

You need to enumerate the Column objects in the Columns collection, get sorted by Column.Position property, and filtered by Column.Visible property like shown in the following samples:

In VB you can use the following function:

Private Sub enumColumns(ByVal g As EXGRIDLibCtl.Grid)
    Dim cArray() As EXGRIDLibCtl.Column
    With g
        ReDim Preserve cArray(.Columns.Count)
        For Each c In .Columns
            If (c.Visible) Then
                Set cArray(c.Position) = c
            End If
        Next
    End With
    For Each c In cArray
        If Not c Is Nothing Then
            Debug.Print c.Caption & "(" & c.Index & ")"
        End If
    Next
End Sub

In C++ you can use the following function:

static void enumColumns( EXGRIDLib::IGrid* pGrid )
{
	if ( pGrid != NULL )
	{
		EXGRIDLib::IColumnsPtr spColumns = pGrid->Columns;
		if ( spColumns != NULL )
		{
			long nColumns = spColumns->Count;
			long* rgVisibleColumns = new long[nColumns]();
			for ( long iColumn = 0; iColumn < nColumns; iColumn++ )
				if ( EXGRIDLib::IColumn* pColumn = spColumns->GetItem( iColumn ) )
					if ( pColumn->Visible )
						rgVisibleColumns[pColumn->Position] = pColumn->Index + 1;
		
			for ( long iColumn = 0; ( iColumn < nColumns ); iColumn++ )
				if ( rgVisibleColumns[iColumn] != 0 )
					if ( EXGRIDLib::IColumn* pColumn = spColumns->GetItem( rgVisibleColumns[iColumn] - 1 ) )
					{
						OutputDebugString( pColumn->Caption );
					}
		}
	}
}

How do I enumerate all selected/active/focus items/rows/values?

The SingleSel property specifies whether the control supports single or multiple selected items. If the SingleSel property is True ( by default ), the user can select a single item/row only. The Items.FocusItem property indicates the handle of the item that has the focus. The FocusColumnIndex property indicates the index of the column that has the focus. The Cell of Items.FocusItem that belongs to the FocusColumnIndex column, defines the active or the focused cell. The control fires the SelectionChanged event when the control's selection is changed. The FocusChanged event occurs when the active cell is changed, in other words when the Items.FocusItem and/or FocusColumnIndex property is changed. The SelectCount property specifies the count of selected items/rows. If the SingleSel property is True, the SelectCount property can be 0 or 1.

The following code displays the active or focused value:

Private Sub Grid1_FocusChanged()
    With Grid1.Items
        Debug.Print .CellCaption(.FocusItem, Grid1.FocusColumnIndex)
    End With
End Sub

The following code displays the selected values:

Private Sub enumSelection(ByVal g As Object)
    With g.Items
        For i = 1 To .SelectCount
            Debug.Print .CellCaption(.SelectedItem(i - 1), 0)
        Next
    End With
End Sub

Private Sub Grid1_SelectionChanged()
    enumSelection Grid1
End Sub

As the control supports multiple columns, the following code displays the selected values for all columns:

Private Sub enumSelection(ByVal g As Object)
    With g.Items
        Dim nColumns As Long
        nColumns = g.Columns.Count
        For i = 1 To .SelectCount
            Dim h As HITEM
            h = .SelectedItem(i - 1)
            For j = 1 To nColumns
                Debug.Print .CellCaption(h, j - 1)
            Next
        Next
    End With
End Sub

Private Sub Grid1_SelectionChanged()
    enumSelection Grid1
End Sub

How can I get the first/last visible column?

You need to enumerate the Column objects in the Columns collection, get sorted by Column.Position property, and filtered by Column.Visible property like shown in the following samples.

In VB you can use a function like follows:

Private Function firstVisibleColumn(ByVal g As EXGRIDLibCtl.Grid) As Long
    Dim cArray() As EXGRIDLibCtl.Column
    With g
        ReDim Preserve cArray(.Columns.Count)
        For Each c In .Columns
            If (c.Visible) Then
                Set cArray(c.Position) = c
            End If
        Next
    End With
    For Each c In cArray
        If Not c Is Nothing Then
            firstVisibleColumn = c.Index
            Exit Function
        End If
    Next
    firstVisibleColumn = -1
End Function

In C++ you can use a function like follows:

static long firstVisibleColumn( EXGRIDLib::IGrid* pGrid )
{
	long nResult = -1;
	if ( pGrid != NULL )
	{
		EXGRIDLib::IColumnsPtr spColumns = pGrid->Columns;
		if ( spColumns != NULL )
		{
			long nColumns = spColumns->Count;
			long* rgVisibleColumns = new long[nColumns]();
			for ( long iColumn = 0; iColumn < nColumns; iColumn++ )
				if ( EXGRIDLib::IColumn* pColumn = spColumns->GetItem( iColumn ) )
					if ( pColumn->Visible )
						rgVisibleColumns[pColumn->Position] = pColumn->Index + 1;

			for ( long iColumn = 0; ( nResult == -1) && ( iColumn < nColumns ); iColumn++ )
				if ( rgVisibleColumns[iColumn] != 0 )
					nResult = rgVisibleColumns[iColumn] - 1;
			delete[] rgVisibleColumns;
		}
	}
	return nResult;
}

In C++ you can use a function like follows: (the sample ignores the sorting columns, grouping columns ).

static long getFirstVisibleColumnIgnoreSortColumns( EXGRIDLib::IGrid* pGrid )
{
	long nResult = -1;
	if ( pGrid != NULL )
	{
		EXGRIDLib::IColumnsPtr spColumns = pGrid->Columns;
		if ( spColumns != NULL )
		{
			long nColumns = spColumns->Count;
			long* rgVisibleColumns = new long[nColumns]();
			for ( long iColumn = 0; iColumn < nColumns; iColumn++ )
				if ( EXGRIDLib::IColumn* pColumn = spColumns->GetItem( iColumn ) )
					if ( pColumn->Visible )
						rgVisibleColumns[pColumn->Position] = pColumn->Index + 1;

			long nSortColumns = spColumns->SortBarColumnsCount;
			for ( long iColumn = 0; iColumn < nSortColumns; iColumn++ )
				if ( EXGRIDLib::IColumn* pColumn = spColumns->GetSortBarColumn( iColumn ) )
					rgVisibleColumns[pColumn->Position] = 0;

			for ( long iColumn = 0; ( nResult == -1) && ( iColumn < nColumns ); iColumn++ )
				if ( rgVisibleColumns[iColumn] != 0 )
					nResult = rgVisibleColumns[iColumn] - 1;
			delete[] rgVisibleColumns;
		}
	}
	return nResult;
}

How can I ensure that the indentation is displayed on the first column, when the user does change the column's position by drag and drop?

You need to handle the LayoutChanged event, and to set the TreeColumnIndex property on the Index of the first visible column as in the following sample:
Private Function firstVisibleColumn(ByVal g As EXGRIDLibCtl.Grid) As Long
    Dim cArray() As EXGRIDLibCtl.Column
    With g
        ReDim Preserve cArray(.Columns.Count)
        For Each c In .Columns
            If (c.Visible) Then
                Set cArray(c.Position) = c
            End If
        Next
    End With
    For Each c In cArray
        If Not c Is Nothing Then
            firstVisibleColumn = c.Index
            Exit Function
        End If
    Next
    firstVisibleColumn = -1
End Function

Private Sub Grid1_LayoutChanged()
    With Grid1
        .TreeColumnIndex = firstVisibleColumn(Grid1)
    End With
End Sub

How can I override the TAB/ENTER key behavior?

By default, the TAB key moves the control's searching column ( SearchColumnIndex, MarkSearchColumn property ). The control receives the TAB key only if the UseTabKey property is True ( by default ).

In order to override the behavior and do what you want you have several approaches like follows. These samples are VB6, but the idea is the same, so you can convert to any other programming languages. 

A). Handle the KeyUp event, and changes the FocusColumnIndex property to SearchColumnIndex such as:

Private Sub Grid1_KeyUp(KeyCode As Integer, Shift As Integer)
    If (KeyCode = vbKeyTab) Then
        With Grid1
            .FocusColumnIndex = Grid1.SearchColumnIndex
            .Edit
        End With
    End If
End Sub

This sample moves the focus column to the next/prev visible column. The sample does not change the focused item, so it won't advance to the next/prev row, if last/first visible column is reached. You can move forward or backward if using the TAB or SHIFT + TAB keys combination.

B). Handle the KeyDown event, and forward the exKeyRight/exKeyLeft key if the TAB key is pressed, such as:

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = vbKeyTab) Then
        KeyCode = IIf(Shift = 0, vbKeyRight, vbKeyLeft)
    End If
End Sub

This sample moves the focus column to the next/prev visible column and advances to the next/prev item, if last/first columns is reached. The sample moves to the right if the TAB key is pressed, SHIFT + TAB moves to the left, and so on. 

C). Handle the KeyDown event, and change the KeyCode parameter to 0, if it is vbKeyTab (9) ( TAB key pressed ). Once the KeyCode is 0, the control will do nothing once the event is done, so let's you override the behavior the way you want.

Here's a VB sample shows how you can do that:

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = vbKeyTab) Then
        KeyCode = 0
        Debug.Print "TAB key catched, do here what you need"
    End If
End Sub

The code let you override the TAB key behavior. 

For instance the next sample, changes the control's FocusColumnIndex property, so the next editor in row is shown ( the columns must provide a specified editor, as by default, the columns has no assigned editors ). If the FocusColumnIndex points to the first column, moves the focus to the next row, and so on.

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = vbKeyTab) Then
        KeyCode = 0
        With Grid1
            .FocusColumnIndex = (.FocusColumnIndex + 1) Mod .Columns.Count
            If (.FocusColumnIndex = 0) Then
                With .Items
                    Dim h As HITEM
                    h = .NextVisibleItem(.FocusItem)
                    If Not (h = 0) Then
                        .SelectItem(h) = True
                    End If
                End With
            End If
        End With
    End If
End Sub

This sample moves the focus column based on the index, not the position of the column. This can be improved and adjusted to your needs, so it just gives an idea on how you can personalize pressing a specified key.

Now, this sample can changed so the TAB key will navigate through the visible columns only, as they are displayed. The BASE idea is the same, just we need to get the collection of visible columns as shown here: How do I enumerate all visible columns as they are displayed?. Once that collection is built, all we need is to change the FocusColumnIndex to one of the enumerated collections.

Here's the changed sample:

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    If (KeyCode = vbKeyTab) Then
        KeyCode = 0
        With Grid1
            Dim cColumns As Collection
            Set cColumns = enumColumns(.Object)
            If Not (cColumns.Count = 0) Then
                .FocusColumnIndex = cColumns.Item((findIndex(.FocusColumnIndex, cColumns) Mod cColumns.Count) + 1)
                If (.FocusColumnIndex = cColumns.Item(1)) Then
                    With .Items
                        Dim h As HITEM
                        h = .NextVisibleItem(.FocusItem)
                        If Not (h = 0) Then
                            .SelectItem(h) = True
                        End If
                    End With
                End If
            End If
        End With
    End If
End Sub

where the enumColumns and findIndex functions are:

Private Function enumColumns(ByVal g As Object) As Collection
    Dim cResult As New Collection
    Dim cArray() As EXGRIDLibCtl.Column
    With g
        ReDim Preserve cArray(.Columns.Count)
        For Each c In .Columns
            If (c.Visible) Then
                Set cArray(c.Position) = c
            End If
        Next
    End With
    For Each c In cArray
        If Not c Is Nothing Then
            cResult.Add c.Index
        End If
    Next
    Set enumColumns = cResult
End Function

Private Function findIndex(ByVal nIndex As Long, ByVal col As Collection) As Long
    Dim c As Variant, i As Long
    i = 1
    For Each c In col
        If (c = nIndex) Then
            findIndex = i
            Exit Function
        End If
        i = i + 1
    Next
    findIndex = 1
End Function

Now, the sample shows how you can navigate through visible columns as they are displayed. If you need to move the focus through the columns with an editor assigned all you need is to change the If (c.Visible) Then with If (c.Visible) And Not (c.Editor.EditType = 0) Then or with any other condition you need.

If your control displays hundred of columns, you can store the enumColumns result to a member variable each time the LayoutChanged event occurs, and use the member during the KeyDown event, instead calling the enumColumns function each time the TAB key is pressed.

These samples give you an idea of how to override the TAB/ENTER key behavior, they can be changed or improved.

Is it possible to select a column by clicking the control's header (method 1)?

By default, clicking a column means sorting the column. If you need to change this behavior, you need to
  • set the SingleSel property on False
  • set the FullRowSelect on exRectSel
  • set the SortOnClick property on exNoSort, so no sorting is performed when user clicks the column's header.

Next, you need to handle the Mouse events to query for the column from point, and change the Selected property of the column, including calling the SelectAll method of the Items object like in the following sample:

Private Sub Grid1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim c As Long
    With Grid1
        c = .ColumnFromPoint(-1, -1)
        If (c >= 0) Then
            .BeginUpdate
            .Columns(c).Selected = Not Grid1.Columns(c).Selected
            .Items.SelectAll
            .EndUpdate
        End If
    End With
End Sub

Check also:

Is it possible to select columns, when clicking the column's header (method 2)?

By default, clicking the column's header indicates sorting the column. The control's SortOnClick property specifies the action should do when user clicks the column's header. In order to change this behavior, we need to set the SortOnClick property on exNoSort, and so we can do the action we need using the ColumnFromPoint property during the MouseDown event lick shown in the following VB sample

Private iColumnSelected As Long

Private Sub Form_Load()
    init
End Sub

Private Sub init()
    iColumnSelected = -1
    With Grid1
        .SortOnClick = exNoSort
    End With
End Sub

Private Sub sel(ByVal c As Long, ByVal bSel As Boolean)
    With Grid1
        .BeginUpdate
            If (bSel) Then
                ' unselects any selected item
                Dim b As Boolean
                b = .SingleSel
                .SingleSel = False
                .Items.UnselectAll
                .SingleSel = b
            End If
            ' unselects the previously column
            If (iColumnSelected >= 0) Then
                .Columns(iColumnSelected).Def(exCellForeColor) = 0
                .Columns(iColumnSelected).Def(exCellBackColor) = 0
            End If
            ' selects the clicked column
            iColumnSelected = c
            If (c >= 0) Then
                .FocusColumnIndex = c
                .Columns(iColumnSelected).Def(exCellForeColor) = .SelForeColor
                .Columns(iColumnSelected).Def(exCellBackColor) = .SelBackColor
            End If
        .EndUpdate
    End With
End Sub

Private Sub Grid1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    ' selects the column being clicked
    sel Grid1.ColumnFromPoint(-1, -1), True
End Sub

Private Sub Grid1_SelectionChanged()
    ' unselects the previously selected column
    sel -1, False
End Sub

The sample selects the column being clicked, and when the items selection is changed the selected column is not shown anymore. This sample give you an idea of how you can select a column by clicking the column's header. 

Check also: 

When you click on a cell the row is highlighted/selected. Is it possible to also highlight/select the column automatically?

By default, clicking an item selects the item. Once you click a different item or column, the FocusChanged event is fired. The Column.Def(exCellBackColor) property specifies the color for the entire column. 

The following VB sample shows how you can highlight/select the column once you click a cell in the item.

Dim iFocusColumnChanged As Long

Private Sub Form_Load()
    iFocusColumnChanged = -1
End Sub

Private Sub Grid1_FocusChanged()
    With Grid1
        .BeginUpdate
        If Not (iFocusColumnChanged = .FocusColumnIndex) Then
            If (iFocusColumnChanged >= 0) Then
                .Columns(iFocusColumnChanged).Def(exCellBackColor) = 0
            End If
            iFocusColumnChanged = .FocusColumnIndex
            If (iFocusColumnChanged >= 0) Then
                .Columns(iFocusColumnChanged).Def(exCellBackColor) = .SelBackColor
            End If
        End If
        .EndUpdate
    End With
End Sub

Check also: 

I have a flat table ( columns and rows, no child items ), the question is how I can navigate up/down/left/right by cell only?

By default, the Left/Right arrow keys moves the focusing column to previously or next column, if the columns has any editors assigned. If no editors are assigned to columns, the left/right collapse/expands the current item, and go to the next visible item after collapsing or expanding the item. The FullRowSelect property indicates how the selection is shown, or if the entire row gets selected or just columns/cells are shown as selected. The EnsureVisibleColumn method ensures that the specified index ( in the column's collection ) fits the control's client area, or in other words, it scrolls the control's content so the column fits the control's client area.

The following VB sample shows how you can navigate per cell ( if each columns have an editor assigned, or the Column.Editor.EditType property is not ReadOnly ) :

With Grid1
    .ShowFocusRect = True
    .ReadOnly = exReadOnly
    .FullRowSelect = exRectSel
End With

The following VB sample shows how you can navigate per cell ( if columns have no editor assigned, or the Column.Editor.EditType property is ReadOnly ) :

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    With Grid1
        If (KeyCode = vbKeyRight) Then
            .SearchColumnIndex = (.SearchColumnIndex + 1) Mod .Columns.Count
            .EnsureVisibleColumn .SearchColumnIndex
            KeyCode = 0
        Else
            If (KeyCode = vbKeyLeft) Then
                .SearchColumnIndex = IIf(.SearchColumnIndex = 0, .Columns.Count - 1, .SearchColumnIndex - 1)
                .EnsureVisibleColumn .SearchColumnIndex
            KeyCode = 0
            End If
        End If
    End With
End Sub

The sample overrides the KeyDown event so when the Left/Right key is pressed the searching/selecting/focusing column is changed to previously or next column. The KeyCode = 0 indicates that control should take no action when the KeyDown event ends. The sample gives an idea how you can change the searching/selecting/focusing column by handling the KeyDown event of the control.

You can use the How do I enumerate all visible columns as they are displayed? to collect the index of the columns being visible as displayed, so the sample will be:

Private Sub Grid1_KeyDown(KeyCode As Integer, Shift As Integer)
    With Grid1
        If (KeyCode = vbKeyRight) Then
            Dim iNext As Long
            iNext = getIndex2VisiblePosition(.SearchColumnIndex, Grid1)
            If (iNext >= 0) Then
                iNext = (iNext + 1) Mod UBound(enumColumns(Grid1))
                .SearchColumnIndex = getVisiblePositon2Index(iNext, Grid1)
                .EnsureVisibleColumn .SearchColumnIndex
            End If
            KeyCode = 0
        Else
            If (KeyCode = vbKeyLeft) Then
                Dim iPrev As Long
                iPrev = getIndex2VisiblePosition(.SearchColumnIndex, Grid1)
                If (iPrev >= 0) Then
                    iPrev = IIf(iPrev > 0, iPrev - 1, UBound(enumColumns(Grid1)) - 1)
                    .SearchColumnIndex = getVisiblePositon2Index(iPrev, Grid1)
                    .EnsureVisibleColumn .SearchColumnIndex
                End If
                KeyCode = 0
            End If
        End If
    End With
End Sub

where the following functions enumerates the visible column, converts the index to visible position, and a visible position to an index:

Private Function enumColumns(ByVal g As EXGRIDLibCtl.Grid) As EXGRIDLibCtl.Column()
    Dim cArray() As EXGRIDLibCtl.Column
    With g
        ReDim Preserve cArray(.Columns.Count)
        For Each c In .Columns
            If (c.Visible) Then
                Set cArray(c.Position) = c
            End If
        Next
    End With
    enumColumns = cArray
End Function

Private Function getIndex2VisiblePosition(ByVal c As Long, ByVal g As EXGRIDLibCtl.Grid) As Long
    Dim cV As Variant, i As Long
    i = 0
    For Each cV In enumColumns(g)
        If (cV.Index = c) Then
            getIndex2VisiblePosition = i
            Exit Function
        End If
        i = i + 1
    Next
    getIndex2VisiblePosition = -1
End Function

Private Function getVisiblePositon2Index(ByVal p As Long, ByVal g As EXGRIDLibCtl.Grid) As Long
    Dim cV As Variant, i As Long
    i = 0
    For Each cV In enumColumns(g)
        If (i = p) Then
            getVisiblePositon2Index = cV.Index
            Exit Function
        End If
        i = i + 1
    Next
    getVisiblePositon2Index = -1
End Function

The sample navigate left/right to the next/previously visible columns.

The ItemFromPoint(-1,-1) property seems that it is not working. What could be the problem?

The ItemFromPoint(-1,-1) property gets the handle if the item, index of the column and the hit-test position from the cursor position. Usually, the you think that the ItemFromPoint(-1,-1) is not working in debug mode, because you have set the breakpoint on the property itself, and you are moving the cursor position by the time the ItemFromPoint property is called. What you can do, is to set the break-point after calling the ItemFromPoint property is called, so the correct position of the cursor is taken when the property is invoked. In other words, please add the following code, and see that the handle of the item being clicked is displayed correctly, like in the following VB sample:

Private Sub Grid1_Click()
    Dim c As Long, hit As EXGRIDLibCtl.HitTestInfoEnum
    Debug.Print Grid1.ItemFromPoint(-1, -1, c, hit)
End Sub

The BeforeExpandItem event seems to be fired when user clicks the drop down filter button. What can be?

The BeforeExpandItem event is fired when an item is about to be expanded, by code or using the control's user interface ( such as clicking the +/- expanding button ). Also, the BeforeExpandItem event may occur for items with the ItemHasChildren property set on True, when the user clicks the filter drop down button. This is by design, to include not-loaded items in the drop down filter window. Usually, the BeforeExpandItem event is used to load virtually a hierarchy, for instance, when the user clicks the +/- expanding button.

The following methods, can be used to prevent firing the BeforeExpandItem event when the user clicks the drop down filter button:
  1. Use no ItemHasChildren property on True, in other words you can load on init time, the entire hierarchy collection
  2. Set the FilterList property of the Column object to exRootItems value (4), so no child items are collected in the drop down filter list
  3. Use a counter that's increased when MouseDown event occurs and it is decreased when MouseUp event is fired. You can use the ColumnFromPoint property to check if the user clicks the headers. During the BeforeExpandItem event you can prevent adding a sub-child if the counter is not zero.

Is there any option to check if a filter is applied to any of the columns in the control (hasfilter)?

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

How can I extend the last column that it matches with the right border of the control (automatically, extendlastcol)?

The ColumnAutoResize, makes the columns to resize so all fit the control's width area. The AllowSizing property of the Column object specifies whether the user can resize the column at runtime.

The following VB sample fixes the first column, and let the second to extent so it matches the right border.

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        .ColumnAutoResize = True
        With .Columns
            With .Add("Fixed")
                .AllowSizing = False
                .Width = 48
            End With
            With .Add("Resizable")
            End With
        End With
        .EndUpdate
        DoEvents
        .Columns("Fixed").AllowSizing = True
    End With
End Sub

Private Sub Form_Resize()
    With Grid1
        .Columns("Fixed").AllowSizing = False
        .Width = ScaleWidth - 2 * .Left
        .Height = ScaleHeight - 2 * .Top
        .Columns("Fixed").AllowSizing = True
    End With
End Sub

The sample can be extended for any number of fixed columns as shown bellow:

Private Sub Form_Load()
    With Grid1
        .BeginUpdate
        .ColumnAutoResize = True
        With .Columns
            For i = 1 To 4
                With .Add("Fixed")
                    .AllowSizing = False
                    .Width = 16
                    .Data = "fixed"
                End With
            Next
            With .Add("Resizable")
            End With
        End With
        .EndUpdate
        DoEvents
        AllowSizing Grid1, True
    End With
End Sub

Private Sub Form_Resize()
    With Grid1
        AllowSizing Grid1, False
        .Width = ScaleWidth - 2 * .Left
        .Height = ScaleHeight - 2 * .Top
        .Columns("Fixed").AllowSizing = True
        AllowSizing Grid1, True
    End With
End Sub

Private Sub AllowSizing(ByVal o As Object, ByVal bAllowSizing As Boolean)
    With o
        Dim c As Variant
        For Each c In .Columns
            If (c.Data = "fixed") Then
                c.AllowSizing = bAllowSizing
            End If
        Next
    End With
End Sub

I can not find the Items.ItemByIndex property. How can I use it?

The Items.ItemByIndex(index) property gets the handle of the item/row giving its index.

If you can not locate the ItemByIndex property in the Items collection you should look for Items.get_ItemByIndex(index), Items[index] or Items(index) instead.

The tooltip is not hidden, when a message box is displayed. How can I programmatically hide the tooltip?

The tooltip is automatically hidden when user moves the mouse or a key is pressed. In case a message box or a form is shown, none of them is happen, so the tooltip may still be shown. For that, you can call the PostMessage .hwnd, 512, 0, 0 before showing your message or dialog like in the following sample. The hWnd indicates the handle of the control ( hWnd property ).
Private Sub Grid1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If (Button = 2) Then
        With Grid1
            Dim i As Long, c As Long, hit As HitTestInfoEnum
            i = .ItemFromPoint(-1, -1, c, hit)
            If Not i = 0 Then
                PostMessage .hwnd, &H200, 0, 0
                MsgBox .Items.CellCaption(i, c)
            End If
        End With
    End If
End Sub

If you want to display a value from a context-menu instead showing a message-box, you should use &H114 instead &H200.

I have two controls, how can I synchronize the scroll position?

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 Grid1_OffsetChanged(ByVal Horizontal As Boolean, ByVal NewVal As Long)
    If (iSyncing = 0) Then
        iSyncing = iSyncing + 1
            Grid2.ScrollPos(Not Horizontal) = NewVal
        iSyncing = iSyncing - 1
    End If
End Sub

Private Sub Grid2_OffsetChanged(ByVal Horizontal As Boolean, ByVal NewVal As Long)
    If (iSyncing = 0) Then
        iSyncing = iSyncing + 1
            Grid1.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.

How can I disable pressing the SPACE key on cells of check-box type ( toggle )?

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 Grid1_KeyPress(KeyAscii As Integer)
    With Grid1
        If (.Editing = 0) Then
            If (KeyAscii = vbKeySpace) Then ' vbKeySpace is 32
                KeyAscii = 0
            End If
        End If
    End With
End Sub

I simply want to select the whole row if I click the first column and only a single cell if I click anywhere else. How can I do that?

The FullRowSelect property of the control specifies whether the entire row is displaying as selected or just a cell. The idea is to change the FullRowSelect property from exItemSel to/from exRectSel/exColumnSel, when the user clicks a column. The ItemFromPoint method gets the column being clicked in the items section, while the ColumnFromPoint property gets the column being clicked in the control's header section only.

The following sample handles the MouseDown event:

Private Sub Grid1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If (Button = 1) Then
        With Grid1
            Dim h As Long, c As Long, hit As HitTestInfoEnum
            h = .ItemFromPoint(-1, -1, c, hit)
            .FullRowSelect = IIf(c = 0, exItemSel, exRectSel)
        End With
    End If
End Sub

A recursive call (infinite loop) occurs once I call the CellValue during the Change event. What can be done?

The control fires the Change event whenever the Items' CellValue property is changed, so a recursive call may occur, if changing the Items.CellValue property during the Change event. The following sample shows you how to prevent this situation:
Dim iChange As Long = 0

Private Sub Grid1_Change(ByVal Item As EXGRIDLibCtl.HITEM, ByVal ColIndex As Long, NewValue As Variant)
    If (iChange = 0) Then
        iChange = iChange + 1
            ' Here you can call/change any CellValue property, and so the Change event is not called
        iChange = iChange - 1
    End If
End Sub

I cant figure out how to configure the filter always filtering for *used* when typing used...?

The control fires the FilterChanging event just about applying the new filter on the control, so the idea is to change the column's Filter property to include the "*" characters.

The following sample shows how you can update the Filter property to include * characters, so a Contains clause is applied when filtering:

Private Sub Grid1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    With Grid1
        Dim iFilterColumnIndex As Long
        iFilterColumnIndex = .ColumnFromPoint(-1, -1)
        If (iFilterColumnIndex >= 0) Then
            .SearchColumnIndex = iFilterColumnIndex
            With Grid1.Columns(iFilterColumnIndex)
                If (.FilterType = exPattern) Then
                    .Filter = Replace(.Filter, "*", "")
                End If
            End With
        End If
    End With
End Sub

Private Sub Grid1_FilterChanging()
    With Grid1
        If (.SearchColumnIndex >= 0) Then
            With .Columns(.SearchColumnIndex)
                If (.FilterType = exPattern) Then
                    If (Len(.Filter) > 0) Then
                        .Filter = "*" & .Filter & "*"
                    End If
                End If
            End With
        End If
    End With
End Sub

The FilterChanging event adds the * characters for a column ( with the exPattern set ) while the MouseDown handler removes any * characters in the Filter property, so no * characters will be displayed when Filter For prompt is shown.

Is it possible to add a column to a grid after it has already been tied to a datasource and alter the value for each row in the AddItem event? When I attempt it, I get an error "Method 'CellValue' of object 'IItems' failed".

The "Method 'CellValue' of object 'IItems' failed" error occurs because the code tries to access a non-existent cell. For instance, you refer the forth cell in the current item, while the control has three columns only. 

You can add a new column after DataSource property, using the Columns.Add method. You must know that the AddItem event is calling during the DataSource property, so at that time the newly column is not known and that is why you get the "Method 'CellValue' of object 'IItems' failed" error. Instead you should enumerate the items and use the new column after adding it.

Can I scroll the control's content by touching the capacitive screen with fingers?

The AutoDrag property indicates what the control does when the user clicks an item and starts dragging it. For instance, using the AutoDrag feature you can automatically lets the user to drag and drop the data to OLE compliant applications like Microsoft Word, Excel and so on. In order to let user scrolls the control's content set the AutoDrag property on:
  • AutoDragEnum.exAutoDragScrollOnShortTouch + AutoDragEnum.exAutoDragScroll

4112 

How can I have Page-Up/Page-Down once the user clicks my button?

Send the WM_KEYDOWN message with the code 33 ( Page-Up), 34 (Page-Down) to the control's hWnd handle as in the following sample:
Private Const WM_KEYDOWN = &H100
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
    (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
 
Private Sub Command1_Click()
    SendMessage Grid1.hwnd, WM_KEYDOWN, 34, 0
End Sub

In case you need to find out the code of the key you want to send, paste and run the following x-script to your exhelper:

handle KeyDown(KeyCode,Shift)
{
	Print(KeyCode)
}

Is there a way to control the amount of scrolling when the left/right arrows are clicked on a horizontal scrollbar ?

The control fires the ScrollButtonClick event once a button with-in the scroll bar is clicked. The ScrollPos property specifies the control's scroll position. The OffsetChanged event notifies your application once the control's scrolling position is changed. The ContinueColumnScroll property specifies whether the control scroll horizontally the content pixel by pixel, or column by column.

A) The following VB6 sample handles the OffsetChanged event, so it advances 16 pixels forward/backward if the user clicks left/right buttons ( 1 pixel horizontal-scroll )

Dim nOffsetChanged As Long, OldVal As Long

Private Sub Grid1_OffsetChanged(ByVal Horizontal As Boolean, ByVal NewVal As Long)
    If (nOffsetChanged = 0) Then
        nOffsetChanged = nOffsetChanged + 1
        With Grid1
            If (Horizontal) Then
                If (Abs(NewVal - OldVal) = 1) Then
                    .ScrollPos(False) = NewVal + IIf(NewVal > OldVal, 16, -16)
                End If
                OldVal = .ScrollPos(False)
            End If
        End With
        nOffsetChanged = nOffsetChanged - 1
    End If
End Sub

where the nOffsetChanged variable member is initialized with zero, and it ensures that no recursive call of OffsetChanged event occurs, and the OldVal member variable is initialized with zero, and it indicates the previously value of the control's scrolling position. The sample works even if clicking continuously the left/right scrolling buttons.

B) The following VB6 sample advances 16 pixels, once the user clicks the left/right scrolling buttons ( The ContinueColumnScroll property is True ) :

Private Sub Grid1_ScrollButtonClick(ByVal ScrollBar As EXGRIDLibCtl.ScrollBarEnum, ByVal ScrollPart As EXGRIDLibCtl.ScrollPartEnum)
    With Grid1
        If (ScrollBar = exHScroll) Then
            If (ScrollPart = exLeftBPart) Then
                .ScrollPos(False) = .ScrollPos(False) - 15
            Else
                If (ScrollPart = exRightBPart) Then
                    .ScrollPos(False) = .ScrollPos(False) + 15
                End If
            End If
        End If
    End With
End Sub

The sample works if pressing and releasing the left/right scrolling button.

How can I find the number of items, while control is running in virtual mode (VirtualMode property is True)?

The ItemsCount method of the IUnboundHandler interface, specifies the number of virtual items you are about to load within the control.

Here's a trick that counts the number of items being shown while control is running in virtual mode (VirtualMode property is True):

Private Sub Grid1_OversizeChanged(ByVal Horizontal As Boolean, ByVal NewVal As Long)
    If Not Horizontal Then
        Debug.Print NewVal + Grid1.Items.VisibleCount
    End If
End Sub

The idea is to add the NewVal parameter of the OversizeChanged event, with VisibleCount property of the Items collection. 

I am using FormatColumn event, the formatted captions disappear when a message box get displayed. What can be done?

The FormatColumn event lets the user to provide the cell's caption before it is displayed on the control's list. The FormatColumn event may be fired multiple times for the same cell, for instance, when the cell's tooltip is required, when collecting captions to be displayed on the column's filter list, and so on. The FormatColumn event is fired for each column whose FireFormatColumn property is True ( by default, the FireFormatColumn property is False, for all columns ). The FormatColumn event may be locked if the user displays a message box, and so the FormatColumn event is not fired, and so the cell's value may be displayed instead. In order to prevent that there are a few options like:
  • Use the FormatColumn property instead, to format the cell's value. The FormatColumn property specifies the format to display the cells in the column. For instance, the "currency(value)" displays the column using the current format for the currency ie, 1000 gets displayed as $1,000.00 if regional setting is US.
  • Use the Out method of eXSkinBox component, to display a modal dialog.