上一篇中实现了 C1FlexGrid的撤销还原功能,这篇是要仿 Excel 做一个行列删除以及单元格的自由合并拆分,楼主怕在原工程里复杂的说不清道不明,所以干脆提取出来做了一个 Demo 来说明实现过程,请指教了。
一 前提概要
C1FlexGrid 中自带的 AllowMerging 属性可以控制单元格的自动合并,条件是相邻单元格的内容相同,就自动合并。
其中 Row 和 Column 还有 C1FlexGrid 本身均可设置 AllowMerging 属性,如果设置某行的 AllowMerging 属性为 true,即 _flex.Rows[i].AllowMerging = true; 则在 i 行内,如果相邻单元格内容相同时是会自动合并的,同理 _flex.Columns[j].AllowMerging = true; 也会自动处理 j 列自动合并。
C1FlexGrid 的 AllowMerging 属性是个枚举值,可选 All、AllHeaders、Cells、ColumnHeaders、None(默认)、RowHeaders,需要注意的是,行列的 AllowMerging 属性是必须结合C1FlexGrid 的 AllowMerging 属性来使用的,比如你要设置行头和列头区域的自动合并,则需要设置 _flex.AllowMerging = AllHeaders; ,其他区域同理。
下面举一个简单的例子,看一下效果:
flex.AllowMerging = AllowMerging.Cell flex[0, 0] = 1 flex[0, 1] = 1 flex.Rows[0].AllowMerging = true flex[1, 1] = 3 flex[2, 1] = 3 flex.Columns[1].AllowMerging = true flex.AllowMerging = AllowMerging.ColumnHeader flex.ColumnHeaders[0, 1] = "A" flex.ColumnHeaders[0, 2] = "A" flex.ColumnHeaders.Rows[0].AllowMerging = true flex.AllowMerging = AllowMerging.RowHeader flex.RowHeaders[0, 0] = "1" flex.RowHeaders[1, 0] = "1" flex.RowHeaders[2, 0] = "1" flex.RowHeaders.Columns[0].AllowMerging = true
flex.AllowMerging = AllowMerging.All;// 为了看到效果
效果如下图所示:
二 正文
现在要做一套可以灵活设置 C1FlexGrid 的合并和拆分机制,需要用到 C1FlexGrid 的 MergeManager 属性,其专门负责管理合并单元格;MergeManger 是实现了接口 IMergeManager,里面有一个方法是
public CellRange GetMergedRange(C1FlexGrid grid, CellType cellType, CellRange range)
该方法会在每次重绘单元格时自动调用,以获取合并单元格区域,从而进行处理;所以我们自己定义一个 MergeManager 来管理合并单元格。
using System.Collections.Generic using C1.Silverlight.FlexGrid namespace SLFlexGridCellMerge { public class MergeManagerExt : IMergeManager { #region 私有变量 private List<CellRange> _mergedRanges;// 合并区域集合 #endregio #region 公开属性 /// <summary> /// 合并单元格集合 /// </summary> public List<CellRange> MergedRange { get { return _mergedRange } set { _mergedRanges = value } } #endregio #region 构造函数 /// <summary> /// 构造函数 /// </summary> public MergeManagerExt() { _mergedRanges = new List<CellRange>() } #endregio #region 公开方法 /// <summary> /// <para>IMergeManager接口方法</para> /// <para>获取range所在合并区域</para> /// </summary> public CellRange GetMergedRange(C1FlexGrid grid, CellType cellType, CellRange range) { CellRange cellRange = range if (cellType == CellType.Cell) { foreach (CellRange mergedRange in _mergedRanges) { if (mergedRange.Contains(range)) { cellRange = mergedRange break } } } return cellRange.Normalize() } /// <summary> /// 获取某个选定区域所在的合并单元格区域 /// </summary> /// <param name="selection">已选定区域</param> /// <returns>选定区域所在的合并单元格区域</returns> public CellRange GetMergedRange(CellRange selection) { CellRange cellRange = selectio foreach (CellRange range in _mergedRanges) { if (range.Intersects(cellRange)) { cellRange = cellRange.Union(range) } } return cellRange.Normalize() } /// <summary> /// 判断选区内是否有合并单元格 /// </summary> /// <param name="selection">选区</param> /// <returns>选区内是否有合并单元格</returns> public bool HasMergedRange(CellRange selection) { bool flag = false CellRange cellRange = GetMergedRange(selection) foreach (CellRange item in _mergedRanges) { if (cellRange.Contains(item)) { flag = true break } } return flag } /// <summary> /// 增加合并单元格范围 /// </summary> /// <param name="cellRange">新增要合并的单元格范围</param> public void AddMergedRange(CellRange selection) { CellRange cellRange = GetMergedRange(selection) if (!cellRange.IsSingleCell) { bool isIn = false;// 是否已经包含在合并单元格中 for (int i = 0; i < _mergedRanges.Count; i++) { // 新增的合并区域包含了已经合并的单元格 if (cellRange.Contains(_mergedRanges[i])) { _mergedRanges.RemoveAt(i) i-- } else if (_mergedRanges[i].Contains(cellRange)) { isIn = true } } if (!isIn) { _mergedRanges.Add(cellRange.Normalize()) } } } /// <summary> /// 拆分单元格 /// </summary> /// <param name="mergedRange">需要拆分的单元格范围</param> public void RemoveMergedRange(CellRange selection) { CellRange cellRange = GetMergedRange(selection) for (int i = 0; i < _mergedRanges.Count; i++) { if (cellRange.Intersects(_mergedRanges[i])) { _mergedRanges.RemoveAt(i) i-- } } } } }
在自定义的 MergeManagerExt 中,利用一个 List 来管理合并区域,然后在接口 IMergeManager 的方法 GetMergedRange 中,根据重绘时扫描到的 range (参数),从 List 中查找包含该 range 的合并区域并返回。
然后就可以将 C1FlexGrid 的 Selection 通过方法 AddMergeRange 和 RemoveMergeRange 添加或移除到合并区域集合(List),进行管理,C1FlexGrid 则在每次重绘单元格时通过接口方法 GetMergedRange 获取合并区域集合进行合并处理,这样就可以达到灵活设置单元格的合并和拆分了。
三 扩展
合并区域集合是一个 List<CellRange> 类型,其中 CellRange 简单的记录了 LeftColumn, TopRow, RightColumn, BottomRow 四个整型值,以标记出范围的左上角和右下角坐标。这样会导致一个问题,就是如果 C1FlexGrid 的行列数目已经固定下来了,不再增删,自然可用;但是如果 C1FlexGrid 的行列也是动态增删,此时合并集合中的 CellRange 所标记的范围坐标并没有即时更新,导致在行列增删后,合并范围移位或者超出 C1FlexGrid 范围。
解决方法是在进行行列增删时,同步更新合并区域集合中的 CellRange。
在插入列时:
- 如果插入的列在合并范围左侧(包括合并范围左列),则将合并范围整体右移1列;
- 如果插入的列在合并范围之间(不包括合并范围左列,包括合并范围右列),则将合并范围扩张1列,其中左列不动,右列+1;
- 如果插入的列在合并范围右侧以外,则不影响该合并范围;
在插入行时同上逻辑,楼主就不赘述了。
删除时就比较复杂了,楼主逻辑能力欠差,就画了一张图表说明:
白色、绿色和蓝色均是表示选中要删除的行(按列算,每一列算作一种情况),黄色则表格某个合并范围;
其中红色标注的数据表示该情况会把整个合并范围移除;
上面这是删除行时的情况列举,删除列的逻辑同理就不说了。在自定义的 MergeManagerExt 中增加几个更新合并范围的方法:
/// <summary> /// 插入列时,与其相关的合并单元格范围更新 /// </summary> /// <param name="colIndex">插入列的索引位置</param> public void InsertColumnUpdate(int colIndex) { for (int i = 0; i < _mergedRanges.Count; i++) { if (_mergedRanges[i].LeftColumn >= colIndex) { int top = _mergedRanges[i].TopRow int left = _mergedRanges[i].LeftColumn + 1 int bottom = _mergedRanges[i].BottomRow int right = _mergedRanges[i].RightColumn + 1 _mergedRanges[i] = new CellRange(top, left, bottom, right) } else if (_mergedRanges[i].LeftColumn < colIndex && colIndex <= _mergedRanges[i].RightColumn) { int top = _mergedRanges[i].TopRow int left = _mergedRanges[i].LeftColum int bottom = _mergedRanges[i].BottomRow int right = _mergedRanges[i].RightColumn + 1 _mergedRanges[i] = new CellRange(top, left, bottom, right) } } } /// <summary> /// 插入行时,与其相关的合并单元格范围更新 /// </summary> /// <param name="rowIndex">插入行的索引位置</param> public void InsertRowUpdate(int rowIndex) { for (int i = 0; i < _mergedRanges.Count; i++) { if (_mergedRanges[i].TopRow >= rowIndex) { int top = _mergedRanges[i].TopRow + 1 int left = _mergedRanges[i].LeftColum int bottom = _mergedRanges[i].BottomRow + 1 int right = _mergedRanges[i].RightColum _mergedRanges[i] = new CellRange(top, left, bottom, right) } else if (_mergedRanges[i].TopRow < rowIndex && rowIndex <= _mergedRanges[i].BottomRow) { int top = _mergedRanges[i].TopRow int left = _mergedRanges[i].LeftColum int bottom = _mergedRanges[i].BottomRow + 1 int right = _mergedRanges[i].RightColum _mergedRanges[i] = new CellRange(top, left, bottom, right) } } } /// <summary> /// 删除选定区域的行时,更新MergeManager内相对应的合并单元区域 /// </summary> /// <param name="selection">当前选定区域所在的行区域</param> public void DeleteRowsUpdate(CellRange selectedRows) { for (int i = 0; i < _mergedRanges.Count; i++) { if (_mergedRanges[i].BottomRow >= selectedRows.TopRow) { CellRange intersection = _mergedRanges[i].Intersection(selectedRows) int topRow = _mergedRanges[i].TopRow int bottomRow = _mergedRanges[i].BottomRow if (_mergedRanges[i].TopRow <= selectedRows.TopRow) { topRow = _mergedRanges[i].TopRow bottomRow = _mergedRanges[i].BottomRow - intersection.RowSpa } else { if (intersection.IsValid) { topRow = selectedRows.TopRow } else { topRow = _mergedRanges[i].TopRow - selectedRows.RowSpa } bottomRow = _mergedRanges[i].BottomRow - selectedRows.RowSpa } if (topRow > bottomRow || ((topRow == bottomRow) && _mergedRanges[i].ColumnSpan == 1)) { _mergedRanges.RemoveAt(i) i-- continue } _mergedRanges[i] = new CellRange(topRow, _mergedRanges[i].LeftColumn, bottomRow, _mergedRanges[i].RightColumn) } } } /// <summary> /// 删除选定区域的列时,更新MergeManager内相对应的合并单元区域 /// </summary> /// <param name="selection">当前选中的区域</param> public void DeleteColumnsUpdate(CellRange selectedColumns) { for (int i = 0; i < _mergedRanges.Count; i++) { if (_mergedRanges[i].RightColumn >= selectedColumns.LeftColumn) { CellRange intersection = _mergedRanges[i].Intersection(selectedColumns) int leftColumn = _mergedRanges[i].LeftColum int rightColumn = _mergedRanges[i].RightColum if (_mergedRanges[i].LeftColumn <= selectedColumns.LeftColumn) { leftColumn = _mergedRanges[i].LeftColum rightColumn = _mergedRanges[i].RightColumn - intersection.ColumnSpa } else { if (intersection.IsValid) { leftColumn = selectedColumns.LeftColum } else { leftColumn = _mergedRanges[i].LeftColumn - selectedColumns.ColumnSpa } rightColumn = _mergedRanges[i].RightColumn - selectedColumns.ColumnSpa } if (leftColumn > rightColumn || ((leftColumn == rightColumn) && _mergedRanges[i].RowSpan == 1)) { _mergedRanges.RemoveAt(i) i-- continue } _mergedRanges[i] = new CellRange(_mergedRanges[i].TopRow, leftColumn, _mergedRanges[i].BottomRow, rightColumn) } } }
四 展示
楼主自然以此做了个 Demo,看看效果吧