Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Delphi TListbox: Why is Item Color Not Updating?

Struggling with item color updates in Delphi TListbox ownerdraw? Learn how to fix selection redraw issues using the State parameter.
Frustrated developer with a Delphi TListBox where selected item color does not change, highlighting OnDrawItem error with odSelected state Frustrated developer with a Delphi TListBox where selected item color does not change, highlighting OnDrawItem error with odSelected state
  • Most color problems in owner-draw TListBox happen because people ignore the odSelected state.
  • Wrong Canvas handling can cause display errors or text you cannot read.
  • Owner-draw mode gives you full control. But, it needs careful event handling for the program to be easy to use.
  • lbOwnerDrawVariable gives you more options for item height. This comes at the cost of performance.
  • For a better-looking screen, other tools like VirtualStringTree work better than making TListBox do too much.

Delphi TListBox Color Issues in OwnerDraw Mode

Using OwnerDraw mode in a Delphi TListBox often leads to a common problem. Selected items do not look selected on screen. This happens because the drawing code is wrong. When you do not handle the State parameter right during custom drawing, users lose important feedback. This causes confusion and a bad experience. This guide will explain why this problem happens. And then it will show you useful ways to make your Delphi TListBox work and look right.


Understanding Delphi TListBox and OwnerDraw

Delphi’s TListBox is a simple way to show a list of items and let people interact with them. Usually, it shows a list of text. Users can pick one or more items from this list. In its normal mode, TListBox automatically places, paints, and shows selected items. But sometimes you need more control. For example, you might want to show different colors, add Unicode icons, use different fonts, or use theme styles. In these cases, programmers often switch to OwnerDraw mode.

Let’s look at the different style options:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

ListBox Styles

  • lbStandard: This is the normal list box style. The VCL automatically draws each item. You cannot change much.
  • lbOwnerDrawFixed: This gives you the same item height for all items. It also gives you full drawing control through the OnDrawItem event.
  • lbOwnerDrawVariable: This lets item heights change. The OnMeasureItem event figures out each item’s vertical size before drawing.

When and Why to Use OwnerDraw

You should think about using OwnerDraw mode in these situations:

  • You are making a screen with specific colors where each list item shows a color based on its data.
  • You want to add things like icons or checkboxes, not just text.
  • You want multi-line descriptions under each item. This is especially true with lbOwnerDrawVariable.

But this extra control has a cost. You are fully responsible for how each item is shown. This includes colors, selection states, fonts, and spacing.


Behind the Scenes: The OwnerDraw Process

So, what happens when you turn on OwnerDraw mode in a TListBox?

Delphi calls the OnDrawItem event every time an item is drawn again. This event gives you the information you need to draw the item yourself.

Key Parameters in the OnDrawItem Event:

  • Index: This is the item's position, starting from zero.
  • Rect: This is the box that shows where you can draw.
  • State: This is a set of flags (TOwnerDrawState). They show if the item is selected, has focus, is off, and so on.

Once you have these, you use the Canvas object. This helps you paint the background and text. You must follow the selection rules and keep good color contrast.

How a Redraw Happens

  1. The list box sees a change. This could be an item selection, scrolling, or resizing.
  2. It then tells the part of the screen where the item(s) are to update.
  3. The VCL calls your OnDrawItem handler for each item that needs an update.
  4. You paint the items using the Canvas inside the Rect area.

Delphi will not paint anything for you in OwnerDraw mode. You are responsible for:

  • Filling the background
  • Drawing text or icons
  • Visual marks, like a focus box

If you do not handle any of these steps right, you might see incomplete drawings, selections you cannot see, or text on top of old drawings.


Root Cause: Ignoring the State Parameter

A common mistake when changing how a Delphi TListBox looks is writing the OnDrawItem handler without using the State parameter. To be exact, if you ignore the odSelected flag, you fail to show selected items apart from others.

Here is an example of wrong or too simple code:

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  ListBox1.Canvas.FillRect(Rect);
  ListBox1.Canvas.TextOut(Rect.Left + 2, Rect.Top, ListBox1.Items[Index]);
end;

This code basically paints all items the same way. It uses a white background with black text. It does not show selection or focus. Users can still click items, and in the program, they may be marked as selected. But there is no visual sign.

This mistake causes confusion on the screen. It also makes it hard for users to interact. Using the State parameter correctly is very important for the screen to act right and be easy to understand.


How to Fix It: Using odSelected Properly

The odSelected flag in the State set shows if the current item is selected. You can use if statements to change drawing settings based on this state. This makes the program interactive and clear again.

Here is a simple but complete example:

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  with ListBox1.Canvas do
  begin
    if odSelected in State then
      Brush.Color := clNavy
    else
      Brush.Color := clWhite;

    FillRect(Rect);

    if odSelected in State then
      Font.Color := clWhite
    else
      Font.Color := clBlack;

    TextOut(Rect.Left + 2, Rect.Top, ListBox1.Items[Index]);
  end;
end;

This code makes sure that:

  • A dark background (clNavy) is used when the item is selected.
  • Text becomes light (clWhite) so you can see it on a dark background.
  • Normal items keep their normal look.

Always match font and background colors that stand out from each other. This helps keep the text easy to read.


Canvas Basics: Painting Items Right

The Canvas object is like your artist’s brush in Delphi’s owner-draw system. It has ways to draw shapes, fill areas, write text, and set how text looks.

Common Canvas Methods

  • FillRect(Rect): This clears and paints the area with the current Brush.Color.
  • TextOut(X, Y, Text): This is a simple way to write text horizontally.
  • DrawFocusRect(Rect): This draws a focus border. It is especially good when you move through items with arrow keys.
  • DrawText(): This is an advanced way to draw text. It lets you wrap text, line it up, and add '…'.

Helpful Practices

  • Do not use hard-coded pixels everywhere. Use values from Rect to make sure your drawing adjusts.
  • Use InflateRect to add small padding.
  • Change Font.Style if you want to make items bold when you hover over them or select them.

Set up Canvas settings before you call any draw operations. This is because their effects happen right away. They use only the settings you have now.


Pitfalls to Avoid

When you make custom-draw code for a TListBox in OwnerDraw mode, here are the mistakes to avoid:

  • 🔴 Missing odSelected or odFocused handling: Your control might not respond or ignore keyboard navigation.
  • 🔴 Failing to use FillRect: Old pictures behind the item might show through. This creates ghosting visual errors.
  • 🔴 Wrong drawing coordinates: Always place things starting from Rect.Left and Rect.Top.
  • 🔴 Hardcoded fonts or colors with no contrast: There are many problems for people who need help seeing.
  • 🔴 Using slow operations inside OnDrawItem: Every time the item repaints, it should be as fast as possible.

And then, remember that system themes and style managers can change your work. This happens if you do not turn them off or follow them.


Diagnosing Display Issues

When things do not look right, finding and fixing owner-draw problems can be hard. Here is how to check and fix them:

📌 Tips:

  • Add a breakpoint inside OnDrawItem. This helps you see what states are actually sent in.
  • Color each item by its index for a short time. This helps you check if the code runs correctly.
  • Use ListBox.Invalidate to start repaints yourself. Do this especially after you change how the list looks.
  • Turn off VCL Styles for a short time. This helps you test if OS themes are getting in the way of your designs.
  • Print odSelected in State to a log. Do this every time the draw method is called. This confirms your selection rules work.

These checks can save many hours of trying things until they work.


Optimizing OwnerDraw Performance

If you have many list items (hundreds or thousands), OnDrawItem might be called many times. Code that is not fast enough will make your screen slow.

Performance Tips:

  • 🚀 Figure out values beforehand. This includes fonts, colors, and icons.
  • 🚀 Cut drawing areas by hand using ExcludeClipRect when you can.
  • 🚀 Use TextExtent and DrawText with flags. This helps handle text that runs over, line spacing, and cutting text short.
  • 🚀 Do not call refresh or update the whole list unless you really need to.

How fast owner-drawn list boxes work depends directly on how complex your drawing code is.


Staying Compatible with Delphi Versions

The way code is made will not always act the same across different Delphi versions. This is especially true when working with visual parts like TListBox.

Key points about compatibility:

  • Older versions (before XE) might not have strong support for themes or high DPI scaling.
  • DrawFocusRect and how focus works are different before and after RAD Studio XE7.
  • Some component packs can change the normal VCL painting. This gets in the way of owner-drawn code.

When you share a component across Delphi versions, always test it with the oldest product setup it works with.


When TListBox Isn't Enough

If you are making a TListBox do too much, like making it look like multi-column lists, image grids, or interactive controls, it is time to think again.

Think about these other options:

  • TVirtualStringTree: This is a popular open-source component. It offers many ways to change it, handles see-through parts, and works very well for many items.
  • Third-party screen packs (DevExpress, TMS): These packs come with designed list boxes, tree views, and controls that use data. They are ready to drop into paid programs.
  • Inherited TCustomControl: For full control, make your own list control. Then handle all drawing and events yourself.

Replacing TListBox might stop you from building a weak structure on top of a control. It was never made for that much work.


Real-World Walkthrough: Colored List With Selection

To make your understanding strong, let's build an easy-to-use and clean-looking Delphi TListBox. It will have item coloring that changes when selected.

Step 1: Set up the TListBox

In the Object Inspector:

  • Set Style := lbOwnerDrawFixed
  • Set ItemHeight := 18 or as needed
  • Connect to the OnDrawItem event.

Step 2: Add List Items in Code

ListBox1.Items.Add('Red');
ListBox1.Items.Add('Green');
ListBox1.Items.Add('Blue');

Step 3: Make the Custom Drawing Logic

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
const
  Colors: array[0..2] of TColor = (clRed, clGreen, clBlue);
var
  TextColor, BackColor: TColor;
begin
  if odSelected in State then
  begin
    BackColor := clSkyBlue;
    TextColor := clWhite;
  end
  else
  begin
    BackColor := clWhite;
    TextColor := Colors[Index mod 3];
  end;

  with ListBox1.Canvas do
  begin
    Brush.Color := BackColor;
    FillRect(Rect);

    Font.Color := TextColor;
    TextOut(Rect.Left + 4, Rect.Top + 2, ListBox1.Items[Index]);
  end;
end;

Final Result

Your custom-drawn list now:

  • Shows selected items apart with backgrounds that look the same.
  • Uses colors that change for each item.
  • Gives clear feedback with simple drawing rules.

Wrapping Up

Changing a Delphi TListBox in OwnerDraw mode is powerful. But it is also easy to make mistakes with. Knowing what part the State parameter plays—especially the odSelected flag—is the first step. This helps you build list screens that work and look good. When you use a planned drawing code with good ways to do things in design and performance, your programs get better. They are easier to use and look good.

For more complex looks or interactions, moving to more modern or third-party controls will be easier to keep up with over time.


References

Embarcadero Technologies. (n.d.). TListBox — Embarcadero RAD Studio VCL Reference. Retrieved from https://docwiki.embarcadero.com

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading