最近在学习winform,国庆前被布置了一个小任务,好不容易大致做出来了,决定记录下来,以此加深印象。
先说下需求:这是一个导入话单标记后并导出的功能
1. 选择excel文件
2. 定义字段
日期 时间 对方号码 通话时长 呼叫类型
3. 点击datagridview 标题 出现下拉菜单 显示定义的字段
4. 标记定义字段列
5. 保存定义字段数据 到 datatable
6 导出datatable
按照需求一步一步来,先设计界面,需要一个DataGridView和两个Button,一个导入,一个导出,我加了个Label和TextBox来提示文件路径。
先在类里面定义几个全局变量,下面的代码中会用到。
1 int colIndex;//点击的单元格列索引 2 int rowIndex;//点击的单元格行索引 3 Dictionary<int, string> dic = new Dictionary<int, string>();//存放excel标题 4 List<string> list = new List<string>(); //存放标记后的标题 5 DataTable dt;//导入的table 6 string filename = "";//Excel文件名
第一步:导入Excel预览,我从网上找了一段excel导入datagridview的代码,具体如下:
1 private DataTable ExcelToDataTable(string path) 2 { 3 4 FileStream fs = File.OpenRead(path); //打开.xls文件 5 6 HSSFWorkbook wk = new HSSFWorkbook(fs); //把xls文件中的数据写入wk中 7 8 var sheet = wk.GetSheetAt(0); //提取第一个sheet 9 var headerRow = sheet.GetRow(0);//提取sheet第一行 10 var cellCount = headerRow.LastCellNum;//提取行的最后一列 11 DataTable table = new DataTable(); 12 //给table添加一个列 13 for (int i = headerRow.FirstCellNum; i < cellCount; i++) 14 { 15 DataColumn col = new DataColumn(headerRow.GetCell(i).StringCellValue); 16 table.Columns.Add(col); 17 } 18 //获取sheet的行数 19 var rowCount = sheet.LastRowNum; 20 //循环逐行将sheet中数据写入table 21 for (int i = (sheet.FirstRowNum + 1); i < rowCount; i++) 22 { 23 var row = sheet.GetRow(i); 24 DataRow datarow = table.NewRow(); 25 for (int j = row.FirstCellNum; j < cellCount; j++) 26 { 27 if (row.GetCell(j) != null) 28 { 29 datarow[j] = row.GetCell(j).ToString(); 30 } 31 } 32 table.Rows.Add(datarow); 33 } 34 wk = null; 35 sheet = null; 36 return table; 37 }
这是个导入的方法,双击导入按钮,在事件里添加如下代码:
1 private void btnImport_Click(object sender, EventArgs e) 2 { 3 OpenFileDialog ofd = new OpenFileDialog(); 4 ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 5 ofd.Filter = "Excel Files(*.xls)|*.xls"; 6 if (ofd.ShowDialog() != DialogResult.OK) 7 { return; } 8 filename = ofd.FileName; 9 textBox1.Text = ofd.FileName; 10 dt = ExcelToDataTable(filename); 11 dataGridView1.DataSource = dt;//ExcelToDataTable(ofd.FileName); 12 13 int ColCount = dataGridView1.Columns.Count; 14 //将Excel所有标题存入dic中 15 for (int i = 0; i < ColCount; i++) 16 { 17 dic.Add(i, dataGridView1.Columns[i].HeaderText); 18 } 19 }
为了方便我将测试的Excel文档放到桌面,Excel的标题我存到了字典里,下面会用到。
到这里基本实现了第一步,点击导入按钮,选中excel文件,显示在datagridview上预览。
第二步、第三步:要求的样式大概是这样:
点击datagridview的标题,弹出下拉菜单,显示定义的字段。因此添加一个ContextMenuStrip控件,将字段一个个添加进去。
要求点击标题弹出下拉菜单,我用到了datagridview的CellCilck事件,这个事件里代码非常简单,只要弹出contexmenustrip就行了,然后加一个对是否为标题行的判断。
1 private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e) 2 { 3 colIndex = e.ColumnIndex;//获取选中列 4 rowIndex = e.RowIndex; 5 if (e.RowIndex != -1)//只允许设置标题行 6 { return; } 7 contextMenuStrip1.Show(MousePosition.X, MousePosition.Y); 8 //获取点击的标题内容 9 // title = dataGridView1.Columns[e.ColumnIndex].HeaderText; 10 }
第四步比较复杂,大概思路是,下拉菜单的选项是固定的,通过contextMenuStrip1_ItemClicked事件里的e.ItemClicked.Text可以获取到你选择的菜单,因此我用了switch case语句来进行判断。
首先是点击datagridview一列的标题,然后选中一个菜单,这一列的标题要变成e.itemClicked,并且标记这一列,所以我写了一个标记的方法表示标记后操作和样式。
1 private void Mark(string item) 2 { 3 dt.Columns[colIndex].ColumnName = item; 4 dataGridView1.Columns[colIndex].HeaderText = item; 5 dataGridView1.Columns[colIndex].DefaultCellStyle.BackColor = Color.LightSteelBlue; 6 dataGridView1.EnableHeadersVisualStyles = false; 7 dataGridView1.Columns[colIndex].HeaderCell.Style.BackColor = Color.LightSlateGray; 8 }
因为涉及到匹配的问题,例如日期格式的列无法标记成“呼叫类型”,“通话时间”等,所以加了一些正则判断,正则表达式以前从没用过,也是网上现学的,写得比较差。
又写了一个判断后的方法:
1 private void MatchItem(string match, string str, string item) 2 { 3 Match m = Regex.Match(match, str); 4 if (m.Success) 5 { 6 Mark(item); 7 //如果该列已标记,则不添加item到list 8 foreach (var i in list) 9 { 10 if (item == i) 11 return; 12 } 13 list.Add(item); 14 } 15 else 16 { 17 contextMenuStrip1.Hide(); 18 MessageBox.Show("该列不为" + item + "!"); 19 } 20 }
至于contextmenustrip_itemClicked事件里只要在每个case里调用MatchItem方法就可以了。
1 private void contextMenuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e) 2 { 3 //dt = ExcelToDataTable(filename); 4 //获取想要标记的的标题 5 string item = e.ClickedItem.Text; 6 //获取用于判断的内容 7 var BeMatch = dataGridView1.Rows[rowIndex + 1].Cells[colIndex].Value.ToString(); 8 //当前列在table是否已经存在 9 int i = 0; 10 foreach (DataColumn dataCol in dt.Columns) 11 { 12 if (i != colIndex && dataCol.ColumnName == item) 13 { 14 contextMenuStrip1.Hide(); 15 var index = i + 1; 16 MessageBox.Show("该表第" + index + "列为" + dataCol.ColumnName + ",无法标记"); 17 return; 18 } 19 i++; 20 } 21 22 switch (item) 23 { 24 case "呼叫类型": 25 { 26 string strType = @"[\u4e00-\u9fbb]"; 27 MatchItem(BeMatch, strType, item); 28 } 29 break; 30 case "对方号码": 31 { 32 string strNum = @"0?[1]+[358]+\d{9}"; 33 MatchItem(BeMatch, strNum, item); 34 } 35 break; 36 case "日期": 37 { 38 string strDate = @"^2\d{7}$"; 39 MatchItem(BeMatch, strDate, item); 40 } 41 break; 42 case "时间": 43 { 44 string strTime = @"^[0-2]\d{1}[0-5]\d{1}[0-5]\d{1}$"; 45 MatchItem(BeMatch, strTime, item); 46 } 47 break; 48 case "通话时长": 49 { 50 string strOften = @"\d"; 51 MatchItem(BeMatch, strOften, item); 52 } 53 break; 54 case "取消设置": 55 { 56 //遍历原标题,获取取消设置的列,移出dt 57 foreach (var kv in dic) 58 { 59 if (kv.Key == colIndex) 60 { 61 list.Remove(dataGridView1.Columns[colIndex].HeaderText); 62 //list.Insert(colIndex,kv.Value); 63 item = kv.Value; 64 dt.Columns[colIndex].ColumnName = item; 65 } 66 } 67 //还原标题和样式 68 dataGridView1.Columns[colIndex].DefaultCellStyle.BackColor = Color.White; 69 dataGridView1.Columns[colIndex].HeaderCell.Style.BackColor = DefaultBackColor; 70 } 71 break; 72 } 73 }
第五步 :保存标记后的字段数据到datatable里,我在网上找了一个方法,附上链接:http://www.jb51.net/article/80620.htm
1 public bool DataToExcel(DataTable dgv, string FileName, string tableName, bool IsWriteRowHeader) 2 { 3 if (dgv == null || dgv.Rows.Count <= 0 || dgv.Columns.Count <= 0) 4 { throw new Exception("请确认表格中有数据"); } 5 6 HSSFWorkbook workbook = new HSSFWorkbook(); 7 MemoryStream ms = new MemoryStream(); 8 try 9 { 10 // 表单名 11 var sheet = workbook.CreateSheet(tableName); 12 13 //DataGridView行数 14 int rowCount = dgv.Rows.Count; 15 //DataGridView列数 16 int colCount = 0; 17 foreach (DataColumn item in dgv.Columns) 18 { 19 if (item.ColumnName != "地图" && item.ColumnName != "详细" && item.ColumnName != "") 20 { colCount++; } 21 } 22 23 var font = workbook.CreateFont(); 24 font.Boldweight = (short)NPOI.SS.UserModel.FontBoldWeight.BOLD; 25 font.FontHeightInPoints = 16; 26 27 28 var font1 = workbook.CreateFont(); 29 font1.Boldweight = (short)NPOI.SS.UserModel.FontBoldWeight.BOLD; 30 font1.FontHeightInPoints = 11; 31 32 33 //数据表格样式 34 var dataStyle = workbook.CreateCellStyle(); 35 dataStyle.BorderBottom = NPOI.SS.UserModel.BorderStyle.THIN; 36 dataStyle.BorderLeft = NPOI.SS.UserModel.BorderStyle.THIN; 37 dataStyle.BorderTop = NPOI.SS.UserModel.BorderStyle.THIN; 38 dataStyle.BorderRight = NPOI.SS.UserModel.BorderStyle.THIN; 39 dataStyle.Alignment = NPOI.SS.UserModel.HorizontalAlignment.CENTER; 40 dataStyle.VerticalAlignment = VerticalAlignment.CENTER; 41 dataStyle.WrapText = true; 42 43 var dataStyle1 = workbook.CreateCellStyle(); 44 dataStyle1.BorderBottom = NPOI.SS.UserModel.BorderStyle.THIN; 45 dataStyle1.BorderLeft = NPOI.SS.UserModel.BorderStyle.THIN; 46 dataStyle1.BorderTop = NPOI.SS.UserModel.BorderStyle.THIN; 47 dataStyle1.BorderRight = NPOI.SS.UserModel.BorderStyle.THIN; 48 dataStyle1.Alignment = NPOI.SS.UserModel.HorizontalAlignment.LEFT; 49 dataStyle1.VerticalAlignment = VerticalAlignment.CENTER; 50 dataStyle1.WrapText = true; 51 52 //标题表格样式 53 var cellTitleStyle = workbook.CreateCellStyle(); 54 cellTitleStyle.BorderBottom = NPOI.SS.UserModel.BorderStyle.THIN; 55 cellTitleStyle.BorderLeft = NPOI.SS.UserModel.BorderStyle.THIN; 56 cellTitleStyle.BorderTop = NPOI.SS.UserModel.BorderStyle.THIN; 57 cellTitleStyle.BorderRight = NPOI.SS.UserModel.BorderStyle.THIN; 58 cellTitleStyle.SetFont(font1); 59 cellTitleStyle.Alignment = NPOI.SS.UserModel.HorizontalAlignment.CENTER; 60 cellTitleStyle.VerticalAlignment = VerticalAlignment.CENTER; 61 cellTitleStyle.WrapText = true; 62 63 //标题表格样式 64 var cellTitleStyle1 = workbook.CreateCellStyle(); 65 cellTitleStyle1.BorderBottom = NPOI.SS.UserModel.BorderStyle.THIN; 66 cellTitleStyle1.BorderLeft = NPOI.SS.UserModel.BorderStyle.THIN; 67 cellTitleStyle1.BorderTop = NPOI.SS.UserModel.BorderStyle.THIN; 68 cellTitleStyle1.BorderRight = NPOI.SS.UserModel.BorderStyle.THIN; 69 cellTitleStyle1.SetFont(font1); 70 cellTitleStyle1.Alignment = NPOI.SS.UserModel.HorizontalAlignment.LEFT; 71 cellTitleStyle1.VerticalAlignment = VerticalAlignment.CENTER; 72 cellTitleStyle1.WrapText = true; 73 74 //标题表格样式 75 var titleStyle = workbook.CreateCellStyle(); 76 titleStyle.BorderBottom = NPOI.SS.UserModel.BorderStyle.THIN; 77 titleStyle.BorderLeft = NPOI.SS.UserModel.BorderStyle.THIN; 78 titleStyle.BorderTop = NPOI.SS.UserModel.BorderStyle.THIN; 79 titleStyle.BorderRight = NPOI.SS.UserModel.BorderStyle.THIN; 80 titleStyle.SetFont(font); 81 titleStyle.Alignment = NPOI.SS.UserModel.HorizontalAlignment.CENTER; 82 titleStyle.VerticalAlignment = VerticalAlignment.CENTER; 83 titleStyle.WrapText = true; 84 85 86 //数据表标题 87 NPOI.SS.Util.CellRangeAddress rangTitle = new NPOI.SS.Util.CellRangeAddress(0, 0, 0, colCount - 1); 88 sheet.AddMergedRegion(rangTitle); 89 var titleCel = sheet.CreateRow(0).CreateCell(0); 90 titleCel.SetCellValue(tableName); 91 titleCel.CellStyle = titleStyle; 92 sheet.GetRow(0).HeightInPoints = 30; 93 94 //数据表列头 95 var colNameRow = sheet.CreateRow(1); 96 for (int i = 0; i < dgv.Columns.Count; i++) 97 { 98 if (dgv.Columns[i].ColumnName != "地图" && dgv.Columns[i].ColumnName != "详细" && dgv.Columns[i].ColumnName != "") 99 { 100 //把表头存入 101 var cel = colNameRow.CreateCell(colNameRow.Cells.Count); 102 cel.SetCellValue(dgv.Columns[i].ColumnName.ToString()); 103 104 if (dgv.Columns[i].ColumnName.ToString() == "通话地址") 105 { 106 cel.CellStyle = cellTitleStyle1; 107 sheet.SetColumnWidth(colNameRow.Cells.Count - 1, 120 * 40 * 5); 108 } 109 else if (dgv.Columns[i].ColumnName.ToString() == "姓名" || dgv.Columns[i].ColumnName.ToString() == "日期") 110 { 111 cel.CellStyle = cellTitleStyle; 112 sheet.SetColumnWidth(colNameRow.Cells.Count - 1, 2 * 2 * 30 * 40); 113 } 114 else 115 { 116 cel.CellStyle = cellTitleStyle; 117 sheet.SetColumnWidth(colNameRow.Cells.Count - 1, dgv.Columns[i].ColumnName.ToString().Length * 30 * 40); 118 } 119 } 120 } 121 122 //数据表数据 123 for (int j = 0; j < rowCount; j++) 124 { 125 var dataRow = sheet.CreateRow(j + 2); 126 var col_index = 0; 127 for (int k = 0; k < dgv.Columns.Count; k++) 128 { 129 if (dgv.Columns[k].ColumnName != "地图" && dgv.Columns[k].ColumnName != "详细" && dgv.Columns[k].ColumnName != "") 130 { 131 //把数据保存到二维数组 132 var cel = dataRow.CreateCell(col_index++); 133 cel.SetCellValue(dgv.Rows[j][k] == null ? "" : dgv.Rows[j][k].ToString()); 134 135 if (dgv.Columns[k].ColumnName.ToString() == "通话地址") 136 { 137 cel.CellStyle = dataStyle1; 138 } 139 else 140 { 141 cel.CellStyle = dataStyle; 142 } 143 } 144 } 145 } 146 workbook.Write(ms); 147 FileStream file = new FileStream(FileName, FileMode.Create); 148 workbook.Write(file); 149 file.Close(); 150 workbook = null; 151 ms.Close(); 152 ms.Dispose(); 153 return true; 154 } 155 catch (Exception ex) 156 { 157 workbook = null; 158 ms.Close(); 159 ms.Dispose(); 160 throw ex; 161 } 162 }
1 private void btnExport_Click(object sender, EventArgs e) 2 { 3 //添加标记的列到table 4 DataTable table = dt.DefaultView.ToTable(false, list.ToArray()); 5 6 try 7 { 8 SaveFileDialog sfd = new SaveFileDialog(); 9 //sfd.FileName = "测试导出.xls"; 10 sfd.Filter = "Excel Files(*.xls)|*.xls"; 11 sfd.FileName = "测试导出"; 12 if (sfd.ShowDialog() == DialogResult.OK) 13 { 14 filename = sfd.FileName; 15 DataToExcel(table, filename, "话单", false); 16 } 17 } 18 catch (Exception ex) 19 { 20 MessageBox.Show(ex.Message); 21 }
做完运行的效果是这样的:
到这里基本结束了,运行后没什么大问题,可能有的bug还没有测出来。第一次写博客,写的不好大家见谅,欢迎提出各种意见和建议。谢谢支持!