I am having a problem when using PrintDocument with margins.
No matter what I do there is always a margin around everything I print, this means that nothing is aligned where it needs to be.
Here is the code I am using to create the PrintDocument
public void Print()
{
PrintDocument printDocument = new PrintDocument();
printDocument.DefaultPageSettings.PaperSize = new PaperSize("A5",583,827);
printDocument.OriginAtMargins = true;
printDocument.DefaultPageSettings.Margins.Top = 0;
printDocument.DefaultPageSettings.Margins.Left = 0;
printDocument.DefaultPageSettings.Margins.Right = 0;
printDocument.DefaultPageSettings.Margins.Bottom = 0;
if (!string.IsNullOrWhiteSpace(PrinterName))
{
printDocument.PrinterSettings.PrinterName = PrinterName;
}
printDocument.PrintController = new StandardPrintController();
printDocument.PrintPage += On_PrintPage;
printDocument.Print();
}
The On_PrintPage method, has various calls to e.Graphics.Draw... methods.
How can I make it so that something I print at 0,0 will print in the very top left of the page. I know that if the printer cannot print that far to the edge of the page it will be blank, however it should do that rather than print 0,0 not in the top left of the page.
I'm really lost here
interestingly the print function is too late to set most of the properties and would only apply to subsequent pages
you need to use PrintDocument.QueryPageSettings event instead and set the properties there and I always set the page settings not just defaults. then drawing at 0,0 should be as close as you can get (printer + driver allows)
The OriginAtMargins property of the PrintDocument, when set to true, it will sets the origin of the provided Graphics object at the top-left corner of the margin. From the MSDN document Remarks section:
Calculating the area available to print requires knowing the physical
size of the paper, the margins for the page, and the location of the
Graphics object origin. When OriginAtMargins is true, the Graphics
object location takes into account the PageSettings.Margins property
value and the printable area of the page. When OriginAtMargins is
false, only the printable area of the page is used to determine the
location of the Graphics object origin, the PageSettings.Margins value
is ignored.
Set it to false, to change it to the top-left corner of the printable area. If you need it to be at the top-left corner of the page, you will need to transform the graphics object to mach that coordinate; there isn't really a way to get around this:
void PrintDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
var printArea = e.PageSettings.PrintableArea;
e.Graphics.TranslateTransform(-printArea.X, -printArea.Y);
// Build up the page here.
}
If you want to print landscape pages as well, this becomes tricky, as none of the properties of PageSettings are affected by this setting. This requires to know the angle the page is rotated by—as PrintableArea isn't always centered on the page—which can be acquired with PageSettings.PrinterSettings.LandscapeAngle. The value can only be either 90 or 270, and the code would look like this:
void PrintDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
var printArea = e.PageSettings.PrintableArea;
if (e.PageSettings.Landscape)
{
var pageSize = e.PageSettings.PageSize;
switch (e.PageSettings.PrinterSettings.LandscapeAngle)
{
case 90:
e.Graphics.TranslateTransform(
dx: -printArea.Y,
dy: -(pageSize.Width - (printArea.X + printArea.Width)));
break;
case 270:
e.Graphics.TranslateTransform(
dx: -(pageSize.Height - (printArea.Y + printArea.Height)),
dy: -printArea.X);
break;
}
}
else
{
e.Graphics.TranslateTransform(-printArea.X, -printArea.Y);
}
// Build up the page here.
}
If you later want to use another transformation on a portion of the page, do not forget to use var gs = e.Save() before transforming and e.Restore(gs) to restore that transformation, instead of calling e.ResetTransform(), as the later will reset the transformation you did in the beginning.
Related
What is the unit of the coordinate system used in Windows Forms printing using the PrintDocument class? This information is needed in order to print something at a specific position and with a specific size.
In the PrintPage event, the PrintPageEventArgs instance has the properties Graphics and PageBounds. They seem to use the same coordinate system.
For an A4 portrait sheet, PageBounds returns a size of 827 by 1169. Given an A4 sheet is 210mm by 297mm, the unit Graphics / PageBounds unit seems to be pixels/points with 100dpi. (827 / 210 * 25.4 = 100.0278, 1169 / 297 * 25.4 = 99.9751).
Using 100dpi to scale and position the objects, the drawing result is correct. But is it always 100dpi? Or how can I query the unit?
(Querying Graphics.DpiX does not work. It returns 600dpi, which is the printer DPI but not the coordinate system DPI.)
private void PrintButton_Click(object sender, EventArgs e)
{
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(PrintDocument_PrintPage);
pd.Print();
}
private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Rectangle bounds = e.PageBounds; // For A4 portrait sheet: {X = 0 Y = 0 Width = 827 Height = 1169}
float dpi = e.Graphics.DpiX; // 600
DrawIt(e.Graphics);
}
Thanks to Jimi who pointed out that the unit is Display. The short answer is: It's always 100dpi for printing.
The Graphics instance uses GraphicsUnit.Display as the PageUnit. And for printers, this is 1/100 inch for printers, i.e. 100dpi. The documentation says "typically" but this probably refers to the video displays.
It also coincides with PrinterUnit.Display, which is always 0.01in.
As the Graphics measurements are also consistent with PageBounds, I can probably safely assume that PageBounds and other PrintPageEventArgs properties also use display units for printers with 100dpi. It's not documented though.
We have made a terms of use application that fills the user's entire screen, and centres the text in the middle of the screen.
We have achieved these by setting the Location to Screen.PrimaryScreen.Bounds.X + 10 and then the width to Width = Screen.PrimaryScreen.Bounds.Width - 10, - this gives effectively 10px padding and otherwise perfectly central text.
The problem however, is when users change the 'Make it easier to read what's on your screen' option to 125%, the Bounds.Width value is incorrect, and the text goes way off the screen.
Interestingly if you set it to 'Larger - 150%' it calculates the screen width just fine.
I've tried using Screen.PrimaryScreen.WorkingArea.Width too, but that just gives the same (incorrect) result.
Is there anyway of resolving this please? Thank you
(edit) Here is the full code which generates the element:
protected override void OnLoad(System.EventArgs e) {
this._webBrowser = new WebBrowser {
IsWebBrowserContextMenuEnabled = false,
AllowWebBrowserDrop = false,
Location = new Point(Screen.PrimaryScreen.Bounds.X + 10, Screen.PrimaryScreen.Bounds.Y + 150),
Width = Screen.PrimaryScreen.Bounds.Width - 20,
ScrollBarsEnabled = true,
DocumentText = "<html><body><div>" + agreement.Terms + "</div></body></html>"
};
}
Sight unseen (always post a code snippet), this happened because you put the code in the wrong place. You are setting the size in the constructor of your Form class. Then, since the form was originally designed at 96 DPI, your form gets auto-scaled to accommodate the larger DPI setting. Which grows the form by 125%, now making it too big.
You must set the size after the form is rescaled. The Load event handler is the best opportunity, in fact one of the few reasons to actually need Load. At that point, the scaling is already applied but the window not yet visible. Therefore the best place to override Size and Location properties. Fix:
protected override void OnLoad(EventArgs e) {
var scr = Screen.FromPoint(this.Location);
this.Bounds = new Rectangle(
scr.WorkingArea.Left + 10, scr.WorkingArea.Top,
scr.WorkingArea.Width - 20, scr.WorkingArea.Bottom);
base.OnLoad(e);
}
After edit, you should not use screen coordinates to set the control's Location and Size property. A child control's location is relative from it's Parent's client area and must fit inside it. Fix:
this._webBrowser = new WebBrowser {
Location = new Point(10, 10),
Width = this.ClientSize.Width - 20,
Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Top,
// etc...
};
Setting the Anchor ensures it resizes along with the parent, probably what you want. This code belongs in the constructor, not OnLoad() since now you do want the browser to get rescaled along with the form. If you had used the designer then you'd automatically would have gotten the correct code.
In C#, .NET 3.5 with winforms I am working on a print preview control. It seems to work fine, but when I print an A4 page I scanned in at real size on an A3 page, teh scale is out by about 3%...
I am using the following code to compute the actual printable area of the page based on the setting selected by the user and I am wondering if I maybe got those calculations wrong?
public static RectangleF GetPrintArea(PageSettings PageSettings)
{
float[] margins;
RectangleF printArea;
// Get the actual page bounds
printArea = PageSettings.Bounds;
// Calculate the hard margins taking into account page orientation
margins = new float[4];
// Left
margins[0] = !PageSettings.Landscape ? PageSettings.HardMarginX : PageSettings.HardMarginY;
// Top
margins[1] = !PageSettings.Landscape ? PageSettings.HardMarginY : PageSettings.HardMarginX;
// Right
margins[2] = margins[0];
// Bottom
margins[3] = margins[1];
// Calculate the real print margins taking into account teh hard and soft margins
// left
margins[0] = Math.Max(margins[0], PageSettings.Margins.Left);
// Top
margins[1] = Math.Max(margins[1], PageSettings.Margins.Top);
// Right
margins[2] = Math.Max(margins[2], PageSettings.Margins.Right);
// Bottom
margins[3] = Math.Max(margins[3], PageSettings.Margins.Bottom);
return new RectangleF(
new PointF(margins[0], margins[1]),
new SizeF(printArea.Width - (margins[0] + margins[2]), printArea.Height - (margins[1] + margins[3]))
);
}
This should return a rectangle which gives the actual area of the page on printing will happen. I use this rectangle for generating both the preview and the printout.
The code for printing out is as follows:
/// <summary>
/// Draws the image on the printing surface
/// </summary>
/// <param name="Graphics">The graohics object with which to draw</param>
protected virtual void PrintImage(Graphics Graphics)
{
RectangleF imageBoundingBox;
RectangleF visibleImageBoundingBox;
RectangleF visibleImage;
// Offset the visible bounding box location by the position of the print area so as to print right within the margins
Graphics.TranslateTransform(-this.Page.PrintableArea.Left, -this.Page.PrintableArea.Top);
// Calculate the bounding box of the scaled image
imageBoundingBox = new RectangleF(this.Page.PrintAreaOrigin.Add(this.ImagePrintLocation), this.Image.Size.Multiply(this.ImagePrintScale));
// Calculate the position and size of the portion of the image bounding box visible in the viewport
visibleImageBoundingBox = RectangleF.Intersect(imageBoundingBox, this.Page.PrintArea);
// Calculate the portion of the image which corresponds to the visible bounding box
visibleImage = new RectangleF(
new PointF(
imageBoundingBox.X < this.Page.PrintArea.Left ? Math.Min(this.Page.PrintArea.Left - imageBoundingBox.X, imageBoundingBox.Width) : 0,
imageBoundingBox.Y < this.Page.PrintArea.Top ? Math.Min(this.Page.PrintArea.Top - imageBoundingBox.Y, imageBoundingBox.Height) : 0
).Divide(this.ImagePrintScale),
visibleImageBoundingBox.Size.Divide(this.ImagePrintScale)
);
// Draw the image
Graphics.DrawImage(this.Image, visibleImageBoundingBox, visibleImage, GraphicsUnit.Pixel);
}
Where Page is a class which contains the page bounds (PageArea), the printable area PrintableArea and the actual print area (PrintArea). The actual print area is the area which is given by the previous rectangle.
Something must be wrong with this approach, but I can't for the life of me figure out what it is. If anybody can identify what's wrong I would be very grateful...
Using System.Drawing.Printing, I want to print a set of lines on print document.
But the problem is it prints every line on the very first page, what ever the coordinates are, also if I draw an image, It prints that on a single page no matter how big it is.
Following is what I have done for printing text on multiple pages:
protected void ThePrintDocument_PrintPage (object sender, System.Drawing.Printing.PrintPageEventArgs ev)
{
float linesPerPage = 0;
float yPosition = 0;
int count = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;
string line = null;
Font printFont = this.richTextBox1.Font;
SolidBrush myBrush = new SolidBrush(Color.Black);
// Work out the number of lines per page, using the MarginBounds.
linesPerPage = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics);
// Iterate over the string using the StringReader, printing each line.
while(count < linesPerPage && ((line=myReader.ReadLine()) != null))
{
// calculate the next line position based on
// the height of the font according to the printing device
yPosition = topMargin + (count * printFont.GetHeight(ev.Graphics));
// draw the next line in the rich edit control
ev.Graphics.DrawString(line, printFont, myBrush, leftMargin, yPosition, new StringFormat());
count++;
}
// If there are more lines, print another page.
if(line != null)
ev.HasMorePages = true;
else
ev.HasMorePages = false;
myBrush.Dispose();
}
What I should do in order to print line on multiple pages, i.e the following code should print on two pages as the length of the line is 1400 and print doc length is 1100, so remaining 300 of the line should be printed on the next page
protected void ThePrintDocument_PrintPage (object sender, System.Drawing.Printing.PrintPageEventArgs ev)
{
Pen P1 = new Pen(Brushes.Violet, 5);
ev.Graphics.DrawLine(P1, new Point(0,0), new Point(500,1400));
}
This is not how printing works in .NET. It does not create a new page just because you print outside the coordinates of the current page. There are events and event arguments which .NET uses to ask from you how many pages your document will contain. It will then invoke the event for printing a page for every page.
See here for an example.
EDIT
Ok, replying to your comment, I can think of two possible solutions: The first solution would involve clipping: intersect your graphic objects with the page's rectangle and print only what's common to both of them. If there were parts outside the clipping region, take that part as the new graphic objects and clip again to print on new page. Repeat this until the remaining graphics fit the page rectangle. However, I can't think of a way of doing that easily.
My second idea is as follows:
Calculate how many pages you would need to print all graphic objects by dividing the bounding rectangle of the graphic objects by the height of the "visual rectangle" of one page (increase number of pages by one if there's a remainder in this division).
Loop the following until reaching the last page
Print all graphic objects to the current page
Decrease all y-coordinates by the "visual height" of a page (effectively moving the graphic objects upwards outside the "bounds of the paper")
While the overhead of printing the entire list of graphic objects for every page may be high, you're leaving the clipping part to the printer driver/the printer itself.
I need to print a label to fit the page.
I'm tryng this but print big than page, width and height seems to be to much
private void PrinterPrintPage(object sender, PrintPageEventArgs e)
{
var b = Tasks.Pop();
if (b.Label == null)
b.Label = GetLabelImage(b.Codice, b.ColoreID);
var rect = e.PageBounds;
e.Graphics.DrawImage(b.Label, rect);
e.HasMorePages = Tasks.ContainTasks();
_printedCount++;
}
As per MSDNs documentation on PrintPageEventArgs.PageBounds,
Most printers cannot print at the very edge of the page.
...first of all, try changing PageBounds to MarginBounds. If this doesn't help, "deflate" the bounds rectangle towards the centre of the page so you move away from the edges.