www.pudn.com > ObjectListViewDemo.zip > ListViewPrinter.cs


/* 
 * ListViewPrinterBase - A helper class to easily print an ListView  
 * 
 * User: Phillip Piper (phillip_piper@bigfoot.com) 
 * Date: 2007-11-01 11:15 AM 
 * 
 * Change log: 
 * 2008-01-16  JPP  - Made all classes public so they can be accessed from a DLL 
 *                  - Corrected initial value bugs 
 * 2007-11-29  JPP  - Made list cells able to wrap, rather than always ellipsing. 
 *                  - Handle ListViewItems having less sub items than there are columns. 
 * 2007-11-21  JPP  - Cell images are no longer erased by a non-transparent cell backgrounds. 
 * v1.1 
 * 2007-11-10  JPP  - Made to work with virtual lists (if using ObjectListView) 
 *                  - Make the list view header be able to show on each page 
 * 2007-11-06  JPP  - Changed to use Pens internally in BlockFormat 
 *                  - Fixed bug where group + following row would overprint footer 
 * v1.0 
 * 2007-11-05  JPP  - Vastly improved integration with IDE 
 *                  - Added support for page ranges, and printing images 
 * 2007-11-03  JPP  Added support for groups 
 * 2007-10-31  JPP  Initial version 
 *  
 * To Do: 
 *  
 * CONDITIONS OF USE 
 * This code may be freely used for any purpose, providate that this code is kept intact,  
 * complete with this header and conditions of use. 
 *  
 * Copyright (C) 2007 Phillip Piper 
 */ 
 
using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Drawing; 
using System.Windows.Forms; 
using System.Drawing.Printing; 
using System.Drawing.Drawing2D; 
 
namespace BrightIdeasSoftware 
{ 
    ///  
    /// A ListViewPrinterBase prints or print previews an ListView. 
    ///  
    ///  
    /// The format of the page header/footer, list header and list rows can all be customised. 
    /// This class works best with ObjectListView class, but still works fine with normal ListViews. 
    /// If you don't have ObjectListView class in your project, you must define WITHOUT_OBJECTLISTVIEW as one 
    /// of the conditional compilation symbols on your projects properties. 
    ///  
    public class ListViewPrinterBase : PrintDocument 
    { 
        #region Constructors 
 
        public ListViewPrinterBase() 
        { 
            this.ListView = null; 
 
            // Give the report a reasonable set of default values 
            this.HeaderFormat = BlockFormat.Header(); 
            this.ListHeaderFormat = BlockFormat.ListHeader(); 
            this.CellFormat = BlockFormat.DefaultCell(); 
            this.GroupHeaderFormat = BlockFormat.GroupHeader(); 
            this.FooterFormat = BlockFormat.Footer(); 
        } 
 
        public ListViewPrinterBase(ListView lv) 
            : this() 
        { 
            this.ListView = lv; 
        } 
 
        #endregion 
 
        #region Control Properties 
 
        ///  
        /// This is the ListView that will be printed 
        ///  
        [Category("Behaviour"), 
        Description("Which listview will be printed by this printer?"), 
        DefaultValue(null)] 
        public ListView ListView 
        { 
            get { return listView; } 
            set { listView = value; } 
        } 
        private ListView listView; 
 
        ///  
        /// Should this report use text only? 
        ///  
        [Category("Behaviour"), 
        Description("Should this report use text only? If this is false, images on the primary column will be included."), 
        DefaultValue(false)] 
        public bool IsTextOnly 
        { 
            get { return isTextOnly; } 
            set { isTextOnly = value; } 
        } 
        private bool isTextOnly = false; 
 
        ///  
        /// Should this report be shrunk to fit into the width of a page? 
        ///  
        [Category("Behaviour"), 
        Description("Should this report be shrunk to fit into the width of a page?"), 
        DefaultValue(true)] 
        public bool IsShrinkToFit 
        { 
            get { return isShrinkToFit; } 
            set { isShrinkToFit = value; } 
        } 
        private bool isShrinkToFit = true; 
 
        ///  
        /// Should this report only include the selected rows in the listview? 
        ///  
        [Category("Behaviour"), 
        Description("Should this report only include the selected rows in the listview?"), 
        DefaultValue(false)] 
        public bool IsPrintSelectionOnly 
        { 
            get { return isPrintSelectionOnly; } 
            set { isPrintSelectionOnly = value; } 
        } 
        private bool isPrintSelectionOnly = false; 
 
        ///  
        /// Should this report use the column order as the user sees them? With this enabled, 
        /// the report will match the order of column as the user has arranged them. 
        ///  
        [Category("Behaviour"), 
        Description("Should this report use the column order as the user sees them? With this enabled, the report will match the order of column as the user has arranged them."), 
        DefaultValue(true)] 
        public bool UseColumnDisplayOrder 
        { 
            get { return useColumnDisplayOrder; } 
            set { useColumnDisplayOrder = value; } 
        } 
        private bool useColumnDisplayOrder = true; 
 
        ///  
        /// Should column headings always be centered, even if on the control itself, they are 
        /// aligned to the left or right? 
        ///  
        [Category("Behaviour"), 
        Description("Should column headings always be centered or should they follow the alignment on the control itself?"), 
        DefaultValue(true)] 
        public bool AlwaysCenterListHeader 
        { 
            get { return slwaysCenterListHeader; } 
            set { slwaysCenterListHeader = value; } 
        } 
        private bool slwaysCenterListHeader = true; 
 
        ///  
        /// Should listview headings be printed at the top of each page, or just at the top of the list? 
        ///  
        [Category("Behaviour"), 
        Description("Should listview headings be printed at the top of each page, or just at the top of the list?"), 
        DefaultValue(true)] 
        public bool IsListHeaderOnEachPage 
        { 
            get { return isListHeaderOnEachPage; } 
            set { isListHeaderOnEachPage = value; } 
        } 
        private bool isListHeaderOnEachPage = true; 
 
        ///  
        /// Return the first page of the report that should be printed 
        ///  
        [Category("Behaviour"), 
        Description("Return the first page of the report that should be printed"), 
        DefaultValue(0)] 
        public int FirstPage 
        { 
            get { return firstPage; } 
            set { firstPage = value; } 
        } 
        private int firstPage = 0; 
 
        ///  
        /// Return the last page of the report that should be printed 
        ///  
        [Category("Behaviour"), 
        Description("Return the last page of the report that should be printed"), 
        DefaultValue(9999)] 
        public int LastPage 
        { 
            get { return lastPage; } 
            set { lastPage = value; } 
        } 
        private int lastPage = 9999; 
 
        ///  
        /// Return the number of the page that is currently being printed. 
        ///  
        [Browsable(false)] 
        public int PageNumber 
        { 
            get 
            { 
                return this.pageNumber; 
            } 
        } 
 
        ///  
        /// Is this report showing groups?  
        ///  
        /// Groups can't be shown when we are printing selected rows only. 
        [Browsable(false)] 
        public bool IsShowingGroups 
        { 
            get 
            { 
                return (this.ListView != null && this.ListView.ShowGroups && !this.IsPrintSelectionOnly && this.ListView.Groups.Count > 0); 
            } 
        } 
 
        #endregion 
 
        #region Formatting Properties 
 
        ///  
        /// How should the page header be formatted? null means no page header will be printed 
        ///  
        [Category("Appearance - Formatting"), 
        Description("How will the page header be formatted? "), 
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 
        public BlockFormat HeaderFormat 
        { 
            get { return headerFormat; } 
            set { headerFormat = value; } 
        } 
        private BlockFormat headerFormat; 
 
        ///  
        /// How should the list header be formatted? null means no list header will be printed 
        ///  
        [Category("Appearance - Formatting"), 
        Description("How will the header of the list be formatted? "), 
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 
        public BlockFormat ListHeaderFormat 
        { 
            get { return listHeaderFormat; } 
            set { listHeaderFormat = value; } 
        } 
        public BlockFormat listHeaderFormat; 
 
        ///  
        /// How should the grouping header be formatted? null means revert to reasonable default 
        ///  
        [Category("Appearance - Formatting"), 
        Description("How will the group headers be formatted?"), 
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 
        public BlockFormat GroupHeaderFormat 
        { 
            get 
            { 
                // The group header format cannot be null 
                if (groupHeaderFormat == null) 
                    groupHeaderFormat = BlockFormat.GroupHeader(); 
 
                return groupHeaderFormat; 
            } 
            set { groupHeaderFormat = value; } 
        } 
        public BlockFormat groupHeaderFormat; 
 
        ///  
        /// How should the list cells be formatted? null means revert to default 
        ///  
        [Category("Appearance - Formatting"), 
        Description("How will the list cells be formatted? "), 
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 
        public BlockFormat CellFormat 
        { 
            get 
            { 
                // The cell format cannot be null 
                if (cellFormat == null) 
                    cellFormat = BlockFormat.DefaultCell(); 
 
                return cellFormat; 
            } 
            set 
            { 
                cellFormat = value; 
            } 
        } 
        private BlockFormat cellFormat; 
 
        ///  
        /// How should the page footer be formatted? null means no footer will be printed 
        ///  
        [Category("Appearance - Formatting"), 
        Description("How will the page footer be formatted?"), 
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 
        public BlockFormat FooterFormat 
        { 
            get { return footerFormat; } 
            set { footerFormat = value; } 
        } 
        private BlockFormat footerFormat; 
 
        ///  
        /// What font will be used to draw the text of the list? 
        ///  
        [Browsable(false)] 
        public Font ListFont 
        { 
            get { return this.CellFormat.Font; } 
            set { this.CellFormat.Font = value; } 
        } 
 
        ///  
        /// What pen will be used to draw the cells within the list? 
        /// If this is null, no grid will be drawn 
        ///  
        /// This is just a conviencence wrapper around CellFormat.SetBorderPen 
        [Browsable(false)] 
        public Pen ListGridPen 
        { 
            get { return this.CellFormat.GetBorderPen(Sides.Top); } 
            set { this.CellFormat.SetBorderPen(Sides.All, value); } 
        } 
 
        ///  
        /// What color will all the borders be drawn in?  
        ///  
        /// This is just a conviencence wrapper around CellFormat.SetBorder 
        [Browsable(false)] 
        public Color ListGridColor 
        { 
            get 
            { 
                Pen p = this.ListGridPen; 
                if (p == null) 
                    return Color.Empty; 
                else 
                    return p.Color; 
            } 
            set 
            { 
                this.ListGridPen = new Pen(new SolidBrush(value), 0.5f); 
            } 
        } 
 
        ///  
        /// What string will be written at the top of each page of the report? 
        ///  
        /// The header can be divided into three parts: left aligned,  
        /// centered, and right aligned. If the given string contains Tab characters, 
        /// everything before the first tab will be left aligned, everything between 
        /// the first and second tabs will be centered and everything after the second 
        /// tab will be right aligned. 
        /// Within each part, the following substitutions are possible: 
        ///  
        /// {0} - The page number 
        /// {1} - The current date/time 
        ///  
        ///  
        [Category("Appearance"), 
        Description("The string that will be written at the top of each page. Use '\\t' characters to separate left, centre, and right parts of the header."), 
        DefaultValue(null)] 
        public String Header 
        { 
            get { return header; } 
            set 
            { 
                header = value; 
                if (!String.IsNullOrEmpty(header)) 
                    header = header.Replace("\\t", "\t"); 
            } 
        } 
        private String header; 
 
        ///  
        /// What string will be written at the bottom of each page of the report? 
        ///  
        /// The footer, like the header, can have three parts, and behaves 
        /// in the same way as described as Header. 
        [Category("Appearance"), 
        Description("The string that will be written at the bottom of each page. Use '\\t' characters to separate left, centre, and right parts of the footer."), 
        DefaultValue(null)] 
        public String Footer 
        { 
            get { return footer; } 
            set 
            { 
                footer = value; 
                if (!String.IsNullOrEmpty(footer)) 
                    footer = footer.Replace("\\t", "\t"); 
            } 
        } 
        private String footer; 
 
        //----------------------------------------------------------------------- 
        // Watermark 
 
        ///  
        /// The watermark will be printed translucently over the report itself 
        ///  
        [Category("Appearance - Watermark"), 
        Description("The watermark will be printed translucently over the report itself?"), 
        DefaultValue(null)] 
        public String Watermark 
        { 
            get { return watermark; } 
            set { watermark = value; } 
        } 
        private String watermark; 
 
        ///  
        /// What font should be used to print the watermark 
        ///  
        [Category("Appearance - Watermark"), 
        Description("What font should be used to print the watermark?"), 
        DefaultValue(null)] 
        public Font WatermarkFont 
        { 
            get { return watermarkFont; } 
            set { watermarkFont = value; } 
        } 
        private Font watermarkFont; 
 
        ///  
        /// Return the watermark font or a reasonable default 
        ///  
        [Browsable(false)] 
        public Font WatermarkFontOrDefault 
        { 
            get 
            { 
                if (this.WatermarkFont == null) 
                    return new Font("Tahoma", 72); 
                else 
                    return this.WatermarkFont; 
            } 
        } 
 
        ///  
        /// What color should be used for the watermark? 
        ///  
        [Category("Appearance - Watermark"), 
        Description("What color should be used for the watermark?"), 
        DefaultValue(typeof(Color), "Empty")] 
        public Color WatermarkColor 
        { 
            get { return watermarkColor; } 
            set { watermarkColor = value; } 
        } 
        private Color watermarkColor = Color.Empty; 
 
        ///  
        /// Return the color of the watermark or a reasonable default 
        ///  
        [Browsable(false)] 
        public Color WatermarkColorOrDefault 
        { 
            get 
            { 
                if (this.WatermarkColor == Color.Empty) 
                    return Color.Gray; 
                else 
                    return this.WatermarkColor; 
            } 
        } 
 
        ///  
        /// How transparent should the watermark be? <=0 is transparent, >=100 is completely opaque. 
        ///  
        [Category("Appearance - Watermark"), 
        Description("How transparent should the watermark be? 0 is transparent, 100 is completely opaque."), 
        DefaultValue(50)] 
        public int WatermarkTransparency 
        { 
            get { return watermarkTransparency; } 
            set { watermarkTransparency = Math.Max(0, Math.Min(value, 100)); } 
        } 
        private int watermarkTransparency = 50; 
 
        #endregion 
 
        #region Accessing 
 
        ///  
        /// Return the number of rows that this printer is going to print 
        ///  
        /// The listview that is being printed 
        /// The number of rows that will be displayed 
        protected int GetRowCount(ListView lv) 
        { 
            if (this.IsPrintSelectionOnly) 
                return lv.SelectedIndices.Count; 
            else 
                if (lv.VirtualMode) 
                    return lv.VirtualListSize; 
                else 
                    return lv.Items.Count; 
        } 
 
        ///  
        /// Return the n'th row that will be printed 
        ///  
        /// The listview that is being printed 
        /// The index of the row to be printed 
        /// A ListViewItem 
        protected ListViewItem GetRow(ListView lv, int n) 
        { 
            if (this.IsPrintSelectionOnly) 
                if (lv.VirtualMode) 
                    return this.GetVirtualItem(lv, lv.SelectedIndices[n]); 
                else 
                    return lv.SelectedItems[n]; 
 
            if (!this.IsShowingGroups) 
                if (lv.VirtualMode) 
                    return this.GetVirtualItem(lv, n); 
                else 
                    return lv.Items[n]; 
 
            // If we are showing groups, things are more complicated. The n'th 
            // row of the report doesn't directly correspond to existing list. 
            // The best we can do is figure out which group the n'th item belongs to 
            // and then figure out which item it is within the groups items. 
            int i; 
            for (i = this.groupStartPositions.Count - 1; i >= 0; i--) 
                if (n >= this.groupStartPositions[i]) 
                    break; 
            int indexInList = n - this.groupStartPositions[i]; 
            return lv.Groups[i].Items[indexInList]; 
        } 
 
        ///  
        /// Get the nth item from the given listview, which is in virtual mode. 
        ///  
        /// The ListView in virtual mode 
        /// index of item to get 
        /// the item 
        virtual protected ListViewItem GetVirtualItem(ListView lv, int n) 
        { 
            throw new ApplicationException("Virtual list items cannot be retrieved. Use an ObjectListView instead."); 
        } 
 
        ///  
        /// Return the i'th subitem of the given row, in the order  
        /// that coumns are presented in the report 
        ///  
        /// The row from which a subitem is to be fetched 
        /// The index of the subitem in display order 
        /// A SubItem 
        protected ListViewItem.ListViewSubItem GetSubItem(ListViewItem lvi, int i) 
        { 
            if (i < lvi.SubItems.Count) 
                return lvi.SubItems[this.GetColumn(i).Index]; 
            else 
                return new ListViewItem.ListViewSubItem(); 
        } 
 
        ///  
        /// Return the number of columns to be printed in the report 
        ///  
        /// An int 
        protected int GetColumnCount() 
        { 
            return this.sortedColumns.Count; 
        } 
 
        ///  
        /// Return the n'th ColumnHeader (ordered as they should be displayed in the report) 
        ///  
        /// Which column 
        /// A ColumnHeader 
        protected ColumnHeader GetColumn(int i) 
        { 
            return this.sortedColumns[i]; 
        } 
 
        ///  
        /// Return the index of group that starts at the given position. 
        /// Return -1 if no group starts at that position 
        ///  
        /// The row position in the list 
        /// The group index 
        protected int GetGroupAtPosition(int n) 
        { 
            return this.groupStartPositions.IndexOf(n); 
        } 
 
        #endregion 
 
        #region Commands 
 
        ///  
        /// Show a Page Setup dialog to customize the printing of this document 
        ///  
        public void PageSetup() 
        { 
            PageSetupDialog dlg = new PageSetupDialog(); 
            dlg.Document = this; 
            dlg.EnableMetric = true; 
            dlg.ShowDialog(); 
        } 
 
        ///  
        /// Show a Print Preview of this document 
        ///  
        public void PrintPreview() 
        { 
            PrintPreviewDialog dlg = new PrintPreviewDialog(); 
            dlg.UseAntiAlias = true; 
            dlg.Document = this; 
            dlg.ShowDialog(); 
        } 
 
        ///  
        /// Print this document after showing a confirmation dialog 
        ///  
        public void PrintWithDialog() 
        { 
            PrintDialog dlg = new PrintDialog(); 
            dlg.Document = this; 
            dlg.AllowSelection = this.ListView.SelectedIndices.Count > 0; 
            dlg.AllowSomePages = true; 
 
            // Show the standard print dialog box, that lets the user select a printer 
            // and change the settings for that printer. 
            if (dlg.ShowDialog() == DialogResult.OK) { 
                this.IsPrintSelectionOnly = (dlg.PrinterSettings.PrintRange == PrintRange.Selection); 
                if (dlg.PrinterSettings.PrintRange == PrintRange.SomePages) { 
                    this.FirstPage = dlg.PrinterSettings.FromPage; 
                    this.LastPage = dlg.PrinterSettings.ToPage; 
                } else { 
                    this.FirstPage = 1; 
                    this.LastPage = 999999; 
                } 
                this.Print(); 
            } 
        } 
 
        #endregion 
 
        #region Event handlers 
 
        override protected void OnBeginPrint(PrintEventArgs e) 
        { 
            base.OnBeginPrint(e); 
 
            // Initialize our state information 
            this.rowIndex = -1; 
            this.indexLeftColumn = -1; 
            this.indexRightColumn = -1; 
            this.pageNumber = 0; 
 
            // Initialize our caches 
            this.sortedColumns = new SortedList(); 
            this.groupStartPositions = new List(); 
 
            this.PreparePrint(); 
        } 
 
        override protected void OnPrintPage(PrintPageEventArgs e) 
        { 
            if (this.ListView == null || this.ListView.View != View.Details) 
                return; 
 
            base.OnPrintPage(e); 
 
            this.pageNumber++; 
 
            // Ignore all pages before the first requested page 
            // Have to allow for weird cases where the last page is before the first page 
            // and where we run out of things to print before reaching the first requested page. 
            int pageToStop = Math.Min(this.FirstPage, this.LastPage + 1); 
            if (this.pageNumber < pageToStop) { 
                e.HasMorePages = true; 
                while (this.pageNumber < pageToStop && e.HasMorePages) { 
                    e.HasMorePages = this.PrintOnePage(e); 
                    this.pageNumber++; 
                } 
 
                // Remove anything drawn 
                e.Graphics.Clear(Color.White); 
 
                // If we ran out of pages before reaching the first page, simply return 
                if (!e.HasMorePages) 
                    return; 
            } 
 
            // If we haven't reached the end of the requested pages, print one. 
            if (this.pageNumber <= this.LastPage) { 
                e.HasMorePages = this.PrintOnePage(e); 
                e.HasMorePages = e.HasMorePages && (this.pageNumber < this.LastPage); 
            } else 
                e.HasMorePages = false; 
        } 
 
        #endregion 
 
        #region List printing 
 
        ///  
        /// Prepare some precalculated fields used when printing 
        ///  
        protected void PreparePrint() 
        { 
            if (this.ListView == null) 
                return; 
 
            // Build sortedColumn so it holds the column in the order they should be printed 
            foreach (ColumnHeader column in this.ListView.Columns) { 
                if (this.UseColumnDisplayOrder) 
                    this.sortedColumns.Add(column.DisplayIndex, column); 
                else 
                    this.sortedColumns.Add(column.Index, column); 
            } 
 
            // If the listview is grouped, build an array to holds the start 
            // position of each group. The way to understand this array is that 
            // the index of the first member of group n is found at groupStartPositions[n]. 
            int itemCount = 0; 
            foreach (ListViewGroup lvg in this.ListView.Groups) { 
                this.groupStartPositions.Add(itemCount); 
                itemCount += lvg.Items.Count; 
            } 
        } 
 
        ///  
        /// Do the actual work of printing on page 
        ///  
        ///  
        protected bool PrintOnePage(PrintPageEventArgs e) 
        { 
            this.CalculateBounds(e); 
            this.CalculatePrintParameters(this.ListView); 
            this.PrintHeaderFooter(e.Graphics); 
            this.ApplyScaling(e.Graphics); 
            bool continuePrinting = this.PrintList(e.Graphics, this.ListView); 
            this.PrintWatermark(e.Graphics); 
            return continuePrinting; 
        } 
 
        ///  
        /// Figure out the page bounds and the boundaries for the list 
        ///  
        ///  
        protected void CalculateBounds(PrintPageEventArgs e) 
        { 
            // Printing to a real printer doesn't take the printers hard margins into account 
            if (this.PrintController.IsPreview) 
                this.pageBounds = (RectangleF)e.MarginBounds; 
            else 
                this.pageBounds = new RectangleF(e.MarginBounds.X - e.PageSettings.HardMarginX, 
                    e.MarginBounds.Y - e.PageSettings.HardMarginY, e.MarginBounds.Width, e.MarginBounds.Height); 
 
            this.listBounds = this.pageBounds; 
        } 
 
        ///  
        /// Figure out the boundaries for various aspects of the report 
        ///  
        /// The listview to be printed 
        protected void CalculatePrintParameters(ListView lv) 
        { 
            // If we are in the middle of printing a listview, don't change the parameters 
            if (this.rowIndex >= 0 && this.rowIndex < this.GetRowCount(lv)) 
                return; 
 
            this.rowIndex = 0; 
 
            // If we are shrinking the report to fit on the page... 
            if (this.IsShrinkToFit) { 
 
                // ...we print all the columns, but we need to figure how much to shrink 
                // them so that they will fit onto the page 
                this.indexLeftColumn = 0; 
                this.indexRightColumn = this.GetColumnCount() - 1; 
 
                int totalWidth = 0; 
                for (int i = 0; i < this.GetColumnCount(); i++) { 
                    totalWidth += this.GetColumn(i).Width; 
                } 
                this.scaleFactor = Math.Min(this.listBounds.Width / totalWidth, 1.0f); 
            } else { 
                // ...otherwise, we print unscaled but have to figure out which columns 
                // will fit on the current page 
                this.scaleFactor = 1.0f; 
                this.indexLeftColumn = ++this.indexRightColumn; 
 
                // Iterate the columns until we find a column that won't fit on the page 
                int width = 0; 
                for (int i = this.indexLeftColumn; i < this.GetColumnCount() && (width += this.GetColumn(i).Width) < this.listBounds.Width; i++) 
                    this.indexRightColumn = i; 
            } 
        } 
 
        ///  
        /// Apply any scaling that is required to the report 
        ///  
        ///  
        protected void ApplyScaling(Graphics g) 
        { 
            if (this.scaleFactor >= 1.0f) 
                return; 
 
            g.ScaleTransform(this.scaleFactor, this.scaleFactor); 
 
            float inverse = 1.0f / this.scaleFactor; 
            this.listBounds = new RectangleF(this.listBounds.X * inverse, this.listBounds.Y * inverse, this.listBounds.Width * inverse, this.listBounds.Height * inverse); 
        } 
 
        ///  
        /// Print our watermark on the given Graphic 
        ///  
        ///  
        protected void PrintWatermark(Graphics g) 
        { 
            if (String.IsNullOrEmpty(this.Watermark)) 
                return; 
 
            StringFormat strFormat = new StringFormat(); 
            strFormat.LineAlignment = StringAlignment.Center; 
            strFormat.Alignment = StringAlignment.Center; 
 
            // THINK: Do we want this to be a property? 
            int watermarkRotation = -30; 
 
            // Setup a rotation transform on the Graphic so we can write the watermark at an angle 
            g.ResetTransform(); 
            Matrix m = new Matrix(); 
            m.RotateAt(watermarkRotation, new PointF(this.pageBounds.X + this.pageBounds.Width / 2, this.pageBounds.Y + this.pageBounds.Height / 2)); 
            g.Transform = m; 
 
            // Calculate the semi-transparent pen required to print the watermark 
            int alpha = (int)(255.0f * (float)this.WatermarkTransparency / 100.0f); 
            Brush brush = new SolidBrush(Color.FromArgb(alpha, this.WatermarkColorOrDefault)); 
 
            // Finally draw the watermark 
            g.DrawString(this.Watermark, this.WatermarkFontOrDefault, brush, this.pageBounds, strFormat); 
            g.ResetTransform(); 
        } 
 
        ///  
        /// Do the work of printing the list into 'listBounds' 
        ///  
        /// The graphic used for drawing 
        /// The listview to be printed 
        /// Return true if there are still more pages in the report 
        protected bool PrintList(Graphics g, ListView lv) 
        { 
            this.currentOrigin = this.listBounds.Location; 
 
            if (this.rowIndex == 0 || this.IsListHeaderOnEachPage) 
                this.PrintListHeader(g, lv); 
 
            this.PrintRows(g, lv); 
 
            // We continue to print pages when we have more rows or more columns remaining 
            return (this.rowIndex < this.GetRowCount(lv) || this.indexRightColumn + 1 < this.GetColumnCount()); 
        } 
 
        ///  
        /// Print the header of the listview 
        ///  
        /// The graphic used for drawing 
        /// The listview to be printed 
        protected void PrintListHeader(Graphics g, ListView lv) 
        { 
            // If there is no format for the header, we don't draw it 
            BlockFormat fmt = this.ListHeaderFormat; 
            if (fmt == null) 
                return; 
 
            // Calculate the height of the list header 
            float height = 0; 
            for (int i = 0; i < this.GetColumnCount(); i++) { 
                ColumnHeader col = this.GetColumn(i); 
                height = Math.Max(height, fmt.CalculateHeight(g, col.Text, col.Width)); 
            } 
 
            // Draw the header one cell at a time 
            RectangleF cell = new RectangleF(this.currentOrigin.X, this.currentOrigin.Y, 0, height); 
            for (int i = this.indexLeftColumn; i <= this.indexRightColumn; i++) { 
                ColumnHeader col = this.GetColumn(i); 
                cell.Width = col.Width; 
                fmt.Draw(g, cell, col.Text, (this.AlwaysCenterListHeader ? HorizontalAlignment.Center : col.TextAlign)); 
                cell.Offset(cell.Width, 0); 
            } 
 
            this.currentOrigin.Y += cell.Height; 
        } 
 
        ///  
        /// Print the rows of the listview 
        ///  
        /// The graphic used for drawing 
        /// The listview to be printed 
        protected void PrintRows(Graphics g, ListView lv) 
        { 
            while (this.rowIndex < this.GetRowCount(lv)) { 
 
                // Will this row fit before the end of page? 
                float rowHeight = this.CalculateRowHeight(g, lv, this.rowIndex); 
                if (this.currentOrigin.Y + rowHeight > this.listBounds.Bottom) 
                    break; 
 
                // If we are printing group and there is a group begining at the current position, 
                // print it so long as the group header and at least one following row will fit on the page 
                if (this.IsShowingGroups) { 
                    int groupIndex = this.GetGroupAtPosition(this.rowIndex); 
                    if (groupIndex != -1) { 
                        float groupHeaderHeight = this.GroupHeaderFormat.CalculateHeight(g); 
                        if (this.currentOrigin.Y + groupHeaderHeight + rowHeight < this.listBounds.Bottom) { 
                            this.PrintGroupHeader(g, lv, groupIndex); 
                        } else { 
                            this.currentOrigin.Y = this.listBounds.Bottom; 
                            break; 
                        } 
                    } 
                } 
                this.PrintRow(g, lv, this.rowIndex, rowHeight); 
                this.rowIndex++; 
            } 
        } 
 
        ///  
        /// Calculate how high the given row of the report should be. 
        ///  
        /// The graphic used for drawing 
        /// The listview to be printed 
        /// The index of the row whose height is to be calculated 
        /// The height of one row in pixels 
        virtual protected float CalculateRowHeight(Graphics g, ListView lv, int n) 
        { 
            // If we're including graphics in the report, we need to allow for the height of a small image 
            if (!this.IsTextOnly && lv.SmallImageList != null) 
                this.CellFormat.MinimumTextHeight = lv.SmallImageList.ImageSize.Height; 
 
            // If the cell lines can't wrap, calculate the generic height of the row 
            if (!this.CellFormat.CanWrap) 
                return this.CellFormat.CalculateHeight(g); 
 
            // If the cell lines can wrap, calculate the height of the tallest cell 
            float height = 0f; 
            ListViewItem lvi = this.GetRow(lv, n); 
            for (int i = 0; i < this.GetColumnCount(); i++) { 
                ColumnHeader column = this.GetColumn(i); 
                int colWidth = column.Width; 
                if (!this.IsTextOnly && column.Index == 0 && lv.SmallImageList != null && lvi.ImageIndex != -1) 
                    colWidth -= lv.SmallImageList.ImageSize.Width; 
                height = Math.Max(height, this.CellFormat.CalculateHeight(g, this.GetSubItem(lvi, i).Text, colWidth)); 
            } 
            return height; 
        } 
 
        ///  
        /// Print a group header 
        ///  
        /// The graphic used for drawing 
        /// The listview to be printed 
        /// The index of the group header to be printed 
        protected void PrintGroupHeader(Graphics g, ListView lv, int groupIndex) 
        { 
            ListViewGroup lvg = lv.Groups[groupIndex]; 
            BlockFormat fmt = this.GroupHeaderFormat; 
            float height = fmt.CalculateHeight(g); 
            RectangleF r = new RectangleF(this.currentOrigin.X, this.currentOrigin.Y, this.listBounds.Width, height); 
            fmt.Draw(g, r, lvg.Header, lvg.HeaderAlignment); 
            this.currentOrigin.Y += height; 
        } 
 
        ///  
        /// Print one row of the listview 
        ///  
        ///  
        ///  
        ///  
        ///  
        ///  
        protected void PrintRow(Graphics g, ListView lv, int row, float rowHeight) 
        { 
            ListViewItem lvi = this.GetRow(lv, row); 
 
            // Print the row cell by cell. We only print the cells that are in the range 
            // of columns that are chosen for this page 
            RectangleF cell = new RectangleF(this.currentOrigin, new SizeF(0, rowHeight)); 
            for (int i = this.indexLeftColumn; i <= this.indexRightColumn; i++) { 
                ColumnHeader col = this.GetColumn(i); 
                cell.Width = col.Width; 
                this.PrintCell(g, lv, lvi, row, i, cell); 
                cell.Offset(cell.Width, 0); 
            } 
            this.currentOrigin.Y += rowHeight; 
        } 
 
        ///  
        /// Print one cell of the listview 
        ///  
        ///  
        ///  
        ///  
        ///  
        ///  
        ///  
        ///  
        virtual protected void PrintCell(Graphics g, ListView lv, ListViewItem lvi, int row, int column, RectangleF cell) 
        { 
            BlockFormat fmt = this.CellFormat; 
            ColumnHeader ch = this.GetColumn(column); 
 
            // Are we going to print an icon in this cell? We print an image if it 
            // isn't a text only report AND it is a primary column AND the cell has an image and a image list. 
            if (!this.IsTextOnly && ch.Index == 0 && lvi.ImageIndex != -1 && lv.SmallImageList != null) { 
                // Trick the block format into indenting the text so it doesn't write the text into where the image is going to be drawn 
                const int gapBetweenImageAndText = 3; 
                float textInsetCorrection = lv.SmallImageList.ImageSize.Width + gapBetweenImageAndText; 
                fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) + textInsetCorrection); 
                fmt.Draw(g, cell, this.GetSubItem(lvi, column).Text, ch.TextAlign); 
                fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) - textInsetCorrection); 
 
                // Now draw the image into the area reserved for it 
                RectangleF r = fmt.CalculatePaddedTextBox(cell); 
                if (lv.SmallImageList.ImageSize.Height < r.Height) 
                    r.Y += (r.Height - lv.SmallImageList.ImageSize.Height) / 2; 
                g.DrawImage(lv.SmallImageList.Images[lvi.ImageIndex], r.Location); 
            } else { 
                // No image to draw. SImply draw the text 
                fmt.Draw(g, cell, this.GetSubItem(lvi, column).Text, ch.TextAlign); 
            } 
        } 
 
        ///  
        /// Print the page header and page footer 
        ///  
        ///  
        protected void PrintHeaderFooter(Graphics g) 
        { 
            if (!String.IsNullOrEmpty(this.Header)) 
                PrintPageHeader(g); 
 
            if (!String.IsNullOrEmpty(this.Footer)) 
                PrintPageFooter(g); 
        } 
 
        ///  
        /// Print the page header 
        ///  
        ///  
        protected void PrintPageHeader(Graphics g) 
        { 
            BlockFormat fmt = this.HeaderFormat; 
            if (fmt == null) 
                return; 
 
            float height = fmt.CalculateHeight(g); 
            RectangleF headerRect = new RectangleF(this.listBounds.X, this.listBounds.Y, this.listBounds.Width, height); 
            fmt.Draw(g, headerRect, this.SplitAndFormat(this.Header)); 
 
            // Move down the top of the area available for the list 
            this.listBounds.Y += height; 
            this.listBounds.Height -= height; 
        } 
 
        ///  
        /// Print the page footer 
        ///  
        ///  
        protected void PrintPageFooter(Graphics g) 
        { 
            BlockFormat fmt = this.FooterFormat; 
            if (fmt == null) 
                return; 
 
            float height = fmt.CalculateHeight(g); 
            RectangleF r = new RectangleF(this.listBounds.X, this.listBounds.Bottom - height, this.listBounds.Width, height); 
            fmt.Draw(g, r, this.SplitAndFormat(this.Footer)); 
 
            // Decrease the area available for the list 
            this.listBounds.Height -= height; 
        } 
 
        ///  
        /// Split the given string into at most three parts, using Tab as the divider.  
        /// Perform any substitutions required 
        ///  
        ///  
        ///  
        private String[] SplitAndFormat(String text) 
        { 
            String s = String.Format(text, this.pageNumber, DateTime.Now); 
            return s.Split(new Char[] { '\x09' }, 3); 
        } 
 
        #endregion 
 
        #region Private variables 
 
        // These are our state variables. 
        private int rowIndex; 
        private int indexLeftColumn; 
        private int indexRightColumn; 
        private int pageNumber; 
 
        // Cached values 
        private SortedList sortedColumns; 
        private List groupStartPositions; 
 
        // Per-page variables 
        private RectangleF pageBounds; 
        private RectangleF listBounds; 
        private PointF currentOrigin; 
        private float scaleFactor; 
 
        #endregion 
    } 
 
    ///  
    /// This ListViewPrinterBase handles only normal ListViews, while this class knows about the specifics of ObjectListViews 
    ///  
    public class ListViewPrinter : ListViewPrinterBase 
    { 
        public ListViewPrinter() 
        { 
        } 
 
#if !WITHOUT_OBJECTLISTVIEW 
        ///  
        /// Get the nth item from the given listview, which is in virtual mode. 
        ///  
        /// The ListView in virtual mode 
        /// index of item to get 
        /// the item 
        override protected ListViewItem GetVirtualItem(ListView lv, int n) 
        { 
            return ((VirtualObjectListView)lv).MakeListViewItem(n); 
        } 
 
        ///  
        /// Calculate how high each row of the report should be. 
        ///  
        /// The graphic used for drawing 
        /// The listview to be printed 
        /// The font used for the list 
        /// The height of one row in pixels 
        override protected float CalculateRowHeight(Graphics g, ListView lv, int n) 
        { 
            float height = base.CalculateRowHeight(g, lv, n); 
            if (lv is ObjectListView) 
                height = Math.Max(height, ((ObjectListView)lv).RowHeight); 
            return height; 
        } 
 
        ///  
        /// If the given BlockFormat doesn't specify a background, take it from the SubItem or the ListItem. 
        ///  
        protected bool ApplyCellSpecificBackground(BlockFormat fmt, ListViewItem lvi, ListViewItem.ListViewSubItem lvsi) 
        { 
            if (fmt.BackgroundBrush != null) 
                return false; 
 
            if (lvi.UseItemStyleForSubItems) 
                fmt.BackgroundColor = lvi.BackColor; 
            else 
                fmt.BackgroundColor = lvsi.BackColor; 
 
            return true; 
        } 
 
        protected override void PrintCell(Graphics g, ListView lv, ListViewItem lvi, int row, int column, RectangleF cell) 
        { 
            if (this.IsTextOnly || !(lv is ObjectListView)) { 
                base.PrintCell(g, lv, lvi, row, column, cell); 
                return; 
            } 
 
            OLVColumn olvc = (OLVColumn)this.GetColumn(column); 
 
            BaseRenderer renderer = null; 
            if (olvc.Renderer == null) 
                renderer = new BaseRenderer(); 
            else { 
                renderer = olvc.Renderer; 
 
                // Nasty hack. MS themed ProgressBarRenderer will not work on printer graphic contexts. 
                if (renderer is BarRenderer) 
                    ((BarRenderer)renderer).UseStandardBar = false; 
            } 
 
            renderer.IsDrawBackground = false; 
            renderer.Aspect = null; 
            renderer.Column = olvc; 
            renderer.IsItemSelected = false; 
            renderer.Font = this.CellFormat.Font; 
            renderer.TextBrush = this.CellFormat.TextBrush; 
            renderer.ListItem = (OLVListItem)lvi; 
            renderer.ListView = (ObjectListView)lv; 
            renderer.RowObject = ((OLVListItem)lvi).RowObject; 
            renderer.SubItem = this.GetSubItem(lvi, column); 
            renderer.CanWrap = this.CellFormat.CanWrap; 
 
            // Use the cell block format to draw the background and border of the cell 
            bool bkChanged = this.ApplyCellSpecificBackground(this.CellFormat, renderer.ListItem, renderer.SubItem); 
            this.CellFormat.Draw(g, cell, "", "", ""); 
            if (bkChanged) 
                this.CellFormat.BackgroundBrush = null; 
 
            // The renderer draws into the text area of the block. Unfortunately, the renderer uses Rectangle's  
            // rather than RectangleF's, so we have to convert, trying to prevent rounding errors 
            RectangleF r = this.CellFormat.CalculatePaddedTextBox(cell); 
            Rectangle r2 = new Rectangle((int)r.X + 1, (int)r.Y + 1, (int)r.Width - 1, (int)r.Height - 1); 
            renderer.Render(g, r2); 
 
            // TODO: Put back the previous value rather than just assuming it was true 
            if (renderer is BarRenderer) 
                ((BarRenderer)renderer).UseStandardBar = true; 
        } 
#endif 
    } 
 
    ///  
    /// Specify which sides of a block will be operated on 
    ///  
    public enum Sides 
    { 
        Left = 0, 
        Top = 1, 
        Right = 2, 
        Bottom = 3, 
        All = 4 
    } 
 
    public class BlockFormat : System.ComponentModel.Component 
    { 
        public BlockFormat() 
        { 
 
        } 
 
        #region Public properties 
 
        ///  
        /// In what font should the text of the block be drawn? If this is null, the font from the listview will be used 
        ///  
        [Category("Appearance"), 
        Description("What font should this block be drawn in?"), 
        DefaultValue(null)] 
        public Font Font 
        { 
            get { return font; } 
            set { font = value; } 
        } 
        private Font font; 
 
        ///  
        /// Return the font that should be used for the text of this block or a reasonable default 
        ///  
        [Browsable(false)] 
        public Font FontOrDefault 
        { 
            get 
            { 
                if (this.Font == null) 
                    return new Font("Ms Sans Serif", 12); 
                else 
                    return this.Font; 
            } 
        } 
 
        ///  
        /// What brush will be used to draw the text?  
        ///  
        ///  
        /// If this format is used for cells and this is null AND an ObjectListView is being printed,  
        /// then the text color from the listview will be used. 
        /// This is useful when you have setup specific colors on a RowFormatter delegate, for example. 
        ///  
        ///  
        [Browsable(false)] 
        public Brush TextBrush = null; 
 
        ///  
        /// Return the brush that will be used to draw the text or a reasonable default 
        ///  
        [Browsable(false)] 
        public Brush TextBrushOrDefault 
        { 
            get 
            { 
                if (this.TextBrush == null) 
                    return Brushes.Black; 
                else 
                    return this.TextBrush; 
            } 
        } 
 
        ///  
        /// What color will be used to draw the text? 
        /// This is a convience method used by the IDE. Programmers should call TextBrush directly. 
        ///  
        [Category("Appearance"), 
        Description("What color should text in this block be drawn in?"), 
        DefaultValue(typeof(Color), "Empty")] 
        public Color TextColor 
        { 
            get 
            { 
                if (this.TextBrush == null || !(this.TextBrush is SolidBrush)) 
                    return Color.Empty; 
                else 
                    return ((SolidBrush)this.TextBrush).Color; 
            } 
            set 
            { 
                if (value.IsEmpty) 
                    this.TextBrush = null; 
                else 
                    this.TextBrush = new SolidBrush(value); 
            } 
        } 
 
        ///  
        /// What brush will be used to paint the background? 
        ///  
        [Browsable(false)] 
        public Brush BackgroundBrush = null; 
 
        ///  
        /// What color will be used to draw the background? 
        /// This is a convience method used by the IDE. 
        ///  
        [Category("Appearance"), 
        Description("What color should be used to paint the background of this block?"), 
        DefaultValue(typeof(Color), "Empty")] 
        public Color BackgroundColor 
        { 
            get 
            { 
                if (this.BackgroundBrush == null || !(this.BackgroundBrush is SolidBrush)) 
                    return Color.Empty; 
                else 
                    return ((SolidBrush)this.BackgroundBrush).Color; 
            } 
            set 
            { 
                this.BackgroundBrush = new SolidBrush(value); 
            } 
        } 
 
        ///  
        /// When laying out our header can the text be wrapped? 
        ///  
        [Category("Appearance"), 
        Description("When laying out our header can the text be wrapped?"), 
        DefaultValue(false)] 
        public bool CanWrap 
        { 
            get { return canWrap; } 
            set { canWrap = value; } 
        } 
        private bool canWrap = false; 
 
        ///  
        /// If this is set, at least this much vertical space will be reserved for the text, 
        /// even if the text is smaller. 
        ///  
        [Browsable(false)] 
        public float MinimumTextHeight 
        { 
            get { return minimumTextHeight; } 
            set { minimumTextHeight = value; } 
        } 
        private float minimumTextHeight = 0; 
 
        //---------------------------------------------------------------------------------- 
        // All of these attributes are solely to make them appear in the IDE 
        // When programming by hand, use Get/SetBorderPen()  
        // rather than these methods. 
 
        [Category("Appearance"), Description("Width of the top border"), DefaultValue(0.0f)] 
        public float TopBorderWidth 
        { 
            get { return this.GetBorderWidth(Sides.Top); } 
            set { this.SetBorder(Sides.Top, value, this.GetBorderBrush(Sides.Top)); } 
        } 
        [Category("Appearance"), Description("Width of the Left border"), DefaultValue(0.0f)] 
        public float LeftBorderWidth 
        { 
            get { return this.GetBorderWidth(Sides.Left); } 
            set { this.SetBorder(Sides.Left, value, this.GetBorderBrush(Sides.Left)); } 
        } 
        [Category("Appearance"), Description("Width of the Bottom border"), DefaultValue(0.0f)] 
        public float BottomBorderWidth 
        { 
            get { return this.GetBorderWidth(Sides.Bottom); } 
            set { this.SetBorder(Sides.Bottom, value, this.GetBorderBrush(Sides.Bottom)); } 
        } 
        [Category("Appearance"), Description("Width of the Right border"), DefaultValue(0.0f)] 
        public float RightBorderWidth 
        { 
            get { return this.GetBorderWidth(Sides.Right); } 
            set { this.SetBorder(Sides.Right, value, this.GetBorderBrush(Sides.Right)); } 
        } 
        [Category("Appearance"), Description("Color of the top border"), DefaultValue(typeof(Color), "Empty")] 
        public Color TopBorderColor 
        { 
            get { return this.GetSolidBorderColor(Sides.Top); } 
            set { this.SetBorder(Sides.Top, this.GetBorderWidth(Sides.Top), new SolidBrush(value)); } 
        } 
        [Category("Appearance"), Description("Color of the Left border"), DefaultValue(typeof(Color), "Empty")] 
        public Color LeftBorderColor 
        { 
            get { return this.GetSolidBorderColor(Sides.Left); } 
            set { this.SetBorder(Sides.Left, this.GetBorderWidth(Sides.Left), new SolidBrush(value)); } 
        } 
        [Category("Appearance"), Description("Color of the Bottom border"), DefaultValue(typeof(Color), "Empty")] 
        public Color BottomBorderColor 
        { 
            get { return this.GetSolidBorderColor(Sides.Bottom); } 
            set { this.SetBorder(Sides.Bottom, this.GetBorderWidth(Sides.Bottom), new SolidBrush(value)); } 
        } 
        [Category("Appearance"), Description("Color of the Right border"), DefaultValue(typeof(Color), "Empty")] 
        public Color RightBorderColor 
        { 
            get { return this.GetSolidBorderColor(Sides.Right); } 
            set { this.SetBorder(Sides.Right, this.GetBorderWidth(Sides.Right), new SolidBrush(value)); } 
        } 
 
        private Color GetSolidBorderColor(Sides side) 
        { 
            Brush b = this.GetBorderBrush(side); 
            if (b != null && b is SolidBrush) 
                return ((SolidBrush)b).Color; 
            else 
                return Color.Empty; 
        } 
 
        #endregion 
 
        #region Accessing 
 
        ///  
        /// Get the padding for a particular side. 0 means no padding on that side. 
        /// Padding appears before the border does. 
        ///  
        /// Which side 
        /// The width of the padding 
        public float GetPadding(Sides side) 
        { 
            if (this.Padding.ContainsKey(side)) 
                return this.Padding[side]; 
            else 
                return 0.0f; 
        } 
 
        ///  
        /// Set the padding for a particular side. 0 means no padding on that side. 
        ///  
        /// Which side 
        /// How much padding 
        public void SetPadding(Sides side, float value) 
        { 
            if (side == Sides.All) { 
                this.Padding[Sides.Left] = value; 
                this.Padding[Sides.Top] = value; 
                this.Padding[Sides.Right] = value; 
                this.Padding[Sides.Bottom] = value; 
            } else 
                this.Padding[side] = value; 
        } 
 
        ///  
        /// Get the pen of the border on a particular side.  
        ///  
        /// Which side 
        /// The pen of the border 
        public Pen GetBorderPen(Sides side) 
        { 
            if (this.BorderPens.ContainsKey(side)) 
                return this.BorderPens[side]; 
            else 
                return null; 
        } 
 
        ///  
        /// Get the width of the border on a particular side. 0 means no border on that side. 
        ///  
        /// Which side 
        /// The width of the border 
        public float GetBorderWidth(Sides side) 
        { 
            Pen p = this.GetBorderPen(side); 
            if (p == null) 
                return 0; 
            else 
                return p.Width; 
        } 
 
        ///  
        /// Get the width of the border on a particular side. 0 means no border on that side. 
        ///  
        /// Which side 
        /// The width of the border 
        public Brush GetBorderBrush(Sides side) 
        { 
            Pen p = this.GetBorderPen(side); 
            if (p == null) 
                return null; 
            else 
                return p.Brush; 
        } 
 
        ///  
        /// Change the brush and width of the border on a particular side. 0 means no border on that side. 
        ///  
        /// Which side 
        /// How wide should it be? 
        /// What brush should be used to paint it 
        public void SetBorder(Sides side, float width, Brush brush) 
        { 
            this.SetBorderPen(side, new Pen(brush, width)); 
        } 
 
        ///  
        /// Change the pen of the border on a particular side. 
        ///  
        /// Which side 
        /// How wide should it be? 
        /// What pen should be used to draw it 
        public void SetBorderPen(Sides side, Pen p) 
        { 
            if (side == Sides.All) { 
                this.areSideBorderEqual = true; 
                this.BorderPens[Sides.Left] = p; 
                this.BorderPens[Sides.Top] = p; 
                this.BorderPens[Sides.Right] = p; 
                this.BorderPens[Sides.Bottom] = p; 
            } else { 
                this.areSideBorderEqual = false; 
                this.BorderPens[side] = p; 
            } 
        } 
        private bool areSideBorderEqual = false; 
 
        ///  
        /// Get the distance that the text should be inset from the border on a given side 
        ///  
        /// Which side 
        /// Distance of text inset 
        public float GetTextInset(Sides side) 
        { 
            return GetKeyOrDefault(this.TextInsets, side, 0f); 
        } 
 
        ///  
        /// Set the distance that the text should be inset from the border on a given side 
        ///  
        /// Which side 
        /// Distance of text inset 
        public void SetTextInset(Sides side, float value) 
        { 
            if (side == Sides.All) { 
                this.TextInsets[Sides.Left] = value; 
                this.TextInsets[Sides.Top] = value; 
                this.TextInsets[Sides.Right] = value; 
                this.TextInsets[Sides.Bottom] = value; 
            } else 
                this.TextInsets[side] = value; 
        } 
 
        // I hate the fact that Dictionary doesn't have a method like this! 
        private ValueT GetKeyOrDefault(Dictionary map, KeyT key, ValueT defaultValue) 
        { 
            if (map.ContainsKey(key)) 
                return map[key]; 
            else 
                return defaultValue; 
        } 
 
        private Dictionary BorderPens = new Dictionary(); 
        private Dictionary TextInsets = new Dictionary(); 
        private Dictionary Padding = new Dictionary(); 
 
        #endregion 
 
        #region Calculating 
 
        ///  
        /// Calculate how height this block will be when its printed on one line 
        ///  
        /// The Graphic to use for renderering 
        ///  
        public float CalculateHeight(Graphics g) 
        { 
            return this.CalculateHeight(g, "Wy", 9999999); 
        } 
 
        ///  
        /// Calculate how height this block will be when it prints the given string  
        /// to a maximum of the given width 
        ///  
        /// The Graphic to use for renderering 
        /// The string to be considered 
        /// The max width for the rendering 
        /// The height that will be used 
        public float CalculateHeight(Graphics g, String s, int width) 
        { 
            width -= (int)(this.GetTextInset(Sides.Left) + this.GetTextInset(Sides.Right) + 0.5f); 
            StringFormat fmt = new StringFormat(); 
            fmt.Trimming = StringTrimming.EllipsisCharacter; 
            if (!this.CanWrap) 
                fmt.FormatFlags = StringFormatFlags.NoWrap; 
            float height = g.MeasureString(s, this.FontOrDefault, width, fmt).Height; 
            height = Math.Max(height, this.MinimumTextHeight); 
            height += this.GetPadding(Sides.Top); 
            height += this.GetPadding(Sides.Bottom); 
            height += this.GetBorderWidth(Sides.Top); 
            height += this.GetBorderWidth(Sides.Bottom); 
            height += this.GetTextInset(Sides.Top); 
            height += this.GetTextInset(Sides.Bottom); 
            return height; 
        } 
 
        private RectangleF ApplyInsets(RectangleF cell, float left, float top, float right, float bottom) 
        { 
            return new RectangleF(cell.X + left, 
                cell.Y + top, 
                cell.Width - (left + right), 
                cell.Height - (top + bottom)); 
        } 
 
        ///  
        /// Given a bounding box return the box after applying the padding factors 
        ///  
        ///  
        ///  
        public RectangleF CalculatePaddedBox(RectangleF cell) 
        { 
            return this.ApplyInsets(cell, 
                this.GetPadding(Sides.Left), 
                this.GetPadding(Sides.Top), 
                this.GetPadding(Sides.Right), 
                this.GetPadding(Sides.Bottom)); 
        } 
 
        ///  
        /// Given an already padded box, return the box into which the text will be drawn. 
        ///  
        ///  
        ///  
        public RectangleF CalculateBorderedBox(RectangleF cell) 
        { 
            return this.ApplyInsets(cell, 
                this.GetBorderWidth(Sides.Left), 
                this.GetBorderWidth(Sides.Top), 
                this.GetBorderWidth(Sides.Right), 
                this.GetBorderWidth(Sides.Bottom)); 
        } 
 
        ///  
        /// Given an already padded and bordered box, return the box into which the text will be drawn. 
        ///  
        ///  
        ///  
        public RectangleF CalculateTextBox(RectangleF cell) 
        { 
            return this.ApplyInsets(cell, 
                this.GetTextInset(Sides.Left), 
                this.GetTextInset(Sides.Top), 
                this.GetTextInset(Sides.Right), 
                this.GetTextInset(Sides.Bottom)); 
        } 
 
        ///  
        /// Apply paddeding and text insets to the given rectangle 
        ///  
        ///  
        ///  
        public RectangleF CalculatePaddedTextBox(RectangleF cell) 
        { 
            return this.CalculateTextBox(this.CalculateBorderedBox(this.CalculatePaddedBox(cell))); 
        } 
 
        #endregion 
 
        #region Rendering 
 
        ///  
        /// Draw the given string aligned within the given cell 
        ///  
        /// Graphics to draw on 
        /// Cell into which the text is to be drawn 
        /// The string to be drawn 
        /// How should the string be aligned 
        public void Draw(Graphics g, RectangleF r, String s, HorizontalAlignment align) 
        { 
            switch (align) { 
                case HorizontalAlignment.Center: 
                    this.Draw(g, r, null, s, null); 
                    break; 
                case HorizontalAlignment.Left: 
                    this.Draw(g, r, s, null, null); 
                    break; 
                case HorizontalAlignment.Right: 
                    this.Draw(g, r, null, null, s); 
                    break; 
                default: 
                    break; 
            } 
        } 
 
        ///  
        /// Draw the array of strings so that the first string is left aligned, 
        /// the second is centered and the third is right aligned. All strings  
        /// are optional. Extra strings are ignored. 
        ///  
        /// Graphics to draw on 
        /// Cell into which the text is to be drawn 
        /// Array of strings 
        public void Draw(Graphics g, RectangleF r, String[] strings) 
        { 
            String left = null, centre = null, right = null; 
 
            if (strings.Length >= 1) 
                left = strings[0]; 
            if (strings.Length >= 2) 
                centre = strings[1]; 
            if (strings.Length >= 3) 
                right = strings[2]; 
 
            this.Draw(g, r, left, centre, right); 
        } 
 
        public void Draw(Graphics g, RectangleF r, String left, String centre, String right) 
        { 
            RectangleF paddedRect = this.CalculatePaddedBox(r); 
            RectangleF paddedBorderedRect = this.CalculateBorderedBox(paddedRect); 
            this.DrawBackground(g, paddedBorderedRect); 
            this.DrawText(g, paddedBorderedRect, left, centre, right); 
            this.DrawBorder(g, paddedRect); 
            //g.DrawRectangle(new Pen(Color.Red, 0.5f), r.X, r.Y, r.Width, r.Height); 
        } 
 
        private void DrawBackground(Graphics g, RectangleF r) 
        { 
            if (this.BackgroundBrush != null) { 
                // Enlarge the background area by half the border widths on each side 
                RectangleF r2 = this.ApplyInsets(r, 
                      this.GetBorderWidth(Sides.Left) / -2, 
                      this.GetBorderWidth(Sides.Top) / -2, 
                      this.GetBorderWidth(Sides.Right) / -2, 
                      this.GetBorderWidth(Sides.Bottom) / -2); 
                this.DrawFilledRectangle(g, this.BackgroundBrush, r2); 
            } 
        } 
 
        private void DrawBorder(Graphics g, RectangleF r) 
        { 
            if (this.areSideBorderEqual && this.GetBorderPen(Sides.Top) != null) { 
                Pen p = this.GetBorderPen(Sides.Top); 
                this.DrawOneBorder(g, Sides.Top, r.X, r.Y, r.Width, r.Height, true); 
            } else { 
                this.DrawOneBorder(g, Sides.Top, r.X, r.Y, r.Right, r.Y, false); 
                this.DrawOneBorder(g, Sides.Bottom, r.X, r.Bottom, r.Right, r.Bottom, false); 
                this.DrawOneBorder(g, Sides.Left, r.X, r.Y, r.X, r.Bottom, false); 
                this.DrawOneBorder(g, Sides.Right, r.Right, r.Y, r.Right, r.Bottom, false); 
            } 
        } 
 
        private void DrawOneBorder(Graphics g, Sides side, float x1, float y1, float x2, float y2, bool isRectangle) 
        { 
            Pen p = this.GetBorderPen(side); 
 
            if (p == null) 
                return; 
 
            if (p.Brush is LinearGradientBrush) { 
                LinearGradientBrush lgr = (LinearGradientBrush)p.Brush; 
                LinearGradientBrush lgr2 = new LinearGradientBrush(new PointF(x1, y1), new PointF(x2, y2), lgr.LinearColors[0], lgr.LinearColors[1]); 
#if !MONO 
                lgr2.Blend = lgr.Blend; 
#endif 
                lgr2.WrapMode = WrapMode.TileFlipXY; 
                p.Brush = lgr2; 
            } 
 
            if (isRectangle) 
                g.DrawRectangle(p, x1, y1, x2, y2); 
            else 
                g.DrawLine(p, x1, y1, x2, y2); 
        } 
 
        private void DrawFilledRectangle(Graphics g, Brush brush, RectangleF r) 
        { 
            if (brush is LinearGradientBrush) { 
                LinearGradientBrush lgr = (LinearGradientBrush)brush; 
                LinearGradientBrush lgr2 = new LinearGradientBrush(r, lgr.LinearColors[0], lgr.LinearColors[1], 0f); 
#if !MONO 
                lgr2.Blend = lgr.Blend; 
#endif 
                lgr2.WrapMode = WrapMode.TileFlipXY; 
                g.FillRectangle(lgr2, r); 
            } else 
                g.FillRectangle(brush, r); 
        } 
 
        private void DrawText(Graphics g, RectangleF r, string left, string centre, string right) 
        { 
            RectangleF textRect = this.CalculateTextBox(r); 
            Font font = this.FontOrDefault; 
            Brush textBrush = this.TextBrushOrDefault; 
 
            StringFormat fmt = new StringFormat(); 
            if (!this.CanWrap) 
                fmt.FormatFlags = StringFormatFlags.NoWrap; 
            fmt.LineAlignment = StringAlignment.Center; 
            fmt.Trimming = StringTrimming.EllipsisCharacter; 
 
            if (!String.IsNullOrEmpty(left)) { 
                fmt.Alignment = StringAlignment.Near; 
                g.DrawString(left, font, textBrush, textRect, fmt); 
            } 
 
            if (!String.IsNullOrEmpty(centre)) { 
                fmt.Alignment = StringAlignment.Center; 
                g.DrawString(centre, font, textBrush, textRect, fmt); 
            } 
 
            if (!String.IsNullOrEmpty(right)) { 
                fmt.Alignment = StringAlignment.Far; 
                g.DrawString(right, font, textBrush, textRect, fmt); 
            } 
            //g.DrawRectangle(new Pen(Color.Red, 0.5f), textRect.X, textRect.Y, textRect.Width, textRect.Height); 
            //g.FillRectangle(Brushes.Red, r); 
        } 
 
        #endregion 
 
        #region Standard formatting styles 
 
        ///  
        /// Return the default style for cells 
        ///  
        static public BlockFormat DefaultCell() 
        { 
            BlockFormat fmt = new BlockFormat(); 
 
            fmt.Font = new Font("MS Sans Serif", 9); 
            //fmt.TextBrush = Brushes.Black; 
            fmt.SetBorderPen(Sides.All, new Pen(Color.Blue, 0.5f)); 
            fmt.SetTextInset(Sides.All, 2); 
            fmt.CanWrap = true; 
 
            return fmt; 
        } 
 
        ///  
        /// Return a minimal set of formatting values. 
        ///  
        static public BlockFormat Minimal() 
        { 
            return BlockFormat.Minimal(new Font("Times New Roman", 12)); 
        } 
        static public BlockFormat Minimal(Font f) 
        { 
            BlockFormat fmt = new BlockFormat(); 
 
            fmt.Font = f; 
            fmt.TextBrush = Brushes.Black; 
            fmt.SetBorderPen(Sides.All, new Pen(Color.Gray, 0.5f)); 
            fmt.SetTextInset(Sides.All, 3.0f); 
 
            return fmt; 
        } 
 
        ///  
        /// Return a set of formatting values that draws boxes 
        ///  
        static public BlockFormat Box() 
        { 
            return BlockFormat.Box(new Font("Verdana", 24)); 
        } 
        static public BlockFormat Box(Font f) 
        { 
            BlockFormat fmt = new BlockFormat(); 
 
            fmt.Font = f; 
            fmt.TextBrush = Brushes.Black; 
            fmt.SetBorderPen(Sides.All, new Pen(Color.Black, 0.5f)); 
            fmt.BackgroundBrush = Brushes.LightBlue; 
            fmt.SetTextInset(Sides.All, 3.0f); 
 
            return fmt; 
        } 
 
        ///  
        /// Return a format that will nicely print headers. 
        ///  
        static public BlockFormat Header() 
        { 
            return BlockFormat.Header(new Font("Verdana", 24)); 
        } 
        static public BlockFormat Header(Font f) 
        { 
            BlockFormat fmt = new BlockFormat(); 
 
            fmt.Font = f; 
            fmt.TextBrush = Brushes.WhiteSmoke; 
            fmt.BackgroundBrush = new LinearGradientBrush(new Point(1, 1), new Point(2, 2), Color.DarkBlue, Color.WhiteSmoke); 
            fmt.SetTextInset(Sides.All, 3.0f); 
            fmt.SetPadding(Sides.Bottom, 10); 
 
            return fmt; 
        } 
 
        ///  
        /// Return a format that will nicely print report footers. 
        ///  
        static public BlockFormat Footer() 
        { 
            return BlockFormat.Footer(new Font("Verdana", 10, FontStyle.Italic)); 
        } 
        static public BlockFormat Footer(Font f) 
        { 
            BlockFormat fmt = new BlockFormat(); 
 
            fmt.Font = f; 
            fmt.TextBrush = Brushes.Black; 
            fmt.SetPadding(Sides.Top, 10); 
            fmt.SetBorderPen(Sides.Top, new Pen(Color.Gray, 0.5f)); 
            fmt.SetTextInset(Sides.All, 3.0f); 
 
            return fmt; 
        } 
 
        ///  
        /// Return a format that will nicely print list headers. 
        ///  
        static public BlockFormat ListHeader() 
        { 
            return BlockFormat.ListHeader(new Font("Verdana", 12)); 
        } 
        static public BlockFormat ListHeader(Font f) 
        { 
            BlockFormat fmt = new BlockFormat(); 
 
            fmt.Font = f; 
            fmt.TextBrush = Brushes.Black; 
            fmt.BackgroundBrush = Brushes.LightGray; 
            fmt.SetBorderPen(Sides.All, new Pen(Color.DarkGray, 1.5f)); 
            fmt.SetTextInset(Sides.All, 1.0f); 
 
            fmt.CanWrap = true; 
 
            return fmt; 
        } 
 
        ///  
        /// Return a format that will nicely print group headers. 
        ///  
        static public BlockFormat GroupHeader() 
        { 
            return BlockFormat.GroupHeader(new Font("Verdana", 10, FontStyle.Bold)); 
        } 
        static public BlockFormat GroupHeader(Font f) 
        { 
            BlockFormat fmt = new BlockFormat(); 
 
            fmt.Font = f; 
            fmt.TextBrush = Brushes.Black; 
            fmt.SetPadding(Sides.Top, f.Height / 2); 
            fmt.SetPadding(Sides.Bottom, f.Height / 2); 
            fmt.SetBorder(Sides.Bottom, 3f, new LinearGradientBrush(new Point(1, 1), new Point(2, 2), Color.DarkBlue, Color.White)); 
            fmt.SetTextInset(Sides.All, 1.0f); 
 
            return fmt; 
        } 
 
        #endregion 
    } 
}