dcsimg

Customizing the Common File Open Dialog

WEBINAR:
On-Demand

Application Security Testing: An Integral Part of DevOps


BOOL CALLBACK EnumChildProc(HWND hWnd, LPARAM lParam) { int id = ::GetDlgCtrlID(hWnd); switch(id) { case LOOK_IN_COMBO : // Combo box on top of the dialog ::EnableWindow(hWnd, FALSE); break; } return TRUE; }
The id for the combo box is identified by LOOK_IN_COMBO, is 1137 and is defined in the header file.

Setting the Hook

The hook is set as follows, in the OnInitDialog() handler:

HookHandle = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC) Hooker, 
(HINSTANCE)NULL, (DWORD)GetCurrentThreadId());
The hook is of type WH_CALLWNDPROC. This means that the hook gets all messages first, before the target window procedure(s) gets it. Therefore, custom processing can be made at this point, since we get a first crack at the message.

Inside the Hook Procedure

The hook procedure is passed a pointer to a CWPSTRUCT in its LPARAM. This structure contains the following information, about the message intended for a particular window:

a. LPARAM

b. WPARAM

c. Message

d. Window Handle

For our purposes, the window handle and the message are the most important. Much processing will depend on these two parameters.

The main reason we have this hook is that we cannot subclass the controls on the dialog straight away, since some of the controls, such as the list view control and toolbar do not even exist in the common dialog template, which is present in the VC++ include directory. Also, their ids are not known-they are not available in the template, which makes things much harder to work with. Stranger still, there is a list box control with an id lst1 that seems to be in the template for no rhyme or reason. This is hidden, and the list view control sits on top of it, when the dialog shows up.

Two of the most important things when working with windows are its handle and id. One major problem with the sub-classing approach is as follows: In order to sub-class a control, the control must be there first! The list view control on start up shows the folders and files. We can now sub-class it, but then, the damage is already done-it shows the folders and files-we need only the files! Ideally, we need something that intercepts the list view control before it initializes, so that we can remove the folders-and then, we can let the list view control continue to display only the files. Using a hook is most convenient for us since it circumvents certain problems of traditional sub-classing. One of the main advantages of using a WH_CALLBACK type hook is that before a control gets the message, we get it first. Therefore, we can trap an intended message for the list view control, and modify it by changing processing. Further, the CWPSTRUCT pointer passed in to the hook via the LPARAM has vital information that we can put to use-the window handle and message.

The hook is a catchall. In other words, all messages generated go to the hook first. Since we want to select which windows we need to modify, we must first identify the target window handle. This is done by using the GetClassName() API on the window handle obtained via the CWPSTRUCT. GetClassName() returns the class name as a string, of the window were interested in. For the list view controls handle, GetClassName() returns "syslistview32" and for the toolbar, it returns "toolbarwindow32". And for the last control we want to modify, the edit control, it returns "edit".

The following code shows how to obtain the class name for a control whose handle is identified in the CWPSTRUCT pointer:


CWPSTRUCT *x = (CWPSTRUCT*)lParam;

GetClassName(x -> hwnd, szClassName, MAX_CHAR);

Using the relevant class names, we do processing accordingly. For example, if it is a "syslistview32" based control, do something, if it is a "toolbarwindow32" based control, do something else, etc. This is achieved by making simple calls to strcmp().

Customizing the List View Control

Here is the code:

if(strcmp(_strlwr(szClassName), "syslistview32") == 0) 
{
 switch(x -> message)
 { 
  case WM_NCPAINT : 
  case LAST_LISTVIEW_MSG : // Magic message sent after all items are inserted
   int count = ListView_GetItemCount(x -> hwnd);
   for(int i = 0; i < count; i++)
   {
    item.mask = LVIF_TEXT | LVIF_PARAM;
    item.iItem = i;

    item.iSubItem = 0;

    item.pszText = szItemName; 

    item.cchTextMax = MAX_CHAR;

    ListView_GetItem(x -> hwnd, &item);

    if(GetFileAttributes(szItemName) == FILE_ATTRIBUTE_DIRECTORY)

    ListView_DeleteItem(x -> hwnd, i);
   }

  break; 

 } // end switch 

 HideToolbarBtns(hWndToolbar);

} // end if
A check is performed first using strcmp() to make sure it is the list view control. If it is, we switch to the message part of the CWPSTRUCT pointer (in this case, x). Two messages are trapped, WM_NCPAINT needed so that folder items can be removed before the list view control actually shows up and LAST_LISTVIEW_MSG (defined in my header file) which is the last message the list view control receives after displaying all items. The message has a value of 4146, which I figured out by studying the messages to the list view control.

