Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding DrawItem for ListBox - unselected items are not redrawn

This is a C# desktop application. The DrawStyle property of my ListBox is set to OwnerDrawFixed.

The problem: I override DrawItem to draw text in different fonts, and it works. But when I start resizing the form at the runtime, the selected item is drawn correctly, but the rest of them are not redrawn, causing text looking corrupt for unselected items.

Here's my code:

private void listDevices_DrawItem(object sender, DrawItemEventArgs e)
{
    e.DrawBackground();

    string textDevice = ((ListBox)sender).Items[e.Index].ToString();

    e.Graphics.DrawString(textDevice,
        new Font("Ariel", 15, FontStyle.Bold), new SolidBrush(Color.Black), 
        e.Bounds, StringFormat.GenericDefault);


    // Figure out where to draw IP
    StringFormat copy = new StringFormat(
        StringFormatFlags.NoWrap |
        StringFormatFlags.MeasureTrailingSpaces
    );
    copy.SetMeasurableCharacterRanges(new CharacterRange[] {new CharacterRange(0, textDevice.Length)});

    Region[] regions = e.Graphics.MeasureCharacterRanges(
        textDevice, new Font("Ariel", 15, FontStyle.Bold), e.Bounds, copy);

    int width = (int)(regions[0].GetBounds(e.Graphics).Width);
    Rectangle rect = e.Bounds;
    rect.X += width;
    rect.Width -= width;

    // draw IP
    e.Graphics.DrawString(" 255.255.255.255",
        new Font("Courier New", 10), new SolidBrush(Color.DarkBlue),
        rect, copy);

    e.DrawFocusRectangle();
}

listDevices.Items.Add("Device001");
listDevices.Items.Add("Device002");

Also, the item that is drawn correctly (the selected one) is flickering on form resizing. No biggie, but if anyone know why.... tnx

like image 771
flamey Avatar asked Dec 29 '22 10:12

flamey


2 Answers

Put the following code in the Resize event:

private void listDevices_Resize(object sender, EventArgs e) {
    listDevices.Invalidate();
}

This should cause everything to be redrawn.

To stop the flickering, you need double buffering.

To do this, make a new class, derived from ListBox, and put the following in the constructor:

this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Or just paste this into a code file:

using System.Windows.Forms;

namespace Whatever {
    public class DBListBox : ListBox {
        public DBListBox(): base() {
            this.DoubleBuffered = true;
            // OR
            // this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        }
    }
}

Replace "Whatever" with the namespace your project uses, or make it something more useful. AFter compiling, you should be able to add a DBListBox in the form designer.

like image 180
Vincent McNabb Avatar answered Feb 04 '23 11:02

Vincent McNabb


I repro the problem. There are several mistakes in the code, the font name is "Arial", you should not adjust rect.Width, you forget to call Dispose() on the fonts, brushes and regions. But they don't explain the behavior. There's something wrong with the clipping area that prevents the text from being properly updated. I don't see where that occurs, the Graphics object state is okay.

Graphics.DrawString() is a very troubled method, you should really avoid it. All Windows Forms controls, including ListBox, use TextRenderer.DrawText(). That solves the problem when I use it. I know measuring is more difficult, you could work around that by displaying the IP address at a fixed offset. Looks better too, they'll line up in a column that way.

It flickers because you use e.DrawBackground(). That erases the existing text, you draw the text right back on it. I don't think double-buffering is going to fix that, you'd have to draw the entire item so you don't have to draw the background. Tricky if you can't get the exact size of the text with the large font, a workaround is to draw into a bitmap first.

like image 32
Hans Passant Avatar answered Feb 04 '23 12:02

Hans Passant