Using the ‘Open File’ and ‘Save File’ dialogs, GetOpenFileName and GetSaveFileName APIs

Recently, someone on the MSDN forum asked if there are any built-in ‘Open File’ and ‘Save File’ dialogs available in the Windows Mobile platform. The answer, of course, is yes. The APIs that get the task done are GetOpenFileName() and GetSaveFileName(). These APIs allow the user to select directories or files for opening or saving. I wrote a small application to demonstrate how to use these APIs. The application has a multiline edit control and menu options for opening or saving text files (see video at the end). When you open a text file, the files contents are shown on the edit control, and saving to a file writes the contents of the edit control to a user selected text file. Pretty simple.

First, lets take a look at GetOpenFileName() API, the DoOpenFile() method in the application reads the contents of the file and writes it to the edit control, here is the function: (error checking is left out, as usual)

1. Get the full path to the file
2. Read the contents of the file into a buffer
3. Convert the buffer into wide char
4. Display the wide chars on the edit control

BOOL DoOpenFile(HWND hDlg)
{
    TCHAR   szFile[MAX_PATH] = TEXT("\0");
    char    *szFileContent = NULL;
    OPENFILENAME   ofn;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    DWORD dwFileSize = 0, bytesToRead = 0, bytesRead = 0;
   
    memset( &(ofn), 0, sizeof(ofn));
    ofn.lStructSize   = sizeof(ofn);
    ofn.hwndOwner = hDlg;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrFilter = TEXT("Txt (*.txt)\0 *.txt\0");  
    ofn.lpstrTitle = TEXT("Open File");
    ofn.Flags = OFN_EXPLORER;

    //get the filename the user wants to open
    if (GetOpenFileName(&ofn))
    {
        //ofn.lpstrFile contains the full path to the file, get a handle to it
        hFile = CreateFile(ofn.lpstrFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        if (hFile == INVALID_HANDLE_VALUE)
            return FALSE;

        //get file size in bytes
        dwFileSize = GetFileSize(hFile, NULL);

        szFileContent = (char*)malloc(dwFileSize+1);
       
        memset(szFileContent, 0, dwFileSize+1);

        //read the files contents into a buffer
        if (ReadFile(hFile, szFileContent, dwFileSize, &bytesRead, NULL))
        {
            TCHAR *wszTemp = NULL;
            HWND hEdit = NULL;

            wszTemp = (TCHAR*)malloc(bytesRead*sizeof(TCHAR));

            //convert the chars in buffer to wide chars
            mbstowcs(wszTemp, szFileContent, bytesRead);
            hEdit = GetDlgItem(hDlg, IDC_EDIT);

            //set the content of edit control
            SetWindowText(hEdit, wszTemp);

            free(wszTemp);
        }

        //free resources
        free(szFileContent);
        CloseHandle(hFile);
    }

    return TRUE;
}

The APIs and the OPENFILENAME structure are explained well in the MSDN documentation, so I am not going to repeat the same things here. Follow the links.

The DoSaveFile() method in the application allows the user to save the contents of edit control into a file, here is the code:

1. Get the full path to the file
2. Read the contents of edit control into a wide char buffer
3. Convert the wide char into char (multibyte string)
4. Write the chars to the file

BOOL DoSaveFile(HWND hDlg)
{
    TCHAR   szFile[MAX_PATH] = TEXT("\0");
    OPENFILENAME   ofn;
    HANDLE hFile = INVALID_HANDLE_VALUE;
   
    memset( &(ofn), 0, sizeof(ofn));
    ofn.lStructSize   = sizeof(ofn);
    ofn.hwndOwner = hDlg;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrFilter = TEXT("Text (*.txt)\0*.txt\0");  
    ofn.lpstrTitle = TEXT("Save File As");
    ofn.Flags = OFN_HIDEREADONLY;
    ofn.lpstrDefExt = TEXT("txt");

    //get the filename the user wants to save to
    if (GetSaveFileName(&ofn))
    {
        HWND hEdit = NULL;
        DWORD dwTextLen = 0, bytesWritten = 0;
        TCHAR *wszEditText = NULL;
        char *szEditText = NULL;

        //ofn.lpstrFile contains the full path of the file, get a handle to it
        hFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        if (hFile == INVALID_HANDLE_VALUE)
            return FALSE;

        hEdit = GetDlgItem(hDlg, IDC_EDIT);

        //get the text length of the edit controls contents
        dwTextLen = GetWindowTextLength(hEdit);

        wszEditText = (TCHAR*)malloc((dwTextLen+1)*sizeof(TCHAR));

        memset(wszEditText, 0, (dwTextLen+1)*sizeof(TCHAR));

        //read edit controls contents into buffer
        GetWindowText(hEdit, wszEditText, dwTextLen+1);

        szEditText = (char*)malloc(dwTextLen+1);
       
        //convert the wide char read from edit control to char
        wcstombs(szEditText, wszEditText, dwTextLen);

        //save the contents into file
        if (WriteFile(hFile, szEditText, dwTextLen, &bytesWritten, NULL))
        {
        }

        //free resources
        free(wszEditText);
        free(szEditText);
        CloseHandle(hFile);
    }
   
    return TRUE;
}

Here is a video demo of how things work,  

 

Fun with menus Part II, Radio Menu Items and CheckMenuRadioItem()

Previously – Fun with menus – Part I

A few days back I set about trying to get radio items into menus. This is helpful if you want only one of the items in the menu to be active at a time, say, for e.g. a "Select Level" popup menu which has "Level 1", "Level 2" and "Level 3" subitems and only one of them needs to be shown as active or currently selected. You get the idea. No? Here, this pic should help,

Radio Menu Items 

A bit of searching led me to the CheckMenuRadioItem() API, and I thought, this is simple. I just need to get the handle to the popup menu, and pass the identifier of the subitem I want selected. And done. Turns out ‘get the handle to the popup menu‘ part isn’t very straight forward.

For future references in this post, here is the menu bar and the popup menu that I used in the resource script (refer pic above)

the menubar,
IDR_MENU SHMENUBAR DISCARDABLE
BEGIN
    IDR_MENU_POPUP,
    2,
    I_IMAGENONE, IDM_OK, TBSTATE_ENABLED, TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE, IDS_OK, 0, NOMENU,
   
    I_IMAGENONE, IDR_MENU_POPUP, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE,
    IDS_MENU, 0, 0,
END

the popup menu,
IDR_MENU_POPUP MENU DISCARDABLE
BEGIN
    POPUP "Menu"
    BEGIN
        MENUITEM "Toolbox..",    IDM_MENU_TOOLBOX
        POPUP "Speed"
        BEGIN
            MENUITEM "8x",        IDM_MENU_8X
            MENUITEM "16x",        IDM_MENU_16X
            MENUITEM "32x",        IDM_MENU_32X
        END
        MENUITEM "Settings..",    IDM_MENU_SETTINGS
        MENUITEM SEPARATOR
        MENUITEM "Exit",        IDM_MENU_EXIT
    END
END

 

What I did wrong

I looked up the functions available with menus and it seemed easy, LoadMenu(), GetSubMenu() and CheckMenuRadioItem() should do the trick, so here is the handler for the menuitem ‘8x‘, under WM_COMMAND; the handler tries to set ‘8x‘ as the current selection,

case IDM_MENU_8X:
    {
        HMENU hMenu = NULL, hSubMenu = NULL, hSubSubMenu = NULL;

        //handle to the menu bar
        hMenu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_MENU_POPUP));

        if (hMenu)
        {
            TCHAR szText[128] = TEXT("");
            int i = -1;

            //handle to the menu "Menu"
            hSubMenu = GetSubMenu(hMenu, 0);

            MENUITEMINFO mii;

            ZeroMemory(&mii, sizeof(mii));
            mii.cbSize = sizeof(MENUITEMINFO);
            mii.fMask = MIIM_TYPE;
            mii.fType = MFT_STRING;
            mii.dwTypeData = szText;
            mii.cch = 127;
            i = GetMenuItemInfo(hSubMenu, 1, TRUE, &mii); // mii.dwTypeData = "Speed"

            ZeroMemory(&mii, sizeof(mii));
            mii.cbSize = sizeof(MENUITEMINFO);
            mii.fMask = MIIM_TYPE;
            mii.fType = MFT_STRING;
            mii.dwTypeData = szText;
            mii.cch = 127;

            //handle to the menu "Speed"
            hSubSubMenu = GetSubMenu(hSubMenu, 1);

            i = GetMenuItemInfo(hSubSubMenu, 1, TRUE, &mii); //mii.dwTypeData = "16X"

            i = CheckMenuRadioItem(hSubSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_8X, MF_BYCOMMAND);
        }
    }
    break;
 

