读写数据都完成了,现在来看看如何生成客户端界面。使用Atals的TabContainer是个不错的选择。比较的是如何保持单元格原有的格式,特别是在有单元格合并的情况下。
首先,你得有一个TabContainer控件在页面中,然后需要根据工作表的数量添加相应的TabPanel。可以以如下方式添加:
XmlNodeList sheet_node_list = xml.GetElementsByTagName("WORKSHEET"); for (int m = 0; m < sheet_node_list.Count; m++) // 遍历所有的工作表 { AjaxControlToolkit.TabPanel panel = null; // TabPanel控件,我们的表格将放在其中 if (TabContainer1.Controls.Count < m + 1) // TabContainer可能包含有TabPanel,通常放置TabContainer控件后,会有1个 { panel = new AjaxControlToolkit.TabPanel(); // 需要添加新的TabPanel TabContainer1.Controls.Add(panel); } else panel = TabContainer1.Tabs[m]; // 使用已经存在的TabPanel panel.HeaderText = sheet_node_list[m].Attributes["name"].Value; // 将TabPanel的标签设置为工作表的名字 panel.ID = "Panel" + m; // 设置ID panel.Attributes.Add("Width", sheet_node_list[m].Attributes["width"].Value); // 关于表格尺寸的获取,后面再说 panel.Attributes.Add("Height", sheet_node_list[m].Attributes["height"].Value); int _rowNum = Math.Max(1, int.Parse(sheet_node_list[m].Attributes["row"].Value) + 1); // 获取表格最大行列数 int _colNum = Math.Max(1, int.Parse(sheet_node_list[m].Attributes["col"].Value) + 1); // create table Table tbl = new Table(); // 我们的表格,总是生成新滴 tbl.ID = panel.HeaderText + "_Table"; // 设置ID,其客户端ClintID将是ctrl_xxx_yy_工作表名_Table的形式,JS脚本将会用到 tbl.Attributes.Add("activeCell", "null"); // 当前活动的单元格,用于JS脚本 tbl.Attributes.Add("cellspacing", "0"); // 设置表格的界面表现格式 tbl.Attributes.Add("cellpadding", "0"); tbl.BorderColor = System.Drawing.Color.AliceBlue; // 加个边框 tbl.BorderStyle = BorderStyle.Solid; tbl.BorderWidth = 1; createTableHeader(tbl, _rowNum, _colNum); // 创建表头,模拟Excel的行列标识 fillTableData(tbl, _rowNum, _colNum, sheet_node_list[m]); //填充单元格数据 panel.Controls.Add(tbl); //将表格添加到TabPanel中 }
最后在浏览器中显示的结果如下图所示:
现在来看看createTableHeader()方法,该方法生成表头:
protected void createTableHeader(Table tbl, int _rowNum, int _colNum) { bool isHeaderCell = false; // 头部标志 // fill header for (int r = 0; r < _rowNum; r++) // 遍历行 { TableRow row = new TableRow(); // 生成行 for (int c = 0; c < _colNum; c++) // 遍历列 { if ( c == 0 || r == 0 ) { TableCell cell = new TableCell(); // 生成单元格 isHeaderCell = false; // 初始化标志 cell.Style.Add("border-left", "1px solid black"); // 设置格式 cell.Style.Add("border-top", "1px solid black"); cell.Attributes.Add("width", "83pt"); // 默认宽度 if ( c == 0 ) // 0列? { if (r == 0) // 0行? { cell.Text = " "; // 填个空格 isHeaderCell = true; // 设置标志 } else { cell.Text = r.ToString(); // 0列从1-N行表头,填行号 isHeaderCell = true; // 设置标志 if (r == _rowNum - 1) cell.Style.Add("border-bottom", "1px solid black"); // 最后一行,画底线 } } else { if ( r == 0 ) // N列(N大于0)0行,列头 { cell.Text = getColLetter(c - 1); // 根据列号生成A-IV的列标识 isHeaderCell = true; // 设置标志 } } if ( isHeaderCell ) // 设置表头格式:居中,silver色为底 { cell.Attributes.Add("align", "center"); cell.Style.Add("background-Color", "silver"); row.Cells.Add(cell); // 添加单元格到行对象中 } } } row.Style.Add("border", "1px solid black"); // 设置行边框 tbl.Rows.Add(row); // 将行添加到列 } }
其中用到的从列号生成A-IV型的Excel列标识的方法,作为课后作业,请同学自行完成。
生成表头后,再向里面填充数据:
protected void fillTableData(Table tbl, int _rowNum, int _colNum, XmlNode sheet_node) { tbl.Attributes.Add("cellpadding", "0"); // 添加表格属性 tbl.Attributes.Add("cellspacing", "0"); tbl.Style.Add("border-collapse", "collapse"); // 设置表格样式 tbl.Style.Add("table-layout", "fixed"); tbl.Style.Add("width", sheet_node.Attributes["width"].Value + "pt"); // 关于如何取得工作表尺寸的方法,后文再叙 tbl.Style.Add("border", "1px solid black"); // 黑色边框 foreach (XmlNode row_node in sheet_node.ChildNodes) // 遍历行 { int rowIdx = int.Parse(row_node.Attributes["idx"].Value); //获取行号 tbl.Rows[rowIdx].Attributes.Add("height", row_node.Attributes["height"].Value); // 获取行高 if (row_node.ChildNodes.Count == 0) // 空行? { for (int i = 1; i <= _colNum - 1; i++) // 添加空单元格 { tbl.Rows[rowIdx].Cells.Add(createEmptyCell()); } } else foreach (XmlNode cell_node in row_node.ChildNodes) // 遍历行的单元格 { int colIdx = int.Parse(cell_node.Attributes["col"].Value); // 列号 int colSpan = int.Parse(cell_node.Attributes["colspan"].Value); // 列合并的跨度 int rowSpan = int.Parse(cell_node.Attributes["rowspan"].Value); // 行合并的跨度 bool hasFormula = bool.Parse(cell_node.Attributes["hasFormula"].Value); // 包含公式? if (cell_node.PreviousSibling != null) // 是不是第一个有数据的单元格? { // 如果相邻两个<CELL>不连续,需要填充中间部分 int prevColIdx = int.Parse(cell_node.PreviousSibling.Attributes["col"].Value), // 前一单元格的列号和列跨度 prevColSpan = int.Parse(cell_node.PreviousSibling.Attributes["colspan"].Value); if (prevColIdx + prevColSpan < colIdx) // 如果存在空档,则填充之 { for (int i = prevColIdx + prevColSpan; i < colIdx; i++) { tbl.Rows[rowIdx].Cells.Add(createEmptyCell()); // 添加空单元格 } } } else { if (colIdx > 1) // 如果起始有数据的单元格不是第一列,则需要填充空白部分 { for (int i = 1; i < colIdx; i++) { tbl.Rows[rowIdx].Cells.Add(createEmptyCell()); } } } // 现在是戏肉部分,添加有数据的单元格 { TableCell cell = new TableCell(); // 设置字体样式 cell.Style.Add("font-family", cell_node.Attributes["font-name"].Value); cell.Style.Add("color", cell_node.Attributes["font-color"].Value); cell.Style.Add("font-size", cell_node.Attributes["font-size"].Value); cell.Style.Add("border", "1px solid black"); cell.Style.Add("width", cell_node.Attributes["width"].Value); // 设置尺寸 cell.Attributes.Add("width", cell_node.Attributes["width"].Value); cell.Attributes.Add("height", cell_node.Attributes["height"].Value); // 设置对齐方式 cell.Attributes.Add("align", cell_node.Attributes["align"].Value); cell.Attributes.Add("valign", cell_node.Attributes["valign"].Value); // 设置行列跨度 // 注意:没有直接设置rowspan属性,而是交给客户端脚本完成 cell.Attributes.Add("_rowspan", cell_node.Attributes["rowspan"].Value); cell.Attributes.Add("colspan", cell_node.Attributes["colspan"].Value); // 添加数据域 cell.Attributes.Add("hasFormula", cell_node.Attributes["hasFormula"].Value); cell.Attributes.Add("formula", cell_node.Attributes["formula"].Value); cell.Attributes.Add("dataField", hasFormula ? cell_node.Attributes["formula"].Value : cell_node.Attributes["value2"].Value); cell.Attributes.Add("value2", cell_node.Attributes["value2"].Value); // 使用value2,而不用value,避免与HTML元素属性的冲突 if (hasFormula) cell.Attributes.Add("title", cell_node.Attributes["formula"].Value); // 添加悬停时的提示,这里只针对公式,也可以是普通单元格 cell.Text = cell_node.Attributes["value2"].Value; // 设置显示的文本 tbl.Rows[rowIdx].Cells.Add(cell); // 添加到行 } // 如果没有后续 ,需要补完,直到列尾 if (cell_node.NextSibling == null && colIdx + colSpan < _colNum - 1) { for (int i = colIdx + 1; i <= _colNum - 1; i++) { tbl.Rows[rowIdx].Cells.Add(createEmptyCell()); } } }// for each cell } // for each row }
生成空白单元格的方法实现如下:
protected TableCell createEmptyCell() { TableCell cell = new TableCell(); cell.Style.Add("border", "1px solid black"); // 设置样式 cell.Attributes.Add("hasFormula", "false"); // 设置数据域 cell.Attributes.Add("formula", ""); cell.Attributes.Add("dataField", ""); cell.Attributes.Add("value2", ""); cell.Text = " "; // 设置显示的文本 return cell; }
现在runat="server"端的表格生成工作已经完成,整个表格已经初具雏形,除了行跨度还有问题外。
通过以下脚本,可以解决行跨度问题。本节标题为“服务器端生成”,照理不应该使用客户端脚本,这里只是描述客户端如何实现相应的操作,理论上在服务器端也可以完成相当的工作:
for( var i = tbl.cells.length - 1; i > 1; i-- ) // 从后向前遍历行 { var ctrl = tbl.cells[i]; // 单元格对象 if ( ctrl.cellIndex > 0 && ctrl.parentElement.rowIndex > 0) // 0列0行不用处理 { if ( ctrl._rowspan && ctrl._rowspan > 1 && // 跨度>1并且范围没有超过表格 ctrl.parentElement.rowIndex + 1 < tbl.rows.length ) { for( var n = 1; n < ctrl._rowspan; n++ ) // 合并单元格 { if ( ctrl.cellIndex < tbl.rows[ctrl.parentElement.rowIndex + 1].cells.length ) // 合并一次就判断是否超界 tbl.rows[ctrl.parentElement.rowIndex + 1].deleteCell(ctrl.cellIndex); // 删除下一行同一列的单元格 } ctrl.rowSpan = ctrl._rowspan; // 最后设置单元格的行跨度 } } }
为什么要从后向前遍历行呢?因为当删除一个单元格时,后面的单元格会向前移,倒序处理可以避免这种错位问题。