A little context: I'm developing a Windows Form app that contains a feature where the user prints some info.
The thing is that the size of that information can change from time to time: sometimes it can fit on a single page or sometimes it can be 20+ pages.
For the printings, I'm using .Net's PrintDocument
So I'm using e.HasMorePages to handle the possible multiple pages. Here's a simplified version of my code:
int printIndex = 0;
private void startPrinting(){
PrintDocument printDoc = new PrintDocument();
printDoc.PrinterSettings.PrinterName = "Ticket printer1"
printDoc.PrintPage += new PrintPageEventHandler(printPage);
printDoc.Print();
}
And the printPage method:
private void printPage(object sender, PrintPageEventArgs e)
{
Graphics graphics = e.Graphics;
int yPos = 0;
Font regular = new Font(FontFamily.GenericSansSerif, 10.0f, FontStyle.Regular);
for(int i = printIndex; i < data.Length; i++)
{
if (yPos + 30 >= e.PageBounds.Height)
{
e.HasMorePages = true;
return;
}
else
{
e.HasMorePages = false;
}
graphics.DrawString(data[i], regular, Brushes.Black, yPos, 110);
yPos += 20;
printIndex++;
}
regular.Dispose();
graphics.Dispose();
}
And this works just fine on virtual printers and even some physical printers here at the office. But when the user runs the app on his actual computer (with his actual printer) it prints no more than 3 pages.
I asked a peer and he suggested that Windows is sending the whole document to the printer and maybe some printers can't handle large documents due to low memory issues.
Is it how it works? and if it is: how can I fix it to print more than 3 pages?
Some hints:
All those drawing classes you are using (Graphics, Font, etc.) are wrappers around Win32 GDI objects and are Disposable. If you don't Dispose those things, unexpected results can happen. Read up on the "using" statement and IDisposable, and make sure you clean things up properly. You aren't printing line by line; you are printing page by page (hence the PrintPage event). You should be able to print a lot of pages.
Related
I'd like to display coverarts for each album of an MP3 library, a bit like Itunes does (at a later stage, i'd like to click one any of these coverarts to display the list of songs).
I have a form with a panel panel1 and here is the loop i'm using :
int i = 0;
int perCol = 4;
int disBetWeen = 15;
int width = 250;
int height = 250;
foreach(var alb in mp2)
{
myPicBox.Add(new PictureBox());
myPicBox[i].SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
myPicBox[i].Location = new System.Drawing.Point(disBetWeen + (disBetWeen * (i % perCol) +(width * (i % perCol))),
disBetWeen + (disBetWeen * (i / perCol))+ (height * (i / perCol)));
myPicBox[i].Name = "pictureBox" + i;
myPicBox[i].Size = new System.Drawing.Size(width, height);
myPicBox[i].ImageLocation = #"C:/Users/Utilisateur/Music/label.jpg";
panel1.Controls.Add(myPicBox[i]);
i++;
}
I'm using the same picture per picturebox for convenience, but i'll use the coverart embedded in each mp3 file eventually.
It's working fine with an abstract of the library (around 50), but i have several thousands of albums. I tried and as expected, it takes a long time to load and i cannot really scroll afterward.
Is there any way to load only what's displayed ? and then how to assess what is displayed with the scrollbars.
Thanks
Winforms really isn't suited to this sort of thing... Using standard controls, you'd probably need to either provision all the image boxes up front and load images in as they become visible, or manage some overflow placeholder for the appropriate length so the scrollbars work.
Assuming Winforms is your only option, I'd suggest you look into creating a custom control with a scroll bar and manually driving the OnPaint event.
That would allow you to keep a cache of images in memory to draw the current view [and a few either side], while giving you total control over when they're loaded/unloaded [well, as "total" as you can get in a managed language - you may still need tune garbage collection]
To get into some details....
Create a new control
namespace SO61574511 {
// Let's inherit from Panel so we can take advantage of scrolling for free
public class ImageScroller : Panel {
// Some numbers to allow us to calculate layout
private const int BitmapWidth = 100;
private const int BitmapSpacing = 10;
// imageCache will keep the images in memory. Ideally we should unload images we're not using, but that's a problem for the reader
private Bitmap[] imageCache;
public ImageScroller() {
//How many images to put in the cache? If you don't know up-front, use a list instead of an array
imageCache = new Bitmap[100];
//Take advantage of Winforms scrolling
this.AutoScroll = true;
this.AutoScrollMinSize = new Size((BitmapWidth + BitmapSpacing) * imageCache.Length, this.Height);
}
protected override void OnPaint(PaintEventArgs e) {
// Let Winforms paint its bits (like the scroll bar)
base.OnPaint(e);
// Translate whatever _we_ paint by the position of the scrollbar
e.Graphics.TranslateTransform(this.AutoScrollPosition.X,
this.AutoScrollPosition.Y);
// Use this to decide which images are out of sight and can be unloaded
var current_scroll_position = this.HorizontalScroll.Value;
// Loop through the images you want to show (probably not all of them, just those close to the view area)
for (int i = 0; i < imageCache.Length; i++) {
e.Graphics.DrawImage(GetImage(i), new PointF(i * (BitmapSpacing + BitmapWidth), 0));
}
}
//You won't need a random, just for my demo colours below
private Random rnd = new Random();
private Bitmap GetImage(int id) {
// This method is responsible for getting an image.
// If it's already in the cache, use it, otherwise load it
if (imageCache[id] == null) {
//Do something here to load an image into the cache
imageCache[id] = new Bitmap(100, 100);
// For demo purposes, I'll flood fill a random colour
using (var gfx = Graphics.FromImage(imageCache[id])) {
gfx.Clear(Color.FromArgb(255, rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255)));
}
}
return imageCache[id];
}
}
}
And Load it into your form, docking to fill the screen....
public Form1() {
InitializeComponent();
this.Controls.Add(new ImageScroller {
Dock = DockStyle.Fill
});
}
You can see it in action here: https://www.youtube.com/watch?v=ftr3v6pLnqA (excuse the mouse trails, I captured area outside the window)
I currently have a function that is able to printout a DataGridView onto a page using System.Drawing.Printing.PrintDocument - the printing utility runs through my print page function (PrintDoc_PagePrint) until it runs out of rows (where it will set HasMorePages to false.)
I'm trying to work out the total number of pages before I print so I can put "page x of y" at the bottom of each page. One way is to work out how many rows fit on each page and work out how many pages there are based on how many rows there are in total, but this doesn't seem very versatile as it relies on each row being the same height, which depending on how it's programmed, may not always be the case.
The way I want to do it is to do a "phantom" print - or basically print it to a null printer in the background without the user knowing. When it does this first print, it can increment a global variable TotalPages each time it runs the print function, then once the phantom print is done stop TotalPages being incremented next time it is printed (presumably just by setting a bool once the phantom print is done.) This would be more versatile, and would work for data grids with different row heights, or any other type of data I wanted to print.
My question is - is there any way to run a sample print in the background? This is done after the user has selected page size and orientation etc, so we do know those essential details, but just before the print preview dialogue is displayed.
Here's some code I have... it sort of works, but for some reason it doesn't work all the time!
// Phantom print to determine number of pages. Writes to TotalPages var.
// The next print won't write to TotalPages when FirstPreviewDone is set to true.
var printEventArgs = new PrintEventArgs();
// Create a graphics object of the page size to "print" to.
int x = 0;
int y = 0;
int width = printDoc.DefaultPageSettings.PaperSize.Width;
int height = printDoc.DefaultPageSettings.PaperSize.Height;
Rectangle marginBoundsRectangle = new Rectangle(x, y, width, height);
Rectangle pageBoundsRectangle = new Rectangle(0, 0, printDoc.DefaultPageSettings.PaperSize.Width, printDoc.DefaultPageSettings.PaperSize.Height);
Bitmap b = new Bitmap(width, height);
// Swap everything if it's in landscape.
if (printDoc.DefaultPageSettings.Landscape)
{
marginBoundsRectangle = new Rectangle(y, x, height, width);
pageBoundsRectangle = new Rectangle(0, 0, printDoc.DefaultPageSettings.PaperSize.Height, printDoc.DefaultPageSettings.PaperSize.Width);
b = new Bitmap(height, width);
}
Graphics graphics = Graphics.FromImage(b);
var printPageEventArgs = new PrintPageEventArgs(graphics, marginBoundsRectangle, pageBoundsRectangle, printDoc.DefaultPageSettings);
printPageEventArgs.HasMorePages = true;
PrintDoc_BeginPrint(null, printEventArgs);
while (printPageEventArgs.HasMorePages && !printPageEventArgs.Cancel)
{
try
{
PrintDoc_PrintPage(null, printPageEventArgs);
}
catch (Exception ex)
{
MessageBoxEx.Show(ex.Message, "Error printing - Check logs", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
This is already answered in the below question...
Is there a better way to get the page count from a PrintDocument than this?
Hope this helps...
int iPageCount = rptDocument.FormatEngine.GetLastPageNumber(new ReportPageRequestContext());
I have a serious problem here. Iam programming a tool in which the user can design a Control Cubicle for a switchgear. The Cubicle is drawn with Panels and Pictureboxes, which is working nice and looks good.
Now I want to make an Export function which exports the designed Cubicle into an pdf file.
So far so good, the function works - on my computer only!
I use CopyFromScreen to get a Screenshot of the Panel in which the cubicle is shown, save that into a file and put it into a pdf file ( I also tried to get a picture of the panel using DrawToBitmap, but this isnt working properly as it is drawing some Pictureboxes over others). On my computer it captures the panel correctly and shows the correct picture in the pdf, however on every other computer it takes a picture of what is behind the Form. This is quite confusing as I have no idea why it should do that and why it is working on my system. So far I tried a few things to force the window to be on top but nothing will work, everybody gets pictures of the desktop or the window that is behind.
Code:
void takeScreenshot()
{
this.TopMost = true;
this.BringToFront();
this.Focus();
Application.DoEvents();
System.Drawing.Rectangle bounds = Mainpanel.Bounds;
bounds.Width = bounds.Width - 6;
bounds.Height = bounds.Height - 4;
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(Mainpanel.PointToScreen(new Point()).X + 3, Mainpanel.PointToScreen(new Point()).Y + 2, 0, 0, bounds.Size);
}
bitmap.Save(Application.StartupPath + "\\data\\programdata\\temppic.bmp");
}
this.TopMost = false;
}
Iam actually quite desperate, without the export the program is useless.
Does anyone have an idea how to solve this?
Jonathan
Use this code.
void takeScreenshot()
{
Application.DoEvents();
System.Drawing.Rectangle bounds = Mainpanel.Bounds;
bounds.Width = bounds.Width - 6;
bounds.Height = bounds.Height - 4;
using (Bitmap bitmap = new Bitmap(Mainpanel.Width, Mainpanel.Height))
{
Mainpanel.DrawToBitmap(bitmap, new Rectangle(3, 2, bounds.Width, bounds.Height));
bitmap.Save(Application.StartupPath + "\\data\\programdata\\temppic.bmp");
}
}
I am working on a small project for a Retail Management Software, which will be using a POS printer (I think that's what we call it). I need a create a bill for it in the end. But i am stuck here, and not able to proceed. So suppose if I generate my bill in a separate form with appropriate dimensions (of width of POS bills), will i be able to print it properly?
I am using C# and .NET 4.0 framework. I don't have much knowledge about POS devices. I am working for really a small local client which needs a basic model of software. I am also a fresher so please help me out.
If my question is not clear, let me know i will try to elaborate my thought.
I know this is an old post, but for those still looking for a solution, I can tell you what I did.
After spending many hours messing with OPOS and POS for .Net, I ended up just abandoning those and just using the built-in System.Drawing.Printing libraries. The OPOS and POS for .Net ended up being a pain to get working and ultimately didn't work as well as the built-in libraries.
I'm using an Epson TM-T20II receipt printer.
Here's some code that worked well for me.
public static void PrintReceiptForTransaction()
{
PrintDocument recordDoc = new PrintDocument();
recordDoc.DocumentName = "Customer Receipt";
recordDoc.PrintPage += new PrintPageEventHandler(ReceiptPrinter.PrintReceiptPage); // function below
recordDoc.PrintController = new StandardPrintController(); // hides status dialog popup
// Comment if debugging
PrinterSettings ps = new PrinterSettings();
ps.PrinterName = "EPSON TM-T20II Receipt";
recordDoc.PrinterSettings = ps;
recordDoc.Print();
// --------------------------------------
// Uncomment if debugging - shows dialog instead
//PrintPreviewDialog printPrvDlg = new PrintPreviewDialog();
//printPrvDlg.Document = recordDoc;
//printPrvDlg.Width = 1200;
//printPrvDlg.Height = 800;
//printPrvDlg.ShowDialog();
// --------------------------------------
recordDoc.Dispose();
}
private static void PrintReceiptPage(object sender, PrintPageEventArgs e)
{
float x = 10;
float y = 5;
float width = 270.0F; // max width I found through trial and error
float height = 0F;
Font drawFontArial12Bold = new Font("Arial", 12, FontStyle.Bold);
Font drawFontArial10Regular = new Font("Arial", 10, FontStyle.Regular);
SolidBrush drawBrush = new SolidBrush(Color.Black);
// Set format of string.
StringFormat drawFormatCenter = new StringFormat();
drawFormatCenter.Alignment = StringAlignment.Center;
StringFormat drawFormatLeft = new StringFormat();
drawFormatLeft.Alignment = StringAlignment.Near;
StringFormat drawFormatRight = new StringFormat();
drawFormatRight.Alignment = StringAlignment.Far;
// Draw string to screen.
string text = "Company Name";
e.Graphics.DrawString(text, drawFontArial12Bold, drawBrush, new RectangleF(x, y, width, height), drawFormatCenter);
y += e.Graphics.MeasureString(text, drawFontArial12Bold).Height;
text = "Address";
e.Graphics.DrawString(text, drawFontArial10Regular, drawBrush, new RectangleF(x, y, width, height), drawFormatCenter);
y += e.Graphics.MeasureString(text, drawFontArial10Regular).Height;
// ... and so on
}
Hopefully it helps someone skip all the messing around with custom drivers. :)
Well I designed a POS application (in Delphi) and altough there are many little issues with printing a receipt or a bill it´s not rocket science. I just print a receipt by simply, well, printing like any other printer, the difference is that you send lines of 30-38 characters long (depending on the printer driver, font and size). To start, you could print using 2 methods: Sending Ascii characters and sending printer commands (to set the font style, color, etc) for that specific printer or the second method is to set the font, size, etc using C# like printing to any other normal/desktop printer.
You could try printing following the example of the page and my suggestions:
http://ondotnet.com/pub/a/dotnet/2002/06/24/printing.html
I have never printed anything using C#. I was just wondering what the standard way to do this was. On my form I have a few listboxes and a few textboxes. I would like to print their contents and show them in a print preview, with a nice layout in a table. Then from their I would like the user to be able to print.
Thanks in advance!
Here's a nice little tutorial on basic printing in C#. It deals with text but could be extended easily to draw anything else.
Printing in C# is very similar to custom painting in C#. The big difference is that the coordinate system is flipped from the screen representation and that you have to account for spanning of pages (if/when necessary.) The way you print is also a bit counter intuitive in that you have to initiate the print process and then handle the page print event.
Example:
Here is a simple example of a print event handler that assumes the presence of list box control named listBox1 with some items in it. It draws each item as well as a box around it.
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
Font font = new Font("Arial", 10f);
Graphics g = e.Graphics;
Pen rectPen = new Pen(Color.Black, 2f);
Brush brush = Brushes.Black;
// find widest width of items
for (int i=0; i<listBox1.Items.Count; i++)
if(maxItemWidth < (int)g.MeasureString(listBox1.Items[i].ToString(), font).Width)
maxItemWidth = (int)g.MeasureString(listBox1.Items[i].ToString(), font).Width;
// starting positions:
int itemHeight = (int)g.MeasureString("TEST", font).Height + 5;
int maxItemWidth = 0;
int xpos = 200;
int ypos = 200;
// print
for (int i = 0; i < listBox1.Items.Count; i++)
{
g.DrawRectangle(rectPen, xpos, ypos, maxItemWidth, itemHeight );
g.DrawString(listBox1.Items[i].ToString(), font, brush, xpos, ypos);
ypos += itemHeight;
}
e.HasMorePages = false;
}
You will want to use System.Drawing.Printing libraries. You'll use the PrintDocument.Print method which you can find on the MSDN Page with Example
One method is summarized nicely here at CodeProject having a Print implementation.
As for Print Preview, somebody has tackled an implementation here.