C# 如何找到实际的可打印区域?(打印文件)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8761633/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 04:19:18  来源:igfitidea点击:

How to find the actual printable area? (PrintDocument)

c#printinggdi+printdocument

提问by Trevor Elliott

Why is finding out this magic Rectangle so difficult?

为什么找出这个神奇的矩形如此困难?

In the OnPrintPage event I have PrintPageEventArgs and I am trying to draw using the Graphics within the bounds of the maximum printable area.

在 OnPrintPage 事件中,我有 PrintPageEventArgs 并且我试图在最大可打印区域的范围内使用图形进行绘制。

I have tried using PageBounds, PrintableArea, Graphics.VisibleClipBounds, etc. All fail to consistently get the drawing area, especially when switching from Landscape to Portrait layout. PrintableArea does not seem to ever change when you switch from Landscape to Portrait.

我曾尝试使用 PageBounds、PrintableArea、Graphics.VisibleClipBounds 等。都无法始终如一地获得绘图区域,尤其是从横向布局切换到纵向布局时。当您从横向切换到纵向时,PrintableArea 似乎永远不会改变。

I have also noticed that there is a difference in how Graphics.VisibleClipBounds is set depending on if I'm doing a print preview and an actual print. In a preview it always shows Portrait width/height, so I have to check if it is a preview and I have to manually swap the width/height when it is a Landscape.

我还注意到,Graphics.VisibleClipBounds 的设置方式有所不同,具体取决于我是进行打印预览还是实际打印。在预览中它总是显示纵向宽度/高度,所以我必须检查它是否是预览,并且当它是横向时我必须手动交换宽度/高度。

I need an algorithm to calculate the printable area as it relates to the current Graphics context, not an arbitrary theoretical print area that isn't used in actual drawing.

我需要一种算法来计算可打印区域,因为它与当前的 Graphics 上下文相关,而不是在实际绘图中未使用的任意理论打印区域。



My concern is dealing with the Graphics matrix offset. So far I have noticed severe inconsistencies between how the Graphics context is pre-translated using the hard margins depending on factors like:

我关心的是处理图形矩阵偏移量。到目前为止,我已经注意到如何使用硬边距预翻译 Graphics 上下文之间存在严重的不一致,具体取决于以下因素:

  • If OriginAtMargins is true or false (not behaving as I would think)
  • If I'm printing to a printer, or using the PrintPreviewControl (I have to check if this is a print to preview or a print to page to handle the translation properly)
  • If I'm using my printer at home or my printer at work (both behave differently)
  • 如果 OriginAtMargins 是真还是假(不像我想的那样表现)
  • 如果我打印到打印机,或使用 PrintPreviewControl(我必须检查这是打印预览还是打印到页面以正确处理翻译)
  • 如果我在家中使用打印机或在工作中使用打印机(两者表现不同)

Is there a standard way to handle this? Should I just reset the matrix? When I set OriginAtMargins to true, the Graphics is pre-translated to 84,84, but my margins are 100,100. The hard margins are 16,16. Shouldn't it be translated to 100,100? Since 0,0 should be at the page bounds, not the hard margins.

有没有标准的方法来处理这个问题?我应该重置矩阵吗?当我将 OriginAtMargins 设置为 true 时,Graphics 被预转换为 84,84,但我的边距为 100,100。硬边距为 16,16。不应该翻译成100,100吗?由于 0,0 应该在页面边界,而不是硬边距。

Basically my method should always work at getting the best printable rectangle. I just need a consistent, device-independent way of making sure that my drawing origin (0, 0) is at the top-left of the page in order for the above Rectangle to be of any use to me.

基本上我的方法应该总是能够获得最好的可打印矩形。我只需要一种一致的、与设备无关的方式来确保我的绘图原点 (0, 0) 位于页面的左上角,以便上述矩形对我有用。

采纳答案by BenSwayne

Your question lacks a little clarity as to what the "best" rectangle is. I'm going to assume you mean the largest rectangle that will be 100% visible when printed.

您的问题对“最佳”矩形是什么缺乏明确性。我假设您的意思是打印时 100% 可见的最大矩形。

So lets start by making sure we understand what the print document graphics object "origins" are and how the OriginAtMargins property affects this origin.

因此,让我们首先确保我们了解打印文档图形对象“原点”是什么以及 OriginAtMargins 属性如何影响该原点。

OriginAtMargins - Gets or sets a value indicating whether the position of a graphics object associated with a page is located just inside the user-specified margins or at the top-left corner of the printable areaof the page.
- PrintDocument Class Definition on MSDN

