Finding Files From Within WinCE Applications

Environment: WinCE, C++

In this lesson, we’ll begin to explore the techniques for allowing the user to find and access files. The main tool for this job is our old Win32 friend, the Common File Dialog, but it works a little differently under CE. Our vehicle for examining its functionality is a demo application called FileOpen. The main sample code file for this app is provided separately, so we’ll just include snippets of key code in the discussion that follows. To see the snippets in context, refer to the accompanying source file.

Getting Started

The business of the FileOpen example all starts to happen when the IDM_OPEN_FILE message is received in WndProc(). First, we launch ChooseFilePathDlgProc(), which allows the user to choose a path. This path will be used to initialize the common file open dialog.

case IDM_OPEN_FILE:
  DialogBox( hInst,
             TEXT("Pathnames"),
             hWnd,
             ChooseFilePathDlgProc );

Next, we call ChooseFilePermsDlgProc() to give the user a choice of file open permissions. These permissions will regulate the action of CreateFile() when we attempt to open the file.

DialogBox( hInst,
           TEXT("FILE_PERMISSIONS"),
           hWnd,
           ChooseFilePermsDlgProc );

In DoFileOpen(), we display the common file dialog and allow the user to choose an existing file or specify a filename to use in opening the file.

lpof = DoFileOpen( hWnd, FILE_OPEN );
if(!lpof)
{
  break;
}

Finally, we attempt to open the file with a call to CreateFile(), and report our success or failure to the user with MessageBox().

//Open the file for reading.
hTestFile = CreateFile( (LPTSTR)lpof->lpstrFile,
            GENERIC_READ | GENERIC_WRITE ,
            FILE_SHARE_READ, NULL, dwFileCreateFlags,
            FILE_ATTRIBUTE_NORMAL, NULL);
if( !hTestFile )
{
  MessageBox( hWnd, TEXT("Try Different Permissions"),
              TEXT("File Open Failed"), MB_OK );
  break;
}
if( dwFileCreateFlags == OPEN_EXISTING )
    MessageBox( hWnd, lpof->lpstrFile,
                TEXT("Existing File Opened"), MB_OK );
{

}
if( dwFileCreateFlags == CREATE_NEW )
{
  MessageBox( hWnd, lpof->lpstrFile,
              TEXT("New File Created"), MB_OK );
}

When we are finished, we close the file like this:

CloseHandle(hTestFile );
break;

Let’s take a close look at the initialization of the ChooseFilePathDlgProc() dialog. In this function, the first thing we have to do is detect the platform type. This allows us to correctly load the list box with directory name strings.

case WM_INITDIALOG:
hWndList = GetDlgItem(hDlg,IDC_PATHNAMES );
//Init paths list based on platform type
memset( &tszPlatType, 0x0, sizeof(tszPlatType));
rc = SystemParametersInfo( SPI_GETPLATFORMTYPE,
                           sizeof(tszPlatType),
                           &tszPlatType, 0);
if( (lstrcmp( tszPlatType, TEXT("Jupiter") ) == 0 )
              ||
  ( lstrcmp( tszPlatType, TEXT("HPC") ) == 0 ) )
{
  iBaseStringID = IDS_HPC_PATH1;
  iMaxStringID  = IDS_HPC_PATH11;
}

if( lstrcmp( tszPlatType, TEXT("Palm PC") ) == 0 )
{
  iBaseStringID = IDS_PPC_PATH1;
  iMaxStringID  = IDS_PPC_PATH7;
}

The pathname strings are stored consecutively in the resource file. By initializing the iBaseStringID with the lowest string ID and the iMaxStringID with the highest, we can load them with a loop. Notice that we use a CE-style call to LoadString(), which returns a pointer to the text of the string in the resource file. The return from this function must be cast to (LPCTSTR) when it is used in this way because its declaration specifies a return type of int. The word immediately preceding the returned address contains the string length.

for( i = iBaseStringID; i < iMaxStringID; i++ )
{
  lpszPathString = (LPCTSTR)LoadString( hInst,
                                        i,
                                        NULL,
                                        NULL);
  lpbStrlen = (LPBYTE)lpszPathString;
  lpbStrlen -= 2 * sizeof( BYTE );
  wCharCnt = (short int)(*lpbStrlen);

We use the string length to allocate a buffer on the local heap. Notice that the string length is reported in characters, not in bytes. Also, we add one to this length because we need space for a terminating null. This brings us to why we are allocating a buffer in the first place—the strings stored in the resource file are not null terminated, so you can’t use them directly in calls that require a terminal null.

lpszPath = (LPTSTR)LocalAlloc( LPTR,
                 (wCharCnt + 1) * sizeof(TCHAR));
LoadString(hInst, i, lpszPath, (int)wCharCnt + 1);

Finally, we insert the pathname string at the end of the list and free the buffer.

  ListBox_InsertString(hWndList, -1, lpszPath);
  LocalFree(lpszPath);
}
  return TRUE;

