Every time I work with a Windows list box control, the same thing happens--I want more! Most recently, I wanted to be able to change the color in which the list box displayed items. It seemed like a good idea to have the more interesting (and appropriate) items highlighted in red, while the rest of the items were black. The problem with this desire, of course, is that the list box simply wasn't made to handle different colors in its display. There's a solution to this dilemma, though, and its name is owner-drawn list boxes.
__fastcall TColorListBox::TColorListBox(TComponent* Owner)
: TCustomListBox(Owner)
{
Style = lbOwnerDrawVariable;
}
s
This
code simply tells the underlying list box component that it will be of the
owner-draw variable style. The owner-draw part indicates to the component that
you--the programmer--will handle the drawing part of the display. The variable
part indicates that you'd also like control over the height of each item in the
list. This step isn't strictly necessary for this component, but you might as
well learn about it!
void __fastcall TColorListBox::MeasureItem(
int Index, int &Height)
{
Height = 12;
}
In this case, you simply want to make each item be a certain size: 12 pixels
high. Note that you pass the Height parameter by reference, allowing it
to be modified within the method and returned to the calling function.
void __fastcall TColorListBox::DrawItem(
int Index, const Windows::TRect
&Rect, TOwnerDrawState State)
{
Canvas->Font->Color = GetColor(Index);
Canvas->TextRect(Rect, Rect.Left, Rect.Top,
Items->Strings[Index].
c_str());
}
There isn't a lot to the method. You get the color for your index by passing
the index of the item (given to you by the list box itself) to the GetColor
method. This method, which you'll write very shortly, simply returns the color
assigned to a given list box item.
Once you have the color, you just use the TextRect method of the Canvas property to display the string in the area given to you for this item. The area is defined by the height of the item (returned by MeasureItem) and the width of the list box. Given this information, you can easily display the string in your desired color.
Note that you use the text stored in the list box's Strings property. Doing so permits the list box to work exactly like a normal list box, but with added functionality!
To add the new property, you must first decide how to represent it internally. The internal representation may have nothing to do with the external representation to the programmer, but it will drive how that external representation works. In this case, since there's an indeterminate number of entries, I decided to use the Standard Template Library (STL) vector class to represent the colors. To do so, add the following section to your header file for TColorListBox:
private: std::vector<TColor> FColors;Next, you need to add the Color property definition to the class, using a public entry like the following:
__property TColor Colors[int nIndex] =
{read=GetColor, write=SetColor};
Again, there's nothing surprising here. The property is an array, so it takes
an index to represent the number of the item for which to set the color.
You also need to add the SetColor and GetColor methods. Here's the header file
entry for these methods:
protected:
virtual void __fastcall SetColor(
int Index, TColor clr );
virtual TColor __fastcall GetColor(
int Index );
Finally, you need to implement the SetColor and GetColor methods. Listing
A contains the relevant code extracts, which go in the source file
(TColorListBox.cpp).
Listing A: The SetColor and Get Color methods
void __fastcall TColorListBox::SetColor(
int Index, TColor clr)
{
if ( Index < 0 )
return;
// See if we already have this many elements
// in the array. If we don't do this it will
// throw an exception when we try to set the
// color.
if ( Index < FColors.size() )
FColors[Index] = clr;
else
{
// Hold onto the current size
int nCurSize = FColors.size();
// Resize the array
FColors.resize( Index+1 );
// Initialize the remainder of the
// entries to black (our default)
for ( int i=nCurSize; i<=Index; ++i )
FColors[i] = clBlack;
// Finally, set the one they want
FColors[Index] = clr;
}
}
TColor __fastcall TColorListBox::GetColor(int Index)
{
if ( Index >= 0 && Index < FColors.size() )
return FColors[Index];
return clBlack;
}
The code is pretty straightforward. For the set case, you simply check to see
if that many entries have already been created. If so, the entry the user wants
to set replaces the one already in the array. If the entry doesn't already
exist, the array is resized to the proper size and all elements are initialized
to the default black color.
For the get case, which we also use in the drawing code, you once again consult the array to see whether the element is already there. If so, you return the value at that location. If the array isn't yet that size, the user hasn't set a color for this item, and you return the default black color. Note that in all cases, you check for invalid entries, such as an index less than zero. Doing so is just good programming practice that you should use in your own coding.