t e m p o r a l 
 d o o r w a y 

Using Owner Draw To Line Up Items In A Combo Box

 

Introduction

This article shows how to line up items in columns in a combo box.

The normal TComboBox is a thin wrapper around the Windows common control. As such, it does not provide any method for displaying items as columns. However, it is simple to create a combo that lines up its items.

While this article shows the way to implement this as a "one off" in an application, you will usually want to implement a VCL component that descends from TComboBox instead. That allows you to reuse the component in many different contexts.

The Approach

TComboBox, like many VCL controls, allows you to completely control the drawing of the control using the OwnerDraw facility.

OwnerDraw requires you to set the Style to be one of csOwnerDrawFixed or csOwnerDrawVariable. The example uses csOwnerDrawFixed for simplicity.

csOwnerDrawFixed requires you to implement the OnDrawItem event handler, which actually draws the specific entry in the combobox.

csOwnerDrawVariable requires you to implement OnMeasureItem so that you can tell Windows how much space will be needed for your drawing activity and will not be discussed further.

The combobox knows how many items to draw based on the number of items in the Items property. However, you are not required to draw those items. Normally, however, you would do so. The example shows a simple form of this.

The Example

Here is the user interface with the combo dropped down...

You can see that the text is lined up, even though each word is of a separate length.

Here is the code for the form with the combo...

object Form1: TForm1
    Left = 4
    Top = 115
    Width = 251
    Height = 99
    Caption = 'Form1'
    Color = clBtnFace
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    OldCreateOrder = False
    PixelsPerInch = 96
    TextHeight = 13
    object ComboBox1: TComboBox
      Left = 16
      Top = 8
      Width = 145
      Height = 22
      Style = csOwnerDrawFixed
      ItemHeight = 16
      TabOrder = 0
      OnDrawItem = ComboBox1DrawItem
      Items.Strings = (
       'This,is,more,stuff'
       'There,are,some,things'
       'We,are,writing'
       '')
    end
end

The following is the OnDrawItem event handler...

void __fastcall TForm1::ComboBox1DrawItem(TWinControl *Control, int Index,
         TRect &Rect, TOwnerDrawState State)
{
   TComboBox *ComboBox = dynamic_cast<TComboBox *>(Control);
   static const int ColumnSize = 50;
   
   if (ComboBox)
   {
      TStringList *ColumnContent = new TStringList;

      try
      {
         ColumnContent->CommaText = ComboBox->Items->Strings[Index];

         for (int ColumnIndex = 0; ColumnIndex < ColumnContent->Count; ColumnIndex++)
         {
            ComboBox->Canvas->TextOut // Don't use TextRect, because that call clears the line
            (
               Rect.Left + ColumnIndex * ColumnSize,
               Rect.Top,
               ColumnContent->Strings[ColumnIndex]
            );
         };
      }
      __finally
      {
         delete ColumnContent;
      };
   };
}

The event handler expects commas in the Items property. After it has verified that it is being called for a TComboBox (the dynamic_cast returns null if it is not), the handler creates a TStringList to leverage the ability of the TStringList to parse comma-delimited text with its CommaText property. Then it iterates across the string list to extract the words to display, and displays them at a specific and fixed pixel offset. Note that the display uses the Rect argument as the basis of the offset; this is because TextOut operates in window-relative coordinates, and, otherwise, the text will not be properly offset from the top of the dropped down combobox (which is, itself, a window).

Also note the comment on TextOut - using TextRect, which might seem natural, will cause only the last column to be displayed, as it apparently blots out the background with the background color before drawing text.

The exception block uses __finally to ensure that the storage for the string list is reclaimed.

 

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved