I am attempting to print something using a C# Winforms application. I can't seem to understand how multiple pages works. Let's say I have the following code in my constructor:
private string _stringToPrint;
_stringToPrint = "";
for (int i = 0; i < 120; i++)
{
_stringToPrint = _stringToPrint + "Line " + i.ToString() + Environment.NewLine;
}
Then I have this code on my button click event:
private void MnuFilePrintClick(object sender, EventArgs e)
{
var pd = new PrintDocument();
pd.PrintPage += pd_PrintPage;
var z = new PrintPreviewDialog { Document = pd };
z.ShowDialog(this);
}
void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
var font = new Font("Arial", 10f, FontStyle.Regular);
g.DrawString(_stringToPrint, font, Brushes.Black, new PointF(10f, 10f));
}
Right now, when I run this code, it is giving me one page and after like 70 lines, it just runs off the paper. How would I print this string so that it prints enough for one page, then runs over to the second page, etc..?
You could have a counter and set the amount of lines you want per page like so:
private string[] _stringToPrint = new string[100]; // 100 is the amount of lines
private int counter = 0;
private int amtleft = _stringToPrint.Length;
private int amtperpage = 40; // The amount of lines per page
for (int i = 0; i < 120; i++)
{
_stringToPrint[i] ="Line " + i.ToString();
}
Then in pd_PrintPage:
void pd_PrintPage(object sender, PrintPageEventArgs e)
{
int currentamt = (amtleft > 40)?40:amtleft;
Graphics g = e.Graphics;
var font = new Font("Arial", 10f, FontStyle.Regular);
for(int x = counter; x < (currentamt+counter); x++)
{
g.DrawString(_stringToPrint[x], font, Brushes.Black, new PointF(10f, (float)x*10));
// x*10 is just so the lines are printed downwards and not on top of each other
// For example Line 2 would be printed below Line 1 etc
}
counter+=currentamt;
amtleft-=currentamt;
if(amtleft<0)
e.HasMorePages = true;
else
e.HasMorePages = false;
// If e.HasMorePages is set to true and the 'PrintPage' has finished, it will print another page, else it wont
}
I have had bad experiences with e.HasMorePages so this may not work.
Let me know if this works and I hope it helps!
Related
using C#, I wrote the below code to print out images in an array (size = totalToPrint) of Picturebox pictureBoxArr[] each in size of 100x100 pixel. I want to print them vertically with the 20-pixel distance between them heightDistanceBetweenImages the problem is that it only prints on 1 page (say letter size) no matter how many images ( then it only prints 8 images and dismisses the rest). How can I solve this problem, and print it on multiple pages?
int totalToPrint;
int xFirstAncorPoint = 100;
int yFirstAncorPoint = 100;
int ImagSize = 100; // Squre of 100x100 pixel
int heightDistanceBetweenImages = 20;
PrintDialog pd = new PrintDialog();
PrintDocument pDoc = new PrintDocument();
pDoc.PrintPage += PrintPicture;
pd.Document = pDoc;
if (pd.ShowDialog() == DialogResult.OK)
{
pDoc.Print();
}
}
public void PrintPicture(Object sender, PrintPageEventArgs e)
{
Bitmap bmp1 = new Bitmap(ImagSize , totalToPrint * (ImagSize + heightDistanceBetweenImages));
for (int i = 0; i < totalToPrint; i++)
{
pictureBoxArr[i].DrawToBitmap(bmp1, new Rectangle(0, i * (heightDistanceBetweenImages + ImagSize), pictureBoxArr[0].Width, pictureBoxArr[0].Height));
}
e.Graphics.DrawImage(bmp1, xFirstAncorPoint, yFirstAncorPoint);
bmp1.Dispose();
}
You are about half way there. PrintDocument would be your future reference.
The PrintPage gives you more than just a drawing space; it also has your page bounds, page margins, etc. It also has HasMorePages property that you set if you need to print more pages. This property defaults to false, so you were only printing 1 page. Also if anything is outside the bounds of the page, it would not print that. With a little change here and there, you would end up with something like this.
// using queue to manage images to print
Queue<Image> printImages = new Queue<Image>();
int totalToPrint;
int xFirstAncorPoint = 100;
int yFirstAncorPoint = 100;
int ImagSize = 100; // Squre of 100x100 pixel
int heightDistanceBetweenImages = 20;
private void btnPrintTest_Click(object sender, EventArgs e) {
PrintDialog pd = new PrintDialog();
PrintDocument pDoc = new PrintDocument();
pDoc.PrintPage += PrintPicture;
pd.Document = pDoc;
if (pd.ShowDialog() == DialogResult.OK) {
// add image references to printImages queue.
for (int i = 0; i < pictureBoxArr.Length; i++) {
printImages.Enqueue(pictureBoxArr[i].Image);
}
pDoc.Print();
}
}
private void PrintPicture(object sender, PrintPageEventArgs e) {
int boundsHeight = e.MarginBounds.Height; // Get height of bounds that we are expected to print in.
int currentHeight = yFirstAncorPoint;
while (currentHeight <= boundsHeight && printImages.Count > 0) {
var nextImg = printImages.Peek();
int nextElementHeight = nextImg.Height + heightDistanceBetweenImages;
if (nextElementHeight + currentHeight <= boundsHeight) {
e.Graphics.DrawImage(nextImg, new PointF(xFirstAncorPoint, currentHeight + heightDistanceBetweenImages));
printImages.Dequeue();
}
currentHeight += nextElementHeight;
}
// how we specify if we may have more pages to print
e.HasMorePages = printImages.Count > 0;
}
Hopefully this gets you on the right path and with some minor tweaks for your code, you will have what you need.
Using instructions, I found on stackoverflow I can print across multiple pages. But if I print the document multiple times without leaving the app, the pages are combined.
Result: First time 4 separate pages after 4 prints only 1 single page.
Here is my code (the form just has a single button). I think I need to reset something somewhere but can't figure it out. Appreciate your help!
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
namespace PrintIt
{
public partial class Form1 : Form
{
int N, Y, PageNr;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
printDocument1.DocumentName = "Test Print";
printDialog1.Document = printDocument1;
printDocument1.PrintPage += new PrintPageEventHandler(printit);
// Initialize the dialog's PrinterSettings property to hold user
// defined printer settings.
pageSetupDialog1.PageSettings =
new System.Drawing.Printing.PageSettings();
//Show the dialog storing the result.
DialogResult result = pageSetupDialog1.ShowDialog();
if (result == DialogResult.OK)
{
printDocument1.DefaultPageSettings.PaperSize = pageSetupDialog1.PageSettings.PaperSize;
printDocument1.DefaultPageSettings.Landscape = pageSetupDialog1.PageSettings.Landscape;
string message = "Would you like to view the print preview first?";
string caption = "Print Preview";
MessageBoxButtons buttons = MessageBoxButtons.YesNo;
Y = 0;
N = 0;
PageNr = 1;
result = MessageBox.Show(message, caption, buttons); // Displays the MessageBox.
if (result == System.Windows.Forms.DialogResult.Yes)
{
printPreviewDialog1.Document = printDocument1;
((ToolStripButton)((ToolStrip)printPreviewDialog1.Controls[1]).Items[0]).Enabled = false;//disable the direct print from printpreview.as when we click that Print button PrintPage event fires again.
printPreviewDialog1.ShowDialog();
}
if (printDialog1.ShowDialog() == DialogResult.OK)
{
printDocument1.Print();
}
}
}
private void printit(System.Object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
Font LFont = new Font("Arial", 12);
float fontHeight = LFont.GetHeight();
int startY = 40;
int pageHeight = printDocument1.DefaultPageSettings.PaperSize.Height;
e.Graphics.DrawString("Page:" + PageNr, LFont, Brushes.Black, PageNr * 100, 10);
for (int i = Y; i < 200; i++)
{
e.Graphics.DrawString("Line: " + i, LFont, Brushes.Black, PageNr * 100, startY + N);
N += (int)fontHeight;
if (startY + N >= pageHeight - 100)
{
e.HasMorePages = true;
N = 0;
Y = i;
PageNr += 1;
return;
}
}
e.HasMorePages = false;
}
}
}
Finally figured it out. At first, I added the necessary print objects from the toolbox onto the form design. While this works it does not handle multiple pages correctly.
I now removed those objects from the form design and added them in code when the button is pressed:
PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog();
PrintDocument printDocument1 = new PrintDocument();
PageSetupDialog pageSetupDialog1 = new PageSetupDialog();
PrintDialog printDialog1 = new PrintDialog();
Works like a charm!
I want this to take a given piece of text and display it as a paragraph correctly (jumping to new line when needed) using DrawString and for(;;) loop, but the result is bonkers.
private void pnlText_Paint(object sender, PaintEventArgs e)
{
pnlText.Font = new Font("Calibri", 14, FontStyle.Regular);
SizeF lineSize = new SizeF();
for (int i = 0; i < DisplayText.Length; i++)
{
currentLine += DisplayText[i];
lineSize = e.Graphics.MeasureString(currentLine, pnlText.Font);
if (DisplayText[i].ToString() == " " && lineSize.Width >= 820)
{
paragraph += currentLine + "\n";
currentLine = null;
}
}
using (SolidBrush br = new SolidBrush(Color.Black))
{
e.Graphics.DrawString(paragraph, pnlText.Font, br, 5, 5);
}
}
The output:
one sentence whole paragraph
I am dumbstruck, it makes no sense for it to repeat like that.
I have tried using String Builder and replacing DisplayText.Length with a number and it still doesn't work right. Using foreach and drawing each char seperately works, albeit messes up spacing.
Also initially I didn't have "paragraph" string and I simply drew each line inside the loop, but that somehow caused the end of the text to be cut off and pasted at the very beginning which is even more insane. I am getting suspicious my computer is possessed with deamons.
It seems that your paragraph variable has a shared state. Verify you reset the state for each button click like that:
private void pnlText_Paint(object sender, PaintEventArgs e)
{
pnlText.Font = new Font("Calibri", 14, FontStyle.Regular);
SizeF lineSize = new SizeF();
paragraph = ""; // that should reset the paragraph variable between invocations
for (int i = 0; i < DisplayText.Length; i++)
{
currentLine += DisplayText[i];
lineSize = e.Graphics.MeasureString(currentLine, pnlText.Font);
if (DisplayText[i].ToString() == " " && lineSize.Width >= 820)
{
paragraph += currentLine + "\n";
currentLine = null;
}
}
using (SolidBrush br = new SolidBrush(Color.Black))
{
e.Graphics.DrawString(paragraph, pnlText.Font, br, 5, 5);
}
}
Or simply define it inside the pnlText_Paint method
Hope it helps :)
private void pnlText_Paint(object sender, PaintEventArgs e)
{
pnlText.Font = new Font("Calibri", 14, FontStyle.Regular);
SizeF lineSize = new SizeF();
currentLine = null;
paragraph = null;
for (int i = 0; i < DisplayText.Length; i++)
{
currentLine += DisplayText[i];
lineSize = e.Graphics.MeasureString(currentLine, pnlText.Font);
if (DisplayText[i].ToString() == " " && lineSize.Width >= 820)
{
paragraph += currentLine + "\n";
currentLine = null;
}
}
paragraph += currentLine;
using (SolidBrush br = new SolidBrush(Color.Black))
{
e.Graphics.DrawString(paragraph, pnlText.Font, br, 5, 5);
}
}
Followed Hans Kesting advice and now it works. Something is calling the paint event more than once, hence the strings are not null to begin with.
Thats my printpage Event Handler Code
private void printDocument2_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
int yaxis = 0;
Font font = new Font("Arial", 12, FontStyle.Bold);
for (int i = 0; i < 300; i++)
{
e.Graphics.DrawString("Store Inventory Report", font, Brushes.Black, 0, yaxis);
yaxis += 30;
}
}
This code only prints line 37 times. . . its not printing full page. . i generates its pdf, pdf also have line 37 times.
With full printing did you mean printing all the 300 lines base on your code? If it is you should set the parameter:
e.HasMorePages = true;
Set it to false when you reach the condition where all contents are already printed.
For your case it would be something like this:
private int startIndex = 0; //Keeps track of printed index
private int limit = 300; //Limit of items to print
private void printDocument2_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
int yaxis = 0;
Font font = new Font("Arial", 12, FontStyle.Bold);
for (int i = startIndex; i < limit; i++)
{
e.Graphics.DrawString("Store Inventory Report", font, Brushes.Black, 0, yaxis);
yaxis += 30;
//Reaches bottom? break the loop
if (yaxis > e.PageBounds.Bottom)
{
startIndex = i + 1; //Set to next page's data index
break;
}
}
//Still has pages?
e.HasMorePages = startIndex < limit;
}
I have this list with 5 indices and I want to print the title on a new page, what do I write so it prints it on the next page?
list<> // 5 indices
void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics graphic = e.Graphics;
float fontHeight = font.GetHeight();
int startX = 10;
int startY = 10;
for (int i = 0; i < list.Count; i++)
{
graphic.DrawString(list[i].Title, new Font("Courier New", 18), new SolidBrush(Color.Black), startX, startY);
}
}
You have to maintain an instance variable which points to the current index of the item in the list to display , and increment it every time a page is printed.
int currIndex = 0;
void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics graphic = e.Graphics;
float fontHeight = font.GetHeight();
int startX = 10;
int startY = 10;
graphic.DrawString(list[currIndex++].Title, new Font("Courier New", 18), new SolidBrush(Color.Black), startX, startY);
if(currIndex == list.Count)
{
e.HasMorePages = false;
currIndex = 0;
}
else
{
e.HasMorePages = true;
}
}
The printDocument object calls that same PrintPage() method for every page. If you want different content to appear on different pages, you need code that lives outside of that method to store the current state, and code within the method to check that state and resume/continue printing based on the state you stored outside of the method.
If you have instructions in the PrintPage() to print the title, then the title will be printed. End of story.