Getting Records from the Remote Device

In this example, you are going to explore techniques for retrieving records from a remote database, once again looking into the functioning of the RemoteDBScan example program. If you are unfamiliar with the steps leading up to this point, you can review the previous lessons and the sample source code, which are all available on this Web site. Start from the point at which the user requests access to the database records.

Responding to User Requests for Record Retrieval

When the user selects a database from the list control and chooses the Remote Database Access.Get Database Records menu item, you land in the main frame window’s menu handler.

This member function has three jobs:

  • It checks to make sure that user has selected a database from which to retrieve records.
  • It makes sure that the database actually contains records.
  • If both these conditions are met, it launches the dialog that displays the database records.

First, you check for a row selection by calling the CListCtrl member GetFirstSelectedItemPosition(). This function returns a value of type POSITION, but if there is no selected row, it returns NULL. This is subtle and tricky—the value returned, pos, is a 1-based index. The list control refers to items using 0-based indices. In the first step, you are only interested in whether or not there is a selection. If you want to manipulate the returned selection, you have to decrement and properly cast pos to land on the correct list view item.

void CMainFrame::OnGetDatabaseRecords()
{

    //get list ctrl
    CRemoteDBScanView* pListView =
                      (CRemoteDBScanView*)this->GetActiveView();
    CListCtrl& ListCtrl = pListView->GetListCtrl();

    //get currently selected row
    POSITION pos = ListCtrl.GetFirstSelectedItemPosition();

    if( pos == NULL )
    {
        AfxMessageBox( "You must select a database from the list.",
                       MB_OK, 0 );
        return;

    }

Now, you use the returned position to query the item in which you stored the number of records contained in this database. You decrement pos, and assign this new value to a variable of type int, ipos. You set lvitem.iItem to ipos, and lvitem.iSubItem to 3, which is the 0-based index of the list control column containing the database record counts. You set the lvitem.mask to LVIF_TEXT, which signals that you are only interested in retrieving the item’s caption string. You call ListCtrl.GetItem(), passing the initialized LVITEM structure as its parameter.

    //pos is a 1-based index, so you must decrement
    int ipos = (int)--pos;

    //get the item

    LVITEM lvitem;
    CHAR szBuff[12];

    memset( &szBuff, 0x0, sizeof(szBuff));
    lvitem.mask = LVIF_TEXT ;
    //pos is a 1-based index, so you must decrement
    lvitem.iItem      = ipos;
    lvitem.iSubItem   = 3;
    lvitem.pszText    = szBuff;
    lvitem.cchTextMax = sizeof(szBuff);

    ListCtrl.GetItem(&lvitem);

On successful return, you have the caption, and you compare it to 0—if there are no records in the database, you message the user and bail out here.

    int iLex = strcmp( szBuff, "0" );
    //message and bail if the item has no records
    if ( iLex == 0 )
    {
        AfxMessageBox( "This database has no records.", MB_OK, 0);
        return;
    }

If you clear all of these hurdles, it’s time to open the remote database and recover the records. The CMainFrame class declares a member variable for the dialog that does this job. Here is an excerpt from the class header that depicts the relationship between the dialog and the CMainFrame class:

// Implementation
public:
    CRecordListDialog m_RecordListDialog;

At the bottom of CMainFrame::OnGetDatabaseRecords(), you put up the modal dialog:

    //got a selection & some records, so put up the dialog
    m_RecordListDialog.DoModal();

}

Initializing the CRecordListDialog

First, you initialize the dialog’s window caption to reflect the name of the database from which you are retrieving records. To do this, first you get a pointer to the CMainFrame object that owns the dialog; then you get a pointer to the CRemoteDBScanView object that owns the list control; and finally, you get the list control itself.

BOOL CRecordListDialog::OnInitDialog()
{
    CDialog::OnInitDialog();

    //Set dialog title to name of this database

    CMainFrame* pFrame = (CMainFrame*)this->GetParent();
    CRemoteDBScanView* pListView =
        (CRemoteDBScanView*)pFrame->GetActiveView();
    CListCtrl& ListCtrl = pListView->GetListCtrl();

Once again, you query the selected item position, receiving a POSITION value that you decrement, and cast so that you can use it as a list control item index.

    //get currently selected row
    POSITION pos = ListCtrl.GetFirstSelectedItemPosition();

    //pos is a 1-based index, so you must decrement
    int ipos = (int)--pos;

You retrieve the database name by initializing lvitem.iItem with the index of the selected row and lvitem.iSubItem with 0, the index of the list control column that contains the database names.

    //set caption for the DB you are scanning
    LVITEM lvitem;
    CHAR szBuff[124];

    memset( &szBuff, 0x0, sizeof(szBuff));
    lvitem.mask = LVIF_TEXT ;
    //pos is a 1-based index, so you must decrement
    lvitem.iItem      = ipos;
    lvitem.iSubItem   = 0;
    lvitem.pszText    = szBuff;
    lvitem.cchTextMax = sizeof(szBuff);

The database name is returned in szBuff, which was sized so that you could safely catenate the rest of the dialog’s caption text.

    ListCtrl.GetItem(&lvitem);
    strcat(szBuff, " Records" );

You set the string in the dialog’s title bar by calling the CWnd base class function, SetWindowText().

    this->SetWindowText(szBuff);

Now, you open the selected database using the CEOID value you stored in the database name’s item data. You save the database CEOID in a CRecordListDialog public member variable, m_globalCEOID.

    //get the saved CEOID
    m_globalCEOID = (CEOID)ListCtrl.GetItemData(ipos);

You initialize RAPI, and if successful, proceed to open the database.

    //init rapi
    HRESULT hr = CeRapiInit();
    if( hr != ERROR_SUCCESS )
        {return FALSE;}

You looked at this function in depth in the course of the treatment of the CE database API, but if you skipped over those lessons, you can examine it again briefly here.

The parameters to CeOpenDatabase(), in the order shown, are the address of a variable of type CEOID, a pointer to a Unicode string containing the name of the database to open, the index of the SORTORDERSPEC to apply when the database is opened, a flag that tells whether to let the application increment the current position in the database of serial read operations or whether the system should automatically increment the current position, and the handle to a window that can display message boxes for the called function.

In this case, you are opening the database by CEOID value, and so pass NULL for the address of the name string. Also, you are opening the database with the express intent of serially reading through its record set, so you set the fourth parameter to CEDB_AUTOINCREMENT. This means that you won’t have to handle the database seek operation except before your first read, when you set the current position to the first database record. The last parameter, m_hWnd, is a CWnd base class member variable that stores this object’s window handle.

    //open using  CEOID
    m_globalHDB =
        CeOpenDatabase (&m_globalCEOID, NULL,
                        0,  CEDB_AUTOINCREMENT, m_hWnd);
     if( m_globalHDB == INVALID_HANDLE_VALUE )
     { return FALSE; }

If you are successful, you save the returned database handle in the CRecordListDialog member variable, m_globalHDB. To do any sort of operation on a CE database, you must first explicitly set the “current record” by calling CeSeekDatabase(). The parameters to CeSeekDatabase(), in the order shown, are the handle to the open database, the seek type flag, the offset to which to seek from the flagged database position, and the address of a variable that receives the index of the record to which the seek operation positioned. Notice that when you seek to the beginning of the database, the seek offset is set to 1 rather than to 0.

    //seek to the beginning of the database
    CEOID oid     = 0;
    DWORD dwIndex = 0;
    int iRecIndex = 1;

    oid = CeSeekDatabase (m_globalHDB, CEDB_SEEK_BEGINNING,
                          iRecIndex, &dwIndex);

You read the first record with a call to CeReadRecordProps(). The parameters to this function, in the order shown, are the handle to the open database; a flag that tells the system to dynamically allocate a buffer to hold the record data; the address of a DWORD that receives the count of properties returned for this record; a NULL flag that indicates you want to retrieve all properties for this record; the address of a pointer to the buffer in which CeReadRecordProps() placed the record data; and the address of a DWORD that gives the size in bytes of the record data.

    //get the first record

    // Read all properties for the record. Have the system
    // allocate the buffer containing the data.
    DWORD dwRecSize = 0;
    WORD wProps     = 0;
    PBYTE pRecord   = NULL;

    oid = CeReadRecordProps (m_globalHDB, CEDB_ALLOWREALLOC,
                             &wProps, NULL,
                             &(LPBYTE )pRecord, &dwRecSize);
    if( oid == ERROR_INSUFFICIENT_BUFFER )
        {return FALSE;}

If you successfully read the record, you format the record data and display it in the list box, using the CRecordListDialog member function PublishRecord. You insert a blank line in the list control to provide a separation between records, and continue reading until CeReadRecordProps() returns 0, indicating that there are no more records.

    //while more records
    while (oid != 0 )
    {
       //format the record and insert data in the list control
        PublishRecord( oid, wProps, (PCEPROPVAL)pRecord, dwRecSize );
        //add a line between records
        m_RecordListCtrl.InsertString( -1, "" );

       //read the record
        oid = CeReadRecordProps (m_globalHDB, CEDB_ALLOWREALLOC,
                                 &wProps, NULL,
                                 &(LPBYTE )pRecord, &dwRecSize);

    }

When the OnInitDialog() function’s work is done, you close the remote database with a call to CloseHandle(), and uninitialize the RAPI subsystem.

    //close db
    CloseHandle(m_globalHDB);

    //unint rapi
    CeRapiUninit();

    return TRUE;  // return TRUE unless you set the focus to a control
                  // EXCEPTION: OCX Property Pages should return FALSE
}

Looking Ahead

In the next lesson, you’ll see how to interpret the retrieved records and format them for display on the desktop side of the connection.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read