1

I am using PrintDocument.Print() to start a print process, in which I print a data grid (C1FlexGrid) and some header and footer information. It's a somewhat complicated printing process. I'm using standard PrintDocument methods but because of what I want to have hit the page, I'm in control of everything that is happening.

The problem I am having is that I want to shrink the area into which the grid control will be drawn. When I draw my headers and footers, I am calculating the space that they will consume, and what should remain for the grid to occupy. The grid control has its own PrintDocumentGridRenderer class that provides the PrintPage() method that I call to get it to render the grid on the PrintDocument's Graphics object.

I cannot figure out how I can restrict the area into which the grid can fit, but do it after I've already drawn the header/footer and know what that remaining space is.

Here's some code, heavily stripped to what I think is the essence:

private void PrintDocument_PrintPage(Object sender, PrintPageEventArgs e)
{
    //I tried putting a non-drawing version of DrawHeadersAndFooters() here to get the calculated space and then reset the Margin...but it's always one call behind the Graphics object, meaning that it has no effect on the first page.  In fact, because Setup() gets called with two different margins at that point, the pages end up very badly drawn.

    _gridRenderer.Setup(e);  //this is the PrintDocumentGridRender object and Setup() figures out page layout (breaks and such)

    DrawHeadersAndFooters(e.Graphics, e.MarginBounds);
    Int32 newX = _printProperties.GridBounds.X - e.MarginBounds.X;
    Int32 newY = _printProperties.GridBounds.Y - e.MarginBounds.Y;
    e.Graphics.TranslateTransform(newX, newY);
    _gridRenderer.PrintPage(e, _currentPage - 1);  //grid control's print method
    e.HasMorePages = _currentPage < _printProperties.Document.PrinterSettings.ToPage;
    _currentPage++;
}

