6 Replies - 14516 Views - Last Post: 06 June 2012 - 04:02 PM Rate Topic: -----

#1 fullyunknown   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 44
  • Joined: 23-March 12

Print Preview Slow and Error using Graphics.DrawString

Posted 04 June 2012 - 11:40 AM

I am trying to create my own report. I can make the report display the needed results when its just doing it for one page. The issue comes when i want to print all results so I use the below code
e.HasMorePages = true;


to show all the records from a dataset. The PrintPreview takes about 1 second per a page to create, which is forever, when it comes to 350+ pages. Then after a while, around page 465, it gets a general exception cause of the length of time I am guessing.
Error Received:

Quote

System.Runtime.InteropServices.ExternalException was unhandled by user code
Message=A generic error occurred in GDI+.
Source=System.Drawing
ErrorCode=-2147467259
StackTrace:
at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
at System.Drawing.Graphics.DrawString(String s, Font font, Brush brush, RectangleF layoutRectangle, StringFormat format)
at System.Drawing.Graphics.DrawString(String s, Font font, Brush brush, PointF point)
at ARBSM_Database.frmWelcome.printDocument1_PrintPage(Object sender, PrintPageEventArgs e) in C:\frmWelcome.cs:line 394
at System.Drawing.Printing.Printdocument.OnPrintPage(PrintPageEventArgs e)
at System.Drawing.Printing.Printdocument._OnPrintPage(PrintPageEventArgs e)
at System.Drawing.Printing.PrintController.PrintLoop(PrintDocument document)
at System.Drawing.Printing.PrintController.Print(PrintDocument document)
at System.Drawing.Printing.Printdocument.Print()
at System.Windows.Forms.PrintPreviewControl.ComputePreview()
at System.Windows.Forms.PrintPreviewControl.CalculatePageInfo()
InnerException:



The below code is just showing column 1 of the dataset table. Total records in the dataset is about 56,000. Does anyone know why it would be getting a error or taking as long as it does?

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            e.HasMorePages = true;
            PointF pointF = new PointF(300, 10);

            //Header
            string headerText = "Balance Drawer";
            Font headerFont = new Font("Consolas", 24);
            Brush headerBrush = Brushes.Black;

            e.Graphics.DrawString(headerText, headerFont, headerBrush, pointF);

            //Body
            string printLineText = "";
            Font printFont = new Font("Consolas", 12);
            Brush printBrush = Brushes.Black;
            
            pointF.Y += 2 * printFont.Height; //double space down from header
            pointF.X = 10; //left align

            //This is the part that is taking forever to generate a preview.
            DataTable tbl = AppGlobal.DSGlobal.Tables["Bills"];
            foreach (DataRow row in tbl.Rows)
            {
                printLineText = row.ItemArray[0].ToString();
                e.Graphics.DrawString(printLineText, printFont, printBrush, pointF);//this is the line getting the error
                pointF.Y += printFont.Height;
            }

        }

        private void buttonPrint_Click(object sender, EventArgs e)
        {
            printPreviewDialog1.Document = printDocument1;
            printPreviewDialog1.ShowDialog();
        }



Is This A Good Question/Topic? 0
  • +

Replies To: Print Preview Slow and Error using Graphics.DrawString

#2 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7052
  • View blog
  • Posts: 23,972
  • Joined: 05-May 12

Re: Print Preview Slow and Error using Graphics.DrawString

Posted 05 June 2012 - 12:46 PM

Based on the code you posted, you are always setting e.HasMorePages to be always true. I'm guessing that print preview eventually just stops asking you for more pages, but that isn't your primary question. Your primary question was about the slow rendering and the crash.

Again, based on your code, in your lines 22-28, you should break out of your loop when you reach the bottom of the page, otherwise you are just wasting time rendering to something that won't show up on the page anyways. I suspect that the exception you are seeing is when GDI+ eventually hits it's limits for text extents when you call it with a large enough pointF.Y value.

Only if you break out of the loop, should you consider setting the e.HasMorePages.

Alternately, you can look at the approach taken in the MSDN tutorial on how to print multiple pages: http://msdn.microsof...y/cwbe712d.aspx

This post has been edited by Skydiver: 05 June 2012 - 12:49 PM

Was This Post Helpful? 2
  • +
  • -

#3 fullyunknown   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 44
  • Joined: 23-March 12

Re: Print Preview Slow and Error using Graphics.DrawString

Posted 05 June 2012 - 05:23 PM

Thank you for your help. It seems I thought printing to multiply pages to be easier then it really turns out to be. Appreciate the link and I guess I will keep trying to get my Print to work based off the example.
Mainly having issues as the example is for printing out a text file that was read into the program. While I am trying to print each row of column 1 of a particular table to the page.

So I modified my code to match the example to see if i could at least get something to print and it looks like i fail at copying :) .
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            int charactersOnPage = 0;
            int linesPerPage = 0;
            
            //Body
            Font printFont = new Font("Consolas", 12);
            Brush printBrush = Brushes.Black;

            e.Graphics.MeasureString(stringToPrint, printFont, e.MarginBounds.Size, StringFormat.GenericTypographic, out charactersOnPage, out linesPerPage);


            e.Graphics.DrawString(stringToPrint, printFont, printBrush, e.MarginBounds, StringFormat.GenericTypographic);
            stringToPrint = stringToPrint.Substring(charactersOnPage);
            e.HasMorePages = false;
            
        }

        private void buttonPreview_Click(object sender, EventArgs e)
        {
            DataTable tbl = AppGlobal.DSGlobal.Tables["Bills"];
            foreach (DataRow row in tbl.Rows)
            {
                stringToPrint += row.ItemArray[0].ToString() + System.Environment.NewLine;
            }
            
            printPreviewDialog1.Document = printDocument1;
            printPreviewDialog1.ShowDialog();
        }



With this code the measure string and drawstring together can take about 15-30 seconds to create Page 1, then when page 1 displays; its blank. So looks like I am going to have stare at it longer to see how to make the example work with what i am trying to do. unless someone happens to see my error before me and guide me in the right direction. any help would be much appreciated.
Was This Post Helpful? 0
  • +
  • -

#4 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7052
  • View blog
  • Posts: 23,972
  • Joined: 05-May 12

Re: Print Preview Slow and Error using Graphics.DrawString

Posted 05 June 2012 - 06:40 PM

Are you sure the delay for the dialog to come up is not because of your lines 21-25 having to deal with 56,000 rows?

I would start out first with commenting out lines 21-25 and replacing it with a simple stringToPrint = "Hello, World";

Make sure that works and works in a reasonable amount of time. I suspect that it will be amazingly fast.

Once you have "Hello, World" working, restore your old code and switch over to using a StringBuilder. Additionally, just do the first 300 rows of your data. I think that part of slow down is that you just appending to the string using the += operator. The StringBuilder is much more efficient for appending. Also limit yourself to only a few hundred rows so that you know the code works for a few pages.

Only once you've got that working, take the 300 row throttle and see if the performance is acceptable. If it is good enough, you are done. If not, then it's time to look at getting your data from the database in chunks and/or using a worker thread rather than pulling everything before bringing up the dialog.

Follow up:
I played with this a little bit, and found performance to be pathetically slow once the 300 record limit is removed. I learned a couple of things:
1) Not only is string appending slow, but lopping off the beginning of the string is slow as well if you follow the MSDN sample code.
2) I was expecting the calls to MeasureText() and DrawText() to more or less have an O(N) time based on the available printable area. Since the print area is essentially fixed, it should be O(1). The behavior I'm seeing is that it has an O(N) based on the length of the string passed in. The more I think about this, the more it makes sense since the string needs to be marshaled from managed code to unmanaged GDI+ code each time either is called.
3) At 12 point font, it looks like you only get about 45 lines to a page. Are 56,000 / 46 is over 1,200 pages.
4) PrintPreview will keep on rendering pages, but the UI seems to only show you the first 1000 pages.

The #3 has the biggest implication: Are you willing to kill that many trees? Or is the target of the print job a PDF? If it's a PDF, do you really need print preview and/or printing in general. Isn't one electronic format as good as another? So why not just keep the data in the database, or just use a plain old text file?

If you really want to print, the other implications of this are, in your PrintPage(), you'll want to render just enough to fill the page. Paging is going to be a must. The strategy used in the sample to hold on to the entire text of the document just will not do for production quality code. If you know that every page is going to have the same number of lines, on the first page, you'll have to do the MeasureText() to figure out the number of lines, and then retrieve just enough data for those lines and send them to be rendered to the print device. The good part is that on succeeding pages, there is no need to call MeasureText(). That will help with the speed.

The part of figuring out how many lines can be as simple as using some dummy text, or as complicated as sending real data to be measured. I think that dummy text is good enough.

This post has been edited by Skydiver: 05 June 2012 - 09:17 PM

Was This Post Helpful? 0
  • +
  • -

#5 fullyunknown   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 44
  • Joined: 23-March 12