In the grand scheme of things, the next bit of information we acquire from the user is the file open permissions. There’s nothing unusual about the dialog code for this, so we won’t dissect it here. However, our dialog offers only non-destructive permission choices. When we open the file with a call to CreateFile(), we’ll look at a more complete list.

In DoFileOpen(), we initialize the structure that configures the common File Open dialog.

Notice that we specify a filter string for the dialog by using the TEXT macro. The sequence following the first null ( ) inside the quoted string sets the filter pattern the dialog will use to discriminate the filenames it makes visible to the user.

const LPTSTR pszOpenFilter =
             TEXT ("All Documents (*.*)*.*");

We allocate an OPENFILENAME structure, and we also allocate a buffer for the filename. The pointer to the filename buffer is a member of the OPENFILENAME structure, and the application is responsible both for initializing the pointer and providing space for the filename.

// Allocate space for the OPENFILENAME struct
lpof = (LPOPENFILENAME)LocalAlloc( LPTR,
                                   sizeof(OPENFILENAME));
if (!lpof)
  { goto FAIL;}

// Allocate space for the FILENAME string
pszFileName = (TCHAR*)LocalAlloc(LPTR, MAX_PATH);
if (!pszFileName)
  { goto FAIL;}

This bit of code that follows is actually superfluous in this case because the LPTR argument to LocalAlloc() causes the allocated block to be zero initialized. However, if this were a stack-based buffer, you’d want to set the first element to null to display a blank edit control for the filename. Conversely, if you want a default file name displayed, initialize the filename buffer with that string.

// Make the first char null, as we don't want the
// dialog to init the filename edit ctl.
// pszFileName[0] = '';

Setting the lpstrInitialDir member of the OPENFILENAME structure to the path the user chose from the pathname dialog causes the file open dialog to be initialized with the filenames in that directory. All types of files will be displayed, because that is what we specified above, in the content of pszOpenFilter.

// Initialize File Open structure.
lpof-&lStructSize     = sizeof (OPENFILENAME);
lpof->hwndOwner       = hWnd;
lpof->lpstrFile       = pszFileName;
lpof->nMaxFile        = MAX_PATH;
lpof->lpstrFilter     = pszOpenFilter;
lpof->lpstrInitialDir = (LPCWSTR)&szPath[0];

Once again, we have a bit of superfluous code, at least for this particular case. We test a flag passed to the function to see whether we are opening a file or saving one. I included this to show two things: First, the initialization of the OPENFILENAME structure is nominally the same in either case. (The exception here is if you have different file extensions or initial directory locations for the two operations.) And second, it is a good practice to warn the user before allowing them to overwrite an existing file.

  if (iOpenSaveFlag ==  FILE_SAVE)
{
  //prompt for overwrite, send files to sync directory
  lpof->Flags = OFN_OVERWRITEPROMPT;


  if( !GetSaveFileName (lpof))
  {
     lpof = NULL;
  }
}
else
{

  if( !GetOpenFileName (lpof))
    {
      lpof = NULL;
    }

The DoOpenFile() function then returns either a fully initialized OPENFILENAME structure, or null if there was an error.

Next, we open the file with a call to CreateFile( ).

//Open the file for reading.
hTestFile = CreateFile( (LPTSTR)lpof->lpstrFile,
                       GENERIC_READ | GENERIC_WRITE,
                       FILE_SHARE_READ,
                       NULL,
                       dwFileCreateFlags,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL);

The parameters to CreateFile(), in the order shown, are the filename, the requested type of access, the sharing mode, a NULL placeholder for the unsupported security attributes parameter, the file creation mode, the file attribute flags, and another NULL placeholder for the unsupported hTemplate parameter. Here’s a table of the supported values for file creation, along with their meanings, followed by a similar table of file attributes.

Table 8-1 CreateFile() File Creation Flags

Create Flag Constant Effect of Flag
CREATE_NEW Creates a new file and fails if the specified file already exists.
CREATE_ALWAYS Creates a new file. If the file exists, the function overwrites it and resets attributes.
OPEN_EXISTING Opens the file or fails if the file does not exist.
OPEN_ALWAYS Opens the file, if it exists, or creates the file if it doesn’t exist.
TRUNCATE_EXISTING Opens the file and truncates it to zero length. Fails if the file does not exist.

Table 8-2 CreateFile() Attributes

Attribute Constant Effect of The Attribute
FILE_ATTRIBUTE_ARCHIVE Mark files for backup or removal.
FILE_ATTRIBUTE_HIDDEN File will not be included in an ordinary directory listing.
FILE_ATTRIBUTE_NORMAL No other attributes set. Overridden by any other attribute flag.
FILE_ATTRIBUTE_READONLY File is read only—can’t be written or deleted.
FILE_ATTRIBUTE_SYSTEM Used exclusively by the operating system.

One last bit about file names. If you want to be able to store the maximum number of files on a PC Card device, use filenames of uppercase letters, in 8.3 format. PC Cards use more than one physical entry in their file allocation table to store long filenames and names with mixed case.

Looking Ahead

In our next lesson, we’ll look into techniques for reading and writing files.

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.

Downloads


Download Source – 5 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read