OriginAtMargins - 获取或设置一个值,该值指示与页面关联的图形对象的位置是位于用户指定的边距内还是位于 页面可打印区域左上角
- MSDN 上的 PrintDocument 类定义

So with OriginAtMarginsset to false(default) the graphics object will be adjusted to the PrintableArea rectangle (about 5/32 from each page edge for my laser printer, old laser printers may be more, new inkjets may print right to the edge, software PDF printers will print right to the edge). So 0,0 in my graphics object is actually 16,16 on the physical page of my laser printer (your printer may be different).

因此,OriginAtMargins设置为false(默认)后,图形对象将被调整为 PrintableArea 矩形(对于我的激光打印机,距每个页面边缘约 5/32,旧激光打印机可能更多,新喷墨打印机可能直接打印到边缘,软件 PDF 打印机将打印到边缘)。所以我的图形对象中的 0,0 实际上是我的激光打印机物理页面上的 16,16(您的打印机可能不同)。

With the default 1 inch page margins and OriginAtMarginsset to true, the graphics object will be adjusted to the 100,100,650,1100 rectangle for a normal portrait letter page. This is one inch inside each physical page edge. So 0,0 in your graphics object is actually 100,100 on the physical page.

使用默认的 1 英寸页边距并OriginAtMargins设置为true,图形对象将调整为 100、100、650、1100 矩形以用于正常的纵向字母页面。这是每个物理页面边缘内一英寸。所以图形对象中的 0,0 实际上是物理页面上的 100,100。

Margins are also known as "soft margins" as they are defined in software and not affected by the physical printing device. This means they will be applied to the current page size in software and reflect the actual page dimension portrait or landscape.

边距也称为“软边距”,因为它们是在软件中定义的,不受物理打印设备的影响。这意味着它们将应用于软件中的当前页面大小并反映实际页面尺寸纵向或横向。

PrintableArea is also known as "hard margins" which reflect the physical limitations of your printing device. This will vary from printer to printer, from manufacturer to manufacturer. Because these are hardware measurements, they do not rotate when you set the page to landscape/portrait. The physical limitations won't change on the printer regardless of software print settings, so we need to make sure we apply them on the correct axis depending on our software settings for the print document (orientation).

PrintableArea 也称为“硬边距”,它反映了您的打印设备的物理限制。这将因打印机而异,因制造商而异。因为这些是硬件测量,所以当您将页面设置为横向/纵向时,它们不会旋转。无论软件打印设置如何,打印机上的物理限制都不会改变,因此我们需要确保根据我们的打印文档(方向)软件设置将它们应用到正确的轴上。

So following the rough model of the sample code you posted, here's a PrintDocument.PrintPage event handler that will draw a rectangle as large as possible while still being visible (with the default PrintDocument.OriginsAtMarginsbeing false). If you set PrintDocument.OriginsAtMarginsto trueit will draw a rectangle as large as possible while still being visible inside the configured soft margins (defaults to 1" from page edges).

因此,按照您发布的示例代码的粗略模型,这里有一个 PrintDocument.PrintPage 事件处理程序,它将绘制一个尽可能大的矩形,同时仍然可见(默认PrintDocument.OriginsAtMarginsfalse)。如果设置PrintDocument.OriginsAtMarginstrue它,它将绘制一个尽可能大的矩形,同时在配置的软边距内仍然可见(默认为距页面边缘 1")。

PrintAction printAction = PrintAction.PrintToFile;

private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
    // Save our print action so we know if we are printing 
    // a preview or a real document.
    printAction = e.PrintAction;

    // Set some preferences, our method should print a box with any 
    // combination of these properties being true/false.
    printDocument.OriginAtMargins = false;   //true = soft margins, false = hard margins
    printDocument.DefaultPageSettings.Landscape = false;
}