Re: Print Preview Slow and Error using Graphics.DrawString

Posted 05 June 2012 - 09:16 PM

Well thank you for the reference to StringBuilder. It did speed that part up dramatically. That part itself use to take 15-20 seconds to load, now only .5-1 seconds at the most.(testing the time between code through debuging).

So started testing the print preview with the following..
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            int charactersOnPage = 0;
            int linesPerPage = 0;

            
            //Body
            Font printFont = new Font("Consolas", 12);
            Brush printBrush = Brushes.Black;

            e.Graphics.MeasureString(stringToPrint, printFont, e.MarginBounds.Size, StringFormat.GenericTypographic, out charactersOnPage, out linesPerPage);

            e.Graphics.DrawString(stringToPrint, printFont, printBrush, e.MarginBounds, StringFormat.GenericTypographic);
            
            stringToPrint = stringToPrint.Substring(charactersOnPage);
            e.HasMorePages = (stringToPrint.Length > 0);
            
        }

        private void buttonPreview_Click(object sender, EventArgs e)
        {
            sb.Clear();
            for (int i = 0; i < 300; i++)
            {
                sb.Append(i + System.Environment.NewLine);
            }

            stringToPrint = sb.ToString();
            printPreviewDialog1.Document = printDocument1;
            printPreviewDialog1.ShowDialog();
        }



In the for loop i was changing the 300 to different numbers and re-running the code. As that number got higher, the longer it took for the DrawString and MeasureString to run. Then also pages where showing nothing on them in the print preview. Example: ran this code with 10,000 and it took about 30-35 seconds for the MeasureString and DrawString to run. Then when the Print Preview finally showed. Pages 1-100 where all blank but pages 101-218 showed all the correct numbers they should of shown.
Was This Post Helpful? 0
  • +
  • -

#6 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7052
  • View blog
  • Posts: 23,972
  • Joined: 05-May 12

Re: Print Preview Slow and Error using Graphics.DrawString

Posted 05 June 2012 - 10:48 PM

Try StringBuilder.AppendLine() just to make the code a little bit more readable.

The missing text on the first few pages I think is a side effect of the extra long lines, because attaching a debugger shows that X number of characters were being used on the page.

So you have confirmed my findings from the post #4 that the longer the string sent to MeasureText() and DrawText(), the longer it takes to draw the pages. To add insult to injury, the line of code that does: stringToPrint = stringToPrint.Substring(charactersOnPage); is also tragically slow.

So what I've done to speed things up where I'm only spending fractions of a second per page, as well as, get the correct rendering to show up is to:
- never modify stringToPrint
- pass a shorter string to MeasureText() and DrawText()

I achieved this by using this variant of String.Substring(). The first parameter starts out to be zero and gets bumped up every page by the number of charactersOnPage. When this first parameter exceeds the length of stringToPrint, there are no more pages to print. The second parameter right now is a magic number based on the number of lines and the average number of characters per line. Obviously, some measures are taken to make sure this number also doesn't exceed the number of remaining characters in the string, but you could just as easily call use SafeSubstring() that somebody posted here in the last couple of days.

As I mentioned in post #4, the approach I described in the previous two paragraphs still isn't production quality code. A better approach is to just get enough data to be rendered on the page.

Here is some pseudo-code of what production quality code may look like:
DoPrintPreview()
{
    set DB cursor to first row
    PrintPreviewDialog.ShowDialog();
}

PrintDocumentPrintPageHandler()
{
    Draw headers, footers, and other decorations.
    if first page
        rowsPerPage = compute number of rows that will fit on the page
    rowsLeft = rowsPerPage;
    while(rowsLeft-- > 0 and DB cursor not at end)
        Draw current row
        Adjust DB cursor forward
    HasMorePages = DB cursor not at end
}


This post has been edited by Skydiver: 05 June 2012 - 10:48 PM

Was This Post Helpful? 0
  • +
  • -

#7 fullyunknown   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 44
  • Joined: 23-March 12

Re: Print Preview Slow and Error using Graphics.DrawString

Posted 06 June 2012 - 04:02 PM

Thank you for all your help.

What i was attempting to do, is create a form that basically allowed a user to generate their own report. Depending on how they created their report would depend on how many lines would actually every show. Most of the time they wouldn't be pulling more then 1500 lines to print. But in case they tried to print it all for whatever reason, I wanted the ability for Print Preview. Then they could see, umm thats a lot of pages, and cancel it.

I will look more into everything you stated or I might just only allow exporting to prevent any chance of them trying to print 1200+ pages :). So thanks once again.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1