分类: C/C++
2007-10-15 10:20:13
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.
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.
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 id's 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 we're interested in. For the list view control's 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().
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 toolbar's buttons by calling the helper function HideToolbarBtns() that passes receives the toolbar's handle. How did we come to have the handle of the toolbar? The following code does this-it simply saves the toolbar's handle for later use:
if (strcmp(_strlwr(szClassName), "toolbarwindow32") == 0) { if (!CCustomFileDlg::OnceOnly) // Save toolbar's handle only once { hWndToolbar = x -> hwnd; ++CCustomFileDlg::OnceOnly; } }
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 toobar's 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. There are two ways of doing this: we can either trap the Return key event, which happens when the user presses Return after keying in a path inside the edit box or subclass the edit control on the dialog, so that characters that denote a change of directory cannot be entered. Since this article is all about subclassing, it should come as no surprise we choose the latter approach! This first thing to do is to create your own derived edit control class, as defined in myedit.h:
class CMyEdit : public CEdit { protected: afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); DECLARE_MESSAGE_MAP() };
We have an OnChar handler in the class, since we're interested in trapping Now create an object of type CMyEdit in the customfiledlg.cpp file, and finally here's the implementation in myedit.cpp:
void CMyEdit::OnChar ( UINT nChar, UINT nRepCnt, UINT nFlags ) { // Clear selection, if any DWORD dwSel = GetSel(); LOWORD(dwSel) == HIWORD(dwSel) ? NULL : SetWindowText(""); CString strWindowText; GetWindowText(strWindowText); if (nChar == '\\' || nChar == ':' || (nChar == '.' && strWindowText.GetLength() < 1)) return; // Don't pass on to base for processing CEdit::OnChar(nChar, nRepCnt, nFlags); // Pass on to base for processing }
The above logic takes care of the following:
At this point, you might be wondering why I never made the edit control a member of my custom dialog class, and why I did not subclass that in my dialog's OnInitDialog() and why I instead chose to create a member of the edit control in the .cpp file and handle its subclassing in my hook code. The reason: it doesn't work. I'd be more than happy to hear any explanations from avid readers.
Now, we have an edit control on the file open dialog that effectively prevents the user from navigating to different directories by typing in those directory paths inside the edit control.