t e m p o r a l 
 d o o r w a y 

Data Binding in C++

 

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

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved