Make an owner drawn ListBox that left and right justifies columns of values in C#

The following examples show different ways to make a ListBox control format and align values. Unfortunately the ListBox doesn't provide a way to line up multiple columns of left and right justified values. You can do that yourself by making an owner-drawn ListBox, but it requires some extra work.

This example uses the techniques described in Draw rows of data in left and right justified columns in C# to make an owner-drawn ListBox draw values in justified columns.

When the program starts, it executes the following code.

// Make the ListBox owner-drawn and give it data.
private void Form1_Load(object sender, EventArgs e)
{
    lstBooks.DrawMode = DrawMode.OwnerDrawVariable;
    lstBooks.Items.AddRange(Values);
}

This code makes the ListBox owner-drawn and gives it some data. The variable Values is an array of arrays of strings. In other words, it contains values for each row and each row holds an array of strings.

When you use an owner-drawn ListBox, you must handle two key events: MeasureItem and DrawItem. The MeasureItem event handler shown in the following code is called to tell the owner-drawn ListBox how much room it should leave for a menu item.

// Row and column sizes.
private float RowHeight, RowWidth;
private float[] ColWidths = null;

private const float RowMargin = 10;
private const float ColumnMargin = 10;

// Return the desired size of an item.
private void lstBooks_MeasureItem(object sender, MeasureItemEventArgs e)
{
    // Measure the data if we haven't already done so.
    if (ColWidths == null)
    {
        // Get the row and column sizes.
        GetRowColumnSizes(e.Graphics, lstBooks.Font, Values, out RowHeight, out ColWidths);

        // Add margins.
        for (int i = 0; i < ColWidths.Length; i++) ColWidths[i] += ColumnMargin;
        RowHeight += RowMargin;

        // Get the total row width.
        RowWidth = ColWidths.Sum();
    }

    // Set the desired size.
    e.ItemHeight = (int)RowHeight;
    e.ItemWidth = (int)RowWidth;
}

If ColWidths is null, then the program has not yet measured the data. In that case, this code calls the GetRowColumnSizes method to calculate the maximum row height and the column widths for the data values. For more information on this method, see the previous example Draw rows of data in left and right justified columns in C#.

The code then adds a margin to each column's width and to the maximum row height so the values aren't too crowded when they are drawn.

Finally the event handler sets e.ItemHeight and e.ItemWidth to tell the owner-drawn control how much room to allow for the menu item.

The following DrawItem event handler is called when the owner-drawn ListBox needs to draw a menu item.

// Draw an item.
private void lstBooks_DrawItem(object sender, DrawItemEventArgs e)
{
    string[] values = (string[])lstBooks.Items[e.Index];
    e.DrawBackground();
    if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
    {
        DrawRow(e.Graphics, lstBooks.Font, SystemBrushes.HighlightText, null,
            e.Bounds.X, e.Bounds.Y, RowHeight, ColWidths,
            VertAlignments, HorzAlignments, values, false);
    }
    else
    {
        DrawRow(e.Graphics, lstBooks.Font, Brushes.Black, null,
            e.Bounds.X, e.Bounds.Y, RowHeight, ColWidths,
            VertAlignments, HorzAlignments, values, false);
    }
}

This code gets the data for the ListBox row that it needs to draw. It then calls e.DrawBackground to draw an appropriate background. (If the item is selected (the user clicked it), the background is blue. If the item is not selected, the background is white.)

The code then calls the DrawRow method to draw the row in the ListBox. If the ListBox item is selected (the user clicked it), then it draws the item with the system-defined text highlight color. If the item is not selected, the code calls DrawRow to draw the row with black text.

The code also passes the call to DrawBox the X and Y coordinates of the row's upper left corner as given by the event handler's e.Bounds parameter.

The following code shows the DrawRow method.

// Draw the items in columns.
private void DrawRow(Graphics gr, Font font, Brush brush, Pen box_pen,
    float x0, float y0, float row_height, float[] col_widths,
    StringAlignment[] vert_alignments, StringAlignment[] horz_alignments,
    string[] values, bool draw_box)
{
    // Create a rectangle in which to draw.
    RectangleF rect = new RectangleF();
    rect.Height = row_height;

    using (StringFormat sf = new StringFormat())
    {
        float x = x0;
        for (int col_num = 0; col_num < values.Length; col_num++)
        {
            // Prepare the StringFormat and drawing rectangle.
            sf.Alignment = horz_alignments[col_num];
            sf.LineAlignment = vert_alignments[col_num];
            rect.X = x;
            rect.Y = y0;
            rect.Width = col_widths[col_num];

            // Draw.
            gr.DrawString(values[col_num], font, brush, rect, sf);

            // Draw the box if desired.
            if (draw_box) gr.DrawRectangle(box_pen,
                rect.X, rect.Y, rect.Width, rect.Height);

            // Move to the next column.
            x += col_widths[col_num];
        }
    }
}

This method makes a RectangleF and a StringFormat that it will use to draw the row's items and then loops through those items. For each item, the code sets the StringFormat's Alignment and LineAlignment properties and draws the item at the appropriate location. If the draw_box parameter is true, the code also draws the RectangleF. (This is mostly for debugging.)

After it draws each item, it increases the X coordinate so it is ready to draw the next item.

This example is fairly complicated but still manageable. An alternative would be to use a ListView control instead of a ListBox. The behavior isn't quite the same so it doesn't provide exactly the same look and feel, but the ListView lets you set up more of the details about column justification interactively at design time.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.