July 1996
Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows++: Writing Reusable Code in C++ (Addison-Wesley, 1992). QI am trying to fill a list box with filenames using the MFC function CWnd:: DlgDirList. I am using MFC 4.0 and Windows" 95. The list box fills fine, but instead of long filenames I get the short MS-DOS names. How can I get long filenames in my list box? Charles Parker ACWnd::DlgDirList is a wrapper for the Windows API DlgDirList. Under Microsoft" Windows 95, this function thunks down to the 16-bit version of DlgDirList in USER.EXE. In other words, DlgDirList is really a 16-bit function inherited from Windows 3.1. Because DlgDirList was never ported, it displays the old MS-DOS filenames. (The version of DlgDirList in Windows NT" works fine, as you would expect, since Windows NT has only long filenames.) PRB report Q131286 in the MSDN CD suggests converting your path spec to its short name by calling GetShortPathName before you pass the file spec to DlgDirList, but the PRB doesn't say how to make long names appear in your list box. In other words, the friendly Redmondtonians have cleverly spun the problem as not recognizing long filename specs rather than not displaying long filenames. They admit the flaw and then assert in their typically cryptic fashion, "this behavior is by design," without giving any reason. I suspect it has something to do with compatibility with old apps; maybe they were afraid some old programs that allocated thirteen bytes for 8.3 filenames (plus a zero at the end) would break when the list box got back too many characters under Windows 95. You'd think they'd at least give you a new flag or something to get the long filenames. In any case, the question is what can you do about it? Frankly, not much. You have to forget about DlgDirList and generate the list yourself. Fortunately, it's pretty easy. Two API functions provide all the interface you need to navigate the files in a directory that match a file spec like *.* or *.txt: _findfirst and _findnext, both defined in <io.h>. (Yes, it still exists, even in the land of Windows.) These functions fill a C struct called _finddata_t. The attrib member is a flag field that may contain any combination of the following flags: The various time_t members are what you'd expect; _fsize_t is the size of the file, and "name" is the filename relative to the current working directory. To use the findfile functions, you first call _findfirst with a file spec. This fills fdata with information about the first file in the current directory that matches *.txt. If such a file exists, its information is stored in the _finddata_t struct and ffhandle gets a value other than -1. Usually, you'll examine the attribute bytes to skip over files such as directories or hidden/system files-unless that's what you're interested in-and then call _findnext repeatedly to get information about other files that match your file spec. When there are no more files, _findnext returns a nonzero value, which is your cue to call _findclose to release the ffhandle. _findfirst and company are old functions that you probably know and love from MS-DOS, but they're still important, at times even essential, in Windows. Since this is a C++ column, I wrote a C++ class, CFileSpec, that encapsulates the findfile API (see Figure 1). CFileSpec is entirely self contained; it has nothing to do with list boxes. I use CFileSpec all the time, usually in MS-DOS-shell utility programs that do things with files. For example, I wrote a Unix-like wc (word count) utility using CFileSpec that counts pages as well as lines, words, and characters. CFileSpec is implemented entirely within a single header file, filespec.h, so all I have to do is #include it in any app that uses it. I don't have to worry about linking with an object file or library. The implementation itself is mostly straightforward. There are only two tricks you may not notice right away. First, I derived CFileSpec from _finddata_t, which is why CFileSpec::First and Next pass the this pointer as the second argument to _findfirst and _findnext. I use this tactic only because it makes accessing the information in _finddata_t easier. For example, you can write filespec.attrib to get the attribute byte or filespec.name to get the filename. This is marginally easier than writing something like filespec.data.name, which is what it would be if I made _finddata_t a member instead of a base class. The second trick is that there's no Close function to mirror _findclose. Instead, CFileSpec closes the handle in its destructor. Generally, I use CFileSpec to enumerate the files matching a single spec. However, just in case someone tries to call First with another file spec, CFileSpec::First also closes the findfile handle if it's already open. To test CFileSpec, I wrote a program called DIRLIST that compares the filenames generated by ::DlgDirList and CFileSpec (see Figure 2). As you can see from Figure 3, DlgDirList produces the short MS-DOS names, whereas CFileSpec gives you the long filenames. The main action in DIRLIST occurs when the dialog is initialized. The dialog's OnInitDialog handler subclasses the list boxes and calls CListBox::DlgDirList (actually it's in CWnd) for one list box. It uses CFileSpec to fill the other: Figure 3 Long filenames ahoy! This code adds all the "normal" files in the current directory to my list box. That is, names that do not represent hidden or system files or the names of subdirectories. It's a little more work than calling DlgDirList, but at least you get the long filenames. As an enhancement to CFileSpec, you could add another argument to CFileSpec::First that represents an attribute mask, which you would use to filter out files the way DlgDirList does. With this added functionality, you wouldn't have to check the attribute byte in your app since it would be part of CFileSpec. You could even derive a general-purpose enumerator class from CFileSpec. Such a class would have a function CFileEnumerator::Enumerate(const char* filespec)-perhaps with an attribute mask-and would do the First/Next enumeration, calling some other virtual function (something like CFileEnumerator::Action) for each filename. CFileEnumerator::Action would be abstract; that is, there would be no implementation in CFileEnumerator. Programmers using the file enumerator would have to derive from CFileEnumerator and implement their own Action function to do whatever they want with each file. Such enumerator classes are common in many C++ class libraries. In this case it seems more trouble than it's worth, since CFileSpec is already so simple. QI'm having a tough time trying to figure out how to change the colors of CEdit controls on a dialog box when the focus comes to them, then change back to normal when the focus is lost. I'm building a data-entry application with MFC 4.0. My application contains several dialog boxes and my client wants the current data-entry field to be a different color from the others (for example, white-on-red) so users can more easily see where they are on the form. When the user tabs to the next field, the first one loses focus and reverts to the common background color and the next one gets the focus and changes to the new "data entry color." This is trivial in Visual Basic" 4.0, so I figured it would be easy in MFC too. But I've tried different things and I can't get the (expletive deleted) CEdit controls to do my bidding. Am I just a klutz or is there something wrong with MFC? Don F. Ridgway Tampa, FL ANo, you're not a klutz, and no, there's nothing wrong with MFC. The difficulty you're having setting control colors is an artifact of Windows itself, and your question is one of the most common I get from readers. I've answered it in previous issues, but since MFC has a new mechanism you can use for setting colors (as of release 4.0), I thought I'd revisit the topic. In Windows, there are no simple properties for setting the foreground and background colors of an edit control (or any other dialog control). In Visual Basic, you can simply write, but behind the scenes, Visual Basic goes through a lot of rigamarole to make this work. The mechanics for setting the color of a Windows control require handling the special message WM_CTLCOLOR, which Windows sends when it's about to paint a control. I won't go into a detailed explanation here, as this topic is covered in the Win32" documentation for WM_CTLCOLOR and numerous articles in MSDN (including some of my own, probably). I'll just give you the quick recap for edit controls. To set the color of an edit control, you must first handle WM_CTLCOLOR and check for the code CTLCOLOR_EDIT, in which case you must return the brush you want to use for the background. Second, you must call SetBkColor and SetTextColor on the HDC that Windows passes as part of the WM_CTLCOLOR message. If you think all this is strange, you're in good company. At this point, I need to make a clarification. All of what I just said applies to 16-bit Windows only; in Win32, each of the WM_CTLCOLOR messages have been broken out into its own message. For example, there's WM_CTLCOLOREDIT for edit controls and WM_CTLCOLORBTN for buttons. However, MFC repackages these messages into old-style WM_CTLCOLOR messages, so if you implement your code using WM_CTLCOLOR (as opposed to WM_CTLCOLOREDIT), it'll work in either 16 or 32-bit Windows, which is why I've done it that way. If you only plan to use Win32, you can modify my code to handle WM_CTLCOLOREDIT directly and you won't need to check for the subcode CTLCOLOR_EDIT. Now, on with our regular programming. . . Aside from the obvious weirdness, one problem in particular with this scheme is that WM_CTLCOLOR is not sent to the control as you might hope or even expect. No, Windows sends WM_CTLCOLOR to the parent window-in this case your dialog-which means you now come to a fork in the road. You can implement the solution either as part of the parent dialog or as part of the control (by using a new technique called message reflection). The approach you choose depends on whether you consider the "data entry color" feature to be a feature of the edit control or of the dialog. I'll show you both solutions and describe the pros and cons of each. Let's start with the "data entry color" feature as part of the edit control. For this scenario, you want the edit control-that is, your program's CEdit-derived class-to implement the color feature. This is where the new MFC 4.x message reflection feature comes in. A sample program, called COLDLG1, shows how it works (see Figure 4). Figure 5 shows COLDLG1 in action with the focus control highlighted by red-on-yellow text. Figure 5 COLDLG1 Message reflection lets child windows handle certain messages that would normally go to the parent. For example, WM_CTLCOLOR. In the old days, there was no way to implement a nice, self-contained, object-oriented edit control that could handle its own colors. You had no choice except to handle WM_CTLCOLOR in the dialog. Later, MFC (and I can't remember exactly which version, but one of the early ones) gave us a virtual function, OnChildNotify, that provided a hook for child controls to handle certain parent messages. MFC routed several messages (WM_CTLCOLOR, WM_DRAWITEM, WM_COMMAND, and WM_NOTIFY, to name a few) to the child window's OnChildNotify before the parent got a chance to process them, so all you had to do was override OnChildNotify in your child control. That was pretty good, but it still resembled the old style of programming for Windows with message codes and switch statements. MFC 4.0 carried the idea one step further with message reflection. This is just a fancy name for the ability to handle child-based messages though message maps instead of OnChildNotify. · · · · · · As with other message-map macros, the name of the handler function, in this case CtlColor, is hardwired into the macro; you must use this name. The point is that, with the new message-map entry, WM_CTLCOLOR is handled by CColorEdit instead of its parent dialog. The CColorEdit control can set its own colors appropriately when the focus changes. With this information, you should be able to understand how COLDLG1 works-except for one little kludge. Normally, Windows doesn't repaint an edit control when it gains or loses focus. To make your colors effective, you must tell the edit control to repaint itself. Fortunately, this is trivial: when your edit control gets a WM_SETFOCUS or WM_KILLFOCUS, just Invalidate it and call UpdateWindow. Invalidate/UpdateWindow is the way in Windows to force any window to repaint itself. The dialog instantiates the three CColorEdit controls and the OnInitDialog handler subclasses them, whereupon the CColorEdit message map does its thing. As you'd expect, MFC provides other message reflection macros besides ON_WM_CTLCOLOR_REFLECT. Figure 6 shows the full list. For more information, read MFC Technical Note 62. Now, you may not consider this "data entry color" feature to be a property of the edit controls. You may instead consider it a property of the dialog. Suppose you add a new edit control to the dialog-should you really have to add a new m_edit4 and subclass it in CMyDialog::OnInitDialog? It might be nicer if the dialog detected edit controls automatically and set their colors appropriately. Figure 7 shows my sample program rewritten with a dialog-based implementation. As you would expect, it's a lot simpler. Since Windows already sends WM_CTLCOLOR to the dialog, there's no need to reflect it to the control. In fact, in this implementation, there's no CColorEdit class at all because the dialog handles WM_CTLCOLOR. It sets the appropriate color based on whether or not the edit control has focus. The dialog does the same Invalidate/UpdateWindow trick to repaint edit controls as they gain or lose focus. However, instead of handling WM_SET/KILLFOCUS, the dialog handles WM_COMMAND with notification codes EN_SET/KILLFOCUS since that's how the message arrives from Windows. By using ON_CONTROL_RANGE with a range from zero to infinity, CMyDialog effectively handles all EN_SET/KILLFOCUS notifications for all edit controls. While this approach is guaranteed to trap all EN_SET/KILLFOCUS notifications, there's a slight problem: it's not guaranteed to weed out spurious notifications from other kinds of non-edit controls if they send notification codes that match EN_SETFOCUS or EN_KILLFOCUS. Remember, control notifications are unique only to a particular kind of control. Buttons or combo boxes or flibbertiwidgets are free to use the same numeric values for their notifications as those used by other controls. When my dialog gets EN_SET/KILL_FOCUS, I must check that the control sending the notification is in fact an edit control before I repaint it. I do this by examining the name of its window class, which should be "Edit". The only alternative would be coding separate ON_EN_SET/KILLFOCUS message map entries for each edit control-but you'd have to add a message map entry every time you add a new edit control to your form, which defeats the purpose of a dialog that implements the color feature automatically. Personally, I think the second solution-changing the color as a property of the dialog-is the proper way to go for your app, but I've shown you both solutions because the first one is better in other situations. For example, if you had a special edit control for currency or a Social Security number that did something different with colors. Then, the behavior really is a property of the specific control, not of the dialog. You don't want every edit control to represent currency. Have a question about programming in C++? You can mail it directly to C++ Q&A, Microsoft Systems Journal, 825 Eighth Avenue, 18th Floor, New York, New York 10019, or send it to MSJ (re: C++ Q&A) via: Paul DiLascia struct _finddata_t {
unsigned attrib;
time_t time_create; /*-1forFATfilesystems*/
time_t time_access; /*-1forFATfilesystems */
time_t time_write;
_fsize_t size;
char name[260];
};
_A_NORMAL /* Normalfile-Noread/writerestrictions */
_A_RDONLY /* Read only file */
_A_HIDDEN /* Hidden file */
_A_SYSTEM /* System file */
_A_SUBDIR /* Subdirectory */
_A_ARCH /* Archive file */
_finddata_t fdata;
long ffhandle = _findfirst(*.txt, &fdata);
CFileSpec spec;
for (BOOL more=spec.First("*.*"); more; more=spec.Next()) {
if(!(spec.attrib&(_A_SUBDIR|_A_HIDDEN|_A_SYSTEM)))
m_lb2.AddString(spec.name);
}
Edit1.BackColor = Yellow
Edit1.ForeColor = Red
BOOL CMyEdit:OnChildNotify(UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT* pResult)
{
if (msg==WM_CTLCOLOR) {
// handle it
return TRUE; // handled
}
return FALSE; // let parent handle it
}
BEGIN_MESSAGE_MAP(CColorEdit, CEdit)
ON_WM_CTLCOLOR_REFLECT()
END_MESSAGE_MAP()
/////////////////
// Handle WM_CTLCOLOR reflected from parent:
//
HBRUSH CColorEdit::CtlColor(CDC* pDC, UINT nCtlColor)
{
// handle it
return NULL; // (or brush to use)
}
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_WM_CTLCOLOR()
ON_CONTROL_RANGE(EN_SETFOCUS, 0, 0xFFFFFFFF,
OnEnSetFocus)
ON_CONTROL_RANGE(EN_KILLFOCUS, 0, 0xFFFFFFFF,
OnEnKillFocus)
END_MESSAGE_MAP()
From the July 1996 issue of Microsoft Systems Journal.
Internet:
72400.2702@compuserve.com