private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
    Graphics g = e.Graphics;

    // If you set printDocumet.OriginAtMargins to 'false' this event 
    // will print the largest rectangle your printer is physically 
    // capable of. This is often 1/8" - 1/4" from each page edge.
    // ----------
    // If you set printDocument.OriginAtMargins to 'false' this event
    // will print the largest rectangle permitted by the currently 
    // configured page margins. By default the page margins are 
    // usually 1" from each page edge but can be configured by the end
    // user or overridden in your code.
    // (ex: printDocument.DefaultPageSettings.Margins)

    // Grab a copy of our "soft margins" (configured printer settings)
    // Defaults to 1 inch margins, but could be configured otherwise by 
    // the end user. You can also specify some default page margins in 
    // your printDocument.DefaultPageSetting properties.
    RectangleF marginBounds = e.MarginBounds;

    // Grab a copy of our "hard margins" (printer's capabilities) 
    // This varies between printer models. Software printers like 
    // CutePDF will have no "physical limitations" and so will return 
    // the full page size 850,1100 for a letter page size.
    RectangleF printableArea = e.PageSettings.PrintableArea;

    // If we are print to a print preview control, the origin won't have 
    // been automatically adjusted for the printer's physical limitations. 
    // So let's adjust the origin for preview to reflect the printer's 
    // hard margins.
    if (printAction == PrintAction.PrintToPreview)
        g.TranslateTransform(printableArea.X, printableArea.Y);

    // Are we using soft margins or hard margins? Lets grab the correct 
    // width/height from either the soft/hard margin rectangles. The 
    // hard margins are usually a little wider than the soft margins.
    // ----------
    // Note: Margins are automatically applied to the rotated page size 
    // when the page is set to landscape, but physical hard margins are 
    // not (the printer is not physically rotating any mechanics inside, 
    // the paper still travels through the printer the same way. So we 
    // rotate in software for landscape)
    int availableWidth = (int)Math.Floor(printDocument.OriginAtMargins 
        ? marginBounds.Width 
        : (e.PageSettings.Landscape 
            ? printableArea.Height 
            : printableArea.Width));
    int availableHeight = (int)Math.Floor(printDocument.OriginAtMargins 
        ? marginBounds.Height 
        : (e.PageSettings.Landscape 
            ? printableArea.Width 
            : printableArea.Height));

    // Draw our rectangle which will either be the soft margin rectangle 
    // or the hard margin (printer capabilities) rectangle.
    // ----------
    // Note: we adjust the width and height minus one as it is a zero, 
    // zero based co-ordinates system. This will put the rectangle just 
    // inside the available width and height.
    g.DrawRectangle(Pens.Red, 0, 0, availableWidth - 1, availableHeight - 1);
}

The two lines that determine available width and available height are what I think you were looking for in your question. Those two lines take into account whether you want soft margins or hard margins and whether the print document is configured for landscape or portrait.

确定可用宽度和可用高度的两条线是我认为您在问题中寻找的。这两行考虑了您想要软边距还是硬边距以及打印文档是配置为横向还是纵向。

I used Math.Floor()for the easy way out to just drop anything past the decimal (ex: 817.96 -> 817) just to make sure the available width and height was just inside the available dimensions. I'm "failing safe" here, if you wanted to you could maintain float based co-ordinates (instead of int), just be careful to watch for rounding errors that will result in the clipped graphics (if it rounds 817.96 up to 818 and then the printer driver decides that's no longer visible).

我使用Math.Floor()了一种简单的方法,将小数点后的任何内容都删除(例如:817.96 -> 817),以确保可用的宽度和高度正好在可用尺寸之内。我在这里“失败安全”,如果您愿意,您可以维护基于浮点的坐标(而不是 int),只需注意会导致裁剪图形的舍入错误(如果将 817.96 舍入到 818然后打印机驱动程序决定它不再可见)。

I tested this procedure in both portrait and landscape with both hard margins and soft margins on a Dell 3115CN, a Samsung SCX-4x28 and CutePDF software printer. If this didn't adequately address your question, consider revising your question to clarify "magic rectangle" and "best rectangle".

我在 Dell 3115CN、Samsung SCX-4x28 和CutePDF 软件打印机上使用硬边距和软边距以纵向和横向两种方式测试了此过程。如果这不能充分解决您的问题,请考虑修改您的问题以阐明“魔术矩形”和“最佳矩形”。



EDIT: Notes About "Soft Margins"

编辑:关于“软边距”的注释

Soft margins are applied in software and do not take into consideration the hardware limitations of the printer. This is intentional and by design. You can set the soft margins outside the printable area if you want and the output may be clipped by your printer's driver. If this is undesirable for your application, you need to adjust the margins in your program code. Either you can prevent the user from selecting margins outside the printable area (or warn them if they do) or you can enforce some min/max conditions in your code when you actually start printing (drawing) the document.

软边距应用在软件中,不考虑打印机的硬件限制。这是有意为之。如果需要,您可以在可打印区域之外设置软边距,并且输出可能会被打印机驱动程序剪裁。如果您的应用程序不希望这样,您需要调整程序代码中的边距。您可以阻止用户选择可打印区域之外的边距(或警告他们如果这样做),或者您可以在实际开始打印(绘制)文档时在代码中强制执行一些最小/最大条件。

Example Case:If you set the page margins to 0,0,0,0 in Microsoft Word 2007 a warning dialog pops up that reads "One or more margins are set outside the printable area of the page. Choose the Fix button to increase the appropriate margins." If you click fix, Word will simply copy the hard margins into the soft margins, so the dialog now shows 0.16" for all margins (my laser printer's capabilities).

