Click here to Skip to main content
15,881,709 members
Articles / Programming Languages / Visual Basic

Printing Reports in .NET

Rate me:
Please Sign up or sign in to vote.
4.85/5 (70 votes)
26 Aug 2008CPOL11 min read 438.9K   15.6K   257  
Using the library presented, you can print reports from C# and other .NET languages
// Copyright (c) 2003, Michael Mayer
// See License.txt that should have been included with this source file.
// or see http://www.mag37.com/csharp/articles/Printing/

using System;
using System.Drawing;
using System.Drawing.Printing;
using System.Diagnostics;

namespace ReportPrinting
{
	/// <summary>
	/// ReportSection represents a section of a report.
	/// </summary>
	/// <remarks>
	/// <para>
	/// There are several subclasses of ReportSection, one for text,
	/// one for a grid of data, one for just a simple horizontal line
    /// <seealso cref="ReportPrinting.ReportSectionText"/>
    /// <seealso cref="ReportPrinting.ReportSectionData"/>
    /// </para>
    /// <para>
    /// For the developer trying to inherit from ReportSection, there are
    /// several things to note.
    /// </para>
    /// <para>
    /// First, override the following functions: 
    ///     DoBeginPrint, DoCalcSize, and DoPrint
    /// The order that these are called by the framework is more or less
    /// summarized as follows:
    /// </para> 
    /// <code>
    ///     DoBeginPrint()    // called once before all other functions are called.
    ///     do {
    ///        DoCalcSize()   // called once before each call to DoPrint
    ///        if (Fits)
    ///           DoPrint()   // if it Fits, DoPrint()
    ///     } while (Continued)
    /// </code>    
    /// <para>
    /// But there are many more complicated scenarios.  For example,
    /// if bounds change between the calls DoCalcSize and DoPrint, the following
    /// may occur:
    /// </para>
    /// <code>
    ///     DoBeginPrint()    // called once before all other functions are called.
    ///     do {
    ///        DoCalcSize()   // called once before each call to DoPrint
    ///        if (Fits) {
    ///           BoundsChanged()   // indicates that bounds have changed
    ///           if (!sized) {
    ///              DoCalcSize()  // if BoundsChanged calls ResetSize, 
    ///           }                // then DoCalcSize will be called again
    ///           DoPrint()        // if it Fits, DoPrint()
    ///        }
    ///     } while (Continued)
    /// </code> 
    /// <para>
    /// </para>   
	/// </remarks>
    public abstract class ReportSection
    {
        /// <summary>
        /// Default constructor, does nothing
        /// </summary>
        public ReportSection()
        {
        }

        /************
         * Locals
         */
        HorizontalAlignment horizontalAlignment;
        VerticalAlignment verticalAlignment;
        bool useFullHeight;
        bool useFullWidth;
        float marginLeft;
        float marginRight;
        float marginTop;
        float marginBottom;
        float maxWidth;
        float maxHeight;

        /// <summary>
        /// The content size required for the next call to print
        /// (this is the area inside the margins).
        /// </summary>
        SizeF requiredSize;

        /// <summary>
        /// The size that will be used on the following call to print
        /// (including margins and/or UseFullHeight / UseFullWidth)
        /// </summary>
        SizeF size;

        /// <summary>
        /// True if the next call to print will not complete the section.
        /// False if the next call to print will finish the entire section.
        /// </summary>
        bool continued;

        /// <summary>
        /// True if the section fits in the bounds given, 
        /// false otherwise.
        /// </summary>
        bool fits;

        /// <summary>
        /// Indicates that CalcSize has been run and
        /// the size / continued values are good.
        /// </summary>
        bool sized;

        /// <summary>
        /// Indicates that BeginPrint has been called
        /// </summary>
        bool startedPrinting;

        /// <summary>
        /// The bounds used to size with.
        /// </summary>
        Bounds sizingBounds;


        #region "Properties"

        /// <summary>
        /// Gets or sets the horizontal alignment for this section
        /// </summary>
        public virtual HorizontalAlignment HorizontalAlignment
        {
            get { return this.horizontalAlignment; }
            set { this.horizontalAlignment = value; }
        }

        /// <summary>
        /// Gets or sets the vertical alignment for this section
        /// </summary>
        public virtual VerticalAlignment VerticalAlignment
        {
            get { return this.verticalAlignment; }
            set { this.verticalAlignment = value; }
        }

        /// <summary>
        /// This section consumes the full height passed to it during print.
        /// Default is false.
        /// </summary>
        public virtual bool UseFullHeight
        {
            get { return this.useFullHeight; }
            set { this.useFullHeight = value; }
        }

        /// <summary>
        /// This section consumes the full width passed to it during print.
        /// Default is false.
        /// </summary>
        public virtual bool UseFullWidth
        {
            get { return this.useFullWidth; }
            set { this.useFullWidth = value; }
        }

        /// <summary>
        /// Gets or sets the margin on the left side.
        /// </summary>
        public virtual float MarginLeft
        {
            get { return this.marginLeft; }
            set { this.marginLeft = value; }
        }

