【问题标题】:How to find the actual printable area? (PrintDocument)如何找到实际可打印区域? (打印文件)
【发布时间】:2012-02-04 09:45:36
【问题描述】:

为什么要找出这个神奇的矩形这么难?

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

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

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

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


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

  • 如果 OriginAtMargins 为真或假(不像我想的那样表现)
  • 如果我要打印到打印机或使用 PrintPreviewControl(我必须检查这是要预览的打印还是要正确处理翻译的打印到页面)
  • 如果我在家里使用打印机或在工作中使用打印机(两者的行为不同)

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

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

【问题讨论】:

  • 使用 e.MarginBounds。并将 PrintDocument.OriginAtMargins 设置为 True。
  • 我做了一个简单的项目来测试它,只使用默认的 PrintDocument/PageSetupDialog/PrintDialog 并将 OriginAtMargins 设置为 true 并将边距设置为 0,0,0,0。它似乎可以很好地打印到 PDF (CutePDFWriter),但打印到实际的打印机却不行。我用 3.0f 宽度的笔画了一个矩形,页面甚至没有打印任何东西(页面只是空白)。我认为这意味着它认为没有任何东西可以打印。
  • 如果你想在硬边距上画,那么你最好使用它们。它不能在 0 处打印,那是在硬边距内。如果纸张路径与驾驶员认为的位置有点偏离,您可能仍然没有得到任何东西,这取决于。

标签: c# printing gdi+ printdocument


【解决方案1】:

目前以下内容正在我的打印机上工作。我将 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;
}

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

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

【讨论】:

  • 这段代码对于我理解你的问题来说有点过于复杂了。您根本不需要 TranslateTranform 图形对象(它已经为您完成了),我不会将软边距和硬边距组合在一起(marginBounds 和 printableArea)。我会选择使用软边距或硬边距。例如,请参阅我的答案和说明。
  • 我按要求测试了您的代码。它与我在下面的答案中提供的代码示例相同。我在物理打印上得到了硬边距,但在打印预览上却没有。我将修改我的答案,以包括对打印预览与物理打印输出的调整。我仍然认为你可以更优雅地实现这一点。
【解决方案2】:

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

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

【讨论】:

  • 这是我已经尝试过的说“绘制到 PrintableArea”的过于复杂和错误的例子。它不考虑边距或方向。
  • OP 将问题标记为C#。尽管VBC# 非常相似,但请仅提供C# 代码。
【解决方案3】:

您的问题对于什么是“最佳”矩形不太清楚。我假设您的意思是打印时 100% 可见的最大矩形。

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

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

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

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

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

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

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

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

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

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


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

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

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

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


另类策略

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

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

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);
}

【讨论】:

  • 我真的只是在寻找可打印的矩形。打印以使边距对称只是我事后所做的事情。这不是真正的问题。我一直在寻找的是一种通用算法,它会给我一个 Rectangle 来打印并考虑不同打印机的所有边缘情况等。
  • 如果 OriginAtMargins 为假,您的方法可以正常工作。但是,如果它是真的,当用户选择 0,0,0,0 边距时,它会打印出一个空白页,因为它超出了硬边距。
  • 另外,您的方法不适用于使用 PrintPreviewControl 显示预览。我不知道这是否只是该控件的问题,但您需要检查并通过硬边距偏移绘图以使其模拟打印机,否则它会打印出页面。
  • @Moozhe 上面的内容完全按照您的要求进行,并获得所有情况下的可打印矩形(根据方向调整硬边距)。同样将边距设置为 0,0,0,0 的行为与预期的一样(正如其定义所应该的那样)。您需要防止用户选择低于代码中硬边距的边距,或者在事后转换边距。我认为,如果您使用更清晰的示例用例来编辑您的问题,我可能能够更直接地回答它,而不是通用示例。但是上面的示例代码确实普遍为您提供了硬利润。
猜你喜欢
  • 2020-06-11
  • 1970-01-01
  • 2014-07-11
  • 2011-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-14
相关资源
最近更新 更多