GetMenuItemInfo() api and MENUITEMINFO structure were used while debugging, just to make sure that the menu handles that I was getting were the right ones. All API calls LoadMenu(), GetSubMenu() and CheckMenuRadioItem() were successful, from the return values at least. There was one problem however, the radio item ‘dot’ never showed up. Then I tried to experiment a bit with getting menu handles by position, the result still the same. When I ran out of tricks to pull, I tried to set the radio menu using SetMenuItemInfo(), and a few random variations of GetSubMenu() by menu identifier and by position etc. Darn thing never showed up! It all meant one thing only, there must be something wrong with the menu handles themselves, even though they were valid.

The right way

So next I looked up the samples that ship with the SDK and found a CECamera sample that made use of radio menu items. From what I could tell, it looked like LoadMenu() isn’t the right way to get a handle to the main popup menu. Under WM_CREATE message when you are setting up the application and creating the menu bar using SHCreateMenuBar(), you also do the following,

            if (!SHCreateMenuBar(&mbi))
            {
                g_hWndMenuBar = NULL;
            }
            else
            {
                g_hWndMenuBar = mbi.hwndMB;
 

                //get a handle to the main popup menu
                g_hMainMenu = (HMENU)SendMessage(mbi.hwndMB, SHCMBM_GETSUBMENU, 0, IDR_MENU_POPUP);

                //set the default selection to 8x
                hSubMenu = GetSubMenu(g_hMainMenu, 1);
                CheckMenuRadioItem(hSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_8X, MF_BYCOMMAND);
            }

 

So you send a message to the main menubar window, asking for the popup menu handle using SHCMBM_GETSUBMENU. g_hMainMenu and hSubMenu are both HMENU types. When the application showed up ‘8x‘ was selected by default. The handlers for 16x and 32x set themselves as the current selection, so under WM_COMMAND we have,

case IDM_MENU_8X:                   
    hSubMenu = GetSubMenu(g_hMainMenu, 1);
    CheckMenuRadioItem(hSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_8X, MF_BYCOMMAND);
    break;

case IDM_MENU_16X:
    hSubMenu = GetSubMenu(g_hMainMenu, 1);
    CheckMenuRadioItem(hSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_16X, MF_BYCOMMAND);
    break;

case IDM_MENU_32X:
    hSubMenu = GetSubMenu(g_hMainMenu, 1);
    CheckMenuRadioItem(hSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_32X, MF_BYCOMMAND);
    break;
 

It worked. Video follows,