由于此作业可用的组件具有压倒性的复杂性和/或有限的许可功能,因此我决定从头开始编写此组件。这是我在PHP和VB6中完全可用的功能。但是在尝试添加page
时,我正在撞墙。
关于如何从文件打印,或如何打印单个页面的所有很好的示例(所有图形等都是针对Print事件内部页面的硬编码),但没有关于如何设置集合来保存页面的内容数据,然后发送要打印的数据。
在vb6中,您可以获取页面边框并调用新页面,但在.NET中,似乎不是新的页面方法。
以下是我到目前为止的来源,由于显然缺乏这种基本功能,因此非常粗糙。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using PdfFileWriter;
using System.Drawing.Printing;
using System.ComponentModel;
using System.IO;
using System.Drawing.Printing;
class PDF : PrintDocument {
/// <summary>
/// Logo to display on invoice
/// </summary>
public Image Logo { get; set; }
/// <summary>
/// Pages drawn in document
/// </summary>
private List<Graphics> Pages;
private int CurrentPage;
private string directory;
private string file;
/// <summary>
/// Current X position
/// </summary>
public int X { get; set; }
/// <summary>
/// Current X position
/// </summary>
public int Y { get; set; }
/// <summary>
/// Set the folder where backups, downloads, etc will be stored or retrieved from
/// </summary>
[Editor( typeof( System.Windows.Forms.Design.FolderNameEditor ), typeof( System.Drawing.Design.UITypeEditor ) )]
public string Folder { get { return directory; } set { directory=value; } }
public PDF() {
file = (string)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds.ToString();
directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
CurrentPage = 0;
// initialize pages array
Pages = new List<Graphics>();
PrinterSettings = new PrinterSettings() {
PrinterName = "Microsoft Print to PDF",
PrintToFile = true,
PrintFileName = Path.Combine(directory, file + ".pdf"),
};
DefaultPageSettings = new PageSettings(PrinterSettings) {
PaperSize=new PaperSize("Letter", 850, 1100 ),
Landscape = false,
Margins = new Margins(left: 50, right: 50, top: 50, bottom: 50),
};
}
/// <summary>
/// Get specific page
/// </summary>
/// <param name="page">page number. 1 based array</param>
/// <returns></returns>
public Graphics GetPage( int page ) {
int p = page - 1;
if ( p<0||p>Pages.Count ) { return null; }
return Pages[p];
}
public Graphics GetCurrentPage() {
return GetPage(CurrentPage);
}
protected override void OnBeginPrint( PrintEventArgs e ) {
base.OnBeginPrint( e );
}
protected override void OnPrintPage( PrintPageEventArgs e ) {
base.OnPrintPage( e );
}
protected override void OnEndPrint( PrintEventArgs e ) {
base.OnEndPrint( e );
}
/// <summary>
/// Add a new page to the document
/// </summary>
public void NewPage() {
// Add a new page to the page collection and set it as the current page
Graphics g = Graphics.CreateCraphics(); // not sure if this works, but no CreateGraphics is available
Pages.Add( g );
}
/// <summary>
/// Add a new string to the current page
/// </summary>
/// <param name="text">The string to print</param>
/// <param name="align">Optional alignment of the string</param>
public void DrawString(string text, System.Windows.TextAlignment align = System.Windows.TextAlignment.Left ) {
// add string to document
Pages[CurrentPage].DrawString(text, new Font("Arial", 10), new SolidBrush(Color.Black), new PointF(X, Y));
}
/// <summary>
/// Save the contents to PDF
/// </summary>
/// <param name="FileName"></param>
public void Save( string FileName ) {
// Start the print job looping through pages.
foreach ( Graphics page in Pages ) {
// there doesn't seem to be an addpage method
}
/*
* From stackoverflow article on how to 'print' a pdf to filename as the poster complained
* that the PrinterSettings.PrintFileName property is ignored. Havn't tested yet. Also, no
* such function as 'PrintOut' so further research required.
*
PrintOut(
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
FileName,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value
);
*/
}
}
我不是在寻找一个关于如何编写PDF文档的长篇大论项目,因为它们都非常严格,每个都至少有一个限制,这是我打算设计的布局的问题(从PHP升级这是VB6的升级版)。最终结果布局如下所示&gt;
此报告可能包含更多页面,具体取决于付款和服务中的项目数量。如果有许多项目,子报告的标题将继续转到下一页。例如,如果客户有200个服务,那么这些项目将以类似的方式继续使用相同的&#34;付款&#34;每个连续页面开头的标题栏。
可能有多个详细报告,每个报告从新页面的开头开始,页面计数器将重置并打印这些页面。因此,发票的第6页可能实际上是第二个详细报告的第3页。每个报告的开头和结尾如下(图片描绘了现场数据的布局等)
使上述多报表发票布局在Visual Studio .NET中可行的可靠方法。我期待远离php和vb6的端口代码,我对使用大量分发大小的库或者可笑的复杂/有限许可限制不感兴趣。 Microsoft提供了一些非常强大的内置工具,我并不反对使用内置的PDF打印驱动程序和假脱机数据,即使这有点像黑客,它似乎是最简单的方法这个功能没有第三方控件的限制或膨胀。 (包括开源,因为我看到的那些倾向于对char进行一些非常奇怪的转换,然后可能是乳胶或其他东西,不完全确定所有转换内容是什么)。
了解上述报表样式的组合构成一张发票非常重要,因此每个客户端只有一个pdf文件。如果它有帮助,这是一个VB6向后兼容性方法,暴露传统的&#39;打印&#39;对象printing compatability vb6。这应该有助于澄清我希望创建/使用的本机功能。
我很难吞下上述&#34;没有直接的等同物&#34;声明,因为在内存中创建文档时添加新页面似乎是创建打印文档的一个非常基本的(也是必不可少的)功能。没有必要打印的所有内容必须首先从文件中加载。
答案 0 :(得分:0)
我已经创建了一个.NET打印系统的非常简单的演示模型,它模仿了您指定的基本发票布局。完整代码可用here,但我将总结以下重要部分。我将继续努力并改进它,因为创建它会很有趣。
InvoiceDocument
负责打印Invoice
的实例:
class InvoiceDocument : PrintDocument
{
public InvoiceDocument(Invoice invoice)
{
_invoice = invoice;
_currentSection = new MainPage(this);
}
private Invoice _invoice;
public Invoice Invoice => _invoice;
private InvoiceSection _currentSection;
public InvoiceSection CurrentSection => _currentSection;
#region Fonts
private Font _titleFont = new Font(FontFamily.GenericSansSerif, 18, FontStyle.Bold);
public Font TitleFont => _titleFont;
private Font _headerFont = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Regular);
public Font HeaderFont => _headerFont;
private Font _regularFont = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Regular);
public Font RegularFont => _regularFont;
private Font _boldFont = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Bold);
public Font BoldFont => _boldFont;
#endregion
protected override void OnPrintPage(PrintPageEventArgs e)
{
_currentSection?.Render(e);
}
public void ChangeSection(InvoiceSection nextSection)
{
_currentSection = nextSection;
}
}
InvoiceDocument
由InvoiceSection
的子类组成。每个部分都知道如何打印发票的不同部分,主页,摘要,详细信息等。它还负责知道何时以及如何包装到下一页:
abstract class InvoiceSection
{
protected InvoiceSection(InvoiceDocument invoiceDocument)
{
this.InvoiceDocument = invoiceDocument;
}
public InvoiceDocument InvoiceDocument { get; }
public abstract void Render(PrintPageEventArgs e);
public Invoice Invoice => InvoiceDocument?.Invoice;
}
internal class MainPage : InvoiceSection
{
public MainPage(InvoiceDocument invoiceDocument) : base(invoiceDocument) { }
public override void Render(PrintPageEventArgs e)
{
e.Graphics.FillEllipse(Brushes.Green, e.MarginBounds.Left, e.MarginBounds.Top, e.MarginBounds.Left + 100, e.MarginBounds.Top + 100);
e.Graphics.DrawString(Invoice.CompanyName, InvoiceDocument.TitleFont, Brushes.Black, e.MarginBounds.Left, e.MarginBounds.Top + 30);
e.HasMorePages = true;
InvoiceDocument.ChangeSection(new SummmarySection(InvoiceDocument));
}
}
internal class SummmarySection : InvoiceSection
{
public SummmarySection(InvoiceDocument invoiceDocument) : base(invoiceDocument)
{
}
public override void Render(PrintPageEventArgs e)
{
e.Graphics.FillRectangle(Brushes.LightGray, e.MarginBounds.Left, e.MarginBounds.Top, e.MarginBounds.Width, 20);
e.Graphics.DrawString("Payments", InvoiceDocument.HeaderFont, Brushes.Black, e.MarginBounds.Left + 200, e.MarginBounds.Top + 2);
int y = e.MarginBounds.Top + 25;
while (_currentPaymentIndex < Invoice.Payments.Count && y < e.MarginBounds.Bottom)
{
Payment payment = Invoice.Payments[_currentPaymentIndex];
e.Graphics.DrawString(payment.Description, InvoiceDocument.RegularFont, Brushes.Black, e.MarginBounds.Left + 150, y);
e.Graphics.DrawString($"{payment.Amount:C}", InvoiceDocument.RegularFont, Brushes.Black, e.MarginBounds.Right - 150, y);
y = y + InvoiceDocument.RegularFont.Height;
_currentPaymentIndex++;
}
if (_currentPaymentIndex < Invoice.Payments.Count)
{
e.HasMorePages = true;
}
}
private int _currentPaymentIndex = 0;
}
答案 1 :(得分:0)
这是我对此问题的可行解决方案的实现,允许用户以可重用的方式完全设计文档,而无需发送.Print
命令。
使用图片存储数据的概念部分归因于Bradley Uffner对this question关于合并两个Graphics
对象的评论
以下述方式处理该过程有几个优点和缺点。
<强>优点强>
OnPrintPage
并不过分复杂<强>缺点强>
这也证明了这是多么便携,因为任何人都可以快速重复使用它。我仍在努力包装Draw方法,但是这段代码演示了只需要使用更多绘制方法进行真正扩展的目标,以及我可能错过的其他一些功能。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Drawing.Printing;
using System.ComponentModel;
using System.IO;
class PDF : PrintDocument {
/// <summary>
/// Logo to display on invoice
/// </summary>
public Image Logo { get; set; }
/// <summary>
/// Current X position on canvas
/// </summary>
public int X { get; set; }
/// <summary>
/// Current Y position on canvas
/// </summary>
public int Y { get; set; }
/// <summary>
/// Set the folder where backups, downloads, etc will be stored or retrieved from
/// </summary>
[Editor( typeof( System.Windows.Forms.Design.FolderNameEditor ), typeof( System.Drawing.Design.UITypeEditor ) )]
public string Folder { get { return directory; } set { directory=value; } }
/// <summary>
/// Current font used to print
/// </summary>
public Font Font { get; set; }
/// <summary>
/// Current font color
/// </summary>
public Color ForeColor { get; set; }
private int CurrentPagePrinting { get; set; }
/// <summary>
/// Set printer margins
/// </summary>
public Margins PrintMargins {
get { return DefaultPageSettings.Margins; }
set { DefaultPageSettings.Margins = value; }
}
/// <summary>
/// Pages drawn in document
/// </summary>
public List<Image> Pages { get; private set; }
/// <summary>
/// The current selected page number. 0 if nothing selected
/// </summary>
private int CurrentPage;
/// <summary>
/// The current working directory to save files to
/// </summary>
private string directory;
/// <summary>
/// The currently chosen filename
/// </summary>
private string file;
/// <summary>
/// Public acceisble object to all paperSizes as set
/// </summary>
public List<PrintPaperSize> PaperSizes { get; private set; }
/// <summary>
/// Object for holding papersizes
/// </summary>
public class PrintPaperSize {
public string Name { get; set; }
public double Height { get; set; }
public double Width { get; set; }
public PaperKind Kind { get; set; }
public PrintPaperSize() {
Height = 0;
Width = 0;
Name = "";
Kind = PaperKind.Letter;
}
public PrintPaperSize( string name, double height, double width, PaperKind kind ) {
Height=height;
Width=width;
Name=name;
Kind=kind;
}
}
/// <summary>
/// Set the spacing between lines in percentage. Affects Y position. Range(%): 1 - 1000
/// </summary>
private int lineSpacing;
public int LineSpacing {
get {
return lineSpacing;
}
set {
if(value > 0 && value < 1000) {
lineSpacing = value;
}
}
}
/// <summary>
/// Current papersize selected. used for some calculations
/// </summary>
public PrintPaperSize CurrentPaperSize { get; private set; }
public PDF() {
// set the file name without extension to something safe
file = (string)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds.ToString();
// set the save directory to MyDocuments
directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
CurrentPage = 0;
// initialize pages array
Pages = new List<Image>();
// Set the initial font and color
Font = new System.Drawing.Font("Arial", (float)11.25, FontStyle.Regular, GraphicsUnit.Point);
ForeColor = Color.Black;
lineSpacing = 100;
// set the printer to Microsoft's PDF printer and generate and ensure it will save to a file
PrinterSettings = new PrinterSettings() {
PrinterName = "Microsoft Print to PDF",
PrintToFile = true,
PrintFileName = Path.Combine(directory, file + ".pdf"),
};
// hide the notice 'printing' while spooling job.
PrintController = new StandardPrintController();
// set the printer quality to maximum so we can use this for getting the dpi at this setting
DefaultPageSettings.PrinterResolution.Kind = PrinterResolutionKind.High;
// store all paper sizes at 1 dpi [ reference: https://social.msdn.microsoft.com/Forums/vstudio/en-US/05169a47-04d5-4890-9b0a-7ad11a6a87f2/need-pixel-width-for-paper-sizes-a4-a5-executive-letter-legal-executive?forum=csharpgeneral ]
PaperSizes = new List<PrintPaperSize>();
foreach ( PaperSize P in PrinterSettings.PaperSizes ) {
double W=P.Width/100.0;
double H=P.Height/100.0;
PaperSizes.Add(
new PrintPaperSize() {
Height = H,
Width = W,
Name = P.PaperName,
Kind = P.Kind
}
);
if ( P.PaperName=="Letter" ) {
CurrentPaperSize = PaperSizes[PaperSizes.Count-1];
}
}
// setup the initial page type, orientation, margins,
using ( Graphics g=PrinterSettings.CreateMeasurementGraphics() ) {
DefaultPageSettings = new PageSettings(PrinterSettings) {
PaperSize=new PaperSize( CurrentPaperSize.Name, (Int32)(CurrentPaperSize.Width*g.DpiX), (Int32)(CurrentPaperSize.Height*g.DpiY) ),
Landscape = false,
Margins = new Margins(left: 100, right: 100, top: 10, bottom: 10),
PrinterResolution=new PrinterResolution() {
Kind = PrinterResolutionKind.High
}
};
}
// constrain print within margins
OriginAtMargins = false;
}
public void SetPaperSize( PaperKind paperSize ) {
// TODO: Use Linq on paperSizes
}
/// <summary>
/// Get specific page
/// </summary>
/// <param name="page">page number. 1 based array</param>
/// <returns></returns>
public Image GetPage( int page ) {
int p = page - 1;
if ( p<0||p>Pages.Count ) { return null; }
return Pages[p];
}
/// <summary>
/// Get the current page
/// </summary>
/// <returns>Image</returns>
public Image GetCurrentPage() {
return GetPage(CurrentPage);
}
/// <summary>
/// Before printing starts
/// </summary>
/// <param name="e">PrintEventArgs</param>
protected override void OnBeginPrint( PrintEventArgs e ) {
CurrentPagePrinting=0;
base.OnBeginPrint( e );
}
/// <summary>
/// Print page event
/// </summary>
/// <param name="e">PrintPageEventArgs</param>
protected override void OnPrintPage( PrintPageEventArgs e ) {
CurrentPagePrinting++;
// if page count is max exit print routine
if ( CurrentPagePrinting==Pages.Count ) { e.HasMorePages=false; } else { e.HasMorePages=true; }
// ensure high resolution / clarity of image so text doesn't fuzz
e.Graphics.CompositingMode=CompositingMode.SourceOver;
e.Graphics.CompositingQuality=CompositingQuality.HighQuality;
// Draw image and respect margins (unscaled in addition to the above so text doesn't fuzz)
e.Graphics.DrawImageUnscaled(
Pages[CurrentPagePrinting-1],
// new Point(0,0)
new Point(
DefaultPageSettings.Margins.Left,
DefaultPageSettings.Margins.Top
)
);
base.OnPrintPage( e );
}
/// <summary>
/// After printing has been completed
/// </summary>
/// <param name="e">PrintEventArgs</param>
protected override void OnEndPrint( PrintEventArgs e ) {
base.OnEndPrint( e );
}
/// <summary>
/// Add a new page to the document
/// </summary>
public void NewPage() {
// Add a new page to the page collection and set it as the current page
Bitmap bmp;
using(Graphics g = PrinterSettings.CreateMeasurementGraphics()) {
int w=(Int32)( CurrentPaperSize.Width*g.DpiX )-(Int32)( ( ( DefaultPageSettings.Margins.Left+DefaultPageSettings.Margins.Right )/100 )*g.DpiX );
int h=(Int32)( CurrentPaperSize.Height*g.DpiY )-(Int32)( ( ( DefaultPageSettings.Margins.Top+DefaultPageSettings.Margins.Bottom )/100 )*g.DpiY );
bmp = new Bitmap( w, h );
bmp.SetResolution(g.DpiX, g.DpiY);
}
// reset X and Y positions
Y=0;
X=0;
// Add new page to the collection
Pages.Add( bmp );
CurrentPage++;
}
/// <summary>
/// Change the current page to specified page number
/// </summary>
/// <param name="page">page number</param>
/// <returns>true if page change was successful</returns>
public bool SetCurrentPage( int page ) {
if ( page<1 ) { return false; }
if ( page>Pages.Count ) { return false; }
CurrentPage = page - 1;
return true;
}
/// <summary>
/// Remove the specified page #
/// </summary>
/// <param name="page">page number</param>
/// <returns>true if successful</returns>
public bool RemovePage(int page) {
if ( page<1 ) { return false; }
if ( page>Pages.Count ) { return false; }
if ( Pages.Count-page==0 ) {
CurrentPage = 0;
Pages.RemoveAt(page - 1);
} else {
if ( page==CurrentPage && CurrentPage == 1 ) {
Pages.RemoveAt(page - 1);
} else {
CurrentPage = CurrentPage - 1;
Pages.RemoveAt(page -1);
}
}
return true;
}
/// <summary>
/// Add a new string to the current page
/// </summary>
/// <param name="text">The string to print</param>
/// <param name="align">Optional alignment of the string</param>
public void DrawString(string text, System.Windows.TextAlignment align = System.Windows.TextAlignment.Left ) {
// add string to document
using ( Graphics g=Graphics.FromImage( Pages[CurrentPage - 1] ) ) {
g.CompositingQuality = CompositingQuality.HighQuality;
// get linespacing and adjust by user specified linespacing
int iLineSpacing=(Int32)( g.MeasureString( "X", Font ).Height*(float)( (float)LineSpacing/(float)100 ) );
switch ( align ) {
case System.Windows.TextAlignment.Left:
case System.Windows.TextAlignment.Justify:
g.DrawString( text, Font, new SolidBrush( ForeColor ), new PointF( X, Y ) );
break;
case System.Windows.TextAlignment.Right:
g.DrawString( text, Font, new SolidBrush( ForeColor ), new PointF( Pages[CurrentPage - 1].Width - g.MeasureString( text, Font ).Width, Y ) );
break;
case System.Windows.TextAlignment.Center:
g.DrawString( text, Font, new SolidBrush( ForeColor ), new PointF( ( Pages[CurrentPage-1].Width+g.MeasureString( text, Font ).Width )/2, Y ) );
break;
}
Y+=iLineSpacing;
if( Y + iLineSpacing > Pages[CurrentPage-1].Height ) {
NewPage();
}
}
}
}
// initialize a new PrintDocument
PDF print = new PDF();
// set the font
print.Font = new Font("Helvetica", (float)12, FontStyle.Regular, GraphicsUnit.Point);
// change the color (can be used for shapes, etc once their draw methods are added to the PDF() class)
print.ForeColor = Color.Red;
// create a new page !!!!
print.NewPage();
// add some text
print.DrawString( "Hello World !!" );
// add some right aligned text
print.DrawString( "Aligned Right", System.Windows.TextAlignment.Right );
// add some centered text
print.DrawString( "Aligned Right", System.Windows.TextAlignment.Center );
// change line spacing ( percentage between 1% and 1000% )
print.LineSpacing = 50; // 50% of drawstrings detected line height
// add another page
print.NewPage();
// print a couple lines
print.DrawString( "Hello World" );
print.DrawString( "Hello World" );
// change the color again and print another line
ForeColor = Color.Yellow;
print.DrawString( "Hello World" );
// duplicate a page (clone page 1 as page 3 )
print.NewPage();
print.Pages[print.Pages -1] = print.GetPage(1);
// go back to page 1 and print some more text at specified coordinates
print.SetCurrentPage(1);
print.X = 400;
print.Y = 300;
print.DrawString( "Drawn after 3rd page created" );
// send the print job
print.Print();
// reprint
print.Print();
// show a preview of the 2nd page
/*
Image img = print.GetPage(1);
pictureBox1.Height=(Int32)(print.CurrentPaperSize.Height*img.VerticalResolution);
pictureBox1.Width = (Int32)(print.CurrentPaperSize.Width*img.HorizontalResolution);
pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
pictureBox1.Image = img;
*/