New information has been added to this article since publication.
Refer to the Editor's Update below.
C++ Q&A
Getting a Menu Handle, Declaring GetParam, and Filtering File Names
Paul DiLascia
Code download available at:
CQA0310.exe
(152 KB)
Browse the Code Online
Q I'm trying to handle OnMenuSelect in my MFC app. The declaration for this function is:
void OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu);
When my handler is invoked, none of the menu functions, like GetMenuItemCount, work properly. I added an ASSERT to check for a valid menu handle:
ASSERT(::IsMenu(hSysMenu));
It fails, so the handle isn't valid. How do I get the menu handle?
Frank Connors
A Congratulations; it's not every day you uncover a bug in MFC! In fact, while hSysMenu is declared as HMENU, it isn't a handle at all. What is it, then? Answer: a pointer to an MFC CMenu object. You can prove this with the following code:
void CMainFrame::OnMenuSelect(... HMENU hSysMenu)
{
CMenu* pMenu = DYNAMIC_DOWNCAST(CMenu, (CObject*)hSysMenu);
TRACE("OnMenuSelect, hSysMenu=%p is %s\n", hSysMenu,
::IsMenu(hSysMenu) ? "HMENU" : pMenu ? "CMenu" : "unknown");
return CFrameWnd::OnMenuSelect(nItemID, nFlags, hSysMenu);
}
Figure 1 shows the resulting TRACE stream. As you can see, hSysMenu is, in fact, really a CMenu pointer.
[
Editor's Update - 12/6/2004:
This bug was fixed in MFC 7.1.]
To understand how this can be, you have to delve into the mysterious workings of MFC message maps and message handler functions. When you use ON_WM_MENUSELECT, the macro expands, like so:
// ON_WM_MENUSELECT()
{ WM_MENUSELECT, 0, 0, 0, AfxSig_vwwh,
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< void (AFX_MSG_CALL CWnd::*)
(UINT, UINT, HMENU) > (OnMenuSelect)) }
Figure 1 TraceWin
That's a lot of gobbledygook to create a table entry whose message ID is WM_MENUSELECT and handler function is OnMenuSelect. The important thing here is the special "signature code" AfxSig_vwwh. In order to convert an HWND or HMENU to a pointer to an MFC object like CWnd and CMenu, MFC stores an enum value in your message map. The value identifies the handler's argument types and return value, as shown here:
// from afxmsg_.h
enum AfxSig
{
AfxSig_v_uu_M, // void (UINT, UINT, CMenu*)
AfxSig_b_W_uu, // BOOL (CWnd*, UINT, UINT)
... // etc, over 140 total
};
The naming convention is AfxSig_ return type_ WPARAM types_ LPARAM types. For example, take a look at the declaration for OnSetCursor here:
BOOL OnSetCursor(CWnd* pWnd,
UINT nHitTest, UINT message);
Thus, the symbol for the corresponding signature code is AfxSig_b_W_uu, which is what ON_WM_SETCURSOR uses. Here, b means the return type is BOOL, W means that WPARAM should be converted to CWnd*, and uu means that LPARAM should be converted to two UINTs from the low- and high-order words. When MFC's internal message-processing function CWnd::OnWndMsg sees a message map entry with signature code AfxSig_b_W_uu, it converts the arguments like so:
AFX_MSGMAP_ENTRY* lpEntry = // find it
switch (lpEntry->nSig) {
case AfxSig_b_W_uu:
CWnd* pWnd = CWnd::FromHandle(reinterpret_cast<HWND>(wp));
UINT arg1 = LOWORD(lp);
UINT arg2 = HIWORD(lp);
// call handler w/converted args
*pRes = (*lpEntry->pfn)(pWnd, arg1, arg2);
•••
The point of the signature code is that it tells OnWndMsg how to convert WPARAM and LPARAM without knowing the handler function at compile time. I've glossed over some gnarly details (MFC uses a giant union to cast the function pointer to a function with the appropriate signature), but you get the basic idea. The mechanics aren't important. What's important is that MFC uses an enum to identify the signature of each handler function; the ON_WM_ XXX macros create message map entries with the proper enum codes; and CWnd::OnWndMsg uses the codes to convert WPARAM and LPARAM values to MFC objects before calling your handler function. The result is that your handler functions can deal with MFC objects instead of raw handles.
How does all this relate to your OnMenuSelect problem? Apparently one day the friendly Redmondtonians embarked on a code cleanup because many AfxSig codes are equated to other codes. For example:
// in afxmsg_.h
enum AfxSig {
•••
AfxSig_bWww = AfxSig_b_W_uu,
};
In other words, AfxSig_bWww is another name for AfxSig_b_W_uu. It appears the Redmondtonians adopted the underbar convention in a later release, no doubt because something like uuu is ambiguous (could be either u_uu or uu_u). To maintain backward compatibility, they kept the old names—like AfxSig_bWww. This is where the OnMenuSelect bug crept in. If you look in afxmsg_.h, you'll see the following lines:
enum AfxSig
{
AfxSig_vwwh = AfxSig_v_uu_M,
•••
AfxSig_vwwh means two WORDs and a HANDLE returning void. AfxSig_v_uu_M means two UINTs and a CMenu returning void. WORDs and UINTs are interchangeable, but a HANDLE is definitely not equivalent to a CMenu. ON_WM_MENUSELECT creates a message map entry with AfxSig_vwwh (LPARAM is HMENU), but CWnd::OnWndMsg reads it as AfxSig_v_uu_M and converts LPARAM to CMenu*. Oops. Whoever equated AfxSig_vwwh with AfxSig_l_uu_M forgot to change the declaration for OnMenuSelect. The code does actually run, but hSysMenu has the wrong type.
Now that I've explained the bug, what can you do about it? The easiest thing is to ignore the declaration and cast hSysMenu to a CMenu* in your handler.
void CMainFrame::OnMenuSelect(... HMENU hSysMenu)
{
CMenu* pMenu = (CMenu*)hSysMenu;
UINT nItems = pMenu->GetMenuItemCount();
// etc..
}
Finally, I should point out that despite its name, hSysMenu is not the system menu, but the menu that was clicked.
Q Your column in the August 2002 issue of MSDN® Magazine has been very helpful in converting from C++ to C#. However, I need to call a function in a C/C++ DLL that has a void* as one of its parameters. The void* can be a pointer to a short, long, or character array depending on the value of another parameter, the parameter ID. How should I write the C# wrapper to handle this? Here's an example of my code in C:
// In DLL's header (.h) file:
GetParam(short param_id, void* param);
// In my program:
unsigned short width;
unsigned long exp_time;
char cam_name[50];
GetParam(PARAM_WIDTH,(void*)&width);
GetParam(PARAM_TIME, (void*)&exp_time);
GetParam(PARAM_CAM_NAME, (void*)&cam_name);
The DLL figures out the correct type for param based on param_id. It's a third-party DLL; I didn't write it, so I have no control over it. I can't figure out how to declare GetParam in C#. Do you have any suggestions?
Earl Izydore
A Man, your DLL is sooooo retro. Parameter IDs, void*s... It's like programming the 8080 (for those of us who remember what it was). And to think here we have entered the age of C# and the Web-programmable TiVo, and we're still dealing with grody code like this. Never fear, C# can handle any interop conundrum a legacy programmer can conjure. The reason you're having trouble figuring out how to declare GetParam is because one declaration isn't enough. You need three.
Figure 2 shows a DLL I wrote to simulate your situation. MyLib.dll has a single function, GetParam, that mimics what you've described. The first argument is an enum code that's 1, 2, or 3, depending on which parameter you want to obtain: width, time, or name. The value is returned through the second argument, a void*. The actual type depends on which parameter you request: short for width, long for time, and string (char*) for name.

Figure 2 MyLib.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
// This DLL has a single function, GetParam, which gets the value of three
// different parameters: width, time and name. The parameter is returned
// through a void* pointer which can be a short, long, or string
// depending on which parameter is requested.
//
// You would of course never write such bad code as this; the example is
// intended to mimic old-style legacy code that does this sort of thing.
// The companion program test.cs shows how to call this DLL from C#/.NET
// in a typesafe manner.
//
#include <string.h>
enum {
PARAMID_WIDTH=1,
PARAMID_TIME,
PARAMID_NAME
};
// This is normally defined in windows.h:
#define CALLBACK __stdcall
//////////////////
// Exported fn to get parameter. 1st arg says which param to get; 2nd
// parameter is output buffer whose type depends on param_id.
//
extern "C" __declspec( dllexport ) int GetParam(int param_id,
void* param)
{
switch (param_id) {
case PARAMID_WIDTH:
*((short*)param) = 17; // return param is short
break;
case PARAMID_TIME:
*((long*)param) = 230001; // return param is long
break;
case PARAMID_NAME:
strcpy((char*)param,"Goofy"); // return param is string (char*)
break;
default:
return -1;
}
return 0;
}
Figure 3 shows how to call MyLib from C# in a bug-proof, type-safe manner. The trick is to use three overloaded import declarations, each with a different signature for the different parameter types. Don't forget that in C#, just like in C++, you can overload your functions. That is, you can write several functions with the same name but different argument types. This applies to interop/import declarations as well as ordinary functions—er, I mean methods. The compiler's IQ is high enough to figure out which overloaded method it should call based on the arguments you called it with. Once you realize you can write three declarations, each one uses straightforward rules from Basic Interop Programming 101. To marshal a pointer-to-short or pointer-to-long, use a reference; to marshal an in/out string, use StringBuilder. Figure 3 shows the exact syntax.

Figure 3 Test.cs
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
// This program illustrates how to use several overloaded interop
// declarations to wrap a DLL function that takes a void* parameter that
// can be different things depending on an op code.
//
using System;
using System.Text;
using System.Runtime.InteropServices;
//////////////////
// This class wraps the C DLL (MyLib.dll).
//
class MyLib {
public enum PARAMID { WIDTH=1, TIME, NAME };
// Three GetParam overloads, one for each signature (return/argument
// types). Note that in C#, a pointer-to-int/short/long is just a ref
// parameter, and StringBuilder is used to marshal in/out character
// arrays (strings are call-only, can't be returned).
//
// These imports are private. Callers must go through the GetXxx
// methods,which eliminate the possibility of using the wrong
// parameter ID.
//
[DllImport("MyLib.dll")]
private static extern int GetParam(PARAMID param_id, ref short wid);
[DllImport("MyLib.dll")]
private static extern int GetParam(PARAMID param_id, ref long time);
[DllImport("MyLib.dll")]
private static extern int GetParam(PARAMID param_id, StringBuilder s);
// These wrappers further eliminate the need for param_id.
// Op codes and void*s are soooo yucky!
//
public static short GetWidth() {
short wid=0;
GetParam(PARAMID.WIDTH, ref wid);
return wid;
}
public static long GetTime() {
long time=0;
GetParam(PARAMID.TIME, ref time);
return time;
}
public static String GetName() {
StringBuilder name = new StringBuilder();
GetParam(PARAMID.NAME, name);
return name.ToString();
}
}
//////////////////
// App class with Main entry calls MyLib functions.
//
class MyApp {
// main entry point
[STAThread]
static int Main(string[] args) {
Console.WriteLine("Width is {0}", MyLib.GetWidth());
Console.WriteLine("Time is {0}", MyLib.GetTime());
Console.WriteLine("Name is {0}", MyLib.GetName());
return 0;
}
}
With the new declarations in hand, all you have to do is call GetParam with the proper argument type. C# knows which method to call and how to marshal the arguments. Amazing, isn't it? Of course, you still have to be careful to use the correct param_id/argument type combination. To avoid even the possibility of screwups, you can go one step further, as shown by the code in Figure 3, where the three GetParam overloads are declared private so only the wrapper class itself can call them. Instead of GetParam, programmers call new public methods GetWidth, GetTime, and GetName. For example:
// get "width" parameter
short width = MyLib.GetWidth();
MyLib.GetWidth hides param_id, thereby eliminating the possibility of an accidental param_id/type mismatch.
public static short GetWIdth() {
short wid=0;
GetParam(PARAMID.WIDTH, ref wid);
return wid;
}
Any time you import retro DLLs into your C# application, it's always a win to spend a few minutes doing it right. If you wrap the DLL in type-safe declarations, you'll spare yourself the headache of chasing bugs later.
Q I'm trying to write a custom file open dialog that filters a certain file name from the dialog. I use the OFN_ENABLEINCLUDENOTIFY style and handle the CDN_INCLUDEITEM notification. Unfortunately, the dialog box always ignores my return value if the item has the SFGAO_FILESYSTEM and SFGAO_FILESYSANCESTOR attributes. In other words, if I'm dealing with normal files and directories, or folders that are the parent of a file system folder (such as My Computer), then by design the OFN_ENABLEINCLUDENOTIFY style doesn't apply. My question is, how can I work around this problem? How can I remove certain files from the listview box?
Q I'm trying to write a custom file open dialog that filters a certain file name from the dialog. I use the OFN_ENABLEINCLUDENOTIFY style and handle the CDN_INCLUDEITEM notification. Unfortunately, the dialog box always ignores my return value if the item has the SFGAO_FILESYSTEM and SFGAO_FILESYSANCESTOR attributes. In other words, if I'm dealing with normal files and directories, or folders that are the parent of a file system folder (such as My Computer), then by design the OFN_ENABLEINCLUDENOTIFY style doesn't apply. My question is, how can I work around this problem? How can I remove certain files from the listview box?
Oded Gottdenker
A Gosh, will there ever be an end to questions about the file open dialog? I guess since almost every application opens files, customizing file open is natural. And what's more natural than filtering the file names in the list? The most common way to filter files is by the file name extension—.txt, .doc, or .whatever—but what if you want to filter based on some other criterion, such as the last-modified date, owner, or whether the file name rhymes with "dimple"?
At first glance, Windows® seems to offer exactly what you want. If you create your dialog with OFN_ENABLEINCLUDENOTIFY, Windows sends your hook procedure a CDN_INCLUDEITEM notification for every item it adds to the open list. If you return FALSE, Windows excludes the item. The problem is, Windows doesn't notify you for ordinary files, only pseudo-objects like namespace extensions. When you read the documentation through a magnifying glass, the print is perfectly clear: "The dialog box always includes items that have both the SFGAO_FILESYSTEM and SFGAO_FILESYSANCESTOR attributes, regardless of the value returned by CDN_INCLUDEITEM." Apparently the Redmondtonians added CDN_INCLUDEITEM for their own purposes, which didn't include filtering ordinary file names. Sigh.
There's no need to reach for your hanky. What the Redmondtonians taketh away, you can always add on your lonesome. I modified last month's OpenDlg program to add a file filter that works for ordinary files (see C++ Q&A: Retrieving Hidden Path Names, Mouse Events in C#). Faithful readers will recall that last month I showed how to get the true path name of files listed in the open dialog's list view. I wrote a class, CFileDlgHelper, with a function GetItemPathName to get the true path name of any item in the open dialog's list view. This month I'll use CFileDlgHelper to filter the file names.
Figure 4 shows the code. CMyOpenDlg::FilterFiles is the function that does the work. It iterates the file names in the list, calling a virtual function IncludeFile for each one. If IncludeFile returns TRUE, FilterFiles does nothing. If IncludeFile returns FALSE, FilterFiles deletes the file name from the list view. Here's what it looks like in pseudocode:
void CMyOpenDlg::FilterFiles()
{
for (CString pathname = /* each listview file */) {
if (!IncludeFile(fn)) {
// delete it
}
}
}

Figure 4 MyDlg
MyDlg.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#include "FileDlgHelper.h"
//////////////////
// My open dialog — customized to add preview, debug panes, and Select All
// button.
//
class CMyOpenDlg : public CFileDialog {
protected:
CFileDlgHelper m_dlghelper; // helper for file dialogs
CEdit m_edit1; // edit control for preview
CEdit m_edit2; // another for debug info
// Helpers
CString GetTextPreview(LPCTSTR pszPath);
void AddText(CEdit& wndEdit, LPCTSTR lpText, BOOL bClear=FALSE);
void ShowFileInfo();
public:
CMyOpenDlg();
virtual BOOL OnNotify(WPARAM wp, LPARAM lp, LRESULT* pRes);
// Handle CDN_ notifications
virtual void OnFileNameChange();
virtual void OnFolderChange();
virtual void OnTypeChange();
virtual BOOL OnIncludeItem(IShellFolder* ishf, LPCITEMIDLIST pidl);
// Command/message/UI handlers/overrides
virtual int DoModal();
virtual BOOL OnInitDialog();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnSelectAll();
afx_msg void OnUpdateSelectAll(CCmdUI* pCmdUI);
void FilterFiles();
virtual BOOL IncludeFile(CString pathname);
DECLARE_DYNAMIC(CMyOpenDlg)
DECLARE_MESSAGE_MAP()
};
MyDlg.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#include "StdAfx.h"
#include "MyDlg.h"
#include "FileDlgHelper.h"
#include "Resource.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// IDs of controls in Explorer-style dialog, from Spy
#define ID_FILESOFTYPE 0x0441
#define ID_FILENAME 0x0442
#define ID_LOOKIN 0x0443
IMPLEMENT_DYNAMIC(CMyOpenDlg, CFileDialog)
BEGIN_MESSAGE_MAP(CMyOpenDlg, CFileDialog)
ON_COMMAND(ID_SELECT_ALL,OnSelectAll)
ON_UPDATE_COMMAND_UI(ID_SELECT_ALL,OnUpdateSelectAll)
ON_WM_SIZE()
END_MESSAGE_MAP()
//////////////////
// does path ends in ".txt" ?
//
static BOOL IsTextFileName(CString fn)
{
fn.MakeLower();
return fn.Right(4)==_T(".txt");
}
////////////////
// Constructor
//
CMyOpenDlg::CMyOpenDlg() : CFileDialog(TRUE,
NULL, // no default extension
NULL, // ..or file name
OFN_HIDEREADONLY|OFN_ALLOWMULTISELECT,
_T("Text Files (*.txt)|*.txt|All Files (*.*)|*.*||"))
{
m_ofn.lpstrTitle = _T("Select a file or folder");
}
//////////////////
// Initialize dialog.
// For Explorer, this is actually a child of the main dialog.
//
BOOL CMyOpenDlg::OnInitDialog()
{
// subclass controls...
m_edit1.SubclassDlgItem(IDC_MYDLGINFO1, this);
m_edit2.SubclassDlgItem(IDC_MYDLGINFO2, this);
// initialize helper
m_dlghelper.Init(this);
return CFileDialog::OnInitDialog();
}
//////////////////
// Select all text files.
//
void CMyOpenDlg::OnSelectAll()
{
CFileDlgHelper& fdh = m_dlghelper;
CListCtrl* plc = fdh.GetListCtrl();
for (int i=0; i<plc->GetItemCount(); i++) {
CString fn = fdh.GetItemPathName(i);
if (IsTextFileName(fn)) {
plc->SetItemState(i,LVIS_SELECTED,LVIS_SELECTED);
}
}
plc->SetFocus();
}
//////////////////
// Update "Select All" button: disable if no .txt files.
//
void CMyOpenDlg::OnUpdateSelectAll(CCmdUI* pCmdUI)
{
CFileDlgHelper& fdh = m_dlghelper;
CListCtrl* plc = fdh.GetListCtrl();
for (int i=0; i<plc->GetItemCount(); i++) {
CString fn = fdh.GetItemPathName(i);
if (IsTextFileName(fn)) {
pCmdUI->Enable(TRUE);
return;
}
}
pCmdUI->Enable(FALSE);
}
//////////////////
// DoModal override: use my template
//
int CMyOpenDlg::DoModal()
{
// Add my custom dialog to bottom
m_ofn.lpTemplateName = MAKEINTRESOURCE(IDD_MYOPENDLG);
m_ofn.Flags |= OFN_ENABLETEMPLATE;
return CFileDialog::DoModal();
}
//////////////////
// User selected new file (CDN_SELCHANGE): show info and filter file names
//
void CMyOpenDlg::OnFileNameChange()
{
ShowFileInfo();
FilterFiles();
}
//////////////////
// User selected new folder (CDN_FOLDERCHANGE): show info and filter
// file names
//
void CMyOpenDlg::OnFolderChange()
{
ShowFileInfo();
FilterFiles();
}
//////////////////
// User selected new file type (from drop down)
//
void CMyOpenDlg::OnTypeChange()
{
ShowFileInfo();
}
BOOL CMyOpenDlg::OnNotify(WPARAM wp, LPARAM lp, LRESULT* pRes)
{
OFNOTIFYEX& of = *(OFNOTIFYEX*)lp;
switch(of.hdr.code) {
case CDN_INCLUDEITEM:
*pRes = OnIncludeItem((IShellFolder*)of.psf,
(LPCITEMIDLIST)of.pidl);
return TRUE; // handled
}
return CFileDialog::OnNotify(wp, lp, pRes);
}
//////////////////
// This doesn't work for ordinary file system files. See explanation in
// documentation for CDN_INCLUDEITEM
//
BOOL CMyOpenDlg::OnIncludeItem(IShellFolder* ishf, LPCITEMIDLIST pidl)
{
CString fn = m_dlghelper.GetDisplayNameOf(ishf, pidl,
SHGDN_NORMAL|SHGDN_FORPARSING);
return Default();
}
//////////////////
// Common helper: show information in the preview and debug panes
//
void CMyOpenDlg::ShowFileInfo()
{
CFileDlgHelper& fdh = m_dlghelper;
CString path = GetPathName();
CString fldr = GetFolderPath();
// Create debug message
//
CString s;
s.Format(_T("GetPathName=%s\nGetFolderPath=%s\n"),
(LPCTSTR)path, (LPCTSTR)fldr);
CListCtrl* plc = fdh.GetListCtrl();
s += _T("Selected:\n");
int nSelected = 0;
POSITION pos = plc->GetFirstSelectedItemPosition();
while (pos) {
int i = plc->GetNextSelectedItem(pos);
CString temp;
temp.Format(_T(" %s %s = %s\n"),
(LPCTSTR)fdh.GetItemText(i),
fdh.IsItemFolder(i) ? _T("(FOLDER)") : _T(""),
(LPCTSTR)fdh.GetItemPathName(i));
s += temp;
nSelected++;
}
AddText(m_edit2, s, TRUE);
// Create preview text
//
s.Empty();
if (nSelected==1 && IsTextFileName(path)) {
s = GetTextPreview(path);
} else if (nSelected>1) {
s = _T("[Multiple selected]");
} else if (nSelected==0) {
s = _T("[None selected]");
}
AddText(m_edit1,s,TRUE);
}
//////////////////
// Preview: Read first 256 characters of text file
//
CString CMyOpenDlg::GetTextPreview(LPCTSTR pszPath)
{
const int BUFLEN=256;
char buf[BUFLEN];
CString text;
CFile f;
if (f.Open(pszPath,CFile::modeRead)) {
int len = f.Read(buf, BUFLEN);
text += buf;
if (len==BUFLEN)
text += _T("...");
}
return text;
}
//////////////////
// Helper adds text to an edit control that I've added to the open dialog
//
void CMyOpenDlg::AddText(CEdit& wndEdit, LPCTSTR lpText, BOOL bClear)
{
if (wndEdit) {
// Convert \n to \n\r for Windows brain-damaged edit control !&#%!
// It's 2003, and I'm still writing code like this!
//
LPCTSTR src = lpText;
TCHAR buf[1024];
TCHAR* dst = buf;
TCHAR* endbuf = buf + sizeof(buf) - 1;
while (*src && dst < endbuf) {
if (*src == '\n')
*dst++ = '\r';
*dst++ = *src++;
}
*dst = 0;
wndEdit.SetSel(bClear ? 0 : -1, -1); // end of edit text
wndEdit.ReplaceSel(buf); // append string..
wndEdit.SetSel(0,0);
}
}
//////////////////
// Dialog was sized: reposition my controls. Zzzzz.
//
void CMyOpenDlg::OnSize(UINT nType, int cx, int cy)
{
CWnd* pDlg = GetParent();
CRect rcDlg;
pDlg->GetWindowRect(&rcDlg);
// important to adjust my own size in case of places bar!!
SetWindowPos(NULL,0,0,rcDlg.Width(),rcDlg.Height(),
SWP_NOZORDER|SWP_NOREPOSITION);
CWnd* pCancel = pDlg->GetDlgItem(IDCANCEL);
ASSERT(pCancel);
CRect rcCancel;
pCancel->GetWindowRect(&rcCancel);
int cxRightMargin = rcDlg.right - rcCancel.right;
CWnd* pSelAll = GetDlgItem(ID_SELECT_ALL);
ASSERT(pSelAll);
CRect rc;
pSelAll->GetWindowRect(&rc);
rc.left = rcDlg.right - cxRightMargin - rcCancel.Width();
rc.right = rc.left + rcCancel.Width();
rc.bottom= rc.top + rcCancel.Height();
ScreenToClient(&rc);
pSelAll->SetWindowPos
(NULL,rc.left,rc.top,rc.Width(),rc.Height(),SWP_NOZORDER);
for (int i = IDC_MYDLGINFO1; i<= IDC_MYDLGINFO2; i++) {
CWnd* pInfoWnd = GetDlgItem(i);
ASSERT(pInfoWnd);
pInfoWnd->GetWindowRect(&rc);
rc.right = rcDlg.right - cxRightMargin -1;
ScreenToClient(&rc);
pInfoWnd->SetWindowPos
(NULL,rc.left,rc.top,rc.Width(),rc.Height(),0);
}
}
//////////////////
// Filter files: iterate listview, removing files for which IncludeFile
// returns FALSE.
//
void CMyOpenDlg::FilterFiles()
{
CFileDlgHelper& fdh = m_dlghelper;
CListCtrl* plc = fdh.GetListCtrl();
for (int i=0; i<plc->GetItemCount(); i++) {
CString fn = fdh.GetItemPathName(i);
if (!IncludeFile(fn)) {
plc->DeleteItem(i);
i—;
}
}
}
//////////////////
// Remove files that begin with _ (underbar).
//
BOOL CMyOpenDlg::IncludeFile(CString pathname)
{
if (pathname.Find("\\_")>=0) {
TRACE("CMyOpenDlg::IncludeFile: removing %s\n",(LPCTSTR)pathname);
return FALSE;
}
return TRUE;
}
For my test program, CMyOpenDlg::IncludeFile removes file names that begin with _ (underbar) or live in a folder whose name begins with _. FilterFiles and IncludeFile are straightforward. The only mystery is: who calls FilterFiles? You have to call FilterFiles any time the open dialog populates its listview with file names. This can happen when the user navigates to a new folder, or changes the file name or type (for example, by selecting "*.bmp" instead of "*.jpg", or typing "foo*" in the file name field). In the first case, Windows sends CDN_FOLDERCHANGE; in the second, CDN_SELCHANGE. So CMyDlg calls FilterFiles whenever it gets one of these notifications. Figure 4 shows the details.
If you're thinking CDN_INCLUDEITEM is kind of lame, be glad you're not programming in the Microsoft® .NET Framework. There, you can't customize the file open dialog at all because OpenFileDialog is sealed, which is a fancy way to say the Redmondtonians won't let you derive from it. As soon as I'm appointed Grand Programming Poobah, I promise one of the first action items on my legislative agenda will be to remove "sealed" from the C# vocabulary. The keyword will be shipped to the Museum of Restrictive Programming Techniques where it will be displayed in a glass case. Alas, my appointment may not be soon forthcoming, so until then, happy programming!
Send your questions and comments for Paul to cppqa@microsoft.com.
Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at http://www.dilascia.com.
|
|