Draw text wrapped to fit columns in C#


[draw text]

The post Draw aligned columns of data in C# shows one way to draw text in rows and columns. It uses the Graphics class’s MeasureString method to size the columns so they are big enough to display their data without wrapping.

In a comment, oghenez asked how you could wrap the data if it was too wide to fit in a column. This post explains how to do that.

For starters, the program cannot set the column widths by measuring the text. If it did that, it would make the columns wide enough to display all of the text. To avoid that, you need to somehow decide on the column widths yourself. This example uses the following code to define the column widths and text alignments.

// The column sizes and alignments.
private int[] ColWidths = { 200, 200, 90, 70, 130};
private StringAlignment[] VertAlignments =
{
    StringAlignment.Center,
    StringAlignment.Center,
    StringAlignment.Near,
    StringAlignment.Near,
    StringAlignment.Near,
};
private StringAlignment[] HorzAlignments =
{
    StringAlignment.Near,
    StringAlignment.Near,
    StringAlignment.Far,
    StringAlignment.Center,
    StringAlignment.Near,
};

The first statement initializes the ColWidths array, which indicates the widths that the columns should have in pixels. The rest of this code defines the vertical and horizontal alignments that the program should use to draw text in the different columns.

When the program’s PictureBox needs to redraw itself, the following code draws text for the values stored in the Values array.

// Display the data aligned in columns.
private void picColumns_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.White);
    e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;

    using (StringFormat sf = new StringFormat())
    {
        using (Font font = new Font("Times New Roman", 12))
        {
            const int margin = 4;
            int y = margin;

            // Display the rows.
            foreach (string[] row in Values)
            {
                // Measure the row's entry heights.
                int num_cols = row.Length;
                int max_height = 0;
                for (int col_num = 0; col_num < num_cols; col_num++)
                {
                    SizeF col_size = e.Graphics.MeasureString(
                        row[col_num], font,
                        ColWidths[col_num] - 2 * margin);
                    int new_height = (int)Math.Ceiling(col_size.Height);
                    if (max_height < new_height)
                        max_height = new_height;
                }
                max_height += 2 * margin;

                // Draw the row's entries.
                int x = margin;
                for (int col_num = 0; col_num < num_cols; col_num++)
                {
                    Rectangle box_rect = new Rectangle(x, y,
                        ColWidths[col_num], max_height);
                    Rectangle text_rect = box_rect;
                    text_rect.Inflate(-margin, -margin);

                    sf.Alignment = HorzAlignments[col_num];
                    sf.LineAlignment = VertAlignments[col_num];
                    e.Graphics.DrawString(row[col_num], font,
                        Brushes.Black, text_rect, sf);

                    e.Graphics.DrawRectangle(Pens.Blue, box_rect);

                    x += ColWidths[col_num];
                }

                y += max_height;
            }
        }
    }
}

This code creates a StringFormat object to specify the alignments used to draw text in the various columns. It also creates a font to use to draw text.

Next, the code defines a margin to place around the text. The program sets variable y to the margin’s value so the first piece of text leaves a small gap at the top of the form. The code then loops through the rows in the Values array.

Each entry in the Values array is an array containing the values that should be drawn in its row. The code initializes max_height to zero. It then loops trough the values in this row.

The code uses the Graphics object’s MeasureString method to see how much space each value needs if it is to be written with its column’s given width. When you call MeasureString and specify a width as in this code, the returned SizeF structure is as tall as necessary to draw the text with the indicated width. This code subtracts twice the margin from the column width so it can place a margin between the edges of the column and the text.

The code rounds the returned SizeF structure’s height up to the nearest integer. If the result is greater than max_height, the code updates max_height.

After it has found the largest height required to display each of the row’s pieces of text, the code adds twice the margin to place some extra space around the text.

Next, the program loops through the row’s entries a second time. For each entry, it makes a Rectangle named box_rect to represent the box around the text. This box is as wide as the column (as stored in the ColWidths array) and has the height found by the previous loop (which includes twice the margin).

The code then copies the Rectangle into a new one named text_rect and uses its Inflate method to make it smaller by the amount margin in all directions. That keeps the Rectangle centered but makes it smaller by the amount margin in the left, right, up, and down directions.

Now the program sets the column’s alignment properties in the StringFormat object that it created earlier and uses that object to draw text in text_rect. It then draws the box_rect to place a box around the text.

After drawing the current entry, the code adds the column’s width to variable x so the next column starts shifted to the right.

After it has drawn all of the text for the row, the code increases y by the row’s height to prepare for the next row.

Note that this example does not break across pages. If the data is long, the program will not stop and start a new page. instead it will simply create a very long single page. You could modify the program to display its data in some sort of scrolled window. Or, if you are printing the data, you could display it on multiple pages.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in fonts, graphics, lists and tagged , , , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.