I am extracting information from SQL and writing reports. This should (I hate that word) be easy... but :

All is fine as long as the formatted text fits on one page. However, if the report becomes more involved (more data in SQL) more than one page is required.

I'm used to printers handling this automatically..

So here's some code : (note applicants, interviews, and positions are Queue's that have been filled from the DB, while departments is a string[] array also pulled out of the DB)

This first part just builds the report as a single string to pass to the rptForm web form where it is displayed in a rich text box. From there the user can edit, change the font, etc. Then either save the report as a file (which works fine), or print the report (which only prints as much as will fit on one page and looses the rest).

private void rptDeptDet(object sender, EventArgs e) { 
      string line = "" ;
      for(int i=0 ; i<60 ; i++) line += "-" ;
      string body = line + "\n\n" + getHeader() + "\n\n" + line + 
                "\nCompany department detail report\n" + line + "\n\n" ;
      for(int i=0 ; i<departments.Length ; i++) { 
        int    open = 0 ;
        int  closed = 0 ;
        int  filled = 0 ;
        string openPos   = "" ;
        string closedPos = "" ;
        string filledPos = "" ;
        foreach(Position p in positions) {
          string [] d = p.getPdate().Split(' ') ;
          string when = getMonthName(d[0]) + " " + d[1] + ", " + d[2] ;
          if (p.getDept() == departments[i]) {
            if (p.getStatus() == "filled") {
              filled++ ;
              filledPos += "      " + p.getPname() + ", " + p.getShft() + 
                           " shift\n          Opened date " + when + "\n" ;
            } else if (p.getStatus() == "closed") {
              closed ++ ;
              closedPos += "      " + p.getPname() + ", " + p.getShft() + 
                           " shift\n          Opened date " + when + "\n" ;
            } else {
              open++ ;
              openPos += "      " + p.getPname() + ", " + p.getShft() + 
                         " shift\n          Opened date " + when + "\n" ;
            }
          }
        }
        body += "Department : " + departments[i] + "\n" +
                "    Open positions (" + open.ToString() + ")\n" +
                openPos + "\n" +
                "    Filled positions (" + filled.ToString() + ")\n" +
                filledPos + "\n" +
                "    Closed positions (" + closed.ToString() + ")\n" +
                closedPos + "\n" + line + "\n" ;
      }
      body += "End of report\n" + line + "\n" ;
      Reports rptForm =
         new Reports("HRE_Department_Detail.txt",body) ;
      rptForm.Show() ;
 }

The code for the print event is pretty standard stuff : (this code is in the Report.cs (code behind file) that goes with the Reports windows form.

private void genPrint(object sender, EventArgs e) { 
      PrintDocument pd = new PrintDocument();
      pd.PrintPage += new PrintPageEventHandler(myPrintPage) ; 
      printDialog1.Document = pd ;
      DialogResult result = printDialog1.ShowDialog() ;
      if (result==DialogResult.OK)  pd.Print() ;
} 

private void myPrintPage(object sender, PrintPageEventArgs ev) { 
      string junk = rtbReport.Text ;
      Font printFont = new Font("Courier New", 10);
      ev.Graphics.DrawString(junk,printFont,Brushes.Black,
                                           new Point(50,50)) ;
    }

So how do I get the printer to print more than one page when there is more than one page of data?

You need to determine whether there are still more pages to print and set HasMorePages = true. Following EXAMPLE is from MSDN:

// The PrintPage event is raised for each page to be printed.
   private void pd_PrintPage(object sender, PrintPageEventArgs ev) 
   {
      float linesPerPage = 0;
      float yPos = 0;
      int count = 0;
      float leftMargin = ev.MarginBounds.Left;
      float topMargin = ev.MarginBounds.Top;
      string line = null;

      // Calculate the number of lines per page.
      linesPerPage = ev.MarginBounds.Height / 
         printFont.GetHeight(ev.Graphics);

      // Print each line of the file.
      while(count < linesPerPage && 
         ((line=streamToPrint.ReadLine()) != null)) 
      {
         yPos = topMargin + (count * 
            printFont.GetHeight(ev.Graphics));
         ev.Graphics.DrawString(line, printFont, Brushes.Black, 
            leftMargin, yPos, new StringFormat());
         count++;
      }

      // If more lines exist, print another page.
      if(line != null)
         ev.HasMorePages = true;
      else
         ev.HasMorePages = false;
   }

I had high hopes for that, but it didn't work. I still just get the first page of my document. I've re-written the code in order to process and
understand it.. Here is what I have so far..

// =========================================================================
    // Menu strip File -> Print click event
    // send the data directly to the printer
    private void genPrint(object sender, EventArgs e) { // =====================
      PrintDocument pd = new PrintDocument();
      pd.PrintPage += new PrintPageEventHandler(pd_PrintPage) ; // function below
      printDialog1.Document = pd ;
      DialogResult result = printDialog1.ShowDialog() ;
      if (result==DialogResult.OK)  pd.Print() ;
    } // =======================================================================

    // =========================================================================
    // function used by PrintPageEventHandler
    private void pd_PrintPage(object sender, PrintPageEventArgs ev) { // =======
      string[] pBody = rtbReport.Text.Split('\n') ;
      Font printFont = new Font("Courier New", 10) ;
      float maxLines = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics) ;
      float top  = ev.MarginBounds.Top ;
      float left = ev.MarginBounds.Left ;
            
      for(int i=0 ; i < pBody.Length ; i++) { // Print each line of the file
        ev.HasMorePages = true ;
        for(int num=0 ; num < maxLines && i < pBody.Length ; num++) {
          float yPos = top + (num * printFont.GetHeight(ev.Graphics)) ;
          ev.Graphics.DrawString(pBody[i++],printFont,Brushes.Black,left,yPos) ;
        }
        if (i >= pBody.Length)      // this shouldn't be necessary, but if I leave
          ev.HasMorePages = false ; // it out I get in infinite number of pages
                                    // IF I leave it in, I just get the first page
      }                             // of my document
    }

I have to admit I'm a bit frustrated by this.. something that ought to be simple.. has been simple in every other language I've used.. but I can't get it to work here.

Can anyone PLEASE PLEASE PLEASE shed some light on this for me?

It looks to me like your: string[] pBody = rtbReport.Text.Split('\n') ;
assignment inside of the print page won't work. Don't you need to check for maxlines, return, then reassign pBody where you left off, then allow the print page event to continue?

It looks to me like your: string[] pBody = rtbReport.Text.Split('\n') ;
assignment inside of the print page won't work. Don't you need to check for maxlines, return, then reassign pBody where you left off, then allow the print page event to continue?

If you made that adjustment, then this seems logical:

int i = 0;
            for (; i < pBody.Length && i < maxLines; i++) // for each line?
            { 
                float yPos = top + (i * printFont.GetHeight(ev.Graphics));// calc start line
                ev.Graphics.DrawString(pBody[i], printFont, Brushes.Black, left, yPos); // print line
            }
            if (i >= maxLines)
                ev.HasMorePages = false;
            else
                ex.HasMorePages = true;

I've tried this and it's still only printing the first page of the report.

So let me try walking through my (limited) understanding of what is going on here to figure it out. PLEASE correct as necessary because I seem to be missing something here...

Lets suppose that the document I want to print consists of 85 lines of text, and that a page will hold 55 lines.

So this first part of the code is the same :

// called when the user selects "Print"
private void genPrint(object sender, EventArgs e) {
      PrintDocument pd = new PrintDocument();
      pd.PrintPage += new PrintPageEventHandler(pd_PrintPage) ; 
      printDialog1.Document = pd ;
      DialogResult result = printDialog1.ShowDialog() ;
      if (result==DialogResult.OK)  pd.Print() ;
}

The event is called, it creates a printdocument object and it creates a new handler for the printpage event within printdocument and starts the print process.

(I have to admit that I'm more of an old fashioned functional programmer here - and have the bias that oop is fine for some things but when objects don't work you as a programmer are often screwed)

So when pd.Print() is reached this new event handler is invoked.

