CDataGrid
Some time ago I started to work with database using Visual C++ and I was undeceived in MS DataGrid. I wrote my class CDataGrid
which was derived from CGridCtrl by Chris Maunder and used ADO for access to database.
The sample application
Usage - Step by Step
Step 1- Using CDataGid
Add to StdAfx.h strings.
#import "c:\program files\common files\system\ado\msado15.dll" \
no_namespace \
rename ("EOF", "adoEOF")
#include <afxtempl.h<afxtempl.h>><afxtempl.h>
In this sample, I used Northwind database from MS SQL 7, 2000 and Access. You can find nwind.mdb on Visual Studio 6.0 disk #1.
Next, you should add DataGrid.h and DataGrid.cpp to the project. You will also need to add CGridCtrl
files to the project. You can download the latest version of CGridCtrl
here. (This sample used CGridCtrl
v 2.23)
Add ExString.h and ExString.cpp to the project.
Add DDXFields.h and DDXFields.cpp to the project.
In this sample, I have used these classes:
I developed CDataComboBox
class to access a database from combo box.
If you are going to use CDataComboBox
in your project, you should add DataComboBox.h and DataComboBox.cpp to the project.
Add CDataGrid
variable and connection into your dialog header file:
#include "DataGrid.h"
class CDataGrid_DemoDlg : public CResizableDialog
{
public:
CDataGrid m_Grid;
_ConnectionPtr m_pConnection;
Associate the grid window with C++ object in the DoDataExchange
of your dialog:
DDX_Control(pDX, IDC_GRID, m_Grid);
Into OnInitDialog
add:
m_pConnection.CreateInstance(__uuidof(Connection));
try
{
m_pConnection->Open
(L"Provider=Microsoft.Jet.OLEDB.4.0;"
"Data Source=D:\\DATA\\Nwind.mdb", L"", L"", -1); ......
......
}
catch(_com_error *e)
{
CString Error = e->ErrorMessage();
AfxMessageBox(e->ErrorMessage());
}
catch(...)
{
}
SetFields();
Set fields of the grid and execute the query in the SetFields()
.
void CDataGrid_DemoDlg::SetFields()
{
int n;
m_Grid.m_field.SetSize(2);
n=0;
m_Grid.m_field[n].Field=_T("ProductName");
m_Grid.m_field[n].Caption=_T("Product");
m_Grid.m_field[n].Find=true;
n=1;
m_Grid.m_field[n].Field=_T("QuantityPerUnit");
m_Grid.m_field[n].Caption=_T("QuantityPerUnit");
m_Grid.m_field[n].Find=true;
......
......
m_Grid.Execute(m_pConnection,"ProductId","*","Products","",1);
}
Because we use the virtual mode of the CGridCtrl
, we must override OnNotify
function.
BOOL CDataGrid_DemoDlg::OnNotify(WPARAM wParam,
LPARAM lParam, LRESULT* pResult)
{
if (wParam == (WPARAM)m_Grid.GetDlgCtrlID())
{
*pResult = 1;
GV_DISPINFO *pDispInfo = (GV_DISPINFO*)lParam;
m_Grid.SetValue(pDispInfo);
}
return CResizableDialog::OnNotify(wParam, lParam, pResult);
}
The screenshot below shows us the DataGrid
's work.
Step 2- Using CDataComboBox
Add DataCombo
control into dialog, choose Type Drop List
in the style sheet.
Add CDataGrid
variable into your dialog header file:
#include "DataComboBox.h"
class CDataGrid_DemoDlg : public CResizableDialog
{
public:
CDataComboBox m_cmbDep;
CDataComboBox m_cmbBill;
Associate the combo boxes with C++ objects in the DoDataExchange
of your dialog:
DDX_Control(pDX, IDC_CMB_CAT, m_cmbCat);
DDX_Control(pDX, IDC_CMB_SUP, m_cmbSup);
Fill DataComboBox
: There are two modes of the DataComboBox
: BOUND
and UNBOUND
.
In the 3rd step, I'll show you how to use UNBOUND
mode. But now fill DataComboBox
es using BOUND
mode in the OnInitDialog
.
m_pConnection.CreateInstance(__uuidof(Connection));
try
{
m_pConnection->Open
(L"Provider=Microsoft.Jet.OLEDB.4.0;"
"Data Source=D:\\DATA\\Nwind.mdb", L"", L"", -1); ......
m_cmbCat.Execute(m_pConnection,_
T("SELECT * FROM Categories ORDER BY CategoryName"),
_T("CategoryName"));
m_cmbSup.Execute(m_pConnection,
_T("SELECT * FROM Suppliers ORDER BY CompanyName"),
_T("CompanyName"));
......
......
}
catch(_com_error *e)
{
CString Error = e->ErrorMessage();
AfxMessageBox(e->ErrorMessage());
}
catch(...)
{
}
SetFields();
Create Master-Detail's relation:
Override message CBN_SELCHANGE
of the ComboBoxes4
and create function Requery()
. Remove m_Grid.Execute
from SetFields()
to the Requery()
.
void CDataGrid_DemoDlg::Requery()
{
CString strCat,strSup,strWhere;
if (!m_cmbCat.IsAddPosition())
{
m_strCatId=m_cmbCat.m_pSet->GetCollect(L"CategoryID");
strCat=_T(" CategoryID=")+m_strCatId;
}
if (!m_cmbSup.IsAddPosition())
{
m_strSupId=m_cmbSup.m_pSet->GetCollect(L"SupplierID");
strSup=_T(" SupplierID=")+m_strSupId;
}
strWhere=strCat;
if( ( strSup.GetLength()*strWhere.GetLength() ) ==0 )
strWhere+=strSup;
else
strWhere+=" AND "+strSup;
m_Grid.Execute(m_pConnection,"ProductId"
,"*"
,"Products"
,strWhere
,0);
}
The screenshot below shows us how the relationship works.
Step 3- Changing data in the DataGrid
Create the dialog, place edit boxes and combo boxes into the dialog template. Add essential classes and variables into header file.
#include "DataComboBox.h"
#include "DDXFields.h"
class CDialEdit : public CDialog
{
public:
bool m_catChange,m_supChange;
_RecordsetPtr m_pSet;
CDDXFields m_DDXFields;
enum {EDIT, COPY, NEW};
int m_operation;
CDataComboBox m_cmbCat;
CDataComboBox m_cmbSup;
. . .
. . .
Associate controls into DoDataExchange
:
DDX_Control(pDX, IDC_CMB_CAT, m_cmbCat);
DDX_Control(pDX, IDC_CMB_SUP, m_cmbSup);
m_DDXFields.DDX(pDX);
Add into the constructor:
CDialEdit::CDialEdit(CWnd* pParent )
: CDialog(CDialEdit::IDD, pParent)
{
m_operation=EDIT;
m_catChange=m_supChange=false;
m_DDXFields.SetSize(5);
}
Into OnInitDialog
add:
BOOL CDialEdit::OnInitDialog()
{
CDialog::OnInitDialog();
int i;
m_DDXFields.m_pWnd=this;
i=0;
m_DDXFields.ElementAt(i).Set(_T( "ProductName" ),IDC_ED_PROD) ;
m_DDXFields.ElementAt(i).m_description=_T("Product");
i=1;
m_DDXFields.ElementAt(i).Set(_T( "QuantityPerUnit" ),
IDC_ED_QTY, _T( "1" ),false,true) ;
m_DDXFields.ElementAt(i).m_description=_T("Quantity Per Unit");
i=2;
m_DDXFields.ElementAt(i).Set(_T( "UnitPrice" ),
IDC_ED_PRICE, _T( "0" ),false,true) ;
m_DDXFields.ElementAt(i).m_description=_T("Price") ;
i=3;
m_DDXFields.ElementAt(i).Set(_T( "UnitsInStock" ),
IDC_ED_UNITS, _T( "0" ),false,true) ;
m_DDXFields.ElementAt(i).m_description=_T("Units In Stock");
i=4;
m_DDXFields.ElementAt(i).Set(_T( "Discontinued" ),
IDC_ED_DISCONT,_T( "0" ),false,true) ;
switch (m_operation)
{
case NEW:
break;
default:
m_DDXFields.ReadData(m_pSet);
break;
}
UpdateData(FALSE);
m_cmbSup.Fill();
m_cmbCat.Fill();
. . .
. . .
Create "Save" button in the dialog and function.
void CDialEdit::OnBtnSave()
{
CExString strField;
UpdateData();
try
{
switch (m_operation)
{
case EDIT:
break;
default:
m_pSet->AddNew();
strField=_T("CategoryID");
m_pSet->Fields->GetItem
(strField.Variant())->Value = m_catId.Variant();
strField=_T("SupplierID");
m_pSet->Fields->GetItem
(strField.Variant())->Value = m_supId.Variant();
break;
}
try
{
if( m_DDXFields.WriteData(m_pSet)==-1)
return;
}
catch(...)
{
;
}
CExString strCatId,strSupId;
strCatId=m_cmbCat.GetCurId();
strSupId=m_cmbSup.GetCurId();;
if(strCatId!=m_catId)
{
m_catChange=true;
strField=_T("CategoryID");
m_pSet->Fields->GetItem
(strField.Variant())->Value=strCatId.Variant();
}
if(strSupId!=m_supId)
{
m_supChange=true;
strField=_T("SupplierID");
m_pSet->Fields->GetItem
(strField.Variant())->Value=strSupId.Variant();
}
m_pSet->Update();
CDialog::OnOK();
}
catch( _com_error &e )
{
CString mes1,mes2,mes3,mes4;
mes1.Format(_T("Error:%08lx.\n"), e.Error());
mes2.Format(_T("ErrorMessage:%s.\n"), e.ErrorMessage());
mes3.Format(_T("Source:%s.\n"),
(LPCTSTR) _bstr_t(e.Source()));
mes4.Format(_T("Description:%s.\n"),
(LPCTSTR) _bstr_t(e.Description()));
MessageBox(mes1+mes2+mes3+mes4,
_T("Invalid field "),MB_ICONERROR);
return;
}
catch(...)
{
MessageBox(_T("Unhandled Exception"),
_T("Invalid field ")+strField,MB_ICONERROR);
return;
}
}
Insert into definition of the main dialog class, CArrayStringBox
variables, because we are using m_cmbSup
and m_cmbCat
in UNBOUND
mode.
class CDataGrid_DemoDlg : public CResizableDialog
{
public:
CArrayStringBox m_SupArray;
CArrayStringBox m_CatArray;
Fill it in the OnInitDialog
of the main dialog.
try
{
m_pConnection->Open(L"Provider=Microsoft.Jet.OLEDB.4.0;"
"Data Source=D:\\DATA\\Nwind.mdb", L"", L"", -1);
. . .
. . .
m_CatArray.Fill(m_pConnection,
_T("SELECT * FROM Categories ORDER BY CategoryName"),
_T("CategoryName"),_T("CategoryID"));
m_SupArray.Fill(m_pConnection,
_T("SELECT * FROM Suppliers ORDER BY CompanyName"),
_T("CompanyName"),_T("SupplierID"));
}
catch(_com_error *e)
{
CString Error = e->ErrorMessage();
AfxMessageBox(e->ErrorMessage());
}
catch(...)
{
}
Create the "Edit", "Add", "Copy" and "Delete" buttons in the main dialog and functions:
#include "DialEdit.h"
void CDataGrid_DemoDlg::OnBtnEdit()
{
int nRow=m_Grid.IsSelectRow();
if(nRow==-1)
return;
CDialEdit dlg;
dlg.m_pSet=m_Grid.m_pSet;
dlg.m_catId=m_Grid.m_pSet->GetCollect(L"CategoryID") ;
dlg.m_supId=m_Grid.m_pSet->GetCollect(L"SupplierID") ;
dlg.m_cmbCat.Attach(&m_CatArray,dlg.m_catId);
dlg.m_cmbSup.Attach(&m_SupArray,dlg.m_supId);
dlg.m_operation=CDialEdit::EDIT;
if (dlg.DoModal() == IDOK)
{
if( ((dlg.m_catChange)&&(!m_cmbCat.IsAddPosition()))
|| ( (dlg.m_supChange)&&(!m_cmbSup.IsAddPosition()) ) )
{
if(nRow==1)
{
if(1!=m_Grid.GetRowCount())
m_Grid.SetRowFocus(nRow);
else
m_Grid.SetRowFocus(0);
}
else
{
m_Grid.SetRowFocus(nRow-1);
}
m_Grid.RequerySource();
}
else
m_Grid.Invalidate();
}
}
void CDataGrid_DemoDlg::OnBtnAd()
{
CDialEdit dlg;
dlg.m_pSet=m_Grid.m_pSet;
dlg.m_catId=m_Grid.m_pSet->GetCollect(L"CategoryID") ;
dlg.m_supId=m_Grid.m_pSet->GetCollect(L"SupplierID") ;
dlg.m_cmbCat.Attach(&m_CatArray,dlg.m_catId);
dlg.m_cmbSup.Attach(&m_SupArray,dlg.m_supId);
dlg.m_operation=CDialEdit::NEW;
if (dlg.DoModal() == IDOK)
{
m_Grid.AddNew();
}
}
void CDataGrid_DemoDlg::OnBtnCopy()
{
if(m_Grid.IsSelectRow()==-1)
return;
CDialEdit dlg;
dlg.m_pSet=m_Grid.m_pSet;
dlg.m_operation=CDialEdit::COPY;
dlg.m_catId=m_Grid.m_pSet->GetCollect(L"CategoryID") ;
dlg.m_supId=m_Grid.m_pSet->GetCollect(L"SupplierID") ;
dlg.m_cmbCat.Attach(&m_CatArray,dlg.m_catId);
dlg.m_cmbSup.Attach(&m_SupArray,dlg.m_supId);
if (dlg.DoModal() == IDOK)
{
m_Grid.AddNew();
}
}
void CDataGrid_DemoDlg::OnBtnDel()
{
m_Grid.Delete();
}
Add menu, find dialog and your application is ready.