13

尝试从 WinForms 应用程序打印时遇到 2 个问题。无论我尝试什么,第一个都是非常非常糟糕的质量。第二个是我的左上角页边距很大,winform正在切割。有任何想法吗?这是我的代码:

Bitmap MemoryImage;
    public void GetPrintArea(Panel pnl)
    {
        MemoryImage = new Bitmap(pnl.Width, pnl.Height);
        Rectangle rect = new Rectangle(0, 0, pnl.Width, pnl.Height);
        pnl.DrawToBitmap(MemoryImage, new Rectangle(0, 0, pnl.Width, pnl.Height));
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        if (MemoryImage != null)
        {
            e.Graphics.DrawImage(MemoryImage, 0, 0);
            base.OnPaint(e);
        }
    }
    void printdoc1_PrintPage(object sender, PrintPageEventArgs e)
    {
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
        e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        Rectangle pagearea = e.PageBounds;
        e.Graphics.DrawImage(MemoryImage, (pagearea.Width / 2) - (this.panel1.Width / 2), this.panel1.Location.Y);

    }
    public void Print(Panel pnl)
    {
        panel1 = pnl;
        GetPrintArea(pnl);
        printPreviewDialog1.Document = printdoc1;
        printPreviewDialog1.ShowDialog();
    }
    private void button2_Click(object sender, EventArgs e)
    {
        Print(this.panel1);
    }
4

4 回答 4

18

这一次又一次地出现。只是没有神奇的解决方案,尽管最终问题可能会消失。“视网膜”显示器的出现至关重要。

核心问题是显示器的分辨率比打印机差得多典型的打印机具有每英寸 600 点的分辨率。这使得它能够在一张纸上打印 6600 x 5100 的单个像素。比显示器所能显示的多得多,全高清显示器的最高像素为 1920 x 1080 像素。大约差 5 倍,给予或接受。

当您将显示器上显示的内容打印在一张纸上并尝试保持其大小相同时,这种方法效果不佳。不可避免的是,由于显示器上缺少像素,显示器上的每个像素都会在纸上打印为 5x5 的斑点。如果您尝试保持像素映射一对一,您在纸上获得非常清晰的副本。但它已经变成了一张邮票。

不可避免地,由于这些像素斑点,打印输出看起来非常粗糙。看起来特别差的是文本。操作系统使用许多技巧来使文本在分辨率较差的显示器上看起来不错。抗锯齿是标准的,像 ClearType 这样的技巧旨在利用可以帮助提高感知分辨率的监视器物理特性。这在打印文本时不再起作用,那些抗锯齿像素变成斑点并变得非常明显,完全破坏了效果。对于彩色打印机上的 ClearType 文本尤其不利,现在可以清楚地看到红色和蓝色条纹。

唯一体面的方法是使用实​​际分辨率而不是显示器分辨率渲染到打印机。就像在 .NET 中使用 PrintDocument 类一样。使用报告生成器可以帮助避免为它编写代码。

于 2013-06-02T17:12:38.850 回答
4

您应该在 PrintDocument 打印时获得的 Graphics 对象上绘制自己。这为您提供了所需的所有控制。汉斯·帕桑特所说的一切仍然适用于这里......请记住,这是最简单的实现,只是演示可以实现的,我并不是说这是最简单/最好/最有效的方式......我的代码不占用多个页面、包含在容器中的控件或不是 Label 和 PictureBox 类型的控件。

我使用了来自System.Drawing.Graphics的 Draw... 方法

稍微改编自上面的代码以使其正常工作:

public void GetPrintArea(Panel pnl, Graphics gr)
{
    // scale to fit on width of page...
    if (pnl.Width > 0)
    {
      gr.PageScale = gr.VisibleClipBounds.Width/pnl.Width;
    }
    // this should recurse...
    // just for demo so kept it simple
    foreach (var ctl in pnl.Controls)
    {
        // for every control type
        // come up with a way to Draw its
        // contents
        if (ctl is Label)
        {
            var lbl = (Label)ctl;
            gr.DrawString(
                lbl.Text,
                lbl.Font,
                new SolidBrush(lbl.ForeColor),
                lbl.Location.X,  // simple based on the position in the panel
                lbl.Location.Y);
        }
        if (ctl is PictureBox)
        {
            var pic = (PictureBox)ctl;
            gr.DrawImageUnscaledAndClipped(
                pic.Image,
                new Rectangle(
                    pic.Location.X,
                    pic.Location.Y,
                    pic.Width,
                    pic.Height));
        }
    }
}

void printdoc1_PrintPage(object sender, PrintPageEventArgs e)
{
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
    e.Graphics.InterpolationMode =Drawing2D.InterpolationMode.HighQualityBilinear;
    e.Graphics.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality;
    GetPrintArea(panel1, e.Graphics);
}
于 2013-06-02T18:19:34.323 回答
3

实际上,您可以通过在“矢量”级别而不是位图级别上对它们应用缩放来打印更清晰的控件。

此快照显示了以下技术的结果(请不要介意我的 Win2000-ish UI :-)):

合成的

我们所做的是以与rene在他的回答中显示ControlCollection的非常相似的方式遍历控件。

