PopulateHelper: A Templated Solution for Managing Strings in Control
Recently, I worked on a project where, depending on the scenario, I either had to populate a list-box (CListBox) or a combo-box (CComboBox) control with a list of strings acquired via a database connection. Of course, I also needed the ability to extract the string at a given index within the control. I developed a class that, given an STL vector of strings, would fill a control with these strings. CListBox and CComboBox are MFC objects that both derive from CWnd, and not a super-class that offers an AddString() method, for example. Thus, if I wanted to reduce the amount of methods in my class, the traditional polymorphic manner of collapsing related functionality into a single entity was not available to me. I could have implemented overloaded forms of my methods, as shown in Listing 1.
While the code shown in Listing 1 works, this solution simply cries out for a template-based solution, as shown in Listing 2. Note that in Visual C++ .NET 2003, template member functions in nontemplate classes are now fully supported.
This is simple enough and elegant. However, recall that I also wanted a means to extract the string given an index into the control. In reading the MSDN documentation, I was dismayed to learn that, while PopulateHelper::getData() worked when specialized for a CComboBox control, it would not compile when used with a CListBox. Why, you ask? The reason involves the call to GetLBText(), which is not provided in CListBox and, hence, the compilation error; see Listing 3. It's unclear why Microsoft chose the names GetLBText() for CComboBox and GetText() for CListBox, when the input arguments for the methods are identical (as one would suspect, given the fact that the two controls are so similar in functionality).
So now my conundrum was that if I wanted to implement a truly generic utility class (generic in the sense that it worked across both CComboBox and CListBox controls), I would have to fall back on C++ overloaded methods for the latter method, due to the fact that Microsoft's interfaces were not "orthogonal," so to speak. Inspired by Andrei Alexandrescu's Modern C++ Design (Addison-Wesley, 2001), where he describes how to architect extremely useful template classes or structs he deems "policies" (see Chapter 1), I implemented PopulateHelper as shown in Listing 4.
Granted, Listing 4 might contain more code than if I had overloaded PopulateHelper::getData() on both CComboBox and CListBox. However, in the interest of clarity, one could argue that PopulateHelper::fill() should be overloaded as well. Moreover, there are numerous other instances in MFC and other third-party APIs where this lack of orthogonality can cause problems. What I've shown here is how policy-driven class design can help steer the developer around these issues by using the template support of Visual C++ .NET 2003 to its fullest extent.
Safer Node Browsing with Microsoft's XML DOM
by Matthew Wilson
When working with the Microsoft XML DOM component, I encountered a rather nasty bug. Basically, when browsing child nodes using the node-list object returned from IXMLDOMNode::get_ childNodes() it is sometimes useful to call the IXMLDOMNodeList::reset() method in order to ensure that any previous iteration is cancelled, and that iteration starts from the beginning of the node sequence.
However, when calling this method on a comment node (nodeName: "#comment"), it crashes somewhere (at address offset of 0x13447) inside the call to reset(). Other nodes with empty child sequences do not exhibit the same behavior.
There are a couple of workarounds. You could remember to refrain from enumerating the child nodes of comment nodes within your application code, but a much better solution is to make sure you always call the IXMLDOMNodeList::get_length() method and then only call reset() when the length is greater than 0. This is, presumably, pretty efficient, compared to calling, say, IXMLDOMNodeList::get_item() or IXMLDOMNodeList::get__newEnum() and having to release the objects returned. This technique can be codified in the following simple free function:
inline void reset_node_list(IXMLDOMNodeList *nodelist) { long listLength; if( SUCCEEDED(nodelist->get_length(&listLength)) && listLength > 0) { nodelist->reset(); } }
or within your wrapper class(es). In the child_node_sequence class in the XMLSTL DOM library (http://xmlstl.org/), I use the technique in the begin() method when starting a node sequence iteration.
The XML DOM component I have encountered this bug with is msxml3.dll, version 8.30.9926.0. It may or may not be present in other versions, but that is not really the point. In many deployment scenarios, you may not wish (or be able) to change the installed version of the XML DOM component, so you should code defensively and do the check.
Beware Null ListViewSubItems in .NET
by Matthew Wilson
In a recent .NET application a simple network monitor using ICMP I came across a potentially dangerous artifact of the behavior of the System.Windows.Forms.ListViewItem type. The ListViewItem type has a property, SubItems, that provides access to the subitems (which includes the main item itself) for the item as a collection of type System.Windows.Forms.ListViewItem.ListViewSubItemCollection. In order to add subitems to an existing item, you simply call Add() on the collection instance.
In my application, I wanted to be able to change the subitems in response to various events after the creation of the item. Further, I wanted to be able to change the order of "address" and "status" subitems at compile time by using the manifest constants ADDRESS_INDEX and STATUS_INDEX. This was to enable me to easily change the column order before the final version of the program.
Understandably, using the index operator of the collection to update a subitem resulted in a System.OutOfRangeException being thrown if the subitem did not already exist. This is the case even when you are specifying the "end" index (e.g., index 3 for an array with three items at 0, 1, and 2).
The solution to this is to create the subitems with appropriate initial values at the time of the item, and adding them to the collection, as in:
ListViewItem item = m_items.Items.Add(host); ... // sometime later item.SubItems.Add(""); item.SubItems.Add(""); ... // sometime later subItems[ADDRESS_INDEX] = new ListViewItem.ListViewSubItem(...); subItems[STATUS_INDEX] = new ListViewItem.ListViewSubItem(...);
Being a C++ kind of guy, I didn't like the idea of creating subitems in the call to Add("") which were going to be thrown away. So I tried changing the calls to Add():
item.SubItems.Add((ListViewItem.ListViewSubItem)null); item.SubItems.Add((ListViewItem.ListViewSubItem)null);
This works fine in creating the array (within the enclosing form's constructor), but when the form is "Run()", the program terminates. The exception is, bizarrely, a System.OutOfMemoryException, though the additional information does give "Error creating window handle".
From my point of view, this is an error. The call to Add with null should throw a meaningful exception System.NullReferenceException rather than the program dying later in what could, in a more complex application, seem to be an unrelated manner.
Solution: Don't use null subitems!
Inserter Function Objects for Windows Controls
by Matthew Wilson
STL is receiving ever-increasing recognition in the C++ development community as more STL-compliant software in the form of containers, algorithms, adaptors, and function objects becomes available for our use.
As part of the WinSTL project (http://winstl.org/), I've written function objects that provide back, front, and indexed insertion for list and combo boxes.
The listbox_front_inserter is defined in Listing 5. It can be used with any sequence that contains items that can be converted to CStrings, as in:
vector<CString> strings( . . . ); HWND hwndListbox = . . .; . . . for_each(strings.begin(), strings.end(), winstl::listbox_front_inserter(hwndListBox));
Each item will be inserted into the beginning of the list.
The listbox_back_inserter, which inserts items at the end of the list, has the same definition, but for the details of the sent message, as in:
::SendMessage(m_hwndListbox, LB_ADDSTRING, static_cast<WPARAM>(-1), reinterpret_cast<LPARAM>(s));
The listbox_add_inserter adds items into the list according to its sorted position, and uses:
::SendMessage(m_hwndListbox, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s));
There are corresponding function objects for working with combo boxes: combobox_front_inserter, combobox_back_inserter, and combobox_add_inserter.
Included in the source code archive is a program that demonstrates all six
function objects by adding the items in the PATH environment variable to the
list and combo boxes in front, back, and add forms.
w::d
George Frazier is a software engineer in the System Design and Verification group at Cadence Design Systems Inc. and has been programming for Windows since 1991. He can be reached at georgefrazier@ yahoo.com.