I was asked to publish some help in connection with native printing from WinForm application. It could be useful when you have some invoicing, warehouse managing or accounting application and you have to print invoices (or anything from a winform).
I show you some basics of printing invoice from winform application, of course without seeing the window borders or any unwanted components.
First you have to create a new form and add a Panel component to it. Set the Dock property of the Panel to Top and leave the default name (panel1). Everything in this panel will be printed, so we place our Print button under this panel.
Now add some TableLayoutPanel elemts to the Panel. One for the header, one for the company infos and one for the items. Each TableLayoutPanel will have different count of columns, that’s why you need several TableLayoutPanels. It’s easier to work with multiply tables than only with one with merged columns.
Here’s my final form (click to view it in larger size):
These are my TableLayoutPanels:
- tableLayoutPanel1 – 3 columns, 33.33% width each (company logo, INVOICE, invoice no.)
- tableLayoutPanel2 – 2 columns, 50% width each (Company:, Bill To:)
- tableLayoutPanel3 – 2 columns, 50% width each ({CompanyInfo}, {CustomerInfo})
- tableLayoutPanel4 – 2 columns, 50% width each (label7, label8)
- tableLayoutPanel5 – 4 columns, 25% width each (Payment method, Invoice Date, Completion Date, DuoDate) – 2 rows
- tableLayoutPanel6 – 7 columns, widths are: 33.51%,10.05%,8.18%,10.68%,13.59%,10.54%,13.11% (Product desc., Quantity, Units, etc…)
Under these tables I placed a ListView component with 7 columns and View property set to Details. The column names are columnHeader1…columnHeader7. This will be important in the Resize event of the form, later.
There are 2 more TableLayoutPanels: one with 4 columns and the other with 2 columns. This invoice template is a static one, so you have to implement some paging fucntion if you record dozens of products. This invoice template can show only about 15-20 pieces of products only.
All the TableLayoutPanels have Labels in each table cells with Dock property set to Fill.
Add 2 components from the Toolbox to the form: PrintDocument and PrintPreviewDialog. I use the default names this time: printDocument1 and printPreviewDialog1.
Set the Document property of the printPreviewDialog1 to printDocument1, ShowIcon to false, UseAntiAlias to true.
Set the DocumentName property of the printDocument1 to document and create its PrintPage event (automatic name is printDocument1_PrintPage).
Also create the Click event of the button. Let’s call it btnPrint_Click (as far as my button name is btnPrint and I created the event automatically).
Now let’s create all the methods we need:
- Capturing the screen
- Print button
- Form resize
- PrintPage
Capturing the screen:
[System.Runtime.InteropServices.DllImport("gdi32.dll")] public static extern long BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop); private Bitmap memoryImage; private void CaptureScreen() { Graphics mygraphics = panel1.CreateGraphics(); Size s = panel1.Size; memoryImage = new Bitmap(s.Width, s.Height, mygraphics); Graphics memoryGraphics = Graphics.FromImage(memoryImage); IntPtr dc1 = mygraphics.GetHdc(); IntPtr dc2 = memoryGraphics.GetHdc(); BitBlt(dc2, 0, 0, panel1.ClientRectangle.Width, panel1.ClientRectangle.Height, dc1, 0, 0, 13369376); mygraphics.ReleaseHdc(dc1); memoryGraphics.ReleaseHdc(dc2); }
Print button:
private void btnPrint_Click(object sender, EventArgs e) { CaptureScreen(); PrinterSettings ps = new PrinterSettings(); ps.Copies = 2; IEnumerable paperSizes = ps.PaperSizes.Cast(); PaperSize sizeA4 = paperSizes.First(size => size.Kind == PaperKind.A4); printDocument1.DefaultPageSettings.PaperSize = sizeA4; printPreviewDialog1.ShowDialog(); Close(); }
If you don’t want to show the PreviewDialog, then replace this line:
printPreviewDialog1.ShowDialog();
with this line:
printDocument1.Print();
PrintPage:
private void printDocument1_PrintPage(object sender, PrintPageEventArgs e) { e.Graphics.DrawImage(memoryImage, 0, 0); }
Form resize (because we make a capture of the screen, we want to see all the added products on the invoice):
private void PrintForm_Resize(object sender, EventArgs e) { columnHeader1.Width = Convert.ToInt32((double)lstTetelek.Width * 0.3351); columnHeader2.Width = Convert.ToInt32((double)lstTetelek.Width * 0.1005); columnHeader3.Width = Convert.ToInt32((double)lstTetelek.Width * 0.0818); columnHeader4.Width = Convert.ToInt32((double)lstTetelek.Width * 0.1068); columnHeader5.Width = Convert.ToInt32((double)lstTetelek.Width * 0.1359); columnHeader6.Width = Convert.ToInt32((double)lstTetelek.Width * 0.1054); columnHeader7.Width = Convert.ToInt32((double)lstTetelek.Width * 0.1311); }
That’s why I wrote that remember the column names of the ListView.
I suggest you to store all the data in a custom class, so when you open the form and pass this class as an input parameter to the constructor, all the labels can be easily filled up.
Here’s the final result filled up some data (in Hungarian, sorry… 🙂 ) – click for larger image: