在进行pdf报表的绘制过程中,报表基本都是表格形式的,会有合并行、列的情况,使用的是itextsharp组件;
之前表格的单元格合并和业务是耦合在一起的,都需要开发人员每次绘制表格时,自己用各种办法合并单元格,还需要考虑分页的情况,需要开发人员有较强的逻辑分析能力、足够细致才不会有很多bug,且开发周期长。
在熟悉相关业务之后,我觉得可以把计算单元格的合并和业务分页,开发人员只需关心数据如何组装,单元格的合并及分页由系统自动完成,可以提升开发速度及质量,具体思路如下:
1、组装所有单元格数据(X坐标、Y坐标、单元格内容、是否允许合并、合并单元格时的判断依据的值、合并的行数、合并的列数)。
2、逐行遍历,计算每一行中需要合并的单元格,返回合并后的单元格列表(包含合并了的,以及未合并但需要合并的单元格)。
3、对上一个方法返回的结果逐列遍历,计算每一列中需要合并的单元格,最终返回所有需要合并的单元格数据列表。
4、用原始的单元格数据,一行一行地把单元格数据画到pdf上,画每一个单元格的时候,判断一下该单元格是否被合并了,如果被合并了就先不画;
等到一页pdf画满之后,用这一页的所有单元格数据再组成一个集合,重新计算一下当前页需要合并的单元格数据,然后画到pdf上即可。
5、然后再拿到剩余的没绘制的单元格数据,重复2、3、4,直至把所有单元格都绘制到pdf上。
这里只展示一下计算单元格的合并的代码:
1 /// <summary> 2 /// 根据源数据生成所有合并的单元格信息 3 /// </summary> 4 /// <param name="originalData"></param> 5 /// <returns></returns> 6 public static List<CellInfo> GenerateMergeCellList(List<CellInfo> originalData) 7 { 8 if (originalData == null || !originalData.Any()) 9 { 10 return new List<CellInfo>(); 11 } 12 int maxRowCount = originalData.Max(c => c.XNum);//最大行号下标 13 Dictionary<int, List<CellInfo>> rowMergeResult = new Dictionary<int, List<CellInfo>>(); 14 for (int i = 0; i <= maxRowCount; i++) 15 { 16 List<CellInfo> currentRowData = originalData.Where(c => c.XNum == i).ToList();//当前这一行数据 17 List<CellInfo> rowResult = GenerateRowMergeInfo(currentRowData).ToList();//生成这一行的单元格信息,不包含不允许合并的单元格 18 rowMergeResult.Add(i, rowResult); 19 } 20 List<CellInfo> mergeResult = GenerateMergeInfo(rowMergeResult); 21 return mergeResult; 22 } 23 24 /// <summary> 25 /// 把某一行单元格集合传进去 26 /// 对单元格进行合并计算,返回需要进行列合并计算的单元格集合 27 /// </summary> 28 /// <param name="originalCellList">当前行的数据集合</param> 29 /// <returns>包含已经合并了的单元格信息以及需要进行列合并计算的单元格信息</returns> 30 private static List<CellInfo> GenerateRowMergeInfo(List<CellInfo> originalCellList) 31 { 32 if (originalCellList == null || !originalCellList.Any()) 33 { 34 return new List<CellInfo>(); 35 } 36 int rowIndex = originalCellList.Any() ? originalCellList.First().XNum : 0; 37 List<string> contentList = originalCellList.OrderBy(c => c.YNum).Select(c => c.Content).ToList();//把这一行数据组成一个string串 38 List<CellInfo> result = new List<CellInfo>(); 39 int colspan;//要合并的列数 40 41 for (int i = 0; i < contentList.Count; i++) 42 { 43 colspan = 1; 44 if (!originalCellList.Single(c => c.YNum == i).AllowMerge)//不允许合并的,直接continue 45 { 46 continue; 47 } 48 49 //非最后一个单元格,且当前单元格与下一个单元格内容一致,需要计算一下要合并的单元格 50 //如果是最后一个单元格,就不需要判断是否与下一个单元格内容一致了,直接添加到结果数据中 51 if (i < (contentList.Count - 1) && contentList[i] == contentList[i + 1])//表示当前值和下一个值是相同的 52 { 53 while (i < contentList.Count - 1) 54 { 55 if (contentList[i] == contentList[i + 1]) 56 { 57 ++i; 58 ++colspan; 59 } 60 else 61 { 62 break; 63 } 64 } 65 } 66 CellInfo originalCellFirst = originalCellList.Single(c => c.YNum == (i - colspan + 1)); 67 result.Add(new CellInfo() 68 { 69 XNum = rowIndex, 70 YNum = originalCellFirst.YNum, 71 Content = originalCellFirst.Content, 72 DrawContent = originalCellFirst.DrawContent, 73 Colspan = colspan 74 }); 75 } 76 return result; 77 } 78 79 /// <summary> 80 /// 生成所有合并的单元格信息 81 /// </summary> 82 /// <param name="rowMergeResult">一行一行的单元格数据集合</param> 83 /// <returns></returns> 84 private static List<CellInfo> GenerateMergeInfo(Dictionary<int, List<CellInfo>> rowMergeResult) 85 { 86 List<CellInfo> result = new List<CellInfo>(); 87 int rowspan; 88 int index; 89 CellInfo currentRowCell; 90 CellInfo startRowCell; 91 CellInfo nextRowCell; 92 for (int i = 0; i < rowMergeResult.Count; i++)//循环行 93 { 94 for (int j = 0; j < rowMergeResult[i].Count; j++)//循环列 95 { 96 rowspan = 1; 97 currentRowCell = rowMergeResult[i][j];//当前单元格 98 startRowCell = rowMergeResult[i][j];//初始单元格 99 if (IsContainedMergeCell(result, currentRowCell) || !currentRowCell.AllowMerge)//单元格已经被合并时,不处理 100 { 101 continue; 102 } 103 if (i == rowMergeResult.Count - 1)//最后一行时,直接加到结果集中了(在最后一行会排除掉不合并的单元格) 104 { 105 result.Add(startRowCell); 106 continue; 107 } 108 109 nextRowCell = rowMergeResult[i + 1].FirstOrDefault(r => r.YNum == currentRowCell.YNum && r.Content == currentRowCell.Content && r.Colspan == currentRowCell.Colspan && r.AllowMerge); 110 if (nextRowCell == null)//如果该单元格在竖直方向上不需要合并,即rowspan保持不变 111 { 112 result.Add(startRowCell); 113 continue; 114 } 115 index = 0;//遍历的行数 116 while ((i + index) < rowMergeResult.Count - 1)//循环到倒数第二行 117 { 118 index++; 119 nextRowCell = rowMergeResult[i + index].FirstOrDefault(r => r.YNum == currentRowCell.YNum && r.Content == currentRowCell.Content && r.Colspan == currentRowCell.Colspan && r.AllowMerge); 120 if (nextRowCell == null) 121 { 122 break; 123 } 124 rowspan++; 125 currentRowCell = nextRowCell; 126 } 127 startRowCell.Rowspan = rowspan; 128 result.Add(startRowCell); 129 } 130 } 131 return result.Where(r => !(r.Colspan == 1 && r.Rowspan == 1)).ToList();//去除掉不需要合并的单元格 132 } 133 134 /// <summary> 135 /// 查询单元格是否被合并了 136 /// </summary> 137 /// <param name="mergedCellList">合并的单元格集合</param> 138 /// <param name="source">要查询的单元格信息</param> 139 /// <returns></returns> 140 public static bool IsContainedMergeCell(List<CellInfo> mergedCellList, CellInfo source) 141 { 142 CellInfo cell = mergedCellList.FirstOrDefault(r => source.XNum >= r.XNum && source.YNum >= r.YNum && source.XNum < (r.XNum + r.Rowspan) && source.YNum < (r.YNum + r.Colspan)); 143 return cell != null; 144 }
实体类如下:
1 //实体类如下: 2 public class CellInfo 3 { 4 /// <summary> 5 /// 是否启用DrawContent字段 6 /// </summary> 7 private bool _UsingDrawContent = false; 8 9 /// <summary> 10 /// 单元格绘制的内容 11 /// </summary> 12 private string _DrawContent; 13 14 /// <summary> 15 /// X下标 16 /// </summary> 17 public int XNum { get; set; } 18 19 /// <summary> 20 /// Y下标 21 /// </summary> 22 public int YNum { get; set; } 23 24 /// <summary> 25 /// 单元格内容(AllowMerge=true时,判断是否可以合并的依据) 26 /// </summary> 27 public string Content { get; set; } 28 29 /// <summary> 30 /// 单元格绘制内容 31 /// </summary> 32 public string DrawContent 33 { 34 get 35 { 36 if (_UsingDrawContent) 37 { 38 return _DrawContent; 39 } 40 return Content; 41 } 42 set 43 { 44 _DrawContent = value ?? string.Empty; 45 if (!_DrawContent.Equals(Content)) 46 { 47 _UsingDrawContent = true; 48 } 49 } 50 } 51 52 private bool _allowMerge = true; 53 /// <summary> 54 /// 是否允许合并 55 /// </summary> 56 public bool AllowMerge 57 { 58 set 59 { 60 _allowMerge = value; 61 } 62 get 63 { 64 return _allowMerge; 65 } 66 } 67 68 private int _rowspan = 1; 69 /// <summary> 70 /// 合并的行数 71 /// </summary> 72 public int Rowspan 73 { 74 set 75 { 76 _rowspan = value; 77 } 78 get 79 { 80 return _rowspan; 81 } 82 } 83 84 private int _cowspan = 1; 85 /// <summary> 86 /// 合并的列数 87 /// </summary> 88 public int Colspan 89 { 90 set 91 { 92 _cowspan = value; 93 } 94 get 95 { 96 return _cowspan; 97 } 98 } 99 }
测试代码:
1 static void Main(string[] args) 2 { 3 List<CellInfo> originalData = new List<CellInfo>(); 4 5 #region 原始数据 6 AddCellInfo(originalData, 0, 0, "123", true); 7 AddCellInfo(originalData, 0, 1, "123", true); 8 AddCellInfo(originalData, 0, 2, "456", true); 9 AddCellInfo(originalData, 0, 3, "999", true); 10 AddCellInfo(originalData, 0, 4, "999", true); 11 AddCellInfo(originalData, 0, 5, "789", true); 12 AddCellInfo(originalData, 1, 0, "123", true); 13 AddCellInfo(originalData, 1, 1, "123", true); 14 AddCellInfo(originalData, 1, 2, "789", true); 15 AddCellInfo(originalData, 1, 3, "999", true); 16 AddCellInfo(originalData, 1, 4, "999", true); 17 AddCellInfo(originalData, 1, 5, "999", true); 18 AddCellInfo(originalData, 2, 0, "123", true); 19 AddCellInfo(originalData, 2, 1, "123", true); 20 AddCellInfo(originalData, 2, 2, "789", true); 21 AddCellInfo(originalData, 2, 3, "999", true); 22 AddCellInfo(originalData, 2, 4, "999", true); 23 AddCellInfo(originalData, 2, 5, "789", true); 24 #endregion 25 26 List<CellInfo> mergeCellList = GenerateMergeCellList(originalData); 27 foreach (CellInfo item in mergeCellList) 28 { 29 Console.WriteLine($"({item.XNum},{item.YNum}),clospan:{item.Colspan},rowspan:{item.Rowspan},content:{item.Content}"); 30 } 31 Console.ReadKey(); 32 } 33 34 private static void AddCellInfo(List<CellInfo> resultList, int xNum, int yNum, string content, bool allowMerge) 35 { 36 resultList.Add(new CellInfo() 37 { 38 XNum = xNum, 39 YNum = yNum, 40 Content = content, 41 AllowMerge = allowMerge 42 }); 43 }