示例案例:如果您在 Microsoft Word 2007 中将页边距设置为 0,0,0,0,则会弹出一个警告对话框,内容为“在页面的可打印区域之外设置了一个或多个边距。选择修复按钮以增加适当的边距。” 如果单击修复,Word 将简单地将硬边距复制到软边距中,因此对话框现在显示所有边距为 0.16"(我的激光打印机的功能)。

This is expected behavior. It is not a bug/problem with Microsoft Word if the printed page is clipped because the user ignored this warning and used 0,0,0,0 page margins. This is the same in your application. You need to enforce the limits for whatever if appropriate in your use case. Either with a warning dialog, or you can force the limit more strongly in code (don't offer a choice to the user).

这是预期的行为。如果由于用户忽略了此警告并使用了 0,0,0,0 页边距而导致打印页面被剪裁,这不是 Microsoft Word 的错误/问题。这在您的应用程序中是相同的。如果在您的用例中合适,您需要强制执行任何限制。使用警告对话框,或者您可以在代码中更强烈地强制限制(不要向用户提供选择)。



Alternative Strategy

替代策略

Alright so maybe you don't want to just get the hard margins, but rather get the soft margins and then enforce that the soft margins remain inside the printable area when printing. Let's develop another strategy here.

好吧,也许您不想只获得硬边距,而是要获得软边距,然后在打印时强制软边距保留在可打印区域内。让我们在这里制定另一个策略。

In this example I will use the origins at margins, and allow the user to select any margin they want, but I'm going to enforce in code that the selected margin not be outside the printable area. If the selected margins are outside the printable area, I'm simply going to adjust them to be inside the printable area.

在这个例子中,我将在边距处使用原点,并允许用户选择他们想要的任何边距,但我将在代码中强制所选边距不在可打印区域之外。如果选定的边距在可打印区域之外,我只是将它们调整到可打印区域内。

PrintAction printAction = PrintAction.PrintToFile;

private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
    // Save our print action so we know if we are printing 
    // a preview or a real document.
    printAction = e.PrintAction;

    // We ALWAYS want true here, as we will implement the 
    // margin limitations later in code.
    printDocument.OriginAtMargins = true;

    // Set some preferences, our method should print a box with any 
    // combination of these properties being true/false.
    printDocument.DefaultPageSettings.Landscape = false;
    printDocument.DefaultPageSettings.Margins.Top = 100;
    printDocument.DefaultPageSettings.Margins.Left = 0;
    printDocument.DefaultPageSettings.Margins.Right = 50;
    printDocument.DefaultPageSettings.Margins.Bottom = 0;
}

private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
    Graphics g = e.Graphics;

    // If you set printDocumet.OriginAtMargins to 'false' this event 
    // will print the largest rectangle your printer is physically 
    // capable of. This is often 1/8" - 1/4" from each page edge.
    // ----------
    // If you set printDocument.OriginAtMargins to 'false' this event
    // will print the largest rectangle permitted by the currently 
    // configured page margins. By default the page margins are 
    // usually 1" from each page edge but can be configured by the end
    // user or overridden in your code.
    // (ex: printDocument.DefaultPageSettings.Margins)

    // Grab a copy of our "hard margins" (printer's capabilities) 
    // This varies between printer models. Software printers like 
    // CutePDF will have no "physical limitations" and so will return 
    // the full page size 850,1100 for a letter page size.
    RectangleF printableArea = e.PageSettings.PrintableArea;
    RectangleF realPrintableArea = new RectangleF(
        (e.PageSettings.Landscape ? printableArea.Y : printableArea.X),
        (e.PageSettings.Landscape ? printableArea.X : printableArea.Y),
        (e.PageSettings.Landscape ? printableArea.Height : printableArea.Width),
        (e.PageSettings.Landscape ? printableArea.Width : printableArea.Height)
        );

    // If we are printing to a print preview control, the origin won't have 
    // been automatically adjusted for the printer's physical limitations. 
    // So let's adjust the origin for preview to reflect the printer's 
    // hard margins.
    // ----------
    // Otherwise if we really are printing, just use the soft margins.
    g.TranslateTransform(
        ((printAction == PrintAction.PrintToPreview) 
            ? realPrintableArea.X : 0) - e.MarginBounds.X,
        ((printAction == PrintAction.PrintToPreview) 
            ? realPrintableArea.Y : 0) - e.MarginBounds.Y
    );

    // Draw the printable area rectangle in PURPLE
    Rectangle printedPrintableArea = Rectangle.Truncate(realPrintableArea);
    printedPrintableArea.Width--;
    printedPrintableArea.Height--;
    g.DrawRectangle(Pens.Purple, printedPrintableArea);

    // Grab a copy of our "soft margins" (configured printer settings)
    // Defaults to 1 inch margins, but could be configured otherwise by 
    // the end user. You can also specify some default page margins in 
    // your printDocument.DefaultPageSetting properties.
    RectangleF marginBounds = e.MarginBounds;

    // This intersects the desired margins with the printable area rectangle. 
    // If the margins go outside the printable area on any edge, it will be 
    // brought in to the appropriate printable area.
    marginBounds.Intersect(realPrintableArea);

    // Draw the margin rectangle in RED
    Rectangle printedMarginArea = Rectangle.Truncate(marginBounds);
    printedMarginArea.Width--;
    printedMarginArea.Height--;
    g.DrawRectangle(Pens.Red, printedMarginArea);
}

回答by Trevor Elliott

Currently the following is working on my printer. I have OriginAtMargins set to false. This causes automatic translation to the HardMarginX and HardMarginY when I'm printing to my printer, but NO translation when I'm printing to the PrintPreviewControl. Therefore, I have to check for this case.

目前以下正在我的打印机上工作。我将 OriginAtMargins 设置为 false。当我打印到我的打印机时,这会导致自动转换到 HardMarginX 和 HardMarginY,但是当我打印到 PrintPreviewControl 时没有转换。因此,我必须检查这种情况。

private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
    printAction = e.PrintAction;
    printDocument.OriginAtMargins = false;
}

private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
    Graphics g = e.Graphics;

    if (printAction != PrintAction.PrintToPreview)
        g.TranslateTransform(-e.PageSettings.HardMarginX, -e.PageSettings.HardMarginY);

    RectangleF printArea = GetBestPrintableArea(e);

    g.DrawRectangle(Pens.Red, printArea.X, printArea.Y, printArea.Width - 1, printArea.Height - 1);
}

public RectangleF GetBestPrintableArea(PrintPageEventArgs e)
{
    RectangleF marginBounds = e.MarginBounds;
    RectangleF printableArea = e.PageSettings.PrintableArea;
    RectangleF pageBounds = e.PageBounds;

    if (e.PageSettings.Landscape)
        printableArea = new RectangleF(printableArea.Y, printableArea.X, printableArea.Height, printableArea.Width);

    RectangleF bestArea = RectangleF.FromLTRB(
        (float)Math.Max(marginBounds.Left, printableArea.Left),
        (float)Math.Max(marginBounds.Top, printableArea.Top),
        (float)Math.Min(marginBounds.Right, printableArea.Right),
        (float)Math.Min(marginBounds.Bottom, printableArea.Bottom)
    );

    float bestMarginX = (float)Math.Max(bestArea.Left, pageBounds.Right - bestArea.Right);
    float bestMarginY = (float)Math.Max(bestArea.Top, pageBounds.Bottom - bestArea.Bottom);

    bestArea = RectangleF.FromLTRB(
        bestMarginX,
        bestMarginY,
        pageBounds.Right - bestMarginX,
        pageBounds.Bottom - bestMarginY
    );

    return bestArea;
}

If anyone can try this code on their printer to verify that it works universally, or correct it if I'm wrong, that would be great.

如果任何人都可以在他们的打印机上尝试此代码以验证它是否通用,或者如果我错了就更正它,那就太好了。

I don't know if pre-translation of the origin to the hard margins when OriginAtMargins is false is standard with all printers, or if it's just doing this on my printer.

我不知道当 OriginAtMargins 为 false 时将原点预翻译到硬边距是否是所有打印机的标准,或者它是否只是在我的打印机上执行此操作。

回答by Eric

I think what you need is simply redraw the image to fit whatever the paper size being used. Here's my code:

我认为您需要的只是重新绘制图像以适应所使用的纸张尺寸。这是我的代码:

Protected Overrides Sub OnPrintPage(ByVal e As System.Drawing.Printing.PrintPageEventArgs)
        Dim img As Image = Nothing 'Your image source

        Dim ps As PaperSize = MyBase.PrinterSettings.DefaultPageSettings.PaperSize
        Dim pF As RectangleF = MyBase.PrinterSettings.DefaultPageSettings.PrintableArea
        Dim srcF As New RectangleF(0, 0, pg.ImageSize.Width, pg.ImageSize.Height)
        Dim dstF As New RectangleF(0, 0, pF.Width, pF.Height)

        e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        e.Graphics.DrawImage(img, dstF, srcF, GraphicsUnit.Pixel)

        MyBase.OnPrintPage(e)
End Sub