Databases
Windows CE gives you an entirely unique set of database APIs not available under the other versions of Windows. The database implemented by Windows CE is simple, with only one level and a maximum of four sort indexes, but it serves as an effective tool for organizing uncomplicated data, such as address lists and to-do lists.
Basic Definitions
A Windows CE database is composed of a series of records. Records can contain any number of properties. These properties can be one of the data types shown in Table 9-1.
Records can't contain other records. Also, records can reside on only one database. Windows CE databases can't be locked. However, Windows CE does provide a method of notifying a process that another thread has modified a database.A Windows CE database can have up to four multilevel sort indexes. (In a multilevel sort index, the database sorts by a primary index and then sorts within that index by a second, and even third, index.) These indexes are defined when the database is created but can be redefined later, although the restructuring of a database takes a large amount of time. Each sort index by itself results in a fair amount of overhead, so you should limit the number of sort indexes to what you really need.In short, Windows CE gives you a basic database functionality that helps applications organize simple data structures. The pocket series of Windows CE applications provided by Microsoft with the Pocket PC use the database API to manage the address book, the task list, and e-mail messages. So if you have a collection of data, this database API might just be the best method of managing that data.
Designing a Database
Before you can jump in with a call to CeCreateDatabaseEx2, you need to think carefully about how the database will be used. While the basic limitations of the Windows CE database structure rule out complex databases, the structure is quite handy for managing collections of related data on a small personal device, which, after all, is one of the target markets for Windows CE.
Each record in a database can have as many properties as you need as long as they don't exceed the basic limits of the database structure. The limits are fairly loose. An individual property can't exceed the constant CEDB_MAXPROPDATASIZE, which is set to 65,471. A single record can't exceed CEDB_MAXRECORDSIZE, currently defined as 131,072. The maximum number of records that can be in a single database is 65,535.
Database Volumes
Database files can be stored in volumes on external media as well as directly in the object store. A database volume is nothing more than a specially formatted file where Windows CE databases can be located. Because database volumes can be stored on file systems other than the object store, database information can be stored on Compact Flash Cards or similar external storage devices. The most immediate disadvantage of working with database volumes is that they must be first mounted and then unmounted after you close the databases within the volume. Essentially, mounting the database creates or opens the file that contains one or more databases along with the transaction data for those databases.There are disadvantages to database volumes aside from the overhead of mounting and unmounting the volumes. Database volumes are actual files and therefore can be deleted by means of standard file operations. The volumes are, by default, marked as hidden, but that wouldn't deter the intrepid user from finding and deleting a volume in a desperate search for more space on the device. Databases created directly within the object store aren't files and therefore are much more difficult for the user to accidentally delete.
The Database API
Once you have planned your database and given the restrictions and considerations necessary to it, the programming can begin.
Mounting a Database Volume
If your database is on external media such as a CompactFlash card, you'll need to mount the database volume that contains it. To mount a database volume, call
BOOL CeMountDBVol (PCEGUID pguid, LPWSTR lpszVol, DWORD dwFlags);
This function performs a dual purpose: it can create a new volume or open an existing volume. The first parameter is a pointer to a guid. CeMountDBVol returns a guid that's used by most of the database functions to identify the location of the database file. You shouldn't confuse the CEGUID-type guid parameter in the database functions with the GUID type that is used by OLE and parts of the Windows shell. A CEGUID is simply a handle that tracks the opened database volume.The second parameter in CeMountDBVol is the name of the volume to mount. This isn't a database name, but the name of a file that will contain one or more databases. Since the parameter is a filename, you should define it in \path\name.ext format. The standard extension should be CDB.The last parameter, dwFlags, should be loaded with flags that define how this function acts. The possible flags are the following:
CREATE_NEWCreates a new database volume. If the volume already exists, the function fails.
CREATE_ALWAYSCreates a new database volume. If the volume already exists, it overwrites the old volume.
OPEN_EXISTINGOpens a database volume. If the volume doesn't exist, the function fails.
OPEN_ALWAYSOpens a database volume. If the volume doesn't exist, a new database volume is created.
TRUNCATE_EXISTINGOpens a database volume and truncates it to 0 bytes. If the volume already exists, the function fails.
If the flags resemble the action flags for CreateFile, they should. The actions of CeMountDBVol essentially mirror CreateFile except that instead of creating or opening a generic file, CeMountDBVol creates or opens a file especially designed to hold databases.If the function succeeds, it returns TRUE and the guid is set to a value that is then passed to the other database functions. If the function fails, a call to GetLastError returns an error code indicating the reason for the failure.Database volumes can be opened by more than one process at a time. The system maintains a reference count for the volume. As the last process unmounts a database volume, the system unmounts the volume.
Enumerating Mounted Database Volumes
You can determine which database volumes are currently mounted by repeatedly calling this function:
BOOL CeEnumDBVolumes (PCEGUID pguid, LPWSTR lpBuf, DWORD dwSize);
The first time you call CeEnumDBVolumes, set the guid pointed to by pguid to be invalid. You use the CREATE_INVALIDGUID macro to accomplish this. CeEnumDBVolumes returns TRUE if a mounted volume is found and returns the guid and name of that volume in the variables pointed to by pguid and lpBuff. The dwSize parameter should be loaded with the size of the buffer pointed to by lpBuff. To enumerate the next volume, pass the guid returned by the previous call to the function. Repeat this process until CeEnumDBVolumes returns FALSE. The code below demonstrates this process:
CEGUID guid;
TCHAR szVolume[MAX_PATH];
INT nCnt = 0;
CREATE_INVALIDGUID (&guid);
while (CeEnumDBVolumes (&guid, szVolume, sizeof (szVolume))) {
// guid contains the guid of the mounted volume;
// szVolume contains the name of the volume.
nCnt++; // Count the number of mounted volumes.
}
Unmounting a Database Volume
When you have completed using the volume, you should unmount it by calling this function:
BOOL CeUnmountDBVol (PCEGUID pguid);
The function's only parameter is the guid of a mounted database volume. Calling this function is necessary when you no longer need a database volume and you want to free system resources. Database volumes are unmounted only when all applications that have mounted the volume have called CeUnmountDBVol.
Using the Object Store as a Database Volume
Even though you can store databases in volumes on external media, more often than not you'll want to store the database in the object store. Because many of the database functions require a CEGUID that identifies a database volume, you need a CEGUID that references the system object store. Fortunately, one can be created using this macro:
CREATE_SYSTEMGUID (PCEGUID pguid);
The parameter is, of course, a pointer to a CEGUID. The value set in the CEGUID by this macro can then be passed to any of the database functions that require a separate volume CEGUID.
Creating a Database
You can create a database by calling the function CeCreateDatabaseEx2, which is prototyped as
CEOID CeCreateDatabaseEx2 (PCEGUID pguid, CEDBASEINFOEX *pInfo);
The first parameter is a pguid parameter that identifies the mounted database volume where the database is located. The second parameter is a pointer to a CEDBASEINFOEX structure defined as
typedef struct _CEDBASEINFOEX {
WORD wVersion;
WORD wNumSortOrder;
DWORD dwFlags;
WCHAR szDbaseName[CEDB_MAXDBASENAMELEN];
DWORD dwDbaseType;
DWORD dwNumRecords;
DWORD dwSize;
FILETIME ftLastModified;
SORTORDERSPECEX rgSortSpecs[CEDB_MAXSORTORDER];
} CEDBASEINFOEX, *PCEDBASEINFOEX;
The first field, wVersion, Chapter 10.The szDbaseName field specifies the name of the new database. Unlike filenames, the database name is limited to 32 characters, including the terminating zero. The dwDbaseType field is a user-defined parameter that can be employed to differentiate families of databases. For example, you might want to use a common type value for all databases that your application creates. This allows them to be easily enumerated. At this point, there are no rules for what type values to use. Some example type values used by the Microsoft Pocket suite are listed in Table 9-2.
Database | Value | |
---|---|---|
Contacts | 24 | (18 hex) |
Appointments | 25 | (19 hex) |
Tasks | 26 | (1A hex) |
Categories | 27 | (1B hex) |
The values listed in Table 9-2 aren't guaranteed to remain constant; I simply wanted to show some typical values. If you use a 4-byte value, it shouldn't be too hard to find a unique database type for your application, although there's no reason another application couldn't use the same type.The fields wNumRecords, dwSize, and ftLastModified are ignored during the call to CeCreateDatabaseEx. They are used by other database functions that utilize this same structure.The final field, rgSortSpecs, specifies the sort specification for the database. This parameter contains an array of SORTORDERSPECEX structures defined as
typedef struct _SORTORDERSPECEX {
WORD wVersion;
WORD wNumProps;
WORD wKeyFlags;
CEPROPID rgPropID[CEDB_MAXSORTPROP];
DWORD rgdwFlags[CEDB_MAXSORTPROP];
} SORTORDERSPECEX;
The first field in SORTORDERSPECEX is the wVersion field, which should be set to SORTORDERSPECEX_VERSION. The wNumProps field specifies the number of sort properties used in this sort specification. The wKeyFlags field defines characteristics for the specification. The only flag currently supported is CEDB_SORT_UNIQUE, which indicates that each record in the database must have a unique value in this property.The rgPropID field is an array of structures that contains property IDs, or PEGPROPIDs. A property ID is nothing more than a unique identifier for a property in the database. Remember that a property is one field within a database record. The property ID is a DWORD value with the low 16 bits containing the data type and the upper 16 bits containing an application-defined value. These values are defined as constants and are used by various database functions to identify a property. For example, a property that contained the name of a contact might be defined as
#define PID_NAME MAKELONG (CEVT_LPWSTR, 1)
The MAKELONG macro simply combines two 16-bit values into a DWORD or LONG. The first parameter is the low word or the result, while the second parameter becomes the high word. In this case, the CEVT_LPWSTR constant indicates that the property contains a string, while the second parameter is simply a value that uniquely identifies the Name property, distinguishing it from other string properties in the record.The final field in SORTORDERSPECEX, rgdwFlags, contains an array of flags that define how the sort is to be accomplished. Each entry in the array matches the corresponding entry in the rgPropID array. The following flags are defined for this field:
CEDB_SORT_DESCENDINGThe sort is to be in descending order. By default, properties are sorted in ascending order.
CEDB_SORT_CASEINSENSITIVEThe sort should ignore the case of the letters in the string.
CEDB_SORT_UNKNOWNFIRSTRecords without this property are to be placed at the start of the sort order. By default, these records are placed last.
CEDB_SORT_IGNORENONSPACEThe sort should ignore nonspace characters such as accents during sorting. This flag is valid only for string properties.
CEDB_SORT_IGNORESYMBOLSThe sort should ignore symbols during sorting. This flag is valid only for string properties.
CEDB_SORT_IGNOREKANATYPEThe sort should not differentiate between Hiragana and Katakana characters. This flag is valid only for string properties.
CEDB_SORT_IGNOREWIDTHThe sort should ignore the difference between single-byte characters and the same character represented by a double-byte value. This flag is valid only for string properties.
CEDB_SORT_NONNULLThis flag specifies that this sort property must be present in all records in the database.
A typical database might have three or four sort orders defined. After a database is created, these sort orders can be changed by calling CeSetDatabaseInfoEx2. However, this function is quite resource intensive and can take from seconds up to minutes to execute on large databases.
The value returned by CeCreateDatabaseEx2 is a CEOID. We have seen this kind of value a couple of times so far in this chapter. It's an ID value that identifies the newly created database. If the value is 0, an error occurred while you were trying to create the database. You can call GetLastError to diagnose the reason the database creation failed.The function CeCreateDatabaseEx2 was added to Windows CE .NET 4.0. If an application needs to run on a Windows CE 3.0–based system, such as a Pocket PC 2000 or Pocket PC 2002, the application must use the function CeCreateDatabaseEx to create a database. The chief difference between the two functions is that CeCreateDatabaseEx2 allows multilevel sorting, whereas CeCreateDatabaseEx does not.
Opening a Database
In contrast to what happens when you create a file, creating a database doesn't also open the database. To do that, you must make an additional call to
HANDLE CeOpenDatabaseEx2 (PCEGUID pguid, PCEOID poid, LPWSTR lpszName,
SORTORDERSPECEX *pSort,
DWORD dwFlags,
CENOTIFYREQUEST *pRequest);
The first parameter is the address of the CEGUID that indicates the database volume that contains the database. A database can be opened either by referencing its CEOID value or by referencing its name. To open the database by using its name, set the value pointed to by the poid parameter to 0 and specify the name of the database using the lpszName parameter. If you already know the CEOID of the database, simply put that value in the parameter pointed to by poid. If the CEOID value isn't 0, the function ignores the lpszName parameter.The pSort parameter specifies which of the sort order specifications should be used to sort the database while it's opened. This parameter should point to a SORTORDERSPECEX structure that matches one of the entries in the SORTORDERSPECEX array that was defined when the database was created. The pointer doesn't have to point to the exact entry used when the database was created. Instead, the data within the SORTORDERSPECEX structure must match the data in the original SORTORDERSPECEX array entry. A Windows CE database can have only one active sort order. To use a different sort order, you can open a database again, specifying a different sort order.
The dwFlags parameter can contain either 0 or CEDB_AUTOINCREMENT. If CEDB_AUTOINCREMENT is specified, each read of a record in the database results in the database pointer being moved to the next record in the sort order. Opening a database without this flag means that the record pointer must be manually moved to the next record to be read. This flag is helpful if you plan to read the database records in sequential order.The final parameter points to a structure that specifies how your application will be notified when another process or thread modifies the database. The scheme is a message-based notification that allows you to monitor changes to the database while you have it opened. To specify the window that receives the notification messages, you pass a pointer to a CENOTIFYREQUEST structure that you have previously filled in. This structure is defined as
typedef struct _CENOTIFYREQUEST {
DWORD dwSize;
HWND hWnd;
DWORD dwFlags;
HANDLE hHeap;
DWORD dwParam;
} CENOTIFYREQUEST;
The first field must be initialized to the size of the structure. The hWnd field should be set to the window that will receive the change notifications. The dwFlags field specifies how you want to be notified. If you put 0 in this field, you'll receive notifications in the old database notification scheme. This method used three messages based on the WM_USER constant that is supposed to be reserved for applications. While this method is simpler than the method I'm about to describe, I recommend against using it. Instead, put CEDB_EXNOTIFICATION in the dwFlags field; your window will receive an entirely new and more detailed notification method. This new notification method requires that Windows CE allocate a structure. If you specify a handle to a heap in the hHeap field, the structure will be allocated there. If you set hHeap to 0, the structure will be allocated in your local heap. The dwParam field is a user-defined value that will be passed back to your application in the notification structure.Your window receives a WM_DBNOTIFICATION message in the new notification scheme. When your window receives this message, the lParam parameter points to a CENOTIFICATION structure defined as
typedef struct _CENOTIFICATION {
DWORD dwSize
DWORD dwParam;
UINT uType;
CEGUID guid;
CEOID oid;
CEOID oidParent;
} CENOTIFICATION;
As expected, the dwSize field fills with the size of the structure. The dwParam field contains the value passed in the dwParam field in the CENOTIFYREQUEST structure. This is an application-defined value that can be used for any purpose.The uType field indicates why the WM_DBNOTIFICATION message was sent. It will be set to one of the following values:
DB_CEOID_CREATEDA new file system object was created.
DB_CEOID_DATABASE_DELETEDThe database was deleted from a volume.
DB_CEOID_RECORD_DELETEDA record was deleted in a database.
DB_CEOID_CHANGEDAn object was modified.
The guid field contains the guid for the database volume that the message relates to, while the oid field contains the relevant database record oid. Finally, the oidParent field contains the oid of the parent of the oid that the message references.When you receive a WM_DBNOTIFICATION message, the CENOTIFICATION structure is placed in a memory block that you must free. If you specified a handle to a heap in the hHeap field of CENOTIFYREQUEST, the notification structure will be placed in that heap; otherwise, the system places this structure in your local heap. Regardless of its location, you are responsible for freeing the memory that contains the CENOTIFICATION structure. You do this with a call to
BOOL CeFreeNotification(PCENOTIFYREQUEST pRequest,
PCENOTIFICATION pNotify);
The function's two parameters are a pointer to the original CENOTIFYREQUEST structure and a pointer to the CENOTIFICATION structure to free. You must free the CENOTIFICATION structure each time you receive a WM_DBNOTIFICATION message.
Seeking (or Searching for) a Record
Now that the database is opened, you can read and write the records. But before you can read a record, you must seek to that record. That is, you must move the database pointer to the record you want to read. You accomplish this using
CEOID CeSeekDatabaseEx (HANDLE hDatabase, DWORD dwSeekType, DWORD dwValue,
WORD wNumVals, LPDWORD lpdwIndex);
The first parameter for this function is the handle to the opened database. The dwSeekType parameter describes how the seek is to be accomplished. The parameter can have one of the following values:
CEDB_SEEK_CEOIDSeek a specific record identified by its object ID. The object ID is specified in the dwValue parameter. This type of seek is particularly efficient in Windows CE databases.
CEDB_SEEK_BEGINNINGSeek the nth record in the database. The index is contained in the dwValue parameter.
CEDB_SEEK_CURRENTSeek from the current position n records forward or backward in the database. The offset is contained in the dwValue parameter. Even though dwValue is typed as an unsigned value, for this seek it's interpreted as a signed value.
CEDB_SEEK_ENDSeek backward from the end of the database n records. The number of records to seek backward from the end is specified in the dwValue parameter.
CEDB_SEEK_VALUESMALLERSeek from the current location until a record is found that contains a property that is the closest to but not equal to or over the value specified. The value is specified by a CEPROPVAL structure pointed to by dwValue.
CEDB_SEEK_VALUEFIRSTEQUALStarting with the current location, seek until a record is found that contains the property that's equal to the value specified. The value is specified by a CEPROPVAL structure pointed to by dwValue. The location returned can be the current record.
CEDB_SEEK_VALUENEXTEQUALStarting with the next location, seek until a record is found that contains a property that's equal to the value specified. The value is specified by a CEPROPVAL structure pointed to by dwValue.
CEDB_SEEK_VALUEGREATERSeek from the current location until a record is found that contains a property that is equal to, or the closest to, the value specified. The value is specified by a CEPROPVAL structure pointed to by dwValue.
As you can see from the available flags, seeking in the database is more than just moving a pointer; it also allows you to search the database for a particular record.
As I just mentioned in the descriptions of the seek flags, the dwValue parameter can either be loaded with an offset value for the seeks or point to an array of property values for the searches. The values are described in an array of CEPROPVAL structures, each defined as
typedef struct _CEPROPVAL {
CEPROPID propid;
WORD wLenData;
WORD wFlags;
CEVALUNION val;
} CEPROPVAL;
The propid field must match the property ID values of the sort order you specified when the database was opened. Remember that the property ID is a combination of a data type identifier along with an application-specific ID value that uniquely identifies a property in the database. This field identifies the property to examine when seeking. The wLenData field is ignored. None of the defined flags for the wFlags field is used by CeSeekDatabase, so this field should be set to 0. The val field is actually a union of the different data types supported in the database.Following is a short code fragment that demonstrates seeking to the third record in the database.
DWORD dwIndex;
CEOID oid;
// Seek to the third record.
oid = CeSeekDatabase (g_hDB, CEDB_SEEK_BEGINNING, 3, &dwIndex);
if (oid == 0) {
// There is no third item in the database.
}
Now say we want to find the first record in the database that has a height property of greater than 100. For this example, assume the size property type is a signed long value.
// Define pid for height property as a signed long with ID of 1.
#define PID_HEIGHT MAKELONG (CEVT_I4, 1)
CEOID oid;
DWORD dwIndex;
CEPROPVAL Property;
// First seek to the start of the database.
oid = CeSeekDatabaseEx (g_hDB, CEDB_SEEK_BEGINNING, 0, 1, &dwIndex);
// Seek the record with height > 100.
Property.propid = PID_HEIGHT; // Set property to search.
Property.wLenData = 0; // Not used but clear anyway.
Property.wFlags = 0; // No flags to set
Property.val.lVal = 100; // Data for property
oid = CeSeekDatabaseEx (g_hDB, CEDB_SEEK_VALUEGREATER, (DWORD)&Property,
1, &dwIndex);
if (oid == 0) {
// No matching property found; db pointer now points to end of db.
} else {
// oid contains the object ID for the record,
// dwIndex contains the offset from the start of the database
// of the matching record.
}
Because the search for the property starts at the current location of the database pointer, you first need to seek to the start of the database if you want to find the first record in the database that has the matching property.
Changing the Sort Order
I talked earlier about how CeDatabaseSeekEx depends on the sort order of the opened database. If you want to choose one of the predefined sort orders instead, you must close the database and then reopen it specifying the predefined sort order. But what if you need a sort order that isn't one of the four sort orders that were defined when the database was created? You can redefine the sort orders using this function:
BOOL CeSetDatabaseInfoEx2 (PCEGUID pguid,
CEOID oidDbase,
CEDBASEINFOEX *pNewInfo);
The function takes the CEGUID of the database volume and the object ID of the database you want to redefine and a pointer to a CEDBASEINFOEX structure. This structure is the same one used by CeCreateDatabaseEx2. You can use these functions to rename the database, change its type, or redefine the four sort orders. You shouldn't redefine the sort orders casually. When the database sort orders are redefined, the system has to iterate through every record in the database to rebuild the sort indexes. This can take minutes for large databases. If you must redefine the sort order of a database, you should inform the user of the massive amount of time it might take to perform the operation.
Reading a Record
Once you have the database pointer at the record you're interested in, you can read or write that record. You can read a record in a database by calling the following function:
CEOID CeReadRecordPropsEx (HANDLE hDbase, DWORD dwFlags,
LPWORD lpcPropID,
CEPROPID *rgPropID, LPBYTE *lplpBuffer,
LPDWORD lpcbBuffer,
HANDLE hHeap);
The first parameter in this function is the handle to the opened database. The -lpcPropID parameter points to a variable that contains the number of CEPROPID structures pointed to by the next parameter, rgPropID. These two parameters combine to tell the function which properties of the record you want to read. There are two ways to utilize the lpcPropID and rgPropID parameters. If you want only to read a selected few of the properties of a record, you can initialize the array of CEPROPID structures with the ID values of the properties you want and set the variable pointed to by lpcPropID with the number of these structures. When you call the function, the returned data will be inserted into the CEPROPID structures for data types such as integers. For strings and blobs, where the length of the data is variable, the data is returned in the buffer indirectly pointed to by lplpBuffer.Since CeReadRecordPropsEx has a significant overhead to read a record, it is always best to read all the properties necessary for a record in one call. To do this, simply set rgPropID to NULL. When the function returns, the variable pointed to by lpcPropID will contain the count of properties returned and the function will return all the properties for that record in the buffer. The buffer will contain an array of CEPROPID structures created by the function, immediately followed by the data for those properties, such as blobs and strings, where the data isn't stored directly in the CEPROPID array.One very handy feature of CeReadRecordPropsEx is that if you set CEDB_ALLOWREALLOC in the dwFlags parameter, the function will enlarge, if necessary, the results buffer to fit the data being returned. Of course, for this to work, the buffer being passed to the function must not be on the stack or in the static data area. Instead, it must be an allocated buffer, in the local heap or a separate heap. In fact, if you use the CEDB_ALLOWREALLOC flag, you don't even need to pass a buffer to the function; instead, you can set the buffer pointer to 0. In this case, the function will allocate the buffer for you.Notice that the buffer parameter isn't a pointer to a buffer but the address of a pointer to a buffer. There actually is a method to this pointer madness. Since the resulting buffer can be reallocated by the function, it might be moved if the buffer needs to be reallocated. So the pointer to the buffer must be modified by the function. You must always use the pointer to the buffer returned by the function because it might have changed. Also, you're responsible for freeing the buffer after you have used it. Even if the function failed for some reason, the buffer might have moved or even have been freed by the function. You must clean up after the read by freeing the buffer if the pointer returned isn't 0.
As you might have guessed from the preceding paragraphs, the hHeap parameter allows CeReadRecordPropsEx to use a heap different from the local heap when reallocating the buffer. When you use CeReadRecordPropsEx and you want to use the local heap, simply pass a 0 in the hHeap parameter.The following routine reads all the properties for a record and then copies the data into a structure.
int ReadDBRecord (HANDLE hDB, DATASTRUCT *pData, HANDLE hHeap) {
WORD wProps;
CEOID oid;
PCEPROPVAL pRecord;
PBYTE pBuff;
DWORD dwRecSize;
int i;
// Read all properties for the record.
pBuff = 0; // Let the function allocate the buffer.
oid = CeReadRecordPropsEx (hDB, CEDB_ALLOWREALLOC, &wProps, NULL,
&(LPBYTE)pBuff, &dwRecSize, hHeap);
// Failure on read.
if (oid == 0)
return 0;
// Copy the data from the record to the structure. The order
// of the array is not defined.
memset (pData, 0 , sizeof (DATASTRUCT)); // Zero return struct
pRecord = (PCEPROPVAL)pBuff; // Point to CEPROPVAL
// array.
for (i = 0; i < wProps; i++) {
switch (pRecord->propid) {
case PID_NAME:
lstrcpy (pData->szName, pRecord->val.lpwstr);
break;
case PID_TYPE:
lstrcpy (pData->szType, pRecord->val.lpwstr);
break;
case PID_SIZE:
pData->nSize = pRecord->val.iVal;
break;
}
pRecord++;
}
if (hHeap)
HeapFree (hHeap, 0, pBuff);
else
LocalFree (pBuff);
return i;
}
Because this function reads all the properties for the record, CeReadRecordPropsEx creates the array of CEPROPVAL structures. The order of these structures isn't defined, so the function cycles through each one to look for the data to fill in the structure. After all the data has been read, a call to either HeapFree or LocalFree is made to free the buffer that was returned by CeReadRecordPropsEx.Nothing requires every record to contain all the same properties. You might encounter a situation where you request a specific property from a record by defining the CEPROPID array and that property doesn't exist in the record. When this happens, CeReadRecordPropsEx will set the CEDB_PROPNOTFOUND flag in the wFlags field of the CEPROPID structure for that property. You should always check for this flag if you call CeReadRecordPropsEx and you specify the properties to be read. In the example above, all properties were requested, so if a property didn't exist, no CEPROPID structure for that property would have been returned.
Writing a Record
You can write a record to the database using this function:
CEOID CeWriteRecordProps (HANDLE hDbase, CEOID oidRecord, WORD cPropID,
CEPROPVAL * rgPropVal);
The first parameter is the obligatory handle to the opened database. The oidRecord parameter is the object ID of the record to be written. To create a new record instead of modifying a record in the database, set oidRecord to 0. The cPropID parameter should contain the number of items in the array of property ID structures pointed to by rgPropVal. The rgPropVal array specifies which of the properties in the record to modify and the data to write.
Deleting Properties, Records, and Entire Databases
You can delete individual properties in a record using CeWriteRecordProps. To do this, create a CEPROPVAL structure that identifies the property to delete and set CEDB_PROPDELETE in the wFlags field.To delete an entire record in a database, call
BOOL CeDeleteRecord (HANDLE hDatabase, CEOID oidRecord);
The parameters are the handle to the database and the object ID of the record to delete.You can delete an entire database using this function:
BOOL CeDeleteDatabaseEx (PCEGUID pguid, CEOID oid);
The two parameters are the CEGUID of the database volume and the object ID of the database. The database being deleted can't currently be opened.
Enumerating Databases
Sometimes you must search the system to determine what databases are on the system. Windows CE provides a set of functions to enumerate the databases in a volume. These functions are
HANDLE CeFindFirstDatabaseEx (PCEGUID pguid, DWORD dwDbaseType);
and
CEOID CeFindNextDatabaseEx (HANDLE hEnum, PCEGUID pguid);
These functions act like FindFirstFile and FindNextFile with the exception that CeFindFirstDatabaseEx only opens the search; it doesn't return the first database found. The PCEGUID parameter for both functions is the address of the CEGUID of the database volume you want to search. You can limit the search by specifying the ID of a specific database type in the dwDbaseType parameter. If this parameter is set to 0, all databases are enumerated. CeFindFirstDatabaseEx returns a handle that is then passed to CeFindNextDatabaseEx to actually enumerate the databases.Here's how to enumerate the databases in the object store:
HANDLE hDBList;
CEOID oidDB;
CEGUID guidVol;
// Enumerate the databases in the object store.
CREATE_SYSTEMGUID(&guidVol);
hDBList = CeFindFirstDatabaseEx (&guidVol, 0);
if (hDBList != INVALID_HANDLE_VALUE) {
oidDB = CeFindNextDatabaseEx (hDBList, &guidVol);
while (oidDB) {
// Enumerated database identified by object ID.
MyDisplayDatabaseInfo (hCeDB);
hCeDB = CeFindNextDatabaseEx (hDBList, &guidVol);
}
CloseHandle (hDBList);
}
The code first creates the CEGUID of the object store using the macro CREATE_SYSTEMGUID. That parameter, along with the database type specifier 0, is passed to CeFindFirstDatabaseEx to enumerate all the databases in the object store. If the function is successful, the databases are enumerated by repeatedly calling CeFindNextDatabaseEx.
Querying Object Information
To query information about a database, use this function:
BOOL CeOidGetInfoEx2 (PCEGUID pguid, CEOID oid, CEOIDINFOEX *oidInfo);
These functions return information about not just databases, but any object in the object store. This includes files and directories as well as databases and database records. The function is passed the database volume and object ID of the item of interest and a pointer to a CEOIDINFOEX structure.Here's the definition of the CEOIDINFOEX structure:
typedef struct _CEOIDINFOEX {
WORD wVersion;
WORD wObjType;
union {
CEFILEINFO infFile;
CEDIRINFO infDirectory;
CEDBASEINFOEX infDatabase;
CERECORDINFO infRecord;
};
} CEOIDINFOEX;
This structure starts with a version field that should be set to CEOIDINFOEX_VERSION. The second field indicates the type of the item and a union of four different structures each detailing information about that type of object. The currently supported flags are OBJTYPE_FILE, indicating that the object is a file; OBJTYPE_DIRECTORY, for directory objects; OBJTYPE_DATABASE, for database objects; and OBJTYPE_RECORD, indicating that the object is a record inside a database. The structures in the union are specific to each object type.The CEFILEINFO structure is defined as
typedef struct _CEFILEINFO {
DWORD dwAttributes;
CEOID oidParent;
WCHAR szFileName[MAX_PATH];
FILETIME ftLastChanged;
DWORD dwLength;
} CEFILEINFO;
the CEDIRINFO structure is defined as
typedef struct _CEDIRINFO {
DWORD dwAttributes;
CEOID oidParent;
WCHAR szDirName[MAX_PATH];
} CEDIRINFO;
and the CERECORDINFO structure is defined as
typedef struct _CERECORDINFO {
CEOID oidParent;
} CERECORDINFO;
You've already seen the CEDBASEINFOEX structure used in CeCreateDatabaseEx2 and CeSetDatabaseInfoEx2. As you can see from the preceding structures, CeGetOidInfoEx2 returns a wealth of information about each object. One of the more powerful bits of information is the object's parent oid, which will allow you to trace the chain of files and directories back to the root. These functions also allow you to convert an object ID to a name of a database, directory, or file.The object ID method of tracking a file object should not be confused with the PID scheme used by the shell. Object IDs are maintained by the file system and are independent of whatever shell is being used. This would be a minor point under other versions of Windows, but with the ability of Windows CE to be built as components and customized for different targets, it's important to know what parts of the operating system support which functions.
The AlbumDB Example Program
It's great to talk about the database functions; it's another experience to use them in an application. The example program that follows, AlbumDB, is a simple database that tracks record albums, the artist that recorded them, and the individual tracks on the albums. It has a simple interface because the goal of the program is to demonstrate the database functions, not the user interface. Figure 9-1 shows the AlbumDB window with a few albums entered in the database.

Figure 9-1: The AlbumDB window
Listing 9-1 contains the code for the AlbumDB program. When the program is first launched, it attempts to open a database named Albums in the object store. If the program doesn't find one, it creates a new one. This is accomplished in the OpenCreateDB function.Listing 9-1: The AlbumDB program
AlbumDB.rc
//======================================================================
// Resource file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include "windows.h"
#include "albumdb.h" // Program-specific stuff
//----------------------------------------------------------------------
// Icons and bitmaps
//
ID_ICON ICON "albumdb.ico" // Program icon
//----------------------------------------------------------------------
// Menu
//
ID_MENU MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Delete Database", IDM_DELDB
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_EXIT
END
POPUP "&Album"
BEGIN
MENUITEM "&New", IDM_NEW
MENUITEM "&Edit", IDM_EDIT
MENUITEM "&Delete", IDM_DELETE
MENUITEM SEPARATOR
MENUITEM "&Sort Name", IDM_SORTNAME
MENUITEM "Sort &Artist", IDM_SORTARTIST
MENUITEM "Sort &Category", IDM_SORTCATEGORY
END
POPUP "&Help"
BEGIN
MENUITEM "&About...", IDM_ABOUT
END
END
//----------------------------------------------------------------------
// New/Edit Track dialog template
//
EditTrackDlg DIALOG discardable 10, 10, 135, 40
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER |
DS_MODALFRAME
EXSTYLE WS_EX_CAPTIONOKBTN
CAPTION "Edit Track"
BEGIN
LTEXT "Track Name" -1, 5, 5, 50, 12
EDITTEXT IDD_TRACK, 60, 5, 70, 12,
WS_TABSTOP | ES_AUTOHSCROLL
LTEXT "Time" -1, 5, 20, 50, 12
EDITTEXT IDD_TIME, 60, 20, 50, 12, WS_TABSTOP
END
//----------------------------------------------------------------------
// New/Edit Album data dialog template
//
EditAlbumDlg DIALOG discardable 5, 5, 135, 100
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER |
DS_MODALFRAME
EXSTYLE WS_EX_CAPTIONOKBTN
CAPTION "Edit Album"
BEGIN
LTEXT "Album Name" -1, 5, 5, 50, 12
EDITTEXT IDD_NAME, 60, 5, 72, 12,
WS_TABSTOP | ES_AUTOHSCROLL
LTEXT "Artist" -1, 5, 20, 50, 12
EDITTEXT IDD_ARTIST, 60, 20, 72, 12,
WS_TABSTOP | ES_AUTOHSCROLL
LTEXT "Category" -1, 5, 35, 50, 12
COMBOBOX IDD_CATEGORY, 60, 35, 72, 60,
WS_TABSTOP | CBS_DROPDOWN
LISTBOX IDD_TRACKS, 60, 50, 72, 45,
LBS_USETABSTOPS
PUSHBUTTON "&New Track...",
IDD_NEWTRACK, 3, 50, 52, 12,
WS_TABSTOP
PUSHBUTTON "&Edit Track...",
IDD_EDITTRACK, 3, 65, 52, 12,
WS_TABSTOP
PUSHBUTTON "&Del Track",
IDD_DELTRACK, 3, 80, 52, 12,
WS_TABSTOP
END
//----------------------------------------------------------------------
// About box dialog template
//
aboutbox DIALOG discardable 10, 10, 135, 40
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER |
DS_MODALFRAME
CAPTION "About"
BEGIN
ICON ID_ICON, -1, 3, 5, 10, 10
LTEXT "AlbumDB - Written for the book Programming Windows CE Copyright 2003 Douglas Boling"
-1, 30, 5, 102, 33
END
AlbumDB.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT { // Structure associates
UINT Code; // messages
// with a function.
LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};
//----------------------------------------------------------------------
// Generic defines used by application
#define ID_ICON 1 // App icon resource ID
#define IDC_CMDBAR 2 // Command band ID
#define ID_MENU 3 // Main menu resource ID
#define ID_LISTV 5 // List view control ID
// Menu item IDs
#define IDM_DELDB 101 // File menu
#define IDM_EXIT 102
#define IDM_NEW 110 // Album menu
#define IDM_EDIT 111
#define IDM_DELETE 112
#define IDM_SORTNAME 120 // Sort IDs must be
#define IDM_SORTARTIST 121 // consecutive.
#define IDM_SORTCATEGORY 122
#define IDM_ABOUT 150 // Help menu
// IDs for dialog box controls
#define IDD_NAME 100 // Edit album dialog.
#define IDD_ARTIST 101
#define IDD_NUMTRACKS 102
#define IDD_CATEGORY 103
#define IDD_TRACKS 104
#define IDD_NEWTRACK 105
#define IDD_EDITTRACK 106
#define IDD_DELTRACK 107
#define IDD_TRACK 200 // Edit track dialog.
#define IDD_TIME 201
//----------------------------------------------------------------------
// Program-specific structures
//
// Structure used by New/Edit Album dlg proc
#define MAX_NAMELEN 64
#define MAX_ARTISTLEN 64
#define MAX_TRACKNAMELEN 512
typedef struct {
TCHAR szName[MAX_NAMELEN];
TCHAR szArtist[MAX_ARTISTLEN];
INT nDateRel;
SHORT sCategory;
SHORT sNumTracks;
INT nTrackDataLen;
TCHAR szTracks[MAX_TRACKNAMELEN];
} ALBUMINFO, *LPALBUMINFO;
// Structure used by Add/Edit album track
typedef struct {
TCHAR szTrack[64];
TCHAR szTime[16];
} TRACKINFO, *LPTRACKINFO;
// Structure used by GetItemData
typedef struct {
int nItem;
ALBUMINFO Album;
} LVCACHEDATA, *PLVCACHEDATA;
// Database property identifiers
#define PID_NAME MAKELONG (CEVT_LPWSTR, 1)
#define PID_ARTIST MAKELONG (CEVT_LPWSTR, 2)
#define PID_RELDATE MAKELONG (CEVT_I2, 3)
#define PID_CATEGORY MAKELONG (CEVT_I2, 4)
#define PID_NUMTRACKS MAKELONG (CEVT_I2, 5)
#define PID_TRACKS MAKELONG (CEVT_BLOB, 6)
#define NUM_DB_PROPS 6
//----------------------------------------------------------------------
// Function prototypes
//
int InitApp (HINSTANCE);
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
HANDLE OpenDB (HWND hWnd, LPTSTR lpszName);
HANDLE OpenCreateDB (HWND, int *);
void ReopenDatabase (HWND, INT);
int GetItemData (int, PLVCACHEDATA);
HWND CreateLV (HWND, RECT *);
void ClearCache (void);
int ErrBox (HWND hWnd, LPTSTR lpszFormat, ...);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoNotifyMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDbNotifyMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
// Command functions
LPARAM DoMainCommandDelDB (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandNew (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandEdit (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandDelete (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandSort (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandAbout (HWND, WORD, HWND, WORD);
// Dialog procedures
BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK EditAlbumDlgProc (HWND, UINT, WPARAM, LPARAM);
AlbumDB.cpp
//======================================================================
// AlbumDB - A Windows CE database
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include <windowsx.h> // For Windows controls macros
#include <commctrl.h> // Command bar includes
#include "AlbumDB.h" // Program-specific stuff
// The include and lib files for the Pocket PC are conditionally
// included so that this example can share the same project file. This
// is necessary since this example must have a menu bar on the Pocket
// PC to have a SIP button.
#if defined(WIN32_PLATFORM_PSPC)
#include <aygshell.h> // Add Pocket PC includes.
#pragma comment( lib, "aygshell" ) // Link Pocket PC lib for menu bar.
#endif
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("AlbumDB");
HINSTANCE hInst; // Program instance handle
HANDLE g_hDB = INVALID_HANDLE_VALUE; // Handle to album database
CEOID g_oidDB = 0; // Object ID of the album database
CEGUID g_guidDB; // Guid for database volume
CENOTIFYREQUEST cenr; // Notify request structure.
int g_nLastSort = 0; // Last sort order used
CEDBASEINFOEX g_diex; // Sort order array
// These two variables represent a one-item cache for
// the list view control.
int g_nLastItem = -1;
LPBYTE g_pLastRecord = 0;
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_CREATE, DoCreateMain,
WM_SIZE, DoSizeMain,
WM_COMMAND, DoCommandMain,
WM_NOTIFY, DoNotifyMain,
WM_DESTROY, DoDestroyMain,
WM_DBNOTIFICATION, DoDbNotifyMain,
};
// Command message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
IDM_DELDB, DoMainCommandDelDB,
IDM_EXIT, DoMainCommandExit,
IDM_NEW, DoMainCommandNew,
IDM_EDIT, DoMainCommandEdit,
IDM_DELETE, DoMainCommandDelete,
IDM_SORTNAME, DoMainCommandSort,
IDM_SORTARTIST, DoMainCommandSort,
IDM_SORTCATEGORY, DoMainCommandSort,
IDM_ABOUT, DoMainCommandAbout,
};
// Album category strings; must be alphabetical.
const TCHAR *pszCategories[] = {TEXT ("Classical"), TEXT ("Country"),
TEXT ("New Age"), TEXT ("Rock")};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
HWND hwndMain;
MSG msg;
int rc = 0;
// Initialize this instance.
hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0)
return 0x10;
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){
HWND hWnd;
WNDCLASS wc;
INITCOMMONCONTROLSEX icex;
#if defined(WIN32_PLATFORM_PSPC)
// If Pocket PC, allow only one instance of the application.
HWND hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return -1;
}
#endif
// Save program instance handle in global variable.
hInst = hInstance;
// Create a guid for the database Ex functions that points
// to the object store.
CREATE_SYSTEMGUID(&g_guidDB);
memset (&cenr, 0, sizeof (cenr)); // Initialize the notify request.
// Initialize database info structure.
memset (&g_diex, 0, sizeof (g_diex));
g_diex.wVersion = CEDBASEINFOEX_VERSION;
g_diex.dwFlags = CEDB_VALIDNAME | CEDB_VALIDTYPE |
CEDB_VALIDSORTSPEC;
lstrcpy (g_diex.szDbaseName, TEXT ("\\Albums"));
g_diex.dwDbaseType = 0;
g_diex.wNumSortOrder = 3;
// Create sort property array
int i = 0;
g_diex.rgSortSpecs[i].wVersion = SORTORDERSPECEX_VERSION;
g_diex.rgSortSpecs[i].wNumProps = 2;
g_diex.rgSortSpecs[i].rgPropID[0] = PID_NAME;
g_diex.rgSortSpecs[i].rgdwFlags[0] = 0;
g_diex.rgSortSpecs[i].rgPropID[1] = PID_CATEGORY;
g_diex.rgSortSpecs[i].rgdwFlags[1] = 0;
i++;
g_diex.rgSortSpecs[i].wVersion = SORTORDERSPECEX_VERSION;
g_diex.rgSortSpecs[i].wNumProps = 2;
g_diex.rgSortSpecs[i].rgPropID[0] = PID_ARTIST;
g_diex.rgSortSpecs[i].rgdwFlags[0] = 0;
g_diex.rgSortSpecs[i].rgPropID[1] = PID_NAME;
g_diex.rgSortSpecs[i].rgdwFlags[1] = 0;
i++;
g_diex.rgSortSpecs[i].wVersion = SORTORDERSPECEX_VERSION;
g_diex.rgSortSpecs[i].wNumProps = 3;
g_diex.rgSortSpecs[i].rgPropID[0]= PID_CATEGORY;
g_diex.rgSortSpecs[i].rgdwFlags[0] = 0;
g_diex.rgSortSpecs[i].rgPropID[1] = PID_ARTIST;
g_diex.rgSortSpecs[i].rgdwFlags[1] = 0;
g_diex.rgSortSpecs[i].rgPropID[2] = PID_NAME;
g_diex.rgSortSpecs[i].rgdwFlags[2] = 0;
// Register application main window class.
wc.style = 0; // Window style
wc.lpfnWndProc = MainWndProc; // Callback function
wc.cbClsExtra = 0; // Extra class data
wc.cbWndExtra = 0; // Extra window data
wc.hInstance = hInstance; // Owner handle
wc.hIcon = NULL, // Application icon
wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name
if (RegisterClass (&wc) == 0) return 0;
// Load the command bar common control class.
icex.dwSize = sizeof (INITCOMMONCONTROLSEX);
icex.dwICC = ICC_BAR_CLASSES | ICC_TREEVIEW_CLASSES |
ICC_LISTVIEW_CLASSES;
InitCommonControlsEx (&icex);
// Create main window.
hWnd = CreateWindowEx (0, szAppName, TEXT ("AlbumDB"), WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
// Return fail code if window not created.
if (!IsWindow (hWnd)) return 0;
// Standard show and update calls
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
// Close the opened database.
if (g_hDB != INVALID_HANDLE_VALUE)
CloseHandle (g_hDB);
// Free the last db query if saved.
ClearCache ();
return nDefRC;
}
//======================================================================
// Message handling procedures for MainWindow
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
int i;
//
// Search message list to see if we need to handle this
// message. If in list, call procedure.
//
for (i = 0; i < dim(MainMessages); i++) {
if (wMsg == MainMessages[i].Code)
return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCreateMain - Process WM_CREATE message for window.
//
LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
HWND hwndCB, hwndChild;
int nHeight, nCnt;
RECT rect;
// Convert lParam to pointer to create structure.
LPCREATESTRUCT lpcs = (LPCREATESTRUCT) lParam;
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
SHMENUBARINFO mbi; // For Pocket PC, create
memset(&mbi, 0, sizeof(SHMENUBARINFO)); // menu bar so that we
mbi.cbSize = sizeof(SHMENUBARINFO); // have a sip button.
mbi.hwndParent = hWnd;
mbi.dwFlags = SHCMBF_EMPTYBAR; // No menu
SHCreateMenuBar(&mbi);
SetWindowPos (hWnd, 0, 0, 0, lpcs->cx,lpcs->cy - 26,
SWP_NOMOVE | SWP_NOZORDER);
#endif
// Convert lParam to pointer to create structure.
lpcs = (LPCREATESTRUCT) lParam;
// Create a minimal command bar that has only a menu and an
// exit button.
hwndCB = CommandBar_Create (hInst, hWnd, IDC_CMDBAR);
// Insert the menu.
CommandBar_InsertMenubar (hwndCB, hInst, ID_MENU, 0);
// Add exit button to command bar.
CommandBar_AddAdornments (hwndCB, 0, 0);
nHeight = CommandBar_Height (hwndCB);
// Open the album database. If one doesn't exist, create it.
g_hDB = OpenCreateDB (hWnd, &nCnt);
if (g_hDB == INVALID_HANDLE_VALUE) {
MessageBox (hWnd, TEXT ("Could not open database."), szAppName,
MB_OK);
DestroyWindow (hWnd);
return 0;
}
// Create the list view control in right pane.
SetRect (&rect, 0, nHeight, lpcs->cx, lpcs->cy - nHeight);
hwndChild = CreateLV (hWnd, &rect);
// Destroy frame if window not created.
if (!IsWindow (hwndChild)) {
DestroyWindow (hWnd);
return 0;
}
ListView_SetItemCount (hwndChild, nCnt);
return 0;
}
//----------------------------------------------------------------------
// DoSizeMain - Process WM_SIZE message for window.
//
LRESULT DoSizeMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){
HWND hwndLV;
RECT rect;
hwndLV = GetDlgItem (hWnd, ID_LISTV);
// Adjust the size of the client rect to take into account
// the command bar height.
GetClientRect (hWnd, &rect);
rect.top += CommandBar_Height (GetDlgItem (hWnd, IDC_CMDBAR));
SetWindowPos (hwndLV, NULL, rect.left, rect.top,
(rect.right - rect.left), rect.bottom - rect.top,
SWP_NOZORDER);
CommandBar_AlignAdornments(GetDlgItem (hWnd, IDC_CMDBAR));
return 0;
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
LRESULT DoCommandMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
WORD idItem, wNotifyCode;
HWND hwndCtl;
int i;
// Parse the parameters.
idItem = (WORD) LOWORD (wParam);
wNotifyCode = (WORD) HIWORD (wParam);
hwndCtl = (HWND) lParam;
// Call routine to handle control message.
for (i = 0; i < dim(MainCommandItems); i++) {
if (idItem == MainCommandItems[i].Code)
return (*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl,
wNotifyCode);
}
return 0;
}
//----------------------------------------------------------------------
// DoNotifyMain - Process DB_CEOID_xxx messages for window.
//
LRESULT DoDbNotifyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
CENOTIFICATION *pcen = (CENOTIFICATION *)lParam;
switch (pcen->uType) {
case DB_CEOID_CHANGED:
InvalidateRect (GetDlgItem (hWnd, ID_LISTV), NULL, TRUE);
break;
case DB_CEOID_CREATED:
ReopenDatabase (hWnd, -1);
break;
case DB_CEOID_RECORD_DELETED:
ReopenDatabase (hWnd, -1);
break;
}
CeFreeNotification (&cenr, pcen);
return 0;
}
//----------------------------------------------------------------------
// DoNotifyMain - Process WM_NOTIFY message for window.
//
LRESULT DoNotifyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
int idItem, i;
LPNMHDR pnmh;
LPNMLISTVIEW pnmlv;
NMLVDISPINFO *pLVdi;
LVCACHEDATA data;
HWND hwndLV;
// Parse the parameters.
idItem = (int) wParam;
pnmh = (LPNMHDR)lParam;
hwndLV = pnmh->hwndFrom;
if (idItem == ID_LISTV) {
pnmlv = (LPNMLISTVIEW)lParam;
switch (pnmh->code) {
case LVN_GETDISPINFO:
pLVdi = (NMLVDISPINFO *)lParam;
// Get a pointer to the data either from the cache
// or from the actual database.
GetItemData (pLVdi->item.iItem, &data);
if (pLVdi->item.mask & LVIF_IMAGE)
pLVdi->item.iImage = 0;
if (pLVdi->item.mask & LVIF_PARAM)
pLVdi->item.lParam = 0;
if (pLVdi->item.mask & LVIF_STATE)
pLVdi->item.state = 0;
if (pLVdi->item.mask & LVIF_TEXT) {
switch (pLVdi->item.iSubItem) {
case 0:
lstrcpy (pLVdi->item.pszText, data.Album.szName);
break;
case 1:
lstrcpy (pLVdi->item.pszText, data.Album.szArtist);
break;
case 2:
lstrcpy (pLVdi->item.pszText,
pszCategories[data.Album.sCategory]);
break;
}
}
break;
// Sort by column
case LVN_COLUMNCLICK:
i = ((NM_LISTVIEW *)lParam)->iSubItem + IDM_SORTNAME;
PostMessage (hWnd, WM_COMMAND, MAKELPARAM (i, 0), 0);
break;
// Double click indicates edit
case NM_DBLCLK:
PostMessage (hWnd, WM_COMMAND, MAKELPARAM (IDM_EDIT, 0), 0);
break;
// Ignore cache hinting for db example.
case LVN_ODCACHEHINT:
break;
case LVN_ODFINDITEM:
// We should do a reverse lookup here to see if
// an item exists for the text passed.
return -1;
}
}
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PostQuitMessage (0);
return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandDelDB - Process Program Delete command.
//
LPARAM DoMainCommandDelDB (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
int i, rc;
i = MessageBox (hWnd, TEXT ("Delete the entire database?"),
TEXT ("Delete"), MB_YESNO);
if (i != IDYES)
return 0;
if (g_oidDB) {
CloseHandle (g_hDB);
rc = CeDeleteDatabase (g_oidDB);
if (rc == 0) {
ErrBox (hWnd, TEXT ("Couldn\'t delete database. rc=%d"),
GetLastError());
g_hDB = OpenDB (hWnd, NULL); // Open the database.
return 0;
}
g_hDB = INVALID_HANDLE_VALUE;
g_oidDB = 0;
}
ListView_SetItemCount (GetDlgItem (hWnd, ID_LISTV), 0);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command.
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
SendMessage (hWnd, WM_CLOSE, 0, 0);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandNew - Process Program New command.
//
LPARAM DoMainCommandNew (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
PCEPROPVAL pcepv;
int i, rc;
CEOID oid;
HWND hwndLV = GetDlgItem (hWnd, ID_LISTV);
// Display the new/edit dialog.
pcepv = 0;
rc = DialogBoxParam (hInst, TEXT ("EditAlbumDlg"), hWnd,
EditAlbumDlgProc, (LPARAM)&pcepv);
if (rc == 0)
return 0;
// Write the record.
oid = CeWriteRecordProps(g_hDB, 0, NUM_DB_PROPS, pcepv);
if (!oid)
ErrBox (hWnd, TEXT ("Write Rec fail. rc=%d"), GetLastError());
ClearCache (); // Clear the lv cache.
i = ListView_GetItemCount (hwndLV) + 1; // Increment list view
// count.
ListView_SetItemCount (hwndLV, i);
InvalidateRect (hwndLV, NULL, TRUE); // Force redraw.
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandEdit - Process Program Edit command.
//
LPARAM DoMainCommandEdit (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
PCEPROPVAL pcepv = 0;
int nSel, rc;
WORD wProps = 0;
DWORD dwRecSize, dwIndex;
CEOID oid;
HWND hwndLV = GetDlgItem (hWnd, ID_LISTV);
nSel = ListView_GetSelectionMark (hwndLV);
if (nSel == -1)
return 0;
// Seek to the necessary record.
oid = CeSeekDatabase (g_hDB, CEDB_SEEK_BEGINNING, nSel, &dwIndex);
if (oid == 0) {
ErrBox (hWnd, TEXT ("Db item not found. rc=%d"), GetLastError());
return 0;
}
// Read all properties for the record. Have the system
// allocate the buffer containing the data.
oid = CeReadRecordPropsEx (g_hDB, CEDB_ALLOWREALLOC, &wProps, NULL,
(LPBYTE *)&pcepv, &dwRecSize, 0);
if (oid == 0) {
ErrBox (hWnd, TEXT ("Db item not read. rc=%d"), GetLastError());
return 0;
}
// Display the edit dialog.
rc = DialogBoxParam (hInst, TEXT ("EditAlbumDlg"), hWnd,
EditAlbumDlgProc, (LPARAM)&pcepv);
if (rc == 0)
return 0;
// Write the record.
oid = CeWriteRecordProps(g_hDB, oid, NUM_DB_PROPS, pcepv);
if (!oid)
ErrBox (hWnd, TEXT ("Write Rec fail. rc=%d"), GetLastError());
LocalFree ((LPBYTE)pcepv);
ClearCache (); // Clear the lv cache.
InvalidateRect (hwndLV, NULL, TRUE); // Force redraw.
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandDelete - Process Program Delete command.
//
LPARAM DoMainCommandDelete (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
HWND hwndLV;
TCHAR szText[64];
DWORD dwIndex;
int i, nSel;
CEOID oid;
hwndLV = GetDlgItem (hWnd, ID_LISTV);
nSel = ListView_GetSelectionMark (hwndLV);
if (nSel != -1) {
wsprintf (szText, TEXT ("Delete this item?"));
i = MessageBox (hWnd, szText, TEXT ("Delete"), MB_YESNO);
if (i != IDYES)
return 0;
// Seek to the necessary record.
oid = CeSeekDatabase (g_hDB, CEDB_SEEK_BEGINNING, nSel, &dwIndex);
CeDeleteRecord (g_hDB, oid);
// Reduce the list view count by one and force redraw.
i = ListView_GetItemCount (hwndLV) - 1;
ListView_SetItemCount (hwndLV, i);
ClearCache (); // Clear the lv cache.
InvalidateRect (hwndLV, NULL, TRUE);
}
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandSort - Process the Sort commands.
//
LPARAM DoMainCommandSort(HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
int nSort;
switch (idItem) {
case IDM_SORTNAME:
nSort = 0;
break;
case IDM_SORTARTIST:
nSort = 1;
break;
case IDM_SORTCATEGORY:
nSort = 2;
break;
}
if (nSort == g_nLastSort)
return 0;
ReopenDatabase (hWnd, nSort); // Close and reopen the database.
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandAbout - Process the Help | About menu command.
//
LPARAM DoMainCommandAbout(HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
// Use DialogBox to create modal dialog.
DialogBox (hInst, TEXT ("aboutbox"), hWnd, AboutDlgProc);
return 0;
}
//----------------------------------------------------------------------
// CreateLV - Creates the list view control
//
HWND CreateLV (HWND hWnd, RECT *prect) {
HWND hwndLV;
LVCOLUMN lvc;
// Create album list window.
hwndLV = CreateWindowEx (0, WC_LISTVIEW, TEXT ("),
WS_VISIBLE | WS_CHILD | WS_VSCROLL |
LVS_OWNERDATA | WS_BORDER | LVS_REPORT,
prect->left, prect->top,
prect->right - prect->left,
prect->bottom - prect->top,
hWnd, (HMENU)ID_LISTV,
hInst, NULL);
// Add columns.
if (hwndLV) {
lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT | LVCF_SUBITEM;
lvc.fmt = LVCFMT_LEFT;
lvc.cx = 150;
lvc.pszText = TEXT ("Name");
lvc.iSubItem = 0;
SendMessage (hwndLV, LVM_INSERTCOLUMN, 0, (LPARAM)&lvc);
lvc.mask |= LVCF_SUBITEM;
lvc.pszText = TEXT ("Artist");
lvc.cx = 100;
lvc.iSubItem = 1;
SendMessage (hwndLV, LVM_INSERTCOLUMN, 1, (LPARAM)&lvc);
lvc.mask |= LVCF_SUBITEM;
lvc.pszText = TEXT ("Category");
lvc.cx = 100;
lvc.iSubItem = 2;
SendMessage (hwndLV, LVM_INSERTCOLUMN, 2, (LPARAM)&lvc);
}
return hwndLV;
}
//----------------------------------------------------------------------
// OpenDB - Open database.
//
HANDLE OpenDB (HWND hWnd, LPTSTR lpszName) {
// Reinitialize the notify request structure.
cenr.dwSize = sizeof (cenr);
cenr.hwnd = hWnd;
cenr.dwFlags = CEDB_EXNOTIFICATION;
if (lpszName)
g_oidDB = 0;
return CeOpenDatabaseEx2 (&g_guidDB, &g_oidDB, lpszName,
&g_diex.rgSortSpecs[g_nLastSort],
0, &cenr);
}
//----------------------------------------------------------------------
// OpenCreateDB - Open database, create if necessary.
//
HANDLE OpenCreateDB (HWND hWnd, int *pnRecords) {
int rc;
CEOIDINFO oidinfo;
g_oidDB = 0;
g_hDB = OpenDB (hWnd, TEXT ("\\Albums"));
if (g_hDB == INVALID_HANDLE_VALUE) {
rc = GetLastError();
if (rc == ERROR_FILE_NOT_FOUND) {
g_oidDB = CeCreateDatabaseEx2 (&g_guidDB, &g_diex);
if (g_oidDB == 0) {
ErrBox (hWnd, TEXT ("Database create failed. rc=%d"),
GetLastError());
return 0;
}
g_hDB = OpenDB (hWnd, NULL);
}
}
CeOidGetInfo (g_oidDB, &oidinfo);
*pnRecords = oidinfo.infDatabase.wNumRecords;
return g_hDB;
}
//----------------------------------------------------------------------
// ClearCache - Clears the one-item cache for the list view control
//
void ClearCache (void) {
if (g_pLastRecord)
LocalFree (g_pLastRecord);
g_pLastRecord = 0;
g_nLastItem = -1;
return;
}
//----------------------------------------------------------------------
// ReopenDatabase - Closes and reopens the database
//
void ReopenDatabase (HWND hWnd, int nNewSort) {
int nCnt;
if (nNewSort != -1)
g_nLastSort = nNewSort;
if (g_hDB)
CloseHandle (g_hDB);
ClearCache (); // Clear the lv cache.
g_hDB = OpenCreateDB (hWnd, &nCnt);
ListView_SetItemCount (GetDlgItem (hWnd, ID_LISTV), nCnt);
InvalidateRect (GetDlgItem (hWnd, ID_LISTV), NULL, 0);
return;
}
//----------------------------------------------------------------------
// Get the album data from the database for the requested lv item.
//
int GetItemData (int nItem, PLVCACHEDATA pcd) {
static WORD wProps;
DWORD dwIndex;
CEOID oid;
PCEPROPVAL pRecord = NULL;
DWORD dwRecSize;
int i;
// See if the item requested was the previous one. If so,
// just use the old data.
if ((nItem == g_nLastItem) && (g_pLastRecord))
pRecord = (PCEPROPVAL)g_pLastRecord;
else {
// Seek to the necessary record.
oid = CeSeekDatabase (g_hDB, CEDB_SEEK_BEGINNING, nItem, &dwIndex);
if (oid == 0) {
ErrBox (NULL, TEXT ("Db item not found. rc=%d"),
GetLastError());
return 0;
}
// Read all properties for the record. Have the system
// allocate the buffer containing the data.
oid = CeReadRecordProps (g_hDB, CEDB_ALLOWREALLOC, &wProps, NULL,
(LPBYTE *)&pRecord, &dwRecSize);
if (oid == 0) {
ErrBox (NULL, TEXT ("Db item not read. rc=%d"),
GetLastError());
return 0;
}
// Free old record, and save the newly read one.
if (g_pLastRecord)
LocalFree (g_pLastRecord);
g_nLastItem = nItem;
g_pLastRecord = (LPBYTE)pRecord;
}
// Copy the data from the record to the album structure.
for (i = 0; i < wProps; i++) {
switch (pRecord->propid) {
case PID_NAME:
lstrcpy (pcd->Album.szName, pRecord->val.lpwstr);
break;
case PID_ARTIST:
lstrcpy (pcd->Album.szArtist, pRecord->val.lpwstr);
break;
case PID_CATEGORY:
pcd->Album.sCategory = pRecord->val.iVal;
break;
case PID_NUMTRACKS:
pcd->Album.sNumTracks = pRecord->val.iVal;
break;
}
pRecord++;
}
return 1;
}
//----------------------------------------------------------------------
// InsertLV - Add an item to the list view control.
//
int InsertLV (HWND hWnd, int nItem, LPTSTR pszName, LPTSTR pszType,
int nSize) {
LVITEM lvi;
HWND hwndLV = GetDlgItem (hWnd, ID_LISTV);
lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
lvi.iItem = nItem;
lvi.iSubItem = 0;
lvi.pszText = pszName;
lvi.iImage = 0;
lvi.lParam = nItem;
SendMessage (hwndLV, LVM_INSERTITEM, 0, (LPARAM)&lvi);
lvi.mask = LVIF_TEXT;
lvi.iItem = nItem;
lvi.iSubItem = 1;
lvi.pszText = pszType;
SendMessage (hwndLV, LVM_SETITEM, 0, (LPARAM)&lvi);
return 0;
}
//----------------------------------------------------------------------
// ValidateTime - Trivial error checking of time field
//
BOOL ValidateTime (TCHAR *pStr) {
BOOL fSep = FALSE;
TCHAR *pPtr;
pPtr = pStr;
// See if field contains only numbers and up to one colon.
while (*pPtr) {
if (*pPtr == TEXT (':')) {
if (fSep)
return FALSE;
fSep = TRUE;
} else if ((*pPtr < TEXT ('0')) || (*pPtr > TEXT ('9')))
return FALSE;
pPtr++;
}
// Reject empty field.
if (pPtr > pStr)
return TRUE;
return FALSE;
}
//----------------------------------------------------------------------
// ErrBox - Displays an error string in a message box
//
int ErrBox (HWND hWnd, LPTSTR lpszFormat, ...) {
int nBuf;
TCHAR szBuffer[512];
va_list args;
va_start(args, lpszFormat);
nBuf = _vstprintf(szBuffer, lpszFormat, args);
va_end(args);
MessageBox (hWnd, szBuffer, TEXT("Error"), MB_OK | MB_ICONERROR);
return 0;
}
//======================================================================
// EditTrack dialog procedure
//
BOOL CALLBACK EditTrackDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
static LPTRACKINFO lpti;
switch (wMsg) {
case WM_INITDIALOG:
lpti = (LPTRACKINFO)lParam;
SendDlgItemMessage (hWnd, IDD_TRACK, EM_SETLIMITTEXT,
sizeof (lpti->szTrack), 0);
SendDlgItemMessage (hWnd, IDD_TIME, EM_SETLIMITTEXT,
sizeof (lpti->szTime), 0);
// See if new album or edit of old one.
if (lstrlen (lpti->szTrack) == 0) {
SetWindowText (hWnd, TEXT ("New Track"));
} else {
SetDlgItemText (hWnd, IDD_TRACK, lpti->szTrack);
SetDlgItemText (hWnd, IDD_TIME, lpti->szTime);
}
return TRUE;
case WM_COMMAND:
switch (LOWORD (wParam)) {
case IDOK:
Edit_GetText (GetDlgItem (hWnd, IDD_TRACK),
lpti->szTrack, sizeof (lpti->szTrack));
Edit_GetText (GetDlgItem (hWnd, IDD_TIME),
lpti->szTime, sizeof (lpti->szTime));
if (ValidateTime (lpti->szTime))
EndDialog (hWnd, 1);
else
MessageBox (hWnd, TEXT ("Track time must be entered in mm:ss format"),
TEXT ("Error"), MB_OK);
return TRUE;
case IDCANCEL:
EndDialog (hWnd, 0);
return TRUE;
}
break;
}
return FALSE;
}
//======================================================================
// EditAlbum dialog procedure
//
BOOL CALLBACK EditAlbumDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
static PCEPROPVAL *ppRecord;
static int nTracks;
PCEPROPVAL pRecord, pRecPtr;
TCHAR *pPtr, szTmp[128];
HWND hwndTList, hwndCombo;
TRACKINFO ti;
BOOL fEnable;
int i, nLen, rc;
switch (wMsg) {
case WM_INITDIALOG:
ppRecord = (PCEPROPVAL *)lParam;
pRecord = *ppRecord;
hwndCombo = GetDlgItem (hWnd, IDD_CATEGORY);
hwndTList = GetDlgItem (hWnd, IDD_TRACKS);
Edit_LimitText (GetDlgItem (hWnd, IDD_NAME), MAX_NAMELEN);
Edit_LimitText (GetDlgItem (hWnd, IDD_ARTIST),
MAX_ARTISTLEN);
// Set tab stops on track list box.
i = 110;
ListBox_SetTabStops (hwndTList, 1, &i);
// Initialize category combo box.
for (i = 0; i < dim(pszCategories); i++)
ComboBox_AddString (hwndCombo, pszCategories[i]);
ComboBox_SetCurSel (hwndCombo, 3);
nTracks = 0;
// See if new album or edit of old one.
if (pRecord == 0) {
SetWindowText (hWnd, TEXT ("New Album"));
} else {
// Copy the data from the record to album structure.
for (i = 0; i < NUM_DB_PROPS; i++) {
switch (pRecord->propid) {
case PID_NAME:
SetDlgItemText (hWnd, IDD_NAME,
pRecord->val.lpwstr);
break;
case PID_ARTIST:
SetDlgItemText (hWnd, IDD_ARTIST,
pRecord->val.lpwstr);
break;
case PID_CATEGORY:
ComboBox_SetCurSel (hwndCombo,
pRecord->val.iVal);
break;
case PID_TRACKS:
pPtr = (TCHAR *)pRecord->val.blob.lpb;
for (i = 0; *pPtr; i++){
ListBox_InsertString (hwndTList,i,pPtr);
pPtr += lstrlen (pPtr) + 1;
nTracks++;
}
break;
}
pRecord++;
}
}
// Select first track, or disable buttons if no tracks.
if (nTracks)
ListBox_SetCurSel (GetDlgItem (hWnd, IDD_TRACKS), 3);
else {
EnableWindow (GetDlgItem (hWnd, IDD_DELTRACK),
FALSE);
EnableWindow (GetDlgItem (hWnd, IDD_EDITTRACK),
FALSE);
}
return TRUE;
case WM_COMMAND:
hwndTList = GetDlgItem (hWnd, IDD_TRACKS);
hwndCombo = GetDlgItem (hWnd, IDD_CATEGORY);
pRecord = *ppRecord;
switch (LOWORD (wParam)) {
case IDD_TRACKS:
switch (HIWORD (wParam)) {
case LBN_DBLCLK:
PostMessage (hWnd, WM_COMMAND,
MAKELONG(IDD_EDITTRACK, 0), 0);
break;
case LBN_SELCHANGE:
i = ListBox_GetCurSel (hwndTList);
if (i == LB_ERR)
fEnable = FALSE;
else
fEnable = TRUE;
EnableWindow (GetDlgItem (hWnd,
IDD_DELTRACK), fEnable);
EnableWindow (GetDlgItem (hWnd,
IDD_EDITTRACK), fEnable);
break;
}
return TRUE;
case IDD_NEWTRACK:
memset (&ti, 0, sizeof (ti));
rc = DialogBoxParam (hInst,
TEXT ("EditTrackDlg"), hWnd,
EditTrackDlgProc, (LPARAM)&ti);
if (rc) {
wsprintf (szTmp, TEXT ("%s\t%s"),
ti.szTrack, ti.szTime);
i = ListBox_GetCurSel (hwndTList);
if (i != LB_ERR)
i++;
i = ListBox_InsertString (hwndTList, i,
szTmp);
ListBox_SetCurSel (hwndTList, i);
}
return TRUE;
case IDD_EDITTRACK:
i = ListBox_GetCurSel (hwndTList);
if (i != LB_ERR) {
ListBox_GetText (hwndTList, i, szTmp);
pPtr = szTmp;
while ((*pPtr != TEXT ('\t')) &&
(*pPtr != TEXT ('\0')))
pPtr++;
if (*pPtr == TEXT ('\t'))
*pPtr++ = TEXT ('\0');
lstrcpy (ti.szTime, pPtr);
lstrcpy (ti.szTrack, szTmp);
rc = DialogBoxParam (hInst,
TEXT ("EditTrackDlg"),
hWnd, EditTrackDlgProc,
(LPARAM)&ti);
if (rc) {
wsprintf (szTmp, TEXT ("%s\t%s"),
ti.szTrack, ti.szTime);
i = ListBox_GetCurSel (hwndTList);
ListBox_DeleteString (hwndTList, i);
ListBox_InsertString (hwndTList, i,
szTmp);
ListBox_SetCurSel (hwndTList, i);
}
}
return TRUE;
case IDD_DELTRACK:
// Grab the current selection, and remove
// it from list box.
i = ListBox_GetCurSel (hwndTList);
if (i != LB_ERR) {
rc = MessageBox (hWnd,
TEXT ("Delete this item?"),
TEXT ("Track"), MB_YESNO);
if (rc == IDYES) {
i=ListBox_DeleteString (hwndTList,i);
if (i > 0)
i--;
ListBox_SetCurSel (hwndTList, i);
}
}
return TRUE;
case IDOK:
// Be lazy and assume worst-case size values.
nLen = sizeof (CEPROPVAL) * NUM_DB_PROPS +
MAX_NAMELEN + MAX_ARTISTLEN +
MAX_TRACKNAMELEN;
// See if prev record, alloc if not.
if (pRecord) {
// Resize record if necessary.
if (nLen > (int)LocalSize (pRecord))
pRecPtr =
(PCEPROPVAL)LocalReAlloc (pRecord,
nLen, LMEM_MOVEABLE);
else
pRecPtr = pRecord;
} else
pRecPtr = (PCEPROPVAL)LocalAlloc (LMEM_FIXED,
nLen);
if (!pRecPtr)
return 0;
// Copy the data from the controls to a
// marshaled data block with the structure
// at the front and the data in the back.
pRecord = pRecPtr;
nTracks = ListBox_GetCount (hwndTList);
pPtr = (TCHAR *)((LPBYTE)pRecPtr +
(sizeof (CEPROPVAL) * NUM_DB_PROPS));
// Zero structure to start over.
memset (pRecPtr, 0, LocalSize (pRecPtr));
pRecPtr->propid = PID_NAME;
pRecPtr->val.lpwstr = pPtr;
GetDlgItemText (hWnd, IDD_NAME, pPtr,
MAX_NAMELEN);
pPtr += lstrlen (pPtr) + 1;
pRecPtr++;
pRecPtr->propid = PID_ARTIST;
pRecPtr->val.lpwstr = pPtr;
GetDlgItemText (hWnd, IDD_ARTIST, pPtr,
MAX_ARTISTLEN);
pPtr += lstrlen (pPtr) + 1;
pRecPtr++;
pRecPtr->propid = PID_RELDATE;
pRecPtr->val.iVal = 0;
pRecPtr++;
pRecPtr->propid = PID_CATEGORY;
pRecPtr->val.iVal =
ComboBox_GetCurSel (hwndCombo);
pRecPtr++;
pRecPtr->propid = PID_NUMTRACKS;
pRecPtr->val.iVal = nTracks;
pRecPtr++;
pRecPtr->propid = PID_TRACKS;
pRecPtr->val.blob.lpb = (LPBYTE)pPtr;
// Get the track titles from the list box.
rc = MAX_TRACKNAMELEN;
for (i = 0; i < nTracks; i++) {
// Make sure we have the room in the buff.
rc -= ListBox_GetTextLen(hwndTList, i);
if (rc)
ListBox_GetText (hwndTList, i, pPtr);
else {
nTracks = i;
break;
}
pPtr += lstrlen (pPtr) + 1;
}
*pPtr++ = TEXT ('\0');
pRecPtr->val.blob.dwCount =
(LPBYTE)pPtr - pRecPtr->val.blob.lpb;
*ppRecord = pRecord;
EndDialog (hWnd, 1);
return TRUE;
case IDCANCEL:
EndDialog (hWnd, 0);
return TRUE;
}
break;
}
return FALSE;
}
//======================================================================
// About dialog procedure
//
BOOL CALLBACK AboutDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
switch (wMsg) {
case WM_COMMAND:
switch (LOWORD (wParam)) {
case IDOK:
case IDCANCEL:
EndDialog (hWnd, 0);
return TRUE;
}
break;
}
return FALSE;
}
The program uses a virtual list view control to display the records in the database. As I explained in Chapter 5, virtual list views don't store any data internally. Instead, the control makes calls back to the owning window using notification messages to query the information for each item in the list view control. The WM_NOTIFY handler OnNotifyMain calls GetItemData to query the database in response to the list view control sending LVN_GETDISPINFO notifications. The GetItemInfo function first seeks the record to read and then reads all the properties of a database record with one call to CeReadRecordProps. Since the list view control typically uses the LVN_GETDISPINFO notification multiple times for one item, GetItemInfo saves the data from the last record read. If the next read is of the same record, the program uses the cached data instead of rereading the database.As I've explained before, you can change the way you sort by simply closing the database and reopening it in one of the other sort modes. The list view control is then invalidated, causing it to again request the data for each record being displayed. With a new sort order defined, the seek that happens with each database record read automatically sorts the data by the sort order defined when the database was opened.AlbumDB doesn't provide the option of storing the database on external media. To modify the example to use separate database volumes, only minor changes would be necessary. You'd need to replace the CREATE_SYSTEMGUID macro that fills in the g_guidDB value with a call to CeMountDBVol to mount the appropriate volume. You'd also need to unmount the volume before the application closed.The database API is unique to Windows CE and provides a valuable function for the information-centric devices that Windows CE supports. Although it isn't a powerful SQL-based database, its functionality is a handy tool for the Windows CE developer.The last few chapters have covered memory and the file system. Now it's time to look at the third part of the kernel triumvirate—processes and threads. As with the other parts of Windows CE, the API will be familiar if perhaps a bit smaller. However, the underlying architecture of Windows CE does make itself known.