2018-09-30-csharp-打印

世间千般好


分类 csharp
标签 csharp 打印

走java这条路,已经多年没碰c#了~,这次做了一个C#的项目,有一个打印的需求,做的过程中有些收获。

需求

项目中有一个需求是打印手术记录单。这个手术记录单里面包含一些基本信息,图片,还有一个富文本框。

图

图

打印效果;

第一页

图

第二页

图

第三页

图

打印相关类

打印当然使用的是PrintDocument,PrintDialog,PrintPreviewDialog等几个打印相关的类,参考中有两篇文章介绍这几个类 。

PrintDocument 就是要打印的文档

如下图,关心两个方法,

  • BeginPrint,在打印之前调用一次,可以在这里整理需要打印的东西。
  • PrintPage,对于要打印的每一页调用一次,在这里进行打印。

图

PrintDialog 打印对话框,一些选项,比如用哪台打印机,打印几份

这个没啥好说的。

PrintPreviewDialog 打印预览

这个可以用来预览打印的效果,身边没有打印机的话,可以用这个看打印效果。

A4纸

对于一张A4纸,是有打印范围的,超过范围的内容,打印出来就没了。

我这次测量了一下,打印范围。 左上角是 (100, 100) 可打印高度969,可打印宽度627。

打印思路

说一下这里打印的思路:获得控件的Bitmap(类似于截图),然后调用打印的DrawImage()方法来画图,本来想写出来,但感觉不好说,撸个Demo出来。

ImageHelper类中几个工具方法来源于其它博客,没有记得具体地址了

class ImageHelper
{
    // 将image转换成byte[]数据
    public static byte[] imageToByte(System.Drawing.Image _image)
    {
        MemoryStream ms = new MemoryStream();
        _image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        return ms.ToArray();
    }
    // 将byte[]数据转换成image
    public static Image byteToImage(byte[] myByte)
    {
        MemoryStream ms = new MemoryStream(myByte);
        Image _Image = Image.FromStream(ms);
        return _Image;
    }
    // 截取Bitmap中的部分
    public static Bitmap cutImage(Bitmap b, int x, int y, int width, int height) {
        return b.Clone(new System.Drawing.Rectangle(x, y, width, height), b.PixelFormat);
    }
}

关于RichTextBox的三个问题

1, 即使把RichTextBox放在Panel中,然后使用类似的方法,但是获得的图片中,RichTextBox中的内容都不见了。

Bitmap head = new Bitmap(this.panelSJCX_SSJL_Head.Width, this.panelSJCX_SSJL_Head.Height);
this.panelSJCX_SSJL_Head.DrawToBitmap(head, new Rectangle(0, 0, head.Width, head.Height));

用下面RtbToBitmap()方法 ,可以实现传入RichTextBox得到对应的图片,来源于别的博客

class ImageHelper
{
   // 与 Win32接口通信
    [DllImport("USER32.dll")]
    private static extern Int32 SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
    private const int WM_USER = 0x400;
    private const int EM_FORMATRANGE = WM_USER + 57;
    private const double inch = 14.4; // 与象素的换算。
    // 再是所需要的结构体的定义:
    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
    // 望文生义吧
    [StructLayout(LayoutKind.Sequential)]
    private struct CHARRANGE
    {
        public int cpMin;
        public int cpMax;
    }
    [StructLayout(LayoutKind.Sequential)]
    private struct FORMATRANGE
    {
        public IntPtr hdc;
        public IntPtr hdcTarget;
        public RECT rc;
        public RECT rcPage;
        public CHARRANGE chrg;
    }
    // 将RTB上的内容格式化后画出来
    public static Bitmap RtbToBitmap(RichTextBox rtb)
    {
        // 这个地方是可打印的大小
        return RtbToBitmap(rtb, rtb.Bounds.Width, rtb.Bounds.Height);
    }
    public static Bitmap RtbToBitmap(RichTextBox rtb, int width, int height)
    {
        Bitmap bmp = new Bitmap(width, height);
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                bmp.SetPixel(i, j, Color.White);
            }
        }
        using (Graphics gr = Graphics.FromImage(bmp))
        {
            System.IntPtr hDC = gr.GetHdc(); // 屏幕做为画源
            FORMATRANGE fmtRange;
            RECT rect;
            int fromAPI;
            rect.Top = 0; rect.Left = 0;
            rect.Bottom = (int)(bmp.Height + (bmp.Height * (bmp.HorizontalResolution / 100)) * inch);
            rect.Right = (int)(bmp.Width + (bmp.Width * (bmp.VerticalResolution / 100)) * inch);
            fmtRange.chrg.cpMin = 0;
            fmtRange.chrg.cpMax = -1;
            fmtRange.hdc = hDC;
            fmtRange.hdcTarget = hDC;
            fmtRange.rc = rect;
            fmtRange.rcPage = rect;
            int wParam = 1;
            System.IntPtr lParam = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
            System.Runtime.InteropServices.Marshal.StructureToPtr(fmtRange, lParam, false);
            fromAPI = SendMessage(rtb.Handle, EM_FORMATRANGE, wParam, lParam);
            System.Runtime.InteropServices.Marshal.FreeCoTaskMem(lParam);
            fromAPI = SendMessage(rtb.Handle, EM_FORMATRANGE, wParam, new IntPtr(0));
            gr.ReleaseHdc(hDC);
        }
        return bmp;
    }

2,如何让RichTextBox根据内容的大小而变化高度。

用前面RtbToBitmap()方法 ,可以实现传入RichTextBox得到对应的图片。但是如果RichTextBox的内容大于RichTextBox的高度,那么RichTextBox就会出现滚动条,导致得到的图片,也是带着滚动条,并且有部分内容没有显示出来。

可以使用如下方法, 当内容改变时重新设置RichTextBox的高度:

richTextBox1.ScrollBars = RichTextBoxScrollBars.None;

private void richTextBox1_ContentsResized(object sender, ContentsResizedEventArgs e)
{
	richTextBox1.Height = e.NewRectangle.Height+10;
}

3,如何切割

通过上一步已经得到了RichTextBox的完整图片,但是RichTextBox的内容可能很多,需要多页才能打印完毕,那么我们就要对这个RichTextBox进行切割。

但是切割的话,可能会把字给腰斩了,而且如果字体大小不一样,那就不好切割了。

考虑过,如果RichTextBox高度过高,把一个RichTextBox分成多个RichTextBox,然后每个RichTextBox单独导出图片。但是如何分? 测试过 RichTextBox.RTF和 RichTextBox.Text。

  • 对于RichTextBox.RTF一个切割不好,就容易乱码。— 放弃
  • 对于RichTextBox.Text, 使用 RichTextBox.Lines 获得所有的行,这个是不会出现乱码问题,但是可能有这种情况,一行数据的宽度超过RichTextBox的宽度,那么在RichTextBox中就会显示为两行,如果数据更长,那么会占用更多行。 这样就会导致RichTextBOx的高度无法估计。— 放弃

最后由于字体的大小是固定的为小四, 考虑还是使用对得到的RichTextBox的Bitmap进行分割方式,可以通过计算或者直接通过测试得到分割的时候不会把字进行腰斩的分割点。

如果字体大小不一样的话,这个方法就不行了。

参考