private void DrawHeadersAndFooters(Graphics graphics, Rectangle marginBounds)
{
    Rectangle textRect = new Rectangle();
    Int32 height = 0;
    //loop lines in header paragraph to get total height required
    //there are actually three, across the page, but just one example for bevity...
    if (!String.IsNullOrEmpty(_printProperties.HeaderLeft))
    {
        Int32 h = 0;
        foreach (String s in _printProperties.HeaderLeft.Split(new String[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
            h += (Int32)graphics.MeasureString(s, _printProperties.HeaderLeftFont, width, stringFormat).Height;
        height = (h > height) ? h : height;
    } //repeat for other two, keeping the greatest of 3 heights in the end
    textRect.X = marginBounds.X;
    textRect.Y = (Int32)_printProperties.Document.DefaultPageSettings.PrintableArea.Y;  //a global storage for printing information I need to keep in memory
    textRect.Width = width;
    textRect.Height = height;

    stringFormat.Alignment = StringAlignment.Near;
    graphics.DrawString(_printProperties.HeaderLeft, _printProperties.HeaderLeftFont, new SolidBrush(_printProperties.HeaderLeftForeColor), textRect, stringFormat);

    _printProperties.GridBounds = new Rectangle(marginBounds.X, textRect.Y, marginBounds.Width, marginBounds.Bottom - textRect.Y);  //here I think I have the space into which the grid _should_ be made to fit
}

You can see that in PrintDocument_PrintPage() I am applying a transform to the Graphics object, which scoots the grid down into place, and under the headers.

Screenshot:

enter image description here

So, the question:

I would like to shrink the area bottom-up to get the bottom of that grid to be just above the footers. You can see by looking at the bottom-right corner that the rendered grid image is overlapping the footers that I've already drawn. And that's the help I need. How can I shrink the Graphics drawing space without doing something like ScaleTransform(), which doesn't seem the right idea at all.

DonBoitnott
  • 10,787
  • 6
  • 49
  • 68
  • You are asking for the Graphics.Clip property. That doesn't make sense, you'll simply lose parts of the grid. ScaleTransform() is the obvious solution. The only other thing you can do is pass the available space to the renderer so that it draws fewer rows. – Hans Passant Jun 11 '14 at 21:51
  • I don't see where you are subtracting the height of the footer: `marginBounds.Bottom - (textRect.Y + footerHeight)` maybe? – LarsTech Jun 11 '14 at 23:56
  • @HansPassant - That last sentence is what I was aiming for, but I see no way pass the space. It only takes the print args and a page index. Via the print args, you're giving it a `Graphics` object. But that's where my comment about it being a call behind came from. That `Graphics` object is established before I can calculate the available space, meaning I can't adjust it in time. – DonBoitnott Jun 12 '14 at 11:02
  • @LarsTech - I think you have a point there. It still leaves me with not knowing how to apply that resultant size to the `Graphics` object though. – DonBoitnott Jun 12 '14 at 11:07
  • You can create your own PrintPageEventArgs and pass it to the render. What exactly it uses to size its output is of course unguessable from the question. It ought to pay attention to PageBounds or MarginBounds. There's a programmer that knows the answer, you are not going to find him here. Pick up the phone. – Hans Passant Jun 12 '14 at 11:27
  • I don't know anything about this _gridRenderer — not sure if it knows anything about _printProperties. But _gridRenderer has a page number as one of it's parameters, so I would think that class somehow has a page setup feature to set the orientation and the margins, etc. Of course, you are not forced to use _gridRenderer, you could just print the contents of the grid yourself. – LarsTech Jun 12 '14 at 13:42

1 Answers1

1

The answer proved to be a complete reorganization of the logic. Instead of trying to figure it all out and render it at the same time, I refactored the calculation code into a separate method that I can call ahead of the call to PrintDocument.Print().

It all came down to this little gem that I was previously unaware of:

Graphics graphics = _printProperties.Document.PrinterSettings.CreateMeasurementGraphics();

That gave me a fresh Graphics object for the print document that I could use to do all the calculations ahead of printing. Once I had that, it was a simple matter of storing it and then using the results in the actual header and footer rendering.

It also afforded me the information I needed in order to adjust the Margins of the PrintDocument.DefaultPageSettings prior to the grid's Print() call.

Some code for reference:

//The margins I intend to adjust
System.Drawing.Printing.Margins margins = _printProperties.Document.DefaultPageSettings.Margins;

//Get paper width/height respecting orientation
Int32 paperWidth = 
    _printProperties.Document.DefaultPageSettings.Landscape ? 
    _printProperties.Document.DefaultPageSettings.PaperSize.Height : 
    _printProperties.Document.DefaultPageSettings.PaperSize.Width;
Int32 paperHeight = 
    _printProperties.Document.DefaultPageSettings.Landscape ? 
    _printProperties.Document.DefaultPageSettings.PaperSize.Width : 
    _printProperties.Document.DefaultPageSettings.PaperSize.Height;

//Calculate printable bounds using user-defined margins and paper size
Rectangle marginBounds = new Rectangle(_printProperties.MarginLeft, _printProperties.MarginTop,
    paperWidth - (_printProperties.MarginLeft + _printProperties.MarginRight),
    paperHeight - (_printProperties.MarginTop + _printProperties.MarginBottom));

//Calculate Rectangles for every header/footer element
CalculatePrintRegions(marginBounds);

//If certain elements exist, use the calculated sizes to adjust the margins
Boolean hasHeader =
    !String.IsNullOrEmpty(_printProperties.HeaderLeft) ||
    !String.IsNullOrEmpty(_printProperties.HeaderCenter) ||
    !String.IsNullOrEmpty(_printProperties.HeaderRight);
Boolean hasHeader2 = 
    !String.IsNullOrEmpty(_printProperties.Header2Range) || 
    !String.IsNullOrEmpty(_printProperties.Header2Date);
Boolean hasFooter =
    !String.IsNullOrEmpty(_printProperties.FooterLeft) ||
    !String.IsNullOrEmpty(_printProperties.FooterCenter) ||
    !String.IsNullOrEmpty(_printProperties.FooterRight);
if (hasHeader)
{
    Int32 topAdd = Math.Max(Math.Max(_printProperties.HeaderLeftRect.Height, _printProperties.HeaderCenterRect.Height), _printProperties.HeaderRightRect.Height);
    if (hasHeader2)
        topAdd += Math.Max(_printProperties.Header2RangeRect.Height, _printProperties.Header2DateRect.Height);
    margins.Top = _printProperties.HeaderLeftRect.Top + topAdd + 10;
}
if (hasFooter)
    margins.Bottom = paperHeight - (_printProperties.FooterLeftRect.Top - 10);

//This used to be the starting point, everything above was added in answer to the problem
_printProperties.IsPrinting = true;  //used by drawing code to control drawing of certain elements (such as not showing selection/highlight)
_printProperties.IsPreview = Preview;
_printProperties.IsDryRun = true;  //to get page count into _gridRenderer before displaying prompt
_printProperties.Document.Print();

Note that since I am drawing all header/footer text manually, margins have no impact on it, so adjusting them only influences the grid rendering.

DonBoitnott
  • 10,787
  • 6
  • 49
  • 68