Since we now have a handle to the list view control, we can perform ordinary list view control operations-in this case, we simply run down the entire list of items, checking to see if any item has the FILE_ATTRIBUTE_DIRECTORY attribute set-which would mean it is a directory. If so, we delete it. Finally, we hide the toolbars buttons by calling the helper function HideToolbarBtns() that passes receives the toolbars handle. How did we come to have the handle of the toolbar? The following code does this-it simply saves the toolbars handle for later use:


if(strcmp(_strlwr(szClassName), "toolbarwindow32") == 0)
{
 if(!CCustomFileDlg::OnceOnly) // Save toolbar's handle only once
 {
  hWndToolbar = x -> hwnd;
  ++CCustomFileDlg::OnceOnly;
 }
}

Hiding the Toolbar

Here is the code for HideToolbarBtns():

void HideToolbarBtns(HWND hWndToolbar)
{
 TBBUTTONINFO tbinfo;
 tbinfo.cbSize = sizeof(TBBUTTONINFO);
 tbinfo.dwMask = TBIF_STATE;
 tbinfo.fsState = TBSTATE_HIDDEN | TBSTATE_INDETERMINATE;

 ::SendMessage(hWndToolbar,TB_SETBUTTONINFO,

 (WPARAM)TB_BTN_UPONELEVEL,(LPARAM)&tbinfo);

 ::SendMessage(hWndToolbar,TB_SETBUTTONINFO,

 (WPARAM)TB_BTN_NEWFOLDER,(LPARAM)&tbinfo);
}
The code simply sets the new button states for the toolbar buttons. The hard part was figuring out the ids of the toobars buttons. In this case, we use TB_BTN_UPONELEVEL and TB_BTN_NEWFOLDER, which are the buttons the user might click to go up one level and to create a new folder respectively. Both these are defined in the header file as follows:

const int TB_BTN_UPONELEVEL = 40961;
const int TB_BTN_NEWFOLDER = 40962;
Again, these numbers come from a long time spent with the debugger figuring out the messages send to window handles and experimenting with different ids for the toolbar buttons.

Finally, we need to make sure that the user cannot change directories by entering different paths in the edit box. For this, we need to trap the Return key event, which happens when the user presses enter after keying in a path inside the edit box. Here is the code:


if(strcmp(_strlwr(szClassName), "edit") == 0) 
{
 switch(x -> message)
 {
  case EDIT_ENTER: // User presses Enter
   ::GetWindowText(x -> hwnd, szEditBuff, MAX_CHAR);

  if(ParseForDelims(szEditBuff))

  ::SetWindowText(x -> hwnd, "");

  break;
 } // end switch
} // end if
EDIT_ENTER takes a value of 14 and is defined in the header file. This is the message that is generated when the user presses return at the edit box in the common file open dialog. Since we have the handle to the edit control as well, we get the text inside the edit control, which is actually the new path the user enters. We then call the helper function ParseForDelims() with the path string. The function will return a BOOL, to indicate if the user had entered either a \ or a : or a . any of the presence of which could mean that the user typed in a different path or a wild card sequence. If this is the case, the function returns TRUE, else returns FALSE. Here is the code:

BOOL ParseForDelims(const TCHAR* szEditBuff)
{
 for(int i = 0; i < (int)strlen(szEditBuff); ++i)
  if(szEditBuff[i] == '\\' || szEditBuff[i] == ':' || szEditBuff[i] == '.')
   return TRUE;

 return FALSE;
}
So, if the user did indeed enter a new path, we simply set the text back inside the edit control to NULL:

if(ParseForDelims(szEditBuff))
 ::SetWindowText(x -> hwnd, "");
This effectively prevents the user from navigating to different directories by typing in those directory paths inside the edit control.

Miscellaneous

On a final note, the customized file open common dialog supports multiple selections. All the selections made are stored and parsed into a CStringList object, containing each user selected item which is fully qualified, i.e., contains the full path. A pointer to this list is returned to the client from the exported function, getFileNames(). It is the clients responsibility to free the memory associated with the list. Also note: When passing in the path as a parameter to getFileNames() the client should make sure that the path that depicts the directory is always terminated by a trailing \ and not left open. In other words, "C:\\TEMP" is not acceptable, and should be changed to "C:\\TEMP\\". The way I see it, the former represents a file and the latter represents a directory. This method of ending directories with "\\" clears the ambiguity.

Known Problems

While the program works fine for every other directory, it does not get rid of folders in the Windows directory.

Enhancements

There is a shell interface called ICommDlgBrowser. This has a method called IncludeObject that lets you filter specific items in your common file dialogs. This should be looked into.

