Introduction

While coding an application that displays a detailed report in a ScrollViewer, it was decided that it would be nice to print the report to a printer.

I found that WPF provides a PrintDialog.PrintVisual method for printing any WPF control derived from the Visual class. PrintVisual will only print a single page so you have to scale your control to fit on the page. Unfortunately this would not work for me since the report was sometimes long enough that it could not be read easily when scaled to fit on the page.

Another option for printing provided by WPF is to create a separate view in a FlowDocument. This is probably the best way to print documents, but it was more work than I wished to put into it, not to mention the extra view that would have to be maintained for each control I wished to print.

What I ended up doing may be a bit unorthodox but works well for my purpose of printing a report that is already displayed in the application. I take the control and convert it into a bitmap that will look good on a 300 dpi printer and then chop the bitmap up into pieces that will fit on a page, add the pages to a FixedDocumentand send that to the printer using PrintDialog.PrintDocument.

Using the code

Below is a class that you can bind to that will print any control derived from the FrameworkElement class.

Hide   Shrink    Copy Code
public class PrintCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
} public void Execute(object parameter)
{
if (parameter is FrameworkElement)
{
FrameworkElement objectToPrint = parameter as FrameworkElement;
PrintDialog printDialog = new PrintDialog();
if ((bool)printDialog.ShowDialog().GetValueOrDefault())
{
Mouse.OverrideCursor = Cursors.Wait;
System.Printing.PrintCapabilities capabilities =
printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
double dpiScale = 300.0 / 96.0;
FixedDocument document = new FixedDocument();
try
{
// Change the layout of the UI Control to match the width of the printer page
objectToPrint.Width = capabilities.PageImageableArea.ExtentWidth;
objectToPrint.UpdateLayout();
objectToPrint.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Size size = new Size(capabilities.PageImageableArea.ExtentWidth,
objectToPrint.DesiredSize.Height);
objectToPrint.Measure(size);
size = new Size(capabilities.PageImageableArea.ExtentWidth,
objectToPrint.DesiredSize.Height);
objectToPrint.Measure(size);
objectToPrint.Arrange(new Rect(size)); // Convert the UI control into a bitmap at 300 dpi
double dpiX = 300;
double dpiY = 300;
RenderTargetBitmap bmp = new RenderTargetBitmap(Convert.ToInt32(
capabilities.PageImageableArea.ExtentWidth * dpiScale),
Convert.ToInt32(objectToPrint.ActualHeight * dpiScale),
dpiX, dpiY, PixelFormats.Pbgra32);
bmp.Render(objectToPrint); // Convert the RenderTargetBitmap into a bitmap we can more readily use
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));
System.Drawing.Bitmap bmp2;
using (MemoryStream memoryStream = new MemoryStream())
{
png.Save(memoryStream);
bmp2 = new System.Drawing.Bitmap(memoryStream);
}
document.DocumentPaginator.PageSize =
new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight); // break the bitmap down into pages
int pageBreak = 0;
int previousPageBreak = 0;
int pageHeight =
Convert.ToInt32(capabilities.PageImageableArea.ExtentHeight * dpiScale);
while (pageBreak < bmp2.Height - pageHeight)
{
pageBreak += pageHeight; // Where we thing the end of the page should be // Keep moving up a row until we find a good place to break the page
while (!IsRowGoodBreakingPoint(bmp2, pageBreak))
pageBreak--; PageContent pageContent = generatePageContent(bmp2, previousPageBreak,
pageBreak, document.DocumentPaginator.PageSize.Width,
document.DocumentPaginator.PageSize.Height, capabilities);
document.Pages.Add(pageContent);
previousPageBreak = pageBreak;
} // Last Page
PageContent lastPageContent = generatePageContent(bmp2, previousPageBreak,
bmp2.Height, document.DocumentPaginator.PageSize.Width,
document.DocumentPaginator.PageSize.Height, capabilities);
document.Pages.Add(lastPageContent);
}
finally
{
// Scale UI control back to the original so we don't effect what is on the screen
objectToPrint.Width = double.NaN;
objectToPrint.UpdateLayout();
objectToPrint.LayoutTransform = new ScaleTransform(1, 1);
Size size = new Size(capabilities.PageImageableArea.ExtentWidth,
capabilities.PageImageableArea.ExtentHeight);
objectToPrint.Measure(size);
objectToPrint.Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth,
capabilities.PageImageableArea.OriginHeight), size));
Mouse.OverrideCursor = null;
}
printDialog.PrintDocument(document.DocumentPaginator, "Print Document Name");
}
}
}

The GeneratePageContent method creates one page from a section of the bitmap of the UI control. The content on the page will show everything from top (the first row of the page) to bottom ( the last row of the page.) You could modify this method to add a header and/or footer to each page if desired.

Hide   Shrink    Copy Code
private PageContent generatePageContent(System.Drawing.Bitmap bmp, int top,
int bottom, double pageWidth, double PageHeight,
System.Printing.PrintCapabilities capabilities)
{
FixedPage printDocumentPage = new FixedPage();
printDocumentPage.Width = pageWidth;
printDocumentPage.Height = PageHeight; int newImageHeight = bottom - top;
System.Drawing.Bitmap bmpPage = bmp.Clone(new System.Drawing.Rectangle(0, top,
bmp.Width, newImageHeight), System.Drawing.Imaging.PixelFormat.Format32bppArgb); // Create a new bitmap for the contents of this page
Image pageImage = new Image();
BitmapSource bmpSource =
System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmpPage.GetHbitmap(),
IntPtr.Zero,
System.Windows.Int32Rect.Empty,
BitmapSizeOptions.FromWidthAndHeight(bmp.Width, newImageHeight)); pageImage.Source = bmpSource;
pageImage.VerticalAlignment = VerticalAlignment.Top; // Place the bitmap on the page
printDocumentPage.Children.Add(pageImage); PageContent pageContent = new PageContent();
((System.Windows.Markup.IAddChild)pageContent).AddChild(printDocumentPage); FixedPage.SetLeft(pageImage, capabilities.PageImageableArea.OriginWidth);
FixedPage.SetTop(pageImage, capabilities.PageImageableArea.OriginHeight); pageImage.Width = capabilities.PageImageableArea.ExtentWidth;
pageImage.Height = capabilities.PageImageableArea.ExtentHeight;
return pageContent;
}

The IsRowGoodBreakingPoint method evaluates a row of the bitmap to determine if it is a good place to start a new page. This is a bit magical, but basically if the values of pixels in the row vary in color values to much, then there must be text or something else there so we don't want to break to another page there. ThemaxDeviationForEmptyLine variable is basically a tolerance value that will allow some deviation for table borders, etc.

Hide   Copy Code
private bool IsRowGoodBreakingPoint(System.Drawing.Bitmap bmp, int row)
{
double maxDeviationForEmptyLine = 1627500;
bool goodBreakingPoint = false; if (rowPixelDeviation(bmp, row) < maxDeviationForEmptyLine)
goodBreakingPoint = true; return goodBreakingPoint;
}

The rowPixelDeviation method below is used to calculate how much difference there is in the colors of the pixels across one row of the bitmap. This method uses pointers to quickly go through the bitmap, so you will have to set the Allow unsafe code property for the project.

Hide   Shrink    Copy Code
private double rowPixelDeviation(System.Drawing.Bitmap bmp, int row)
{
int count = 0;
double total = 0;
double totalVariance = 0;
double standardDeviation = 0;
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0,
bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
int stride = bmpData.Stride;
IntPtr firstPixelInImage = bmpData.Scan0; unsafe
{
byte* p = (byte*)(void*)firstPixelInImage;
p += stride * row; // find starting pixel of the specified row
for (int column = 0; column < bmp.Width; column++)
{
count++; count the pixels byte blue = p[0];
byte green = p[1];
byte red = p[3]; int pixelValue = System.Drawing.Color.FromArgb(0, red, green, blue).ToArgb();
total += pixelValue;
double average = total / count;
totalVariance += Math.Pow(pixelValue - average, 2);
standardDeviation = Math.Sqrt(totalVariance / count); // go to next pixel
p += 3;
}
}
bmp.UnlockBits(bmpData); return standardDeviation;
}

As mentioned at the beginning of the article, this was developed for printing UI controls that display some sort of report or details. It is not going to work in its present state if the control contains an image as a background or contains an image that ends up being larger than what will fit on a page vertically.

最新文章

  1. ios UINaviBar 去除分割线
  2. BZOJ2730——[HNOI2012]矿场搭建
  3. 解决MS Azure 不能ping的问题
  4. 学习笔记5_Day09_网站访问量统计小练习
  5. HUD 2846 Repository
  6. Java并发框架——AQS堵塞队列管理(一)——自旋锁
  7. Latex 中插入图片no bounding box 解决方案
  8. iOS 开发之动画篇 - 从 UIView 动画说起
  9. SQLite 的 EXISTS 与 NOT EXISTS
  10. 2019清明期间qbxt培训qwq
  11. npm install 报错(npm ERR! errno -4048,Error: EPERM: operation not permitted,)解决方法
  12. Luogu 3369 / BZOJ 3224 - 普通平衡树 - [无旋Treap]
  13. pycharm的安装和使用
  14. 玩转X-CTR100 l STM32F4 l ADC 模拟数字转换
  15. Spring AOP底层原理
  16. 【CF484E】Sign on Fence(主席树)
  17. Swift 语言附注 类型
  18. hookup_2.10-0.2.3.jar包下载
  19. html之全局属性
  20. centos7安装并配置postgresql

热门文章

  1. shell脚本一条命令直接发送http请求(xjl456852原创)
  2. python标准库基础之mmap:内存映射文件
  3. iphone UIScrollView缩放
  4. ZOJ 3430 Detect the Virus 【AC自动机+解码】
  5. [Regular Expressions] Introduction
  6. swift2.0 UIImagePickerController 拍照 相册 录像
  7. telnet IP不通/sybase central工具无法连接到数据库
  8. RDLC报表上下标实现
  9. OpenGL ES 2.0 光照
  10. Tornado模块分类和各模块之间的关系