|
Data binding is one of the most important features of MapX. Along with the
GeoDictionary, it represents the method by which you get points on a map from
your databases. Unlike the GeoDictionary, you can create points at run time
using Data Binding.
Of course, you could use a loop like the following to create a set of points
for your database rows:
CMapXLayer Layer
= LayerByName(theLayerName); // My function
theProgressBar->Max = theSourceTable->RecordCount;
long CurrentPosition = 0;
if (theSourceTable->FindFirst())
{
CMapXFeature NewFeature;
NewFeature.CreateDispatch(NewFeature.GetClsid());
// create new feature
NewFeature.SetType(miFeatureTypeSymbol);
LPDISPATCH NewFeatureDispatch
= NewFeature.m_lpDispatch;
TField *ID
= theSourceTable->FieldByName(theIDFieldName);
TField *Longitude
= theSourceTable->FieldByName(theLongitudeFieldName);
TField *Latitude
= theSourceTable->FieldByName(theLatitudeFieldName);
Layer.SetVisible(FALSE);
do
{
try
{
NewFeature.SetKeyValue(ID->AsString.c_str());
NewFeature.GetPoint().Set
(
Longitude->AsFloat,
Latitude->AsFloat
);
Layer.AddFeature(NewFeatureDispatch);
}
catch (COleDispatchException
*OleDispatchException)
{
OleDispatchException->ReportError();
OleDispatchException->Delete();
return
FALSE;
}
catch (COleException
*OleException)
{
OleException->ReportError();
OleException->Delete();
return
FALSE;
};
theProgressBar->Position
= theProgressBar->Position + 1;
theApplication->ProcessMessages();
// Allow repaint
}
while (theSourceTable->FindNext());
Layer.SetVisible(TRUE);
}
else
{
return FALSE;
}; |
Unfortunately, this is slow. Data Binding does essentially, the same thing,
but it is much faster.
In the examples which follow, it is assumed that your table contains latitude
and longitude data for each row.
ODBC Data Binding
One of the sources of data you may want to display as points in the map is
an ODBC data source. MapX offers direct support for ODBC Data Binding, as follows:
CMapXFields Fields; Fields.CreateDispatch(Fields.GetClsid());
Fields.Add("id", "id", miAggregationIndividual, miTypeString);
Fields.Add("LONGITUDE", "LONGITUDE", miAggregationSum, miTypeNumeric);
Fields.Add("LATITUDE", "LATITUDE", miAggregationSum, miTypeNumeric);
CMapXBindLayer BindLayer; BindLayer.CreateDispatch(BindLayer.GetClsid());
BindLayer.SetLayerName("Test");
BindLayer.SetRefColumn1("LONGITUDE");
BindLayer.SetRefColumn2("LATITUDE");
BindLayer.SetLayerType(miBindLayerTypeXY);
CMapXODBCQueryInfo QueryInfo; QueryInfo.CreateDispatch(QueryInfo.GetClsid());
QueryInfo.SetSqlQuery("select * from Account");
QueryInfo.SetDataSource("PVBindTest");
QueryInfo.SetConnectString("ODBC;");
CMapXDatasets Datasets = (LPDISPATCH) (Ole2::IDispatch *) Map->Datasets;
Map->AutoRedraw = FALSE;
CMapXDataset ResultDataset = Datasets.Add(miDataSetODBC, QueryInfo,
"AccountDataset", "id", "", BindLayer, Fields);
Map->AutoRedraw = TRUE; |
The problem with the above is that the ODBC Data Binding proceeds without ever
allowing the application to ProcessMessages. This means that counters and progress
bars are not updated, and that if the window is convered and then uncovered,
it does not repaint itself.
It also means that you must provide and install ODBC drivers when distributing
your application. It means you cannot provide a progress indicator. And it means
that you cannot use databases which do not have ODBC drivers. However, there
is an answer.
"Unbound" Data Binding
"Unound" Data Binding lets you bind any data from any source. In C++ Builder,
this typically will be a database you are accessing through a TTable component.
Have a look at the following example:
CMapXFields Fields; Fields.CreateDispatch(Fields.GetClsid());
Fields.Add("id", "id", miAggregationIndividual, miTypeString);
Fields.Add("LONGITUDE", "LONGITUDE", miAggregationSum, miTypeNumeric);
Fields.Add("LATITUDE", "LATITUDE", miAggregationSum, miTypeNumeric);
CMapXBindLayer BindLayer; BindLayer.CreateDispatch(BindLayer.GetClsid());
BindLayer.SetLayerName("Test");
BindLayer.SetRefColumn1("LONGITUDE");
BindLayer.SetRefColumn2("LATITUDE");
BindLayer.SetLayerType(miBindLayerTypeXY);
CMapXDatasets Datasets = (LPDISPATCH) (Ole2::IDispatch *) Map->Datasets;
Map->AutoRedraw = FALSE;
BindingDatabase->LargeLocalTable->DisableControls();
BindingDatabase->LargeLocalTable->FindFirst();
CMapXDataset ResultDataset = Datasets.Add(miDataSetUnbound, "AccountDataset",
"id", "", BindLayer, Fields);
Map->AutoRedraw = TRUE;
BindingDatabase->LargeLocalTable->EnableControls(); |
To make this work you need to provide a routine for the Map->OnRequestData
event:
void __fastcall TBindingTestForm::MapRequestData(TObject *Sender,
const AnsiString DataSetName, int Row, short Field, Variant &Value,
WordBool &Done)
{
static LastRow = 1;
if (Row != LastRow)
{
if (BindingDatabase->LargeLocalTable->FindNext())
{
LastRow = Row;
}
else
{
Done = TRUE;
BindingDatabase->LargeLocalTable->FindFirst();
return;
};
};
if (Field == 1)
{
Value = myIDField->AsString;
}
else if (Field == 2)
{
Value = myLongitudeField->AsFloat;
}
else if (Field == 3)
{
Value = myLatitudeField->AsFloat;
};
RowDisplay->Caption = (int) Row;
ColumnDisplay->Caption = (int) Field;
Application->ProcessMessages();
}; |
There are a few things to keep in mind when running this. First, your routine
will be called perhaps as many as three times for each row. Worse, it will typically
be called for one pass through the table to get the IDs, and at least one more
pass (possibly two) to get the coordinates. Therefore, you must make it run
fast. Make sure the ProcessMessages() call is the last thing in the routine
- this helps somehow. Fortunately, you can at least depend on the routine to
be called with rows in ascending order, and you can exploit that to make your
routine more efficient.
In this case, the "unbound" style of Data Binding is used to traverse a non-ODBC
database. It provides the useful side effect of allowing a progress indicator
and allowing repaints of the main window through ProcessMessages(); However,
you can use it to provide any form of data, Just remember to tell MapInfo the
type of the data you are returning in the Fields.Add
Changes to the MapX Header File
You need to add two inlined routine definitions to the MapX header file (MapX\Samples20\CPP\MapX.h):
CMapXDataset Add(short Type,CMapXODBCQueryInfo &SourceData,LPCTSTR
Name,LPCTSTR GeoField,LPCTSTR SecondaryGeoField,CMapXBindLayer &theBindLayer,CMapXFields
&theFields)
{
struct tagVARIANT ODBCQueryInfo;
ODBCQueryInfo.vt = VT_DISPATCH;
ODBCQueryInfo.pdispVal = SourceData.m_lpDispatch;
struct tagVARIANT BindLayer;
FindLayer.vt = VT_DISPATCH;
FindLayer.pdispVal = theBindLayer.m_lpDispatch;
struct tagVARIANT Fields;
Fields.vt = VT_DISPATCH;
Fields.pdispVal = theFields.m_lpDispatch;
return Add(Type,ODBCQueryInfo,COleVariant(Name),COleVariant(GeoField),COleVariant(SecondaryGeoField),BindLayer,Fields);
}
CMapXDataset Add(short Type,LPCTSTR Name,LPCTSTR GeoField,LPCTSTR
SecondaryGeoField,CMapXBindLayer &theBindLayer,CMapXFields &theFields)
{
struct tagVARIANT BindLayer;
BindLayer.vt = VT_DISPATCH;
BindLayer.pdispVal = theBindLayer.m_lpDispatch;
struct tagVARIANT Fields;
Fields.vt = VT_DISPATCH;
Fields.pdispVal = theFields.m_lpDispatch;
return Add(Type,COptionalVariant(),COleVariant(Name),COleVariant(GeoField),COleVariant(SecondaryGeoField),BindLayer,Fields);
} |
|
|