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.

WPF可视化控件打印的更多相关文章

  1. WPF实现可视化控件打印及打印预览

    打印预览XAML代码: <controls:WindowEx x:Class="SunCreate.Vipf.Client.UI.MapPrintPreview" xmlns ...

  2. 用Synoptic Panel自定义基于图形的可视化控件--制作一张剧场售票统计报表

    数据可视化的一大特点就是能给报表使用者带来感官上的享受.不再是枯燥的数字,而变成一个一个亮丽的图形.之前业界大神公布过一个统计Car Accidents的报表,这个Power BI Report的特点 ...

  3. wpf 大控件 打印 将控件转成 xps格式 并分页打印

    //PayRollPrintList:要打印的 list 可换成自己要打印的类型 private List<PayRoll> _PayRollPrintList = new List< ...

  4. wpf 深度复制控件,打印控件

    原文:wpf 深度复制控件,打印控件 <Window x:Class="WpfApp2.MainWindow" xmlns="http://schemas.micr ...

  5. 示例:自定义WPF底层控件UI库 HeBianGu.General.WpfControlLib V2.0版本

    原文:示例:自定义WPF底层控件UI库 HeBianGu.General.WpfControlLib V2.0版本 一.目的:封装了一些控件到自定义的控件库中,方便快速开发 二.实现功能: 基本实现常 ...

  6. WPF 截图控件之移除控件(九)「仿微信」

    WPF 截图控件之移除控件(九)「仿微信」 WPF 截图控件之移除控件(九)「仿微信」 作者:WPFDevelopersOrg 原文链接: https://github.com/WPFDevelope ...

  7. WPF Popup 控件导致被遮挡内容不刷新的原因

    WPF Popup 控件导致被遮挡内容不刷新的原因 周银辉 今天在写一个WPF控件时用到了Popup控件,很郁闷的情况是:当popup关闭时,原来被popup挡住的界面部分不刷新,非要手动刷新一下(比 ...

  8. Android高手速成--第一部分 个性化控件(View)

    第一部分 个性化控件(View) 主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.Pro ...

  9. 创建 WPF 工具箱控件

    创建 WPF 工具箱控件 WPF (Windows Presentation Framework) 工具箱控件模板允许您创建 WPF 控件,会自动添加到 工具箱 安装扩展的安装. 本主题演示如何使用模 ...

随机推荐

  1. 如何在 Android 手机上实现抓包?

    如何在 Android 手机上实现抓包? http://www.zhihu.com/question/20467503 我想知道某个应用究竟在数据提交到哪里,提交了什么.网上的教程太复杂,不想麻烦.有 ...

  2. python倒计时

    #coding=utf-8 #!/usr/bin/env python import datetime,time i=3 while i==3: spring=datetime.datetime(20 ...

  3. Webservice-Java-CXF

    1)首先呢,还是包的问题,在http://cxf.apache.org/download.html这里可以下到最新版的CXF,当然,我用的是最新版的.接下来还是那句废话,建WEB项目,放入JAR包.而 ...

  4. Android菜鸟的成长笔记(28)——Google官方对Andoird 2.x提供的ActionBar支持

    在Google官方Android设计指南中(链接:http://www.apkbus.com/design/get-started/ui-overview.html)有一个新特性就是自我标识,也就是宣 ...

  5. CentOS7--DNS处理模块DnsPython的简单使用

    初步了解: DnsPython是Python实现的一个DNS工具包,支持几乎所有的记录类型. 安装: # wget http://www.dnspython.org/kits/1.9.4/dnspyt ...

  6. javascript什么是函数

    函数是完成某个特定功能的一组词语.如没有函数,完成任务可能需要五行.十行.甚至更多的代码. 这是未满就可以把完成特定功能的代码块放到一个函数里,直接调用这个函数,就省重复输入大量代码的麻烦. 如何定义 ...

  7. scala中的view bound与context bound

    1.scala中的<%意识是“view bounds”(视界) ,它比<:的使用范围更广,还能进行隐式转换,是一种语法糖. 下面的两种写法是等效的,在编译之后完全一样. object Te ...

  8. Asp.Net Mvc5新特性

    One ASP.NET:统一平台 BootStrap:免费Css响应式页面 路由标记属性:简单,控制器,操作,前缀,参数,URL ASP.NET WEB API 2:路由标记属性,Oauth2.0,O ...

  9. oc随笔四:NSString、NSNumber

    #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { ...

  10. 重写String类,也有些区别,供参考

    头文件如下: #pragma once #include <string> #include <string.h> #include <stdlib.h> #inc ...