Search code examples
c#printinggdi+componentonec1flexgrid

How can I adjust the area into which e.Graphics will be drawing (viewport)?


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.


Solution

  • 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.