When it comes to data management, real power has more to do with precision of access than just about anything else. We need to be able to find specific records or groups of records, modify them, and iterate them. In the BetterBirthdays example, we tackle these issues. We don’t introduce a lot of new APIs for these jobs. Rather, we mostly use the ones you’ve already seen in slightly different ways.
The BetterBirthdays Example Running on an H/PC:
Using Sort Orders
We refine our use of the database functionality in the BetterBirthdays program. The first and most noticeable change is that this time the Birthdays database that has a sorted field name field. We specify this attribute at the time the database is created, and we create the database in BirthdayDlgProc() in response to the WM_INITDIALOG message.
ceSortProp = MAKELONG(CEVT_LPWSTR, 0); switch (message) { case WM_INITDIALOG: //Init the dialog controls InitDlgCtrls( hDlg ); //sort records ascending on name, //without respect to case sosAscendingNameSort.propid = MAKELONG(CEVT_LPWSTR, 0); sosAscendingNameSort.dwFlags = CEDB_SORT_CASEINSENSITIVE;
In order to create a database in which the properties behave like “key” fields in a tabular database, we provide information at database creation time that supports traversing the database records base on the values of specific properties ( or fields ). This information is encoded in a SORTORDERSPEC structure. The typedef for SORTORDERSPEC is shown below:
typedef struct _SORTORDERSPEC { CEPROPID propid; DWORD dwFlags; } SORTORDERSPEC;
The propid member is the now-familiar CEPROPID. The value of the dwFlags member defines the sorting mechanism that will operate on this property.
Table 1: Sort Order Flags and Their Meanings
Sorting Mechanism Flag | Meaning |
CEDB_SORT_DESCENDING | The sort is done in descending order. By default, the sort is done in ascending order. |
CEDB_SORT_CASEINSENSITIVE | The sort operation is case insensitive. This value is valid only for strings. |
CEDB_SORT_UNKNOWNFIRST | Records that do not contain this property are placed before all the other records. By default, such records are placed after all other records. |
CEDB_SORT_GENERICORDER | The default sort order—ascending order, case sensitive—is used. Records that do not contain the sort property are placed at the end of the list. |
A database can have a maximum of four fields with active sort orders. The call to CeCreateDatabase() for the new, improved Birthdays database looks like this:
//Create data store and save the CEOID
globalCEOID = CeCreateDatabase(TEXT("Birthdays"),
0x2468, 1, >sosAscendingNameSort);
A successful call will create a Birthdays database in which we have one sorted field. The names are sorted in ascending alphabetical order, without sensitivity to case.
//if the db exists, CeCreateDatabase will fail rc = GetLastError(); if( rc == ERROR_DUP_NAME ) { //just get the oid, then close globalCEOID = 0; globalHDB = CeOpenDatabase (&globalCEOID, TEXT("Birthdays"), 0, 0, NULL); CloseHandle( globalHDB ); globalHDB = 0;
If the database already exists when we try to create it, CeCreateDatabase will fail. This case becomes important in the BetterBirthdays example because we created a database of this same name in the BirthdayReminder example last month. We can’t use that database for this project, however, because it was created without sort orders. To get the sort of database functionality we need in this case, we’ll have to delete the old database if it exists and then create a new one. Here’s the prototype for the API we’ll use:
BOOL CeDeleteDatabase(CEOID oidDatabase);
CeDeleteDatabase() takes a single parameter, the database CEOID. In part, this is because you can’t delete an open database. If we get an error status of ERROR_DUP_NAME, we open the existing database by name in order to get its CEOID. Then, we close the database to make it deleteable, and finally, delete it by using the CEOID.
Unlike most desktop databases, CE’s native database is somewhat “undefended.” By this I mean that if certain somewhat predictable exceptions or errors occur (disk full, passing bad parameters to database APIs, and so on) you can’t assume that the system will clean up and carry on. You must be rigorous about testing return values of any database function calls that could fail.
}
//uh-oh...
if( (rc == ERROR_DISK_FULL )
||
( rc == ERROR_INVALID_PARAMETER ))
{
iCaption = IDS_CANT_CREATE_DB;
iTitle = IDS_NO_DATABASE;
{ goto FAIL; }
}
Much of the functionality of the BetterBirthdays example hinges on the ability to find a specific record. For this reason, we’ll explore the FindBirthday() function next:
BOOL FindBirthday(HWND hDlg)
{
HWND hwndWho, hwndChange, hwndDelete;
TCHAR* lpwszWho;
int iTextLen, rc;
DWORD dwIndex =0;
CEPROPVAL propBirthday;
//Find out who from the edit control
hwndWho = GetDlgItem(hDlg,IDC_WHO);
iTextLen = SendMessage( hwndWho, WM_GETTEXTLENGTH, 0, 0);
The FindBirthday() function is called when the user clicks the “Find” dialog control. The first few lines of the function get the name to find from the edit control, detecting an empty string if there was no input.
//require user input if( !iTextLen ) {return FALSE;} //allocate for wide character count plus terminal null lpwszWho = (TCHAR* )LocalAlloc(LPTR, (iTextLen + 1) * sizeof(WCHAR) ); //get the input string SendMessage( hwndWho, WM_GETTEXT, (iTextLen + 1), (LPARAM)(LPSTR)lpwszWho);
Next, we open the database, specifying an active sort order. The parameters to CeOpenDatabase(), in the order shown, are the CEOID of the database to open, a NULL placeholder for the pointer to the database name, the CEPROPID of the field on which to activate sorting, an open flag value of zero, and a NULL placeholder for the handle of the window to receive database change notification messages.
//open the database with an active sort order globalHDB = CeOpenDatabase (&globalCEOID, NULL, MAKELONG(CEVT_LPWSTR, 0), 0, NULL); rc = GetLastError(); if( rc == ERROR_INVALID_PARAMETER ) { goto FAIL; } if( globalHDB == INVALID_HANDLE_VALUE ) {goto FAIL; } //seek to this rec propBirthday.propid = MAKELONG(CEVT_LPWSTR, 0) ; propBirthday.val.lpwstr = lpwszWho; propBirthday.wLenData = 0; propBirthday.wFlags = 0;
Finding the record that contains the name specified by the user is a three-step process. First, we initialize a CEPROPVAL structure with the information that specifies what field we plan to search. In this case, we set the PROPID member to the name property, and we set the val.lpwstr member to the address of the string for which we are going to search.
globalRecOID = CeSeekDatabase (globalHDB, CEDB_SEEK_BEGINNING, 0, &dwIndex);
Next, we initialize the position in the database by seeking to the first record. The paramaeters to CeSeekDatabase(), in the order shown, are the handle to the database, a seek type flag, a NULL placeholder for the value to seek, and the address of the variable that will contain the record index on a successful return.
globalRecOID = CeSeekDatabase (globalHDB, CEDB_SEEK_VALUEFIRSTEQUAL, (DWORD)&propBirthday, &dwIndex);
Finally, we perform a search for the value specified by the user. Put another way, we seek to a record containing a property matched by the search criteria.
This time, the parameters to CeSeekDatabase() are the database handle, a seek flag that initiates a search for the first occurrence of a record containing an exact match of the search criteria, the address of a CEVALPROP structure we initialized with the search data, and the address of the variable that contains the index of the found record on successful return. Notice that we save the returned CEOID of the matching record, so that in the future we can manipulate the record directly.
Looking Ahead
In the next look at the BetterBirthdays example, we’ll examine techniques for deleting, modifying, and iterating records.
Download
Download the accompanying source code here.
About the Author
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, she has written several books, including Making Win 32 Applications Mobile.
# # #