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.



About the Author

Nancy Nicolaisen

Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, and entry time data validation. In addition to writing for Developer.com and CodeGuru, she has written several books, including Making Win 32 Applications Mobile.

Comments

  • RAPI: CeOpenDatabase() for Smartphones

    Posted by hfr on 12/08/2005 07:00am

    I have already used CeOpenDatabase() etc. sucessfully for Pocket PC devices (RAPI and non-RAPI version of it). But the RAPI version for CeOpenDatabase() returns a Access denied for Smartphones. Ist there a way to signe some assembly (using codesign.exe) in order to allow RAPI accessing DBs stored on a smartphone? Regards, hfr

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • According to a recent Forrester total economic impact (TEI) study, enterprises can see a significant reduction in total cost of ownership by accessing Oracle Database in the cloud with a pay-as-you-go subscription model. This subscription service gives businesses the ability to scale up application environments for rapid prototyping, with far less time devoted to procuring licenses and deploying IT infrastructure. Read this study to learn how three different companies use Oracle Database in the cloud and the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds