Print the contents of a ListView control in C#


[ListView]

The ListView control, like most controls, includes no support for printing. If you want to display a ListView control’s contents on a print out, you need to do all of the printing yourself.

Unfortunately printing in C# is extremely flexible so it’s hard to anticipate what you might want to do with it. For example, suppose a ListView control contains too many columns to fit on one page. Should the program split the print out across multiple pages? Should it print in landscape mode? Should it reduce the print size? Should it ignore some columns that hold less interesting data?

This example displays a print preview of the contents of a ListView control with its View property set to Details. It makes the printed columns in the print out as wide as they currently are in the control.

Depending on your computer, that may not fit well on a printed page. For example, my laptop screen is wider than an 8.5″ x 11″ page so it can easily display a ListView that won’t fit on one page.

This example also assumes that all of the data will fit on one page vertically. If you must print hundreds of entries, you’ll need to modify the code to span multiple pages. (I may make an example like that at some point.)

At this point, you can probably see that any printing program will require a lot of assumptions.

At design time I added a PrintPreviewDialog named ppdListView and a PrintDocument named pdocListView to the program’s form. I also set the dialog’s Document property to pdocListView. When you click the Preview button, the following event handler starts the printing process.

// Print the ListView's contents.
private void btnPreview_Click(object sender, EventArgs e)
{
    // Start maximized.
    Form frm = ppdListView as Form;
    frm.WindowState = FormWindowState.Maximized;

    // Start at 100% scale.
    ppdListView.PrintPreviewControl.Zoom = 1.0;

    // Display.
    ppdListView.ShowDialog();
}

This event handler first casts the PrintDialog into a Form (a PrintDialog is a kind of form so thaht’s allowed) and maximizes it. It then finds the PrintPreviewControl on the dialog and sets its Zoom level to 100%.

The code then calls the dialog’s ShowDialog method to display the dialog modally. Behind the scenes the dialog invokes the PrintDocument to figure out what it should print. The PrintDocument then raises its PrintPage event to let the program tell it what to print.

The following code shows the PrintDocument object’s PrintPage event handler.

// Print the ListView's data.
private void pdocListView_PrintPage(object sender, PrintPageEventArgs e)
{
    // Print the ListView.
    lvwBooks.PrintData(e.MarginBounds.Location,
        e.Graphics, Brushes.Blue,
        Brushes.Black, Pens.Blue);
}

This code simply invokes the ListView control’s PrintData extension method shown in the following code. This is where all the interesting work occurs.

// Print the ListView's data at the indicated location
// assuming everything will fit within the column widths.
public static void PrintData(this ListView lvw, Point location,
    Graphics gr, Brush header_brush, Brush data_brush, Pen grid_pen)
{
    const int x_margin = 5;
    const int y_margin = 3;
    float x = location.X;
    float y = location.Y;

    // See how tall rows should be.
    SizeF row_size = gr.MeasureString(lvw.Columns[0].Text, lvw.Font);
    int row_height = (int)row_size.Height + 2 * y_margin;

    // Get the screen's horizontal resolution.
    float screen_res_x;
    using (Graphics screen_gr = lvw.CreateGraphics())
    {
        screen_res_x = screen_gr.DpiX;
    }

    // Scale factor to convert from screen pixels
    // to printer units (100ths of inches).
    float screen_to_printer = 100 / screen_res_x;

    // Get the column widths in printer dots.
    float[] col_wids = new float[lvw.Columns.Count];
    for (int i = 0; i < lvw.Columns.Count; i++)
        col_wids[i] = (lvw.Columns[i].Width + 4 * x_margin) *
            screen_to_printer;
    
    int num_columns = lvw.Columns.Count;
    using (StringFormat string_format = new StringFormat())
    {
        // Draw the column headers.
        string_format.Alignment = StringAlignment.Center;
        string_format.LineAlignment = StringAlignment.Center;
        for (int i = 0; i < num_columns; i++)
        {
            RectangleF rect = new RectangleF(
                x + x_margin,
                y + y_margin,
                col_wids[i] - x_margin,
                row_height - y_margin);
            gr.DrawString(lvw.Columns[i].Text,
                lvw.Font, header_brush, rect, string_format);
            rect = new RectangleF(x, y, col_wids[i], row_height);
            gr.DrawRectangle(grid_pen, rect);
            x += col_wids[i];
        }
        y += row_height;

        // Draw the data.
        foreach (ListViewItem item in lvw.Items)
        {
            x = location.X;
            for (int i = 0; i < num_columns; i++)
            {
                RectangleF rect = new RectangleF(
                    x + x_margin, y,
                    col_wids[i] - x_margin, row_height);

                switch (lvw.Columns[i].TextAlign)
                {
                    case HorizontalAlignment.Left:
                        string_format.Alignment = StringAlignment.Near;
                        break;
                    case HorizontalAlignment.Center:
                        string_format.Alignment = StringAlignment.Center;
                        break;
                    case HorizontalAlignment.Right:
                        string_format.Alignment = StringAlignment.Far;
                        break;
                }

                gr.DrawString(item.SubItems[i].Text,
                    lvw.Font, header_brush, rect, string_format);
                rect = new RectangleF(x, y, col_wids[i], row_height);
                gr.DrawRectangle(grid_pen, rect);
                x += col_wids[i];
            }
            y += row_height;
        }
    }
}