Downloads

Download demo project - 48 Kb


Comments

  • Vist a problems

    Posted by gintonic42 on 08/03/2010 06:03am

    Hy, i have used this for my own openfile dialog. Now i have problems, when my app is running under Vista and W7. Anybody has an idea, of improving my software to run under Vista? I am using MSVC 2008 C++ MFC Thanks Bodo

    Reply
  • Up one level

    Posted by Legacy on 03/27/2002 12:00am

    Originally posted by: Kazima

    Is there any way you can disable the up one level button through  policy editor or regedit.
    
    

    Thank you.

    Reply
  • solution to backspace problem

    Posted by Legacy on 03/14/2002 12:00am

    Originally posted by: Ryan J O'Boril

    here is a slick (IMHO) solution to the problem of the user being able to click in the list view and press the backspace key to go up a directory:
    
    

    1. add these global variables in CCustomFileDlg.cpp:

    HWND hWndList = NULL;
    HWND hWndEdit = NULL;

    ////////////////////////////////////////////

    2. then, in the hook function, add a line to store the listview handle:

    if(strcmp(_strlwr(szClassName), _T("syslistview32")) == 0)
    {
    hWndList = x -> hwnd; //store listview handle

    //rest of code .......

    /////////////////////////////////////////////

    3. do the same thing with the edit box:

    if(strcmp(_strlwr(szClassName), _T("edit")) == 0)
    {
    hWndEdit = x->hwnd;

    //rest of code .......

    //////////////////////////////////////////////

    4. Finally, right before the hook function returns, add this line:

    if (GetFocus() == hWndList)
    SetFocus(hWndEdit);


    Basically, the user must first click in the listview to give it the focus before they can press backspace to go up a directory. This modification puts the focus back on the edit box whenever the listview has the focus. Since the list view never has the focus, the user can never use backspace to go up a directory.

    i hope this helps someone who had the same problem i had. in closing, i would just like to say that MFC sucks ass hardcore.

    _ryan

    Reply
  • how to use CFileDialog to select

    Posted by Legacy on 03/07/2002 12:00am

    Originally posted by: prasanna

    how to use CFileDialog class ,such that it provide a way for the user to select a file from the displayed items.
    we should get the name of file &path

    please reply soon
    Thank's in advance

    Reply
  • does not work as it should

    Posted by Legacy on 02/24/2002 12:00am

    Originally posted by: Lolo

    user still can access directory if he presses the backspace key

    Reply
  • Change MessageBox occur in CFileDialog

    Posted by Legacy on 10/11/2001 12:00am

    Originally posted by: Vo Thanh Hoa

    I can change all the texts in the appearance of the File Dialog ( "Look in", "OK", "Cancel") to my own texts. But I can not catch the message box showing up when you enter a wrong path to customize it.
    I have try many ways without success ( message handle "OnFileNameOK", hook procedure in OPENFILESTRUTURE)

    Reply
  • Code Rocks!

    Posted by Legacy on 10/27/2000 12:00am

    Originally posted by: piperatom


    By far the best approach to messing around with common dialogs.
    The hook part has been really described well.
    Thanks mate!

    Reply
  • FTP/HTTP open dialog

    Posted by Legacy on 04/20/2000 12:00am

    Originally posted by: Roman Panlyuk

    I'd like to customize Open/Save dialogs so that I could browse web directories af if they were files. All I need is to be able to intercept ChengeDir action and fill it with my own items. I do not see any events for doung this. Am I bnlind or this is impossible.

    Please also answer to e-mail john@eleks.lviv.ua

    Thanks,
    Roman

    Reply
  • Very cool with a little hint...

    Posted by Legacy on 04/08/2000 12:00am

    Originally posted by: Klaus Bucher

    This article demonstrates very best how to use hook-
    
    functions to manipulate the CommomDialog�s. To this example
    : it�s always possible to go up a folder by the backspace.
    To forbid this, you have to install another hook
    (WH_KEYBOARD) to catch the backspace.


    On my side, I expandend the example so that the initial
    folder is a virtual-root-folder. The user can go down to
    subfolders, and go back up maximal to the initial folder.


    Thank you Shanker

    Klaus

    Reply
  • Resource ID's for the Common File Dialog

    Posted by Legacy on 04/07/2000 12:00am

    Originally posted by: Niall


    I am trying to find the ID's for the toolbar buttons

    in the top right hand corner of the dialog e.g.

    the Move Up button of the commom File Open Dialog

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date
×
We have made updates to our Privacy Policy to reflect the implementation of the General Data Protection Regulation.