|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionEveryone is familiar with the standard 'File Open' and 'File Save' common dialogs. Microsoft has done a great job of providing advanced functionality for these controls. Each of them is essentially a miniature version of Windows Explorer: in addition to presenting a list of files, the user can drag files within the list, create new folders, rename files, drag-n-drop new files into the dialog from outside, and cut, paste or delete files. These operations are all available right in the middle of Opening or Saving the current file. But what if you don't want to offer the user that much freedom? The common dialogs offer a lot of powerful features, but is there any way of limiting those features? In one of our company projects we needed a way to limit a common dialog's functionality to providing a strictly read-only view of files in a folder. This article presents the solution we came up with. AcknowledgementsMuch of the code presented here was derived from code written by Dino Esposito in an article entitled "The Logo and Beyond: Solutions for Writing Great Windows 2000-based Apps". His original article, including downloadable source code, is available in the August 1999 issue of MSJ, accessible on Microsoft's web site. Some of the code was also taken from the Code Project article "Customizing the Windows Common File Open Dialog", written by S h a n x. His original article, including downloadable source code, is available here on the CodeProject web site. S h a n x presents a number of interesting techniques for customizing common dialogs in general. If you'd like an introduction on how to customize common dialogs, I recommend reading the MSDN documentation on the Microsoft MSDN web site. Search for "Customizing Common Dialog Boxes" to uncover useful articles. Definition of a "Read-Only" ViewWe first consider what a "read-only" view of files in a folder involves. This seems like a simple question, but as I discovered, there are subtleties. Several times while writing the code I thought I was finished, but a colleague would come along and ask "What if the user did this?" and point out another loophole. (I think I've got them all covered now :-) Within a standard 'File Open' common dialog, the following operations are available:
For a read-only file dialog, each of the above has to be prevented or disabled. Creating a Common DialogIf you're using MFC, a File Open or File Save common dialog is created by
instantiating a Structure of a 'File Open' Common DialogLike all dialogs, a File Open dialog consists of an overall Parent dialog window which contains various child windows as controls. The diagram below illustrates those elements whose behavior has to be altered to make the dialog read-only. Their window class names and parent-child relationships can be discovered by using a spy utility such as Spy++ (included with Visual Studio), or the useful freeware WinDowse window dowsing tool.
Each of the items shown has to be hooked or subclassed (in the Windows sense) in some way, so as to intercept and defeat its 'undesirable' behaviors. One point of probable confusion is the role played by the
Although the Defeating the Undesired Behaviors(1) Disabling Specific KeystrokesTo a seasoned Windows programmer, it might seem simple to trap and discard
keystrokes you don't want: just get a handle to the appropriate window (probably
the Unfortunately this doesn't work. The problem is that when the common dialog is instantiated, it installs a keyboard hook. This means it sees keyboard messages before they are passed to the dialog window procedure. Subclassing the procedure is therefore of no use: by the time your function sees them, the keystrokes have already been acted on — the file has been deleted, or renamed, or whatever. To get around this we have to install our own keyboard hook once the dialog
has finished initializing. The dialog sends a In the keyboard hook function, we can trap and discard the keystrokes we want
to prevent: // 'NewKeybdHook' is the custom keyboard hook we installed. UINT CALLBACK NewKeybdHook( int nCode, WPARAM wParam, LPARAM lParam ) { // wParam is the key code. if (wParam == VK_DELETE) // user pressed the Delete key? { return 1; // eat it: don't let dialog box see it } In principle the same approach could also be used to trap
(2) Disabling Filename EditingDisabling filename edits turns out to be easy once you know how. Editing of
filenames in a ListView control is enabled or disabled simply by setting the
appropriate bit in the window's style member. The flag of interest is
Clearing the // 'hwndListView' is a handle to the SysListView32 control. DWORD dwStyle = GetWindowStyle( hwndListView ); // get original style flags // Remove the LVS_EDITLABELS style. ::SetWindowLong( hwndListView, GWL_STYLE, dwStyle & ~LVS_EDITLABELS ); The only subtlety here is that the ListView loses the style settings
whenever the user navigates to a different folder. Why? Because whenever a
different folder is chosen, the current But this is not really a problem. The dialog sends a
(3) Disabling the Context MenuThe items shown in a mouse right-click context menu are unpredictable: they depend on whether specific software (such as WinZip or Norton Anti-Virus) is installed on the system, on security settings (user permissions), and the version of the operating system itself. Therefore, while it's possible in theory to disable only specific items on the context menu, in practice doing so quickly becomes a nightmare. Easier and far more reliable is to simply disable the context menu entirely.
This is accomplished by subclassing the // 'NewListViewWndProc' is the subclassed SysListView32 window procedure. LRESULT CALLBACK NewListViewWndProc( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch (uiMsg) { case WM_CONTEXTMENU: // trying to create a context menu? { return 0; // eat this message, prevent context menu } break; The only subtlety here is the same as mentioned earlier: subclassing is lost
whenever the user navigates to a different folder. You must re-subclass the
(4) Preventing File Dragging Within the DialogThis is an example of the Disabling Mouse Left-Button Drags When a file drag is started within the By subclassing the // 'NewShellDefWndProc' is the subclassed SHELLDLL_DefView window procedure. LRESULT CALLBACK NewShellDefWndProc( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch (uiMsg) { case WM_NOTIFY: // notification message NMHDR* pnmhdr = reinterpret_cast< NMHDR * >( lParam ); if (pnmhdr->code == LVN_BEGINDRAG) // user trying to drag? { return 0; // eat this message, don't allow drag } Subclassing of the Disabling Mouse Right-Button Drags Under normal conditions, using the right mouse button to drag a file within the dialog results in a context menu popping up when the file is dropped. Right-dragging a file out of the dialog also gives a context menu when the file is dropped into a different window. Either way, the user has an option to Move the file to a new location. This behavior has to be disabled. Curiously, trapping The code
to eat the mouse right-click messages ends up being just another
// 'NewListViewWndProc' is the subclassed SysListView32 window procedure. LRESULT CALLBACK NewListViewWndProc( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch (uiMsg) { case WM_CONTEXTMENU: // trying to create a context menu? { return 0; // eat this message, prevent context menu } break; case WM_RBUTTONDOWN: // detected any mouse right-click message? case WM_RBUTTONUP: { return 0; // eat it: no right-mouse dragging permitted } break; As before, you must remember to re-subclass the (5) Preventing File Drops Onto the DialogThe A window registers itself as being capable of accepting dropped files by
notifying the operating system through a call to the COM
// 'hwndListView' is a handle to the SysListView32 control. ::RevokeDragDrop( hwndListView ); // remove drag-n-drop registration The only subtlety here is the same as mentioned earlier: drag-n-drop gets
re-enabled whenever the user navigates to a different folder, because the
Why Intercepting Both Drag-n-Drop Handlers is Necessary The reader may wonder: if The answer is, But what happens if you drag files from inside the dialog to outside of it? Now you're in trouble. You'll discover that the file can be moved out of the dialog folder and dropped into another location. This happens because the target window can ask the operating system (COM) to move the file rather than just copy it. This is no good. Therefore (6) Disabling the "New Folder" Toolbar ButtonThe toolbar that appears at the top of the dialog box is a
// 'hwndToolBar' is a handle to the ToolbarWindow32 control. // 'TB_BTN_NEWFOLDER' is the ID for the "New Folder" button. TBBUTTONINFO tbinfo; tbinfo.cbSize = sizeof( TBBUTTONINFO ); tbinfo.dwMask = TBIF_STATE; tbinfo.fsState = TBSTATE_HIDDEN | TBSTATE_INDETERMINATE; ::SendMessage( hwndToolBar, TB_SETBUTTONINFO, (WPARAM)TB_BTN_NEWFOLDER, // which button (LPARAM)&tbinfo ); // new button state The only challenge is how to find the ID's of the buttons in the
Discovering the button ID's in this way only has to be done one-time, in debug mode. Thereafter you can remove the code used to detect them and just use the ID's as hard-wired constants. This is a bit of a kluge, since there is no guarantee that the ID's will be the same across all versions of Windows. However, experiments show that the same ID's are used in Win98, Win2K, WinNT4, and WinXP, so that covers most of the Windows versions currently in use. For convenience, the source code contains a Using the codeThe functionality of the read-only File dialog is encapsulated in a small
class called Win32 API vs. MFCThe For the sample project, I did use MFC to build a dummy dialog that acts as a
launchpad for debugging and testing the Running the DemoRunning the sample project launches the dummy dialog, which looks like this:
Clicking 'Click Me' launches a read-only File Open dialog box showing the
contents of the System directory on your machine. Note that the dialog displays
only I use the System directory as an example because it's a directory that's
always available. This guarantees the demo will work on everybody's machine. But
this is a dangerous folder to run tests on! — I suggest navigating to a less
important folder before trying out SummaryCreating a read-only version of the File Common Dialogs turns out to be possible, though not especially simple. To defeat the unwanted behaviors, it's necessary to install several different types of message hook at different points within the dialog. Doing this without affecting the overall functionality of the dialog requires understanding the responsibilities of each child control within the dialog window. The techniques examined here, while focused on the File dialogs, would hopefully prove useful in customizing the behavior of other common dialogs and controls. Additional ThanksCodeProject members WREY, Paulo Rogério, and shue_20002000 contributed valuable bug reports and helpful suggestions for improving the code submitted in my original article. | ||||||||||||||||||||