ReportEngine.DataBands.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. using FastReport.Data;
  2. using FastReport.Utils;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. namespace FastReport.Engine
  7. {
  8. public partial class ReportEngine
  9. {
  10. #region Private Classes
  11. private class HierarchyItem
  12. {
  13. #region Fields
  14. public int rowNo;
  15. public List<HierarchyItem> items;
  16. public HierarchyItem parent;
  17. #endregion Fields
  18. #region Constructors
  19. public HierarchyItem()
  20. {
  21. items = new List<HierarchyItem>();
  22. }
  23. #endregion Constructors
  24. #region Public Methods
  25. public void Add(HierarchyItem item)
  26. {
  27. items.Add(item);
  28. item.parent = this;
  29. }
  30. #endregion Public Methods
  31. }
  32. #endregion Private Classes
  33. #region Private Methods
  34. private void RunDataBand(DataBand dataBand)
  35. {
  36. if (page.Columns.Count > 1 && Report.Engine.UnlimitedHeight)
  37. dataBand.Columns.Count = page.Columns.Count;
  38. dataBand.InitDataSource();
  39. dataBand.DataSource.First();
  40. int rowCount = dataBand.DataSource.RowCount;
  41. if (dataBand.IsDatasourceEmpty && dataBand.PrintIfDatasourceEmpty)
  42. rowCount = 1;
  43. if (dataBand.CollectChildRows && rowCount > 1)
  44. rowCount = 1;
  45. if (dataBand.MaxRows > 0 && rowCount > dataBand.MaxRows)
  46. rowCount = dataBand.MaxRows;
  47. bool keepFirstRow = NeedKeepFirstRow(dataBand);
  48. bool keepLastRow = NeedKeepLastRow(dataBand);
  49. RunDataBand(dataBand, rowCount, keepFirstRow, keepLastRow);
  50. // do not leave the datasource in EOF state to allow print something in the footer
  51. dataBand.DataSource.Prior();
  52. }
  53. private void RunDataBand(DataBand dataBand, int rowCount, bool keepFirstRow, bool keepLastRow)
  54. {
  55. if (dataBand.IsHierarchical)
  56. {
  57. ShowHierarchy(dataBand, rowCount);
  58. return;
  59. }
  60. bool isFirstRow = true;
  61. bool someRowsPrinted = false;
  62. dataBand.RowNo = 0;
  63. dataBand.IsFirstRow = false;
  64. dataBand.IsLastRow = false;
  65. // check if we have only one data row that should be kept with both header and footer
  66. bool oneRow = rowCount == 1 && keepFirstRow && keepLastRow;
  67. // cycle through records
  68. for (int i = 0; i < rowCount; i++)
  69. {
  70. bool isLastRow = i == rowCount - 1;
  71. if (!dataBand.IsDetailEmpty())
  72. {
  73. dataBand.RowNo++;
  74. dataBand.AbsRowNo++;
  75. dataBand.IsFirstRow = isFirstRow;
  76. dataBand.IsLastRow = isLastRow;
  77. // keep header
  78. if (isFirstRow && keepFirstRow)
  79. StartKeep(dataBand);
  80. // keep together
  81. if (isFirstRow && dataBand.KeepTogether)
  82. StartKeep(dataBand);
  83. // keep detail
  84. if (dataBand.KeepDetail)
  85. StartKeep(dataBand);
  86. // show header
  87. if (isFirstRow)
  88. ShowDataHeader(dataBand);
  89. // keep footer
  90. if (isLastRow && keepLastRow && dataBand.IsDeepmostDataBand)
  91. StartKeep(dataBand);
  92. // start block event
  93. if (isFirstRow)
  94. OnStateChanged(dataBand, EngineState.BlockStarted);
  95. // show band
  96. ShowDataBand(dataBand, rowCount);
  97. // end keep header
  98. if (isFirstRow && keepFirstRow && !oneRow)
  99. EndKeep();
  100. // end keep footer
  101. if (isLastRow && keepLastRow && dataBand.IsDeepmostDataBand)
  102. CheckKeepFooter(dataBand);
  103. // show sub-bands
  104. RunBands(dataBand.Bands);
  105. // up the outline
  106. OutlineUp(dataBand);
  107. // end keep detail
  108. if (dataBand.KeepDetail)
  109. EndKeep();
  110. isFirstRow = false;
  111. someRowsPrinted = true;
  112. if (dataBand.Columns.Count > 1)
  113. break;
  114. }
  115. dataBand.DataSource.Next();
  116. if (Report.Aborted)
  117. break;
  118. }
  119. // complete upto N rows
  120. ChildBand child = dataBand.Child;
  121. if (child != null && child.CompleteToNRows > rowCount)
  122. {
  123. for (int i = 0; i < child.CompleteToNRows - rowCount; i++)
  124. {
  125. child.RowNo = rowCount + i + 1;
  126. child.AbsRowNo = rowCount + i + 1;
  127. ShowBand(child);
  128. }
  129. }
  130. // print child if databand is empty
  131. if (child != null && child.PrintIfDatabandEmpty && dataBand.IsDatasourceEmpty)
  132. {
  133. ShowBand(child);
  134. }
  135. if (someRowsPrinted)
  136. {
  137. // finish block event
  138. OnStateChanged(dataBand, EngineState.BlockFinished);
  139. // show footer
  140. ShowDataFooter(dataBand);
  141. // end KeepTogether
  142. if (dataBand.KeepTogether)
  143. EndKeep();
  144. // end KeepLastRow
  145. if (keepLastRow)
  146. EndKeep();
  147. }
  148. }
  149. private void ShowDataBand(DataBand dataBand, int rowCount)
  150. {
  151. if (dataBand.Columns.Count > 1)
  152. {
  153. dataBand.Width = dataBand.Columns.ActualWidth;
  154. RenderMultiColumnBand(dataBand, rowCount);
  155. }
  156. else
  157. {
  158. if (dataBand.ResetPageNumber && (dataBand.FirstRowStartsNewPage || dataBand.RowNo > 1))
  159. ResetLogicalPageNumber();
  160. if (dataBand.Footer != null && dataBand.CanBreak)
  161. if (dataBand.Footer.KeepWithData && dataBand.Footer.Height + dataBand.Height > FreeSpace)
  162. {
  163. dataBand.AddLastToFooter(dataBand.Footer);
  164. }
  165. ShowBand(dataBand);
  166. }
  167. }
  168. private void RenderMultiColumnBand(DataBand dataBand, int rowCount)
  169. {
  170. if (dataBand.Columns.Layout == ColumnLayout.AcrossThenDown)
  171. RenderBandAcrossThenDown(dataBand, rowCount);
  172. else
  173. {
  174. DataSourceBase dataSource = dataBand.DataSource;
  175. int saveRow = dataSource.CurrentRowNo;
  176. // calc height of each data row. This list is shared across RenderBandDownThenAcross calls.
  177. Hashtable heights = new Hashtable();
  178. for (int i = 0; i < rowCount; i++)
  179. {
  180. dataSource.CurrentRowNo = i + saveRow;
  181. heights[i + saveRow] = CalcHeight(dataBand);
  182. }
  183. dataSource.CurrentRowNo = saveRow;
  184. while (rowCount > 0)
  185. {
  186. rowCount = RenderBandDownThenAcross(dataBand, rowCount, heights);
  187. }
  188. }
  189. }
  190. private void RenderBandAcrossThenDown(DataBand dataBand, int rowCount)
  191. {
  192. DataSourceBase dataSource = dataBand.DataSource;
  193. int saveRow = dataSource.CurrentRowNo;
  194. bool keepLastRow = NeedKeepLastRow(dataBand);
  195. // create output band
  196. using (DataBand outputBand = new DataBand())
  197. {
  198. outputBand.SetReport(Report);
  199. int columnNo = 0;
  200. for (int i = 0; i < rowCount; i++)
  201. {
  202. bool isLastRow = i == rowCount - 1;
  203. dataSource.CurrentRowNo = i + saveRow;
  204. if (columnNo == 0)
  205. {
  206. outputBand.Clear();
  207. outputBand.Assign(dataBand);
  208. outputBand.OutlineExpression = "";
  209. outputBand.Border = new Border();
  210. outputBand.Fill = new SolidFill();
  211. }
  212. // write to the output band
  213. if (Config.RightToLeft)
  214. {
  215. ShowBand(dataBand, outputBand, dataBand.Columns.Positions[dataBand.Columns.Count - columnNo - 1], 0);
  216. }
  217. else
  218. {
  219. ShowBand(dataBand, outputBand, dataBand.Columns.Positions[columnNo], 0);
  220. }
  221. // add outline
  222. AddBandOutline(dataBand);
  223. // outline up
  224. OutlineUp(dataBand);
  225. dataBand.RowNo++;
  226. dataBand.AbsRowNo++;
  227. columnNo++;
  228. if (columnNo == dataBand.Columns.Count || isLastRow)
  229. {
  230. columnNo = 0;
  231. // keep footer
  232. if (isLastRow && keepLastRow && dataBand.IsDeepmostDataBand)
  233. StartKeep(outputBand);
  234. // show output band itself
  235. ShowBand(outputBand, false);
  236. // end keep footer
  237. if (isLastRow && keepLastRow && dataBand.IsDeepmostDataBand)
  238. CheckKeepFooter(dataBand);
  239. }
  240. }
  241. }
  242. dataSource.CurrentRowNo = saveRow + rowCount;
  243. }
  244. private int RenderBandDownThenAcross(DataBand dataBand, int rowCount, Hashtable heights)
  245. {
  246. DataSourceBase dataSource = dataBand.DataSource;
  247. int saveRow = dataSource.CurrentRowNo;
  248. // determine number of rows in a column. Do not take the height into account - it's too complex.
  249. int rowsPerColumn = (int)Math.Ceiling((float)rowCount / dataBand.Columns.Count);
  250. if (rowsPerColumn < dataBand.Columns.MinRowCount)
  251. rowsPerColumn = dataBand.Columns.MinRowCount;
  252. // calculate max height of all columns to check the free space
  253. float maxHeight = 0;
  254. for (int i = 0; i < dataBand.Columns.Count; i++)
  255. {
  256. // calculate column height
  257. float columnHeight = 0;
  258. for (int j = 0; j < rowsPerColumn; j++)
  259. {
  260. int rowIndex = j + i * rowsPerColumn + saveRow;
  261. if (heights[rowIndex] == null)
  262. break;
  263. columnHeight += (float)heights[rowIndex];
  264. }
  265. if (columnHeight > maxHeight)
  266. maxHeight = columnHeight;
  267. }
  268. float saveCurX = CurX;
  269. float startColumnY = CurY;
  270. int columnNo = 0;
  271. if (maxHeight > FreeSpace)
  272. {
  273. // not enough free space. Render rows down then across. After finishing the page,
  274. // run this method again to render remaining rows.
  275. for (int i = 0; i < rowCount; i++)
  276. {
  277. dataSource.CurrentRowNo = i + saveRow;
  278. if (Config.RightToLeft)
  279. {
  280. CurX = dataBand.Columns.Positions[dataBand.Columns.Count - columnNo - 1] + saveCurX;
  281. }
  282. else
  283. {
  284. CurX = dataBand.Columns.Positions[columnNo] + saveCurX;
  285. }
  286. // check free space.
  287. if ((float)heights[i + saveRow] > FreeSpace && i != 0)
  288. {
  289. columnNo++;
  290. if (columnNo == dataBand.Columns.Count)
  291. {
  292. // start the new page
  293. columnNo = 0;
  294. CurX = saveCurX;
  295. EndPage();
  296. // decrease number of available rows and call this method again
  297. rowCount -= i;
  298. return rowCount;
  299. }
  300. else
  301. {
  302. // start the new column
  303. CurY = startColumnY;
  304. }
  305. // check free space again before show a band - we may be at a page end
  306. i--;
  307. continue;
  308. }
  309. if (Config.RightToLeft)
  310. {
  311. CurX = dataBand.Columns.Positions[dataBand.Columns.Count - columnNo - 1] + saveCurX;
  312. }
  313. else
  314. {
  315. CurX = dataBand.Columns.Positions[columnNo] + saveCurX;
  316. }
  317. // show a band
  318. ShowBand(dataBand);
  319. dataBand.RowNo++;
  320. dataBand.AbsRowNo++;
  321. }
  322. // we shouldn't go here...
  323. }
  324. else
  325. {
  326. // we have enough space to render all rows.
  327. float maxY = CurY;
  328. int rowNo = 0;
  329. bool carryoutLastRow = false;
  330. if (dataBand.IsDeepmostDataBand && NeedKeepLastRow(dataBand))
  331. {
  332. float footersHeight = GetFootersHeight(dataBand);
  333. if (footersHeight > FreeSpace - maxHeight)
  334. carryoutLastRow = true;
  335. }
  336. for (int i = 0; i < rowCount; i++)
  337. {
  338. dataSource.CurrentRowNo = i + saveRow;
  339. // carry out the last row
  340. bool isLastRow = i == rowCount - 1;
  341. if (isLastRow && carryoutLastRow)
  342. {
  343. CurX = saveCurX;
  344. CurY = maxY;
  345. EndColumn();
  346. ShowBand(dataBand);
  347. OutlineUp(dataBand);
  348. dataSource.CurrentRowNo = saveRow + rowCount;
  349. return 0;
  350. }
  351. if (Config.RightToLeft)
  352. {
  353. CurX = dataBand.Columns.Positions[dataBand.Columns.Count - columnNo - 1] + saveCurX;
  354. }
  355. else
  356. {
  357. CurX = dataBand.Columns.Positions[columnNo] + saveCurX;
  358. }
  359. // show a band
  360. ShowBand(dataBand);
  361. // outline up
  362. OutlineUp(dataBand);
  363. if (CurY > maxY)
  364. maxY = CurY;
  365. dataBand.RowNo++;
  366. dataBand.AbsRowNo++;
  367. rowNo++;
  368. // check if we need to start a new column
  369. if (rowNo >= rowsPerColumn)
  370. {
  371. columnNo++;
  372. rowNo = 0;
  373. CurY = startColumnY;
  374. }
  375. }
  376. CurX = saveCurX;
  377. CurY = maxY;
  378. }
  379. dataSource.CurrentRowNo = saveRow + rowCount;
  380. return 0;
  381. }
  382. private float GetColumnHeight(Hashtable heights, int startIndex, int rowCount, int rowNo)
  383. {
  384. float result = 0;
  385. for (int i = 0; i < rowCount; i++)
  386. {
  387. if (i + startIndex >= heights.Count)
  388. break;
  389. result += (float)heights[i + startIndex + rowNo];
  390. }
  391. return result;
  392. }
  393. private HierarchyItem MakeHierarchy(DataBand dataBand, int rowCount)
  394. {
  395. Dictionary<string, HierarchyItem> items = new Dictionary<string, HierarchyItem>();
  396. Column idColumn = DataHelper.GetColumn(Report.Dictionary, dataBand.IdColumn);
  397. if (idColumn == null)
  398. return null;
  399. Column parentIdColumn = DataHelper.GetColumn(Report.Dictionary, dataBand.ParentIdColumn);
  400. if (parentIdColumn == null)
  401. return null;
  402. for (int i = 0; i < rowCount; i++)
  403. {
  404. object idColumnValue = dataBand.DataSource[idColumn];
  405. object parentIdColumnValue = dataBand.DataSource[parentIdColumn];
  406. string id = (idColumnValue == null || idColumnValue is DBNull) ? "" : idColumnValue.ToString();
  407. string parentId = (parentIdColumnValue == null || parentIdColumnValue is DBNull) ? "" :
  408. parentIdColumnValue.ToString();
  409. // avoid wrong behavior if wrong data specified
  410. if (id == parentId)
  411. parentId = "";
  412. HierarchyItem item = null;
  413. if (items.ContainsKey(id))
  414. item = items[id];
  415. else
  416. {
  417. item = new HierarchyItem();
  418. items.Add(id, item);
  419. }
  420. item.rowNo = dataBand.DataSource.CurrentRowNo;
  421. HierarchyItem parentItem = null;
  422. if (items.ContainsKey(parentId))
  423. parentItem = items[parentId];
  424. else
  425. {
  426. parentItem = new HierarchyItem();
  427. items.Add(parentId, parentItem);
  428. }
  429. parentItem.Add(item);
  430. dataBand.DataSource.Next();
  431. }
  432. // create the root item
  433. HierarchyItem rootItem = new HierarchyItem();
  434. foreach (HierarchyItem item in items.Values)
  435. {
  436. if (item.parent == null)
  437. {
  438. foreach (HierarchyItem childItem in item.items)
  439. {
  440. rootItem.Add(childItem);
  441. }
  442. }
  443. }
  444. return rootItem;
  445. }
  446. private void ShowHierarchy(DataBand dataBand, HierarchyItem rootItem, int level, string fullRowNo)
  447. {
  448. int saveLevel = hierarchyLevel;
  449. float saveIndent = hierarchyIndent;
  450. string saveRowNo = hierarchyRowNo;
  451. hierarchyLevel = level;
  452. hierarchyIndent = dataBand.Indent * (level - 1);
  453. try
  454. {
  455. if (rootItem.items.Count > 0)
  456. {
  457. ShowBand(dataBand.Header);
  458. int rowNo = 0;
  459. foreach (HierarchyItem item in rootItem.items)
  460. {
  461. rowNo++;
  462. dataBand.RowNo = rowNo;
  463. dataBand.AbsRowNo++;
  464. dataBand.DataSource.CurrentRowNo = item.rowNo;
  465. hierarchyRowNo = fullRowNo + rowNo.ToString();
  466. // show the main hierarchy band
  467. ShowBand(dataBand);
  468. // show sub-bands if any
  469. RunBands(dataBand.Bands);
  470. ShowHierarchy(dataBand, item, level + 1, hierarchyRowNo + ".");
  471. // up the outline
  472. OutlineUp(dataBand);
  473. }
  474. ShowBand(dataBand.Footer);
  475. }
  476. }
  477. finally
  478. {
  479. hierarchyLevel = saveLevel;
  480. hierarchyIndent = saveIndent;
  481. hierarchyRowNo = saveRowNo;
  482. }
  483. }
  484. private void ShowHierarchy(DataBand dataBand, int rowCount)
  485. {
  486. HierarchyItem rootItem = MakeHierarchy(dataBand, rowCount);
  487. if (rootItem == null)
  488. return;
  489. ShowHierarchy(dataBand, rootItem, 1, "");
  490. }
  491. private void ShowDataHeader(DataBand dataBand)
  492. {
  493. DataHeaderBand header = dataBand.Header;
  494. if (header != null)
  495. {
  496. ShowBand(header);
  497. if (header.RepeatOnEveryPage)
  498. AddReprint(header);
  499. }
  500. DataFooterBand footer = dataBand.Footer;
  501. if (footer != null)
  502. {
  503. if (footer.RepeatOnEveryPage)
  504. AddReprint(footer);
  505. }
  506. }
  507. private void ShowDataFooter(DataBand dataBand)
  508. {
  509. dataBand.DataSource.Prior();
  510. DataFooterBand footer = dataBand.Footer;
  511. RemoveReprint(footer);
  512. ShowBand(footer);
  513. RemoveReprint(dataBand.Header);
  514. dataBand.DataSource.Next();
  515. }
  516. #endregion Private Methods
  517. }
  518. }