但是 - 此外,在我们将其位图绘制为预设大小的位图之前,我们将比例应用于控件本身的位置、大小和字体,在这种情况下,该位图大 5 倍(4 倍表示大约 300 DPI,这是有效的打印分辨率大多数打印机)。

这样做的原因是在打印时保持控件上的细线清晰,或者我们可以缩放位图本身,这不会给我们带来任何分辨率方面的好处。通过缩放字体,我们减少了抗锯齿效果并可以提供更好的打印质量。

为此,您可以首先在按钮的单击事件中设置以下内容:

//this will produce 5x "sharper" print
MemoryImage = new Bitmap((Panel1.Width * 5), (Panel1.Height * 5));

Using Graphics g = Graphics.FromImage(MemoryImage) {
    ScaleControls(Panel1, g, 5);
};

PrintPreviewDialog1.Document = printdoc1;
PrintPreviewDialog1.ShowDialog();

现在在ScaleControls递归函数中,我们缩放位置、大小和字体,以便在将每个控件绘制到位图之前使每个控件本身具有更高的分辨率:

private void ScaleControls(Control c, ref Graphics g, double s)
{
    //To detach controls for panels, groupboxes etc.
    List<Control> hold = null;

    foreach (Control ctrl in c.Controls) {
        if (ctrl is GroupBox || ctrl is Panel) {
            //backup reference to controls
            hold = new List<Control>();
            foreach (Control gctrl in ctrl.Controls) {
                hold.Add(gctrl);
            }
            ctrl.Controls.Clear();
        }

        //backup old location, size and font (see explanation)
        Point oldLoc = ctrl.Location;
        Size oldSize = ctrl.Size;
        Font oldFont = ctrl.Font;

        //calc scaled location, size and font
        ctrl.Location = new Point(ctrl.Location.X * s, ctrl.Location.Y * s);
        ctrl.Size = new Size(ctrl.Size.Width * s, ctrl.Height * s);
        ctrl.Font = new Font(ctrl.Font.FontFamily, ctrl.Font.Size * 5,
                             ctrl.Font.Style, ctrl.Font.Unit);

        //draw this scaled control to hi-res bitmap
        using (Bitmap bmp = new Bitmap(ctrl.Size.Width, ctrl.Size.Height)) {
            ctrl.DrawToBitmap(bmp, ctrl.ClientRectangle);
            g.DrawImage(bmp, ctrl.Location);
        }

        //restore control's geo
        ctrl.Location = oldLoc;
        ctrl.Size = oldSize;
        ctrl.Font = oldFont;

        //recursive for panel, groupbox and other controls
        if (ctrl is GroupBox || ctrl is Panel) {
            foreach (Control gctrl in hold) {
                ctrl.Controls.Add(gctrl);
            }

            ScaleControls(ctrl, g, s);
        }
    }
}

最后在打印的事件处理程序中:

double scale = MemoryImage.Width / e.PageBounds.Width;

e.Graphics.DrawImage(MemoryImage, 0, 0,
          Convert.ToInt32(MemoryImage.Width / scale),
          Convert.ToInt32(MemoryImage.Height / scale));

现在,在此示例中,我们就地缩放控件。当然,这并不理想,因为在我们进行打印预览时,它们似乎过着自己的生活。

理想情况下,我们会在迭代时克隆每个控件,并在绘制位图后丢弃它。这也将消除备份几何图形的需要。但是对于原则的例子,我保持原样。我会把它留给你克隆等。

分离控件的原因是,如果我们不这样做(就像现在的代码一样 - 这肯定可以通过提供另一种迭代方法来改变,即预缩放克隆控件)在 f.ex 中未缩放。GroupBox控件将首先打印,然后缩放的控件将在迭代时显示在它们之上。这是因为我们DrawToBitmapGroupBox缩放它的控件之前。这是我留给你处理的事情。

我们正在处理的位图不一定适合用户通过设置打印对话框最终获得的打印分辨率,但是我们可以获得更高的分辨率来使用它反过来产生比糟糕的屏幕位图更好的结果我们最初的决议。

您当然需要添加对其他控件的特殊情况的支持,而不是Panel可以GroupBox容纳其他控件、图像控件等。

于 2013-06-08T07:36:33.510 回答
0

我花了几天时间寻找一种方法来打印高质量的面板及其内容。这没有用,我尝试了其他人的代码,它们要么都是错误的,要么就是质量很差,直到我发现:

http://rkinfopedia.blogspot.com/2008/07/printing-contents-of-panel-control.html

只需将事件处理程序放在打印按钮单击事件处理程序中,并将打印方法也包含在其中,如下所示:

private void button3_Click(object sender, EventArgs e)
{
    printdoc1.PrintPage += new PrintPageEventHandler(printdoc1_PrintPage);
    Print(panel1);
}

并在 override OnPaint 方法中放置一个 if 语句,如下所示:

protected override void OnPaint(PaintEventArgs e)
{
    if (MemoryImage != null)
    {
        e.Graphics.DrawImage(MemoryImage, 0, 0);
        base.OnPaint(e);
    }
}

休息就好了,你最终会得到近乎完美的打印质量

只是想分享这个宝石,欢迎互联网陌生人!

谢谢拉克什先生!

于 2013-10-06T20:11:30.350 回答