private void pd_PrintPage(object sender, PrintPageEventArgs ev) {
      string[] pBody = rtbReport.Text.Split('\n') ;
      Font printFont = new Font("Courier New", 10) ;
      float maxLines = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics) ;
      float top  = ev.MarginBounds.Top ;
      float left = ev.MarginBounds.Left ;
      int i = 0;
      for (; i < pBody.Length && i < maxLines; i++) 
      { 
          float yPos = top + (i * printFont.GetHeight(ev.Graphics));
          ev.Graphics.DrawString(pBody[i], printFont, Brushes.Black, left, yPos); 
      }
      if (i >= maxLines)
          ev.HasMorePages = false;
      else
          ev.HasMorePages = true;
}

The text is pulled from the rich text box and placed in a string[]. This is done so it can be easily split between two pages. I tried printing just one long string taken from the rtb and go the same result as I am getting now (ie. only the first page printed).
The font is declared.
The max number of lines on the page is calculated.
Numbers are found for the upper left corner of the text area.
Then a loop is used so as long as there is more data to print AND there is page left to print it on, the line position is calculated and printed.
AT this point the loop finishes and if there is more to be printed..
--> ok shouldn't if (i >= maxLines) be instead (i < pBody.Length)?
---------- meaning that there is more to print instead of meaning we
---------- are beyond the end of the page?
At the conclusion of the loop, i should be 55, since it was counting the lines printed, and the loop ended because i < maxLines became false.
The code here SHOULD set ev.HasMorePages to true.. YES?

What I'm hazy on is what happens then?

Being a functional guy, I'm thinking that the index of the next line to be printed should be preserved, the current page ejected and a new one loaded, and the printing should resume. You can see this thinking in my original code which used two loops, one that tracked the index of the report body string array, and another inner loop that tracked the line number on the page.

So now 55 lines have been printed and there are 30 more lines to go. I don't see how we are starting over on the second page? If the index of the report (i) gets reset to 0 with another call to this event handler, wouldn't that just reprint the first page again? If it doesn't, since we are using that index to calculate the distance down from the top of the paper for the line draw, wouldn't that distance be an error because it would be beyond the bottom of the page?
If i is 56 say, then the calculation of

float yPos = top + (i * printFont.GetHeight(ev.Graphics));

would be in error.

Ok, I'm starting to ramble here. Could someone enlighten me regarding what is going on here?

You are right about the check on maxLines (I had it backwards).

Can you assign body[] outside of pd_PrintPage? Then retain the lastline printed for each call to pd_PrintPage and pick up where it left off when you make the next call (triggered by ev.HasMorePages)?

For example:

// called when the user selects "Print"
        private void genPrint(object sender, EventArgs e)
        {
            PrintDocument pd = new PrintDocument();
            pd.PrintPage += new PrintPageEventHandler(pd_PrintPage);
            printDialog1.Document = pd;
            DialogResult result = printDialog1.ShowDialog();
            if (result == DialogResult.OK)
            {
                pd.LoadPrintBody();
                pd.Print();
            }
        }

        // inside PrintDocument or parent...
        string[] pBody;
        int lastLinePrinted;
        
        // inside PrintDocument or parent...
        public void LoadPrintBody()
        {
            pBody = rtbReport.Text.Split('\n');
            lastLinePrinted = 0;
        }

        private void pd_PrintPage(object sender, PrintPageEventArgs ev)
        {
            //string[] pBody = rtbReport.Text.Split('\n');

            Font printFont = new Font("Courier New", 10);
            float maxLines = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics);
            float top = ev.MarginBounds.Top;
            float left = ev.MarginBounds.Left;

            // began where left off
            int i = lastLinePrinted + 1;

            for (; i < pBody.Length && i < maxLines; i++)
            {
                float yPos = top + (i * printFont.GetHeight(ev.Graphics));
                ev.Graphics.DrawString(pBody[i], printFont, Brushes.Black, left, yPos);
            }
            if (i < pBody.Length)
            {
                lastLinePrinted = i - 1;
                ev.HasMorePages = true;
            }
            else
                ev.HasMorePages = false;
        }

// inside PrintDocument or parent...

s/b // inside PrintDocument child class or you could place code in dialog class; whereever the pd_PrintPage event method resides.

Does this make sense?

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.