CodePatternDynamicComponentExample.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. using System.Globalization;
  2. using QuestPDF.Elements;
  3. using QuestPDF.Fluent;
  4. using QuestPDF.Helpers;
  5. using QuestPDF.Infrastructure;
  6. namespace QuestPDF.DocumentationExamples.CodePatterns;
  7. public class CodePatternDynamicComponentExample
  8. {
  9. [Test]
  10. public static void Dynamic()
  11. {
  12. var items = Enumerable.Range(0, 25).Select(x => new OrderItem()).ToList();
  13. Document
  14. .Create(document =>
  15. {
  16. document.Page(page =>
  17. {
  18. page.Size(PageSizes.A4);
  19. page.DefaultTextStyle(x => x.FontSize(20));
  20. page.Margin(50);
  21. page.Content()
  22. .Decoration(decoration =>
  23. {
  24. decoration
  25. .Before()
  26. .PaddingBottom(10)
  27. .Text(text =>
  28. {
  29. text.DefaultTextStyle(TextStyle.Default.Bold().FontColor(Colors.Blue.Darken2));
  30. text.Span("Page ");
  31. text.CurrentPageNumber();
  32. text.Span(" of ");
  33. text.TotalPages();
  34. });
  35. decoration
  36. .Content()
  37. .Dynamic(new OrdersTableWithPageSubtotalsComponent(items));
  38. });
  39. });
  40. })
  41. .GeneratePdf("code-pattern-dynamic-component-table-with-per-page-subtotals.pdf");
  42. }
  43. public class OrderItem
  44. {
  45. public string ItemName { get; set; } = Placeholders.Label();
  46. public int Price { get; set; } = Placeholders.Random.Next(1, 11) * 10;
  47. public int Count { get; set; } = Placeholders.Random.Next(1, 11);
  48. }
  49. public struct OrdersTableWithPageSubtotalsComponentState
  50. {
  51. public int ShownItemsCount { get; set; }
  52. }
  53. public class OrdersTableWithPageSubtotalsComponent : IDynamicComponent<OrdersTableWithPageSubtotalsComponentState>
  54. {
  55. private ICollection<OrderItem> Items { get; }
  56. public OrdersTableWithPageSubtotalsComponentState State { get; set; }
  57. public OrdersTableWithPageSubtotalsComponent(ICollection<OrderItem> items)
  58. {
  59. Items = items;
  60. State = new OrdersTableWithPageSubtotalsComponentState
  61. {
  62. ShownItemsCount = 0
  63. };
  64. }
  65. public DynamicComponentComposeResult Compose(DynamicContext context)
  66. {
  67. var header = ComposeHeader(context);
  68. var sampleFooter = ComposeFooter(context, []);
  69. var decorationHeight = header.Size.Height + sampleFooter.Size.Height;
  70. var rows = GetItemsForPage(context, decorationHeight).ToList();
  71. var footer = ComposeFooter(context, rows.Select(x => x.Item));
  72. var content = context.CreateElement(container =>
  73. {
  74. container.Shrink().Decoration(decoration =>
  75. {
  76. decoration.Before().Element(header);
  77. decoration.Content().Column(column =>
  78. {
  79. foreach (var row in rows)
  80. column.Item().Element(row.Element);
  81. });
  82. decoration.After().Element(footer);
  83. });
  84. });
  85. State = new OrdersTableWithPageSubtotalsComponentState
  86. {
  87. ShownItemsCount = State.ShownItemsCount + rows.Count
  88. };
  89. return new DynamicComponentComposeResult
  90. {
  91. Content = content,
  92. HasMoreContent = State.ShownItemsCount < Items.Count
  93. };
  94. }
  95. private static IDynamicElement ComposeHeader(DynamicContext context)
  96. {
  97. return context.CreateElement(element =>
  98. {
  99. element
  100. .Width(context.AvailableSize.Width)
  101. .BorderBottom(1)
  102. .BorderColor(Colors.Grey.Darken2)
  103. .Padding(10)
  104. .DefaultTextStyle(TextStyle.Default.SemiBold())
  105. .Row(row =>
  106. {
  107. row.ConstantItem(50).Text("#");
  108. row.RelativeItem().Text("Item name");
  109. row.ConstantItem(75).AlignRight().Text("Count");
  110. row.ConstantItem(75).AlignRight().Text("Price");
  111. row.ConstantItem(75).AlignRight().Text("Total");
  112. });
  113. });
  114. }
  115. private static IDynamicElement ComposeFooter(DynamicContext context, IEnumerable<OrderItem> items)
  116. {
  117. var total = items.Sum(x => x.Count * x.Price);
  118. return context.CreateElement(element =>
  119. {
  120. element
  121. .Width(context.AvailableSize.Width)
  122. .Padding(10)
  123. .AlignRight()
  124. .Text($"Subtotal: {total}$")
  125. .Bold();
  126. });
  127. }
  128. private IEnumerable<(OrderItem Item, IDynamicElement Element)> GetItemsForPage(DynamicContext context, float decorationHeight)
  129. {
  130. var totalHeight = decorationHeight;
  131. foreach (var index in Enumerable.Range(State.ShownItemsCount, Items.Count - State.ShownItemsCount))
  132. {
  133. var item = Items.ElementAt(index);
  134. var element = context.CreateElement(content =>
  135. {
  136. content
  137. .Width(context.AvailableSize.Width)
  138. .BorderBottom(1)
  139. .BorderColor(Colors.Grey.Lighten2)
  140. .Padding(10)
  141. .Row(row =>
  142. {
  143. row.ConstantItem(50).Text((index + 1).ToString(CultureInfo.InvariantCulture));
  144. row.RelativeItem().Text(item.ItemName);
  145. row.ConstantItem(75).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
  146. row.ConstantItem(75).AlignRight().Text($"{item.Price}$");
  147. row.ConstantItem(75).AlignRight().Text($"{item.Count*item.Price}$");
  148. });
  149. });
  150. var elementHeight = element.Size.Height;
  151. // it is important to use the Size.Epsilon constant to avoid floating point comparison issues
  152. if (totalHeight + elementHeight > context.AvailableSize.Height + Size.Epsilon)
  153. break;
  154. totalHeight += elementHeight;
  155. yield return (item, element);
  156. }
  157. }
  158. }
  159. }