/* * 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: * 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 */ #define WITHOUT_OBJECTLISTVIEW 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. /// 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(false)] 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 = false; /// /// 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 /// 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 } 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]); lgr2.Blend = lgr.Blend; 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); lgr2.Blend = lgr.Blend; 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 } }