Как я могу создать "страницы" с нуля для печати или предварительного просмотра в C#?
Из-за огромной сложности и / или ограниченных возможностей лицензирования компонентов, доступных для этой работы, я решил написать этот компонент с нуля. Это то, что у меня полностью функционально в 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 stackru 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). Макет конечного результата выглядит следующим образом>
Первая страница (главная страница счета)
Вторая страница (ы) [резюме]
В этом отчете может быть больше страниц, в зависимости от того, сколько элементов в платежах и услугах. Заголовок продолжающегося подчиненного отчета переходит на следующую страницу, если имеется много элементов. Например, если у клиента есть 200 услуг, эти позиции будут продолжаться аналогичным образом с использованием одного и того же блока заголовка "Платежи" в начале каждой последующей страницы.
Подробные отчеты
Может быть несколько подробных отчетов, каждый из которых начинается в начале новой страницы, и счетчик страниц сбрасывается и печатается для этих страниц. Таким образом, страница 6 счета может фактически быть страницей 3 второго подробного отчета. Каждый отчет начинается и заканчивается, как показано ниже (на рисунке показана схема полевых данных и т. Д.)
Сообщить о первой странице
Сообщить о последней странице
Что я ищу?
Надежный способ обеспечить работу макета накладной с несколькими отчетами в Visual Studio .NET. Я пытаюсь перенести код из php и vb6, и меня не интересует использование библиотек, которые либо имеют большой размер дистрибутива, либо нелепо сложные / ограниченные лицензионные ограничения. Microsoft предоставляет несколько очень мощных встроенных инструментов, и я не против использования встроенного драйвера печати PDF и спулинга данных, даже если это немного неестественно, но этот способ кажется наименее сложным. этот функционал без ограничений или раздувания сторонних контролей. (в том числе с открытым исходным кодом, поскольку те, на которые я смотрел, обычно делают очень странные преобразования в char, затем, может быть, в латекс или что-то в этом роде, не совсем уверенный в том, о чем идет речь о преобразовании).
НОТА
Очень важно понимать, что комбинация вышеуказанных стилей отчетов составляет ОДИН счет, и, следовательно, только один файл PDF на клиента. Если это поможет, вот метод обратной совместимости VB6, демонстрирующий традиционную совместимость печати объектов "Print" vb6. Это должно помочь прояснить нативный функционал, который я ищу, чтобы создать / использовать.
Мне трудно проглотить вышеупомянутое утверждение "нет прямого эквивалента", поскольку добавление новой страницы при создании документа в памяти кажется довольно простой (и необходимой) функцией создания документа для печати. Не имеет смысла, что все необходимое для печати ДОЛЖНО быть сначала загружено из файла.
2 ответа
Это моя реализация рабочего решения этой проблемы, позволяющая пользователю полностью разработать документ для повторного использования без необходимости отправки .Print
команда.
Концепция использования изображения для хранения данных была частично обусловлена комментарием Bradley Uffner по этому вопросу относительно объединения двух Graphics
объекты
Есть несколько преимуществ и недостатков, связанных с обработкой процесса, описанные ниже.
преимущества
- Материал принтера более модульный, и его можно использовать в других проектах с другими требованиями к печати
- Страницы могут быть удалены или даже вставлены (в зависимости от типа создаваемых отчетов это может сэкономить много времени для запросов к базе данных, когда титульная страница требует сводки, но подробности этой сводки будут напечатаны позже)
- Страницы могут быть сохранены индивидуально в виде файлов изображений
- Страницы могут быть сериализованы
OnPrintPage
не слишком сложный- Динамическое позиционирование страницы на текущей странице или любой другой странице в массиве. Очень легко переключать и размещать данные в другом месте.
Недостатки
- Использует немного больше ресурсов. Потенциальные ограничения памяти, если массив Image становится действительно большим.
Файл класса
Это также демонстрирует, насколько это переносимо, поскольку любой может быстро использовать его. Я все еще работаю над переносом методов 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;
*/
Я создал очень простую демонстрацию системы печати.NET, которая имитирует базовый макет счета, который вы указали. Полный код доступен здесь, но я суммирую важные части ниже. Я собираюсь продолжать работать над этим и улучшать его, потому что это было довольно весело создавать.
На данный момент он производит вывод, который выглядит следующим образом:
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;
}