Using the VCL source to track down bugs

by Brent Knigge

You have probably encountered debugging situations where you lost all hope of ever finding the source of an error. When nothing appears to be wrong with your code, you may have to consider the VCL as a possible source of the problem.

Recently I derived a component from TListBox in order to add a property that mimics the HideSelection property found in TTreeView. I did this so the selection(s) of the list box would be hidden when the control loses focus. This helps eliminate confusion for the user when it appears that multiple controls have focus.

I decided to use the Selected property of TListBox to hide the selection when the list box loses focus. This property returns true if a particular item is selected, and false if no item is selected. You can also set the Selected property in order to select or deselect an item. Selected is an indexed property. That is, you ask for the selected state of a particular item by specifying the item index. For example:

if (ListBox1->Selected[0])
  // the item is selected

Remember that the first item has an index of 0 and the last item has an index of Items->Count - 1. I wanted to write code that iterates over the items in the list box, setting the Selected property of each item to false. My plan was to do this in the OnExit event handler so that the item states were cleared when the list box lost focus. My initial testing was done using a regular list box. The OnExit event handler looked like this:

void __fastcall 
TForm1::ListBox1Exit(TObject *Sender)
{
  for(int i=0;
      i<ListBox1->Items->Count;i++)
    ListBox1->Selected[i] = false;    
}

I added a few input controls to the form and ran the application. I selected a list box item and then tabbed to another control on the form. What happened at that point was a bit confusing. The VCL gave me an EListError exception saying that list index 0 was out of bounds.

Okay, so I might have made a mistake in the for loop. After examining the code in the loop, I couldn’t see any reason for the list index out-of-bounds error. Further investigation revealed that setting the MultiSelect property of the list box to true did not result in an exception. That wasn’t particularly helpful, though, because I needed a customized list box that could handle both single and multiple selection.

Still investigating the cause of the problem, I started hunting around in the VCL source. I found these lines in STDCTRLS.PAS:

procedure TCustomListBox.SetSelected(
  Index: Integer; Value: Boolean);
begin
  if SendMessage(Handle, LB_SETSEL, 
    Longint(Value), Index) = LB_ERR then
    raise EListError.CreateFmt(
      SListIndexError, [Index]);
end;

Now that I knew where the exception was being raised, I could do some further investigation to find the cause of the problem.

Studying the documentation for the LB_SETSEL message, I found that an application sends this message to a list box in order to select an item in a multiple-selection list box. If an error occurs (such as when the list box only supports single-selection) the return value is LB_ERR, and the exception is raised.

Fortunately, I found that there is a way to do what I wanted for a single-selection list box using the Windows API. What I found was that I needed to send the list box an LB_SETCURSEL message. This message is the one that should be used for single-selection list boxes, instead of the LB_SETSEL message.

At this point I simply modified the code to check the value of the MultiSelect property, and take appropriate action based on the value of this property. The following code shows an updated version of my test code, which works whether MultiSelect is true or false:

void __fastcall 
TForm1::ListBox1Exit(TObject *Sender)
{
  if(ListBox1->MultiSelect) {
    for(int i=0;
      i<ListBox1->Items->Count;i++)
      ListBox1->Selected[i] = false;
  }
  else // single selection only.
    SendMessage(ListBox1->Handle, 
      LB_SETCURSEL, -1, 0);
}

This experience taught me that the initial error message that I received from the VCL did not accurately convey the actual problem. In fact, it may have hindered my investigation because I was focusing on the for loop as the source of the problem. As it turns out, there was nothing wrong with my code. The VCL simply was not behaving as I thought it should. Whether this is a bug in TListBox depends on your perspective. The point is that you should not be afraid to open up the VCL source files and examine them to understand what is going on in a given situation. Obviously the VCL source is in Object Pascal. Still, it is easy enough to read and understand, even if you don’t have any Pascal experience.

When tracking down bugs, be sure to examine all possible sources. Look at your own code first, of course, but don’t stop there if the problem persists. Probe deeper by looking into the VCL for possible problems. Once a problem is found and identified, you can usually find a workaround using the Windows API. In the end it may be the difference between understanding a problem and living with a puzzling mystery.