        /// <summary>
        /// Gets or sets the margin on the right side.
        /// </summary>
        public virtual float MarginRight
        {
            get { return this.marginRight; }
            set { this.marginRight = value; }
        }
        
        /// <summary>
        /// Gets or sets the margin on the top.
        /// </summary>
        public virtual float MarginTop
        {
            get { return this.marginTop; }
            set { this.marginTop = value; }
        }

        /// <summary>
        /// Gets or sets the margin on the bottom.
        /// </summary>
        public virtual float MarginBottom
        {
            get { return this.marginBottom; }
            set { this.marginBottom = value; }
        }

        /// <summary>
        /// Gets or sets the MaxWidth this section will consume
        /// on a pass.
        /// </summary>
        /// <remarks>
        /// Default value (0) disables any max.
        /// </remarks>
        public float MaxWidth
        {
            get { return this.maxWidth; }
            set { this.maxWidth = value; }
        }

        /// <summary>
        /// Gets or sets the MaxHeight this section will consume
        /// on a pass.
        /// </summary>
        /// <remarks>
        /// Default value (0) disables any max.
        /// </remarks>
        public float MaxHeight
        {
            get { return this.maxHeight; }
            set { this.maxHeight = value; }
        }


        /// <summary>
        /// Gets the size that will be used on the following call to print
        /// (including margins and/or UseFullHeight / UseFullWidth)
        /// </summary>
        internal protected SizeF Size
        {
            get 
            { 
                return this.size; 
            }
        }

        /// <summary>
        /// Gets the actual required size for content 
        /// that will be used on the following call to print
        /// (excluding margins and/or UseFullHeight / UseFullWidth)
        /// </summary>
        internal protected SizeF RequiredSize
        {
            get 
            { 
                return this.requiredSize; 
            }
        }

        /// <summary>
        /// Gets the boolean flag for if this ReportSection needs
        /// to be continued.
        /// </summary>
        internal protected bool Continued
        {
            get 
            { 
                return this.continued; 
            }
        }

        /// <summary>
        /// Gets the boolean flag for if this ReportSection 
        /// fits within the bounds at all.
        /// </summary>
        internal protected bool Fits
        {
            get 
            { 
                return this.fits; 
            }
        }

        #endregion


        /// <summary>
        /// Limits a bounds down to MaxWidth and MaxHeight
        /// </summary>
        Bounds LimitBounds (Bounds bounds)
        {
            if ((this.MaxWidth > 0) && (bounds.Width > this.MaxWidth))
            {
                bounds.Limit.X = bounds.Position.X + this.MaxWidth;
            }
            if ((this.MaxHeight > 0) && (bounds.Height > this.MaxHeight))
            {
                bounds.Limit.Y = bounds.Position.Y + this.MaxHeight;
            }

            // Take margins into account
            bounds = bounds.GetBounds (this.MarginTop, 
                this.MarginRight, this.MarginBottom, this.MarginLeft);
            return bounds;
        }

        /// <summary>
        /// Sets the size variable based on the requiredSize provided.
        /// The requiredSize should not include margins since
        /// those are added by SetSize().  
        /// It also takes into account FullWidth and FullHeight and
        /// uses the values from bounds if required.
        /// </summary>
        /// <param name="requiredSize">The size required for the section</param>
        /// <param name="bounds">The full bounds allowed</param>
        protected virtual void SetSize (SizeF requiredSize, Bounds bounds)
        {
            this.requiredSize = requiredSize;
            if (this.UseFullWidth)
            {
                this.size.Width = bounds.Width;
            }
            else
            {
                this.size.Width = requiredSize.Width + this.MarginLeft + this.MarginRight;
            }
            if (this.UseFullHeight)
            {
                this.size.Height = bounds.Height;
            }
            else
            {
                this.size.Height = requiredSize.Height + this.MarginTop + this.MarginBottom;
            }
            if (this.MaxWidth > 0)
            {
                this.size.Width  = Math.Min (this.size.Width,  this.MaxWidth);
            }
            if (this.MaxHeight > 0)
            {
                this.size.Height = Math.Min (this.size.Height, this.MaxHeight);
            }
        }


        /// <summary>
        /// Sets the value for fits, not recommended to use this,
        /// but in an emergency you may
        /// </summary>
        /// <param name="val">New value</param>
        protected void SetFits (bool val)
        {
            this.fits = val;
        }

        /// <summary>
        /// Sets the value for continued, not recommended to use this,
        /// but in an emergency you may
        /// </summary>
        /// <param name="val">New value</param>
        protected void SetContinued (bool val)
        {
            this.continued = val;
        }

        /// <summary>
        /// This method is used to perform any required initialization.
        /// </summary>
        /// <param name="g">Graphics object to print on.</param>
        public void BeginPrint (
            Graphics g
            )
        {
            if (!this.startedPrinting)
            {
                this.DoBeginPrint(g);
                this.startedPrinting = true;
            }
        }


        /// <summary>
        /// Resets the size values.  This enables
        /// the CalcSize method to be called again.
        /// </summary>
        public virtual void ResetSize()
        {
            this.sized = false;
        }


        /// <summary>
        /// Method called to Calculate the size required for
        /// the next Print.  Calling this method initializes
        /// the values Size and Continued.  Once these values
        /// are initialized, further calls to CalcSize have
        /// no affect, unless ResetSize() is called.
        /// </summary>
        /// <param name="reportDocument">The parent ReportDocument that is printing.</param>
        /// <param name="g">Graphics object to print on.</param>
        /// <param name="bounds">Bounds of the area to print within.</param>
        public void CalcSize (
            ReportDocument reportDocument,
            Graphics g,
            Bounds bounds)
        {
            BeginPrint(g);
            if (bounds.IsEmpty())
            {
                this.fits = false;
            }
            else if (!this.sized)
            {
                // two default values
                this.sizingBounds = LimitBounds (bounds);
                SectionSizeValues vals = DoCalcSize(reportDocument, g, this.sizingBounds);
                SetSize (vals.RequiredSize, bounds);
                this.fits = vals.Fits;
                this.continued = vals.Continued;
                this.sized = true;
            }
        }

        /// <summary>
        /// Method called to Print this ReportSection.
        /// If CalcSize has not already been called, it will call it.
        /// </summary>
        /// <param name="reportDocument">The parent ReportDocument that is printing.</param>
        /// <param name="g">Graphics object to print on.</param>
        /// <param name="bounds">Bounds of the area to print within.</param>
        public void Print (
            ReportDocument reportDocument,
            Graphics g,
            Bounds bounds)
        {
            Bounds printingBounds = LimitBounds (bounds);
            if (this.sized && (printingBounds != this.sizingBounds))
            {
                SectionSizeValues vals = BoundsChanged (this.sizingBounds, printingBounds);
                SetSize (vals.RequiredSize, bounds);
                this.fits = vals.Fits;
                this.continued = vals.Continued;
            }

            CalcSize (reportDocument, g, bounds);
            if (this.fits)
            {
                DoPrint (reportDocument, g, printingBounds);
            }
            ResetSize ();
        }

        #region "Methods to overload"

        /// <summary>
        /// A struct with values requied by DoCalcSize
        /// </summary>
        protected struct SectionSizeValues
        {
            /// <summary>
            /// The required size for the section.
            /// </summary>
            public SizeF RequiredSize;
            /// <summary>
            /// Whether or not the section fits in the bounds given.
            /// </summary>
            public bool Fits;
            /// <summary>
            /// Whether or not the section must be continued later.
            /// </summary>
            public bool Continued;
        }


        /// <summary>
        /// This method is called after a size and before a print if
        /// the bounds have changed between the sizing and the printing.
        /// Override this function to update anything based on the new location
        /// </summary>
        /// <param name="originalBounds">Bounds originally passed for sizing</param>
        /// <param name="newBounds">New bounds for printing</param>
        /// <returns>SectionSizeValues for the new values of size, fits, continued</returns>
        /// <remarks>To simply have size recalled, implement the following:
        /// <code>
        ///    this.ResetSize();
        ///    return base.BoundsChanged (originalBounds, newBounds);
        /// </code>
        /// </remarks>
        protected virtual SectionSizeValues BoundsChanged (
            Bounds originalBounds,
            Bounds newBounds)
        {
            SectionSizeValues retval = new SectionSizeValues();
            retval.Fits = this.Fits;
            retval.Continued = this.Continued;
            retval.RequiredSize = this.RequiredSize;
            return retval;
        }


        /// <summary>
        /// This method is used to perform any required initialization.
        /// This method is called exactly once.
        /// This method is called prior to DoCalcSize and DoPrint.
        /// </summary>
        /// <param name="g">Graphics object to print on.</param>
        protected abstract void DoBeginPrint (
            Graphics g
            );

        /// <summary>
        /// Called to calculate the size that this section requires on
        /// the next call to Print.  This method will be called once
        /// prior to each call to Print.  
        /// </summary>
        /// <param name="reportDocument">The parent ReportDocument that is printing.</param>
        /// <param name="g">Graphics object to print on.</param>
        /// <param name="bounds">Bounds of the area to print within.
        /// The bounds passed already takes the margins into account - so you cannot
        /// print or do anything within these margins.
        /// </param>
        /// <returns>The values for RequireSize, Fits and Continues for this section.</returns>
        protected abstract SectionSizeValues DoCalcSize (
            ReportDocument reportDocument,
            Graphics g,
            Bounds bounds
            );

        /// <summary>
        /// Called to actually print this section.  
        /// The DoCalcSize method will be called once prior to each
        /// call of DoPrint.
        /// DoPrint is not called if DoCalcSize sets fits to false.
        /// It should obey the values of Size and Continued as set by
        /// DoCalcSize().
        /// </summary>
        /// <param name="reportDocument">The parent ReportDocument that is printing.</param>
        /// <param name="g">Graphics object to print on.</param>
        /// <param name="bounds">Bounds of the area to print within.
        /// These bounds already take the margins into account.
        /// </param>
        protected abstract void DoPrint (
            ReportDocument reportDocument,
            Graphics g,
            Bounds bounds
            );

        #endregion

        }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions