打印质量winform
Posted
技术标签:
【中文标题】打印质量winform【英文标题】:printing quality winform 【发布时间】:2012-09-12 17:05:44 【问题描述】:我在尝试从 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);
【问题讨论】:
您能否从其他应用程序打印出高质量的文件?例如:记事本或 Word 有什么方法可以使用PrintScreen
吗?然后检测表格位置并进行切割。
您真的要打印表单吗?例如为应用程序制作手册我可以看到它的用法,但是您可以使用屏幕截图。对于其他打印,您可能应该只以打印分辨率将输出绘制到打印图形。
【参考方案1】:
这一次又一次地出现。只是没有神奇的解决方案,尽管最终问题可能会消失。 “视网膜”显示器的出现至关重要。
核心问题是显示器的分辨率严重比打印机差。典型的打印机具有每英寸 600 点的分辨率。这使得它能够在一张纸上打印 6600 x 5100 的单个像素。比显示器所能显示的要多得多,很多,全高清显示器的最高像素为 1920 x 1080 像素。大约差 5 倍,给予或接受。
当您将显示器上显示的内容打印在一张纸上并尝试保持其大小相同时,效果不佳。不可避免的是,由于显示器上缺少像素,显示器上的每个像素都会在纸上打印为 5x5 的斑点。如果您尝试保持一对一的像素映射,您将在纸上得到一个非常清晰的副本。但它已经变成了邮票。
不可避免地,由于这些像素斑点,打印输出看起来非常粗糙。 text 看起来特别糟糕。操作系统使用许多技巧来使文本在分辨率较差的显示器上看起来不错。抗锯齿是标准的,像 ClearType 这样的技巧旨在利用可以帮助提高感知分辨率的监视器物理特性。这在打印文本时不再起作用,那些抗锯齿像素变成斑点并变得非常明显,完全破坏了效果。对于彩色打印机上的 ClearType 文本尤其不利,现在可以清楚地看到红色和蓝色条纹。
唯一合适的方法是使用实际分辨率而不是显示器分辨率渲染到打印机。就像在 .NET 中使用 PrintDocument 类一样。使用报告生成器有助于避免为它编写代码。
【讨论】:
If you try to keep the pixel mapping one-to-one, you will get a razor-sharp copy on paper. But it has turned into a postage-stamp.
- 这简直让我笑出声来。汉斯,你是个了不起的人!【参考方案2】:
您应该在 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);
【讨论】:
+1!!这无疑帮助了我勒内!所以谢谢你的信息。我只是自己绘制每个控件(有时是组件)。【参考方案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 中的未缩放控件。前任。 GroupBox
控件将首先打印,然后在迭代时将缩放的控件显示在它们之上。这是因为我们在缩放其控件之前DrawToBitmap
GroupBox
。这件事就交给你处理了。
我们正在处理的位图不一定适合用户通过设置打印对话框最终获得的打印分辨率,但我们可以使用更高的分辨率,从而产生比我们最初的屏幕位图分辨率很差。
您当然需要为除Panel
和GroupBox
之外的其他控件添加对特殊情况的支持,这些控件可以包含其他控件、图像控件等。
【讨论】:
有趣的想法。我想你可以这样做,如果你真的需要在纸上看到你在屏幕上看到的完全相同的东西。但我真的不知道为什么会这样——分组框和按钮是 UI 控件,在死树上没有多大意义。既然这些代码无论如何都得写,不如直接把内容直接渲染到页面上。 @CodyGray 可能我误解了 OP,但我的印象是他想打印面板等的“屏幕截图”,但质量比直接打印直接从屏幕获取的位图要好。 打败这个技巧的是 Form 类将表单的大小严格限制为屏幕的大小。您最终将打印表单的剪辑版本。 Fwiw,让所有内容自动重新缩放的最快方法是分配表单的 Font 属性。 @HansPassant 您始终可以通过中间位图来绘制克隆控件,而无需触摸屏幕上的控件或其位置。绘制缩放到这个位图的克隆的,然后是 im。位图到缩放位置的最终位图应该可以在没有任何剪辑的情况下工作。当然,这取决于一个人愿意花多少工作......)【参考方案4】:我花了几天时间寻找一种方法来打印高质量的面板及其内容。这没有用,我尝试了其他人的代码,它们要么都是错误的,要么就是质量很差,直到我发现:
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);
并在覆盖 OnPaint 方法中放置一个 if 语句,如下所示:
protected override void OnPaint(PaintEventArgs e)
if (MemoryImage != null)
e.Graphics.DrawImage(MemoryImage, 0, 0);
base.OnPaint(e);
一切正常,您最终将获得近乎完美的打印质量
只是想分享这颗宝石,不客气!
谢谢拉克什先生!
【讨论】:
以上是关于打印质量winform的主要内容,如果未能解决你的问题,请参考以下文章