Search code examples
c#.netwinformsvisual-studiodrawing

Printing new pages with e.HasMorePages


I'm a little confused how I can use the HasMorePages property. I'm trying to print some page more depending by the YPosition method, but it causes an infinite loop of printing pages.

This is my code:

        private float YPosition()
        {
            return this.TopMargin + ((float)this.LinesCount * this.Font.GetHeight(this.Graphics) + (float)this.ImagesHeight);
        }

        private void TicketPrintPage(object sender, PrintPageEventArgs e)
        {
            e.Graphics.PageUnit = GraphicsUnit.Millimeter;
            this.Graphics = e.Graphics;
            foreach (Tuple<Object, LineTypes> tuple in this.Objects)
            {
                switch (tuple.Item2)
                {
                    case LineTypes.LINE:
                        this.Graphics.DrawString((String)tuple.Item1, this.Font, this.SolidBrush, this.LeftMargin, this.YPosition(), new StringFormat());
                        this.LinesCount++;
                        break;
                    case LineTypes.IMAGE:
                        Image Image = (Image)tuple.Item1;

                        // Center Image, using PaperSize
                        Graphics GraphicsImage = Graphics.FromImage(Image);
                        RectangleF RectangleF = e.MarginBounds;
                        RectangleF.Offset(-e.PageSettings.HardMarginX, -e.PageSettings.HardMarginY);

                        float InchX = RectangleF.X / 100f + (RectangleF.Width / 100f - (float)Image.Width / GraphicsImage.DpiX) / 2f;
                        Int32 MillimeterX = (Int32)Math.Ceiling(InchX / 0.039370);

                        this.Graphics.DrawImage(Image, new Point((int)this.LeftMargin + (MillimeterX / 2), (int)this.YPosition()));
                        double a = (double)Image.Height / 58.0 * 15.0;
                        this.ImagesHeight += (int)Math.Round(a) + 3;
                        break;
                }
                if ((YPosition() * 4) >= e.PageSettings.PrintableArea.Height)
                {
                    e.HasMorePages = true;
                    return;
                }
                else
                {
                    e.HasMorePages = false;
                }
            }
        }

YPosition represent the height of each line or images in the pages.

How can I prevent the endless loop of printing and stop if all Objects are handled?


Solution

  • Instead of a for loop you need to use a while loop and an Enumerator. The enumerator keeps the state of which object you're handling and that is stored as a member on the form instance. Use the BeginPrint and EndPrint event of the PrintDocument class to initialize and clean-up the enumerator.

    // this is the variable that holds the enumerator instance 
    // once printing has started
    IEnumerator<Tuple<Object, LineTypes>> ObjectsEnumerator;
    
    // this is the event raised by printdocument at the start of printing
    private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
    {
        this.ObjectsEnumerator = Objects.GetEnumerator();
    }
    

    As we now have an enumerator the PrintPage implementation is going to use that. It calls MoveNext and stores the result in HasMorePages. The rest of the code is similar to what you had but make sure to keep the calculation of the position on the page local to the method. Don't (ab)use instance variables for that. Notice the call to Current as the first statement in the while loop.

    private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
        int linesCount = 0;
        float yPosition = 10;
        // take the next item from the list by calling MoveNext
        // and assign the outcome to e.HasMorePages
        while(e.HasMorePages = this.ObjectsEnumerator.MoveNext())
        {
            var tuple = this.ObjectsEnumerator.Current;
            switch (tuple.Item2)
            {
                case LineTypes.LINE:
                    // draw magic
                    e.Graphics.DrawString(tuple.Item1.ToString(), font, solidBrush, new PointF(10,yPosition));
                    yPosition += 300;
                    linesCount++;
                    break;
                case LineTypes.IMAGE:
                    Image Image = (Image)tuple.Item1;
                    // e.Graphics.DrawImage(....);
                    // calculate new yPosition
                    yPosition += Image.Height;
                    break;
            }
            // if we reach the end of the page
            if (yPosition >= e.PageSettings.PrintableArea.Height)
            {
                //we break out of the while loop
                // e.HasMorePages is already set
                break;
            }
        }
    }
    

    With this code you can iterate over your collection, break out of the loop if the page is full and stop iterating and printing when there are no more items to print.

    If printing is done Endprint is called and its task is to clean-up the enumerator.

    private void printDocument1_EndPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
    {
        if (this.ObjectsEnumerator != null)
        {
            this.ObjectsEnumerator.Dispose();
            this.ObjectsEnumerator = null;
        }
    }