上一篇中實現了 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,看看效果吧