The static PrintData extension method is contained in the static ListViewExtensions class. The ListView parameter named lvw is the control whose PrintData method is being called. The method takes as parameters the location where the control should be drawn and the Graphics object on which to draw. Normally that’s the Graphics object used by the PrintPage event handler to represent the printed page.

The method also takes parameters giving the brushes that it should use to draw the data’s headers and the data itself, and a pen used to draw a grid around the data. You could add other parameters to specify such things as background colors and fonts, but that would make this method even more complex and it’s already complicated enough for an example.

The code defines constants x_margin and y_margin, which it uses to allow a bit of space between the data items and the grid lines around them. The code initializes variables x and y to be the upper left corner of the area where the control should be printed.

Next the code uses the Graphics object’s MeasureString method to see how tall the header row should be and adds 2 * y_margin to allow a little extra room.

The next step is one of the more confusing. The control on the screen measures in pixels but the printer, and the Graphics object that represents it, measures in 100ths of an inch. To make the grid and other printed elements line up properly, the code needs to translate from screen coordinates to printer coordinates.

To do that, the code creates a new Graphics object associated with the ListView control. That Graphics object is created as if it were going to be used on the screen. The code reads that object’s DpiX property to get the screen’s horizontal resolution in pixels per inch.

The code then divides 100 by the screen resolution to get a scale factor to convert from pixels to 100ths of an inch.

Next the code loops through the ListView control’s Columns collection to make an array named col_wids holding the widths of each column. (The code could look up the widths as needed, but the col_wids array makes the code a little easier to read.)

Finally the code is ready to print. It creates a StringFormat object and prepares it to center text vertically and horizontally.

It then loops through the ListView control’s columns printing each column’s header. To do that, it makes a RectangleF object indicating where the header should be drawn. That RectangleF is located at the current x and y position. It is as wide as the column and as tall as the previously calculated row height.

After printing the header text, the code uses the Graphics object’s DrawRectangle method to draw a rectangle around the text. It then adds the column’s width to the current x value.

After it has drawn all of the headers, the code adds the row height to y to draw the next line of data moved down one row.

Now the program draws the data. For each row, the code resets x to the left edge of the location where the method should print. Next the code loops through the pieces of data in the row, drawing each piece of text and then drawing a rectangle around it.

That’s basically all there is to it, but I did pull a fast one here. The code uses the Graphics object’s DrawRectangle method but, strangely, there is no version of the DrawRectangle method that takes a RectangleF as a parameter. There are versions that take a Rectangle or four int or float values, but none that takes a RectangleF.

To make drawing rectangles easier, I added the following overloaded extension method to the Graphics class.

// Draw a RectangleF.
public static void DrawRectangle(this Graphics gr, Pen pen,
    RectangleF rectf)
{
    gr.DrawRectangle(pen,
        rectf.Left, rectf.Top, rectf.Width, rectf.Height);
}

You could make lots of modifications to this example, but this version should get you started.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in controls, drawing, graphics, printing and tagged , , , , , , , , , , , , . Bookmark the permalink.

24 Responses to Print the contents of a ListView control in C#

  1. Please help says:

    second page of this article is nice, but the report does not
    do not dump 2 th page

  2. Rod Stephens says:

    I’m not sure I understand what you mean here. Here are the related posts:

    Does one of those do what you need? Or do you mean there’s a problem with one of those?

  3. Satya says:

    Hi,

    Its working fine , But I am facing some issue here.

    I am having listview items with 1000 rows, When I click on “btnPreview_Click” the data is showing only for 50 rows and remaining data is not showing to next page(I mean data is not automatically coming to 2 nd page) . and print is coming only for 50 rows data.

    Where i have to modify the programming code to make success to print 1000 rows, I tried my best but unable to do .
    Pls. help me how to show preview and print data for 1000 rows when I click print image button on print preview page.

    Thanks in advance. ๐Ÿ™‚

  4. Satya says:

    Thank you very much Rod Stephens ๐Ÿ™‚ The post/link you provided is working for me. I love and like it that, I got it what I want.

    Once again many many thanks for that .

    Regards,
    Satya

  5. Anil Raina says:

    sir i m having problem ..
    can u tell me in button preview
    which form is called in the syntax
    Form frm =ppdListView as Form
    and i am adding a new form named Form and the errors occurs here that cannot convert type via reference conversion
    and sir it also gives arrow doesn’t contain a defunition for PrintData
    sir can u hep me

    • RodStephens says:

      It looks like that line of code is trying to convert a ListView control into a Form. You can’t do that because a ListView control is not a type of Form.

      Did you download the example program? The code shown in the post isn’t usually enough to make the program work. Download the example and take a look at it. It should work.

      • Anil Raina says:

        sir i have downloaded and the code is working too..thank u sir..
        now i m trying to printpreview the listview with 11 columns but it is showing error in listviewExtensions Class in the line gr.DrawString(item.SubItems[i].Text, lvw.Font, header_brush, rect, string_format);
        with exception ArgumentOutOf RangeException
        InvalidArgument=Value of ’10’ is not valid for ‘index’.
        Parameter name: index
        Please help me to get out of it

        • RodStephens says:

          It sounds like you’re trying to loop through more columns than there are in the ListView. You should use the debugger to make sure that the item you’re looking at really has 11 subitems. The ListView will happily create an item with fewer subitems. in that case, the last ones will be missing and you would get this error.

          • Anil Raina says:

            what I have to do to get out of it

          • RodStephens says:

            Here’s one solution. Currently this statement prints a sub-item:

            gr.DrawString(item.SubItems[i].Text,
                lvw.Font, header_brush, rect, string_format);

            Change it to this:

            if (i < item.SubItems.Count)
                gr.DrawString(item.SubItems[i].Text,
                    lvw.Font, header_brush, rect, string_format);

            This way it only tries to print the sub-item if it exists.

  6. Anil Raina says:

    Sir, I am having problem in the column named date when i am printing the listview date along with time is coming in printpreview page like 28/07/2015 12:00:00
    Can u help me such that i want only date to be shown on printpreview page and i want to print only date not time.

    • RodStephens says:

      Well, the ListViewItem object’s SubItems.Add method takes a string as a parameter. Are you passing the date into the control as a string? If so, you just need to format it so it only includes the date and not the time. For example, set that entry in the values array to one of the following:

      • the_date.ToLongDateString()
      • the_date.ToShortDateString()
      • the_date.ToString(“d”)
  7. Anil Raina says:

    Sir I want to open a pdf file and a word file by clicking a button and whwnever user update the word file or pdf file the updation will also occur by opening the file from c# application.
    Can u help me

  8. Anil Raina says:

    and I want to show result and pic of student in crystal reports using ms access how can i do it using crystal report using C#

  9. Anil Raina says:

    Sir I want to show data in datagridview from ms access by selecting radiobutton
    can i get the help
    Thanking U sir

    • RodStephens says:

      This is pretty far off topic. It would be better if you look for an example that matches your question.

      There are several ways you can do this. You can select the records yourself and add them to the DataGridView.

      You can also bind the DataGridView to the database and then set the binding source’s Filter property. (To get started, create a new database connection. Then in the Data Sources window, drag the table you want onto the form.)

      The next edition of my book “C# 24-Hour Trainer” will show how to do this step-by-step. It won’t be out for a while, though.

  10. Anil Raina says:

    Can I get the link of your book Sir

  11. Anil Raina says:

    Thank U Sir

  12. Pingback: Print a ListView with large contents in C# - C# HelperC# Helper

  13. Pingback: Print a ListView control's contents on multiple pages in C# - C# HelperC# Helper

Leave a Reply

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