Contents
Introduction
I'm coding a database application using WTL, and so far I've been using a standard list view for viewing data, and separate dialogs for editing. There are lots of different grids out there, but I didn't like the ones available for WTL very much. Not that they are bad or anything, they just didn't fit my needs. And instead of patching and fixing any existing ones to fit my need, it would probably be faster to make my own.
A little note about the demos. The Northwind demo requires a SQL server at localhost with the northwind database installed, and no password for the sa account. You can modify the connection string in MainFrm.h and recompile. The solutions are made with Visual Studio.NET 2003, so they won't open in older versions, but creating a new empty project and adding the files should be ok.
Features
- Number of rows only limited by memory.
- I've been testing with 100000 lines with no problems on my 2 GHz machine. Will also depend on the number of columns and your interacting code. Remember to use
SetRedraw
when adding many lines though.
- Cells can be edited using standard edit control, combo box (either dropdown or dropdownlist), or
DateTimePicker
control.
- Made for database users, and therefore everything uses
_variant_t
(and _bstr_t
for strings).
- Combo boxes use a lookup value to set the value of the column. The ID field is placed in the grid, and the grid displays and edits informative text fields.
There are lots of others things that I probably will add to this grid, but for now this is enough for my needs. I welcome any comments and suggestions you might have for improvements.
How to use
Simple Usage
Let me show you how to create the most basic grid. Here I start with a standard WTL AppWizard application, and replace the main forms view with a grid. After creating the grid I set the style to allow a context menu to be shown if you click the right mouse button in the grid.
Creating the grid
CGridCtrl m_view;
LRESULT CMainFrame::OnCreate(UINT , WPARAM ,
LPARAM , BOOL& )
{
m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
WS_EX_CLIENTEDGE);
m_view.SetExtendedGridStyle(GS_EX_CONTEXTMENU);
}
Adding columns
m_view.AddColumn("Last Name",140,CGridCtrl::EDIT_TEXT);
m_view.AddColumn("First Name",140,CGridCtrl::EDIT_TEXT);
Lookup Columns
Now we will add another column where you can enter the sex of a person. Here you will see that we use more arguments to the AddColumn
function. The first new parameter is alignment, and the last one is what data type this column uses. The default is VT_BSTR
which we used on the first two columns. Now we will store the sex information as integer, so we use VT_I4
.
We also tell the grid, what should be possible to choose from the combobox, we will add to the grid.
m_view.AddColumn("Sex",100,CGridCtrl::EDIT_DROPDOWNLIST,
CGridCtrl::CENTER,VT_I4);
m_view.AddColumnLookup("Sex",1,"Male");
m_view.AddColumnLookup("Sex",2,"Female");
We should now have a grid where we can enter a person's last name, first name, and sex.
Adding rows
You add rows by calling AddRow
and SetItem
. In this example, I use SetRedraw
before and after adding the row. For just one row, this wouldn't be necessary, but for many rows this is a must.
m_view.SetRedraw(FALSE);
long nItem = m_view.AddRow();
m_view.SetItem(nItem,"Last Name","Henden");
m_view.SetItem(nItem,"First Name","Bj�rnar");
m_view.SetItem(nItem,"Sex",1);
m_view.SetRedraw(TRUE);
What about events?
For catching events, I created a class CListener
that you inherit from. This class is not only used for events, but also to query information about cell background color.
class CListener {
public:
virtual bool OnRowChanging(UINT uID,long nRow);
virtual void OnRowChanged(UINT uID,long nRow);
virtual void OnEdit(UINT uID,long nRow);
virtual bool OnDeleteRow(UINT uID,long nRow);
virtual void OnNewRow(UINT uID,long nRow);
virtual void OnModified(UINT uID,LPCTSTR pszColumn,_variant_t vtValue);
virtual void OnRowActivate(UINT uID,long nRow);
virtual COLORREF GetCellColor(UINT uID,long nRow,LPCTSTR pszColumn);
virtual bool OnValidate(UINT uID);
};
The following example will show you how to be notified when a row is deleted from the grid, and set a new background color for rows that are modified.
First inherit CMainFrame
from CGridCtrl::Clistener
, and then override the functions you want.
class CMainFrame : public CFrameWindowImpl<CMainFrame>, ...,
public CGridCtrl::CListener
LRESULT CMainFrame::OnCreate(UINT , WPARAM ,
LPARAM , BOOL& )
{
m_view.SetListener(this);
}
virtual bool OnDeleteRow(UINT uID,long nRow) {
CString str;
str.Format("Do you want to delete row %d?",nRow);
return IDYES==AtlMessageBox(m_hWnd,(LPCTSTR)str,
IDR_MAINFRAME,MB_YESNO|MB_ICONQUESTION);
}
virtual COLORREF GetCellColor(UINT uID,long nRow,LPCTSTR pszColumn) {
_variant_t vt = m_view.GetItem(nRow,"Sex");
if(!m_view.IsNull(vt)) {
if((long)vt==1)
return RGB(192,192,255);
else
return RGB(255,192,192);
}
return (COLORREF)-1;
}
Function reference
Here is a brief function reference. I will only list the function names, and a short description of what it does.
void AddColumn()
Adds a column to the grid. You can't add columns if there are rows in the grid. The last parameter to this function is the name of the column, which can be used as arguments to other functions. If this is omitted, the column title is used as name.
void AddColumnLookup()
Adds a lookup value to a column. This doesn't have to be columns that use dropdownlists, but could be any column.
long AddRow()
Adds a row to the grid and returns the row number inserted. Use this return value to set individual cell values on this row.
void ClearModified()
When cells are edited they set the row status to modified. Call this function to reset the specified row to, not modified. Specify -1 for all rows.
void DeleteAllColumns()
Deletes all columns.
void DeleteAllItems()
Deletes all rows.
void EnsureVisible()
Display the row in the visible area of the grid.
long GetColumnCount()
Returns the number of columns in the grid.
_variant_t GetEditItem()
When in edit mode, call this to get the current value of one of the cells being edited.
_variant_t GetItem()
Returns the value of the row and cell.
bool GetModified()
Returns true
if the row is modified. Use -1 for all rows.
long GetRowCount()
Returns the number of rows in the grid.
long GetSelectedRow()
Returns the number of the selected row, and -1 if no row is selected.
bool IsNull()
A static function that can be used to check if a _variant_t
is null. Returns true
for VT_NULL
and VT_EMPTY
.
BOOL PreTranslateMessage(MSG* pMsg)
This should be called from your main frame PreTranslateMessage
function. Without it, tab between cells will not work. If you use this grid in a modal dialog, tabs will also not work, since PreTranslateMessage
can't be called. Open the dialog modeless instead, and disable the parent window.
void SetColumnFocus()
Set focus to a column. Only matters when in edit mode. Nice if validation for a field fails, and you want to focus the missing value.
void SetItem()
Set the value of a cell.
void SetListener()
Must be set to a class inherited from CGridCtrl::CListener
.
void SetNull()
Static function that can be used to set the value of a _variant_t
to null.
Credits
- Uses atlgdix.h (CMemDC) by Bjarke Viksoe.
- Inspired by Noel Frankinet and his WTL Grid, and used some of his code for drawing and column dragging.