如何使用excel rowindex将特定的Excel行插入到datagridview中

时间:2018-02-19 17:21:54

标签: c# excel .net-3.5 excel-interop

我在excel .formcontainer { display: inline-block; } 中使用小型c#应用程序执行搜索 使用以下方法

workbook

现在找到匹配时我正在调用此方法public void SearchExcelFiles(string FilePath) { string ConnStr = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + FilePath + ";Extended Properties=\"Excel 12.0 Xml;HDR=YES\";"; Microsoft.Office.Interop.Excel.Application oXL = new Microsoft.Office.Interop.Excel.Application(); Microsoft.Office.Interop.Excel.Workbook oWB; Microsoft.Office.Interop.Excel.Range currentFind = null; Microsoft.Office.Interop.Excel.Range firstFind = null; Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); if (!GB_Search.Controls.OfType<TextBox>().Any(x => !string.IsNullOrEmpty(x.Text))) { MessageBox.Show("Enter text for search"); return; } oWB = oXL.Workbooks.Open(FilePath, //---Filename OR FilePath 0, //---object UpdateLinks true, //---object ReadOnly Type.Missing, //5//---object Format "", //---object Password "", //---object WriteResPassword false, //---object ReadOnlyRecommend Excel.XlPlatform.xlWindows, //---object Origin "", //---object Delimiter true, //---object Editable false, //---object Notify 0, //---object Converter true, //---object AddToMru false, //---object Local false); //---object CorruptLoad; //specifying searching range within each excel sheet //Excel.Range oRng = oXL.get_Range("A1", "XFD1048576"); Excel.Range xlCell = xlWSheet.UsedRange.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing); Excel.Range oRng = xlWSheet.get_Range("A1", xlWSheet.UsedRange.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing)); //loop to search witin all excel sheets (workbook) foreach (Excel.Worksheet SheetID in oWB.Worksheets) { //loop within all textboxs value to search if it is exist foreach (TextBox cont in GB_Search.Controls.OfType<TextBox>()) { if (!string.IsNullOrEmpty(cont.Text)) { currentFind = oRng.Find(cont.Text, Type.Missing, Excel.XlFindLookIn.xlValues, Excel.XlLookAt.xlPart, Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlNext, false, Type.Missing, Type.Missing); while (currentFind != null) { //Keep track of the first range you find. if (firstFind == null) { firstFind = currentFind; } //if current address is same as the starting address stop searching else if (currentFind.get_Address(true, true, Excel.XlReferenceStyle.xlR1C1, Type.Missing, Type.Missing) == firstFind.get_Address(true, true, Excel.XlReferenceStyle.xlR1C1, Type.Missing, Type.Missing)) { break; } //keep searching for next value currentFind = oRng.FindNext(currentFind); MessageBox.Show(currentFind.get_Address(true, true, Excel.XlReferenceStyle.xlR1C1, Type.Missing, Type.Missing)); // for test purpose string CurrentAddress = currentFind.get_Address(true, true, Excel.XlReferenceStyle.xlR1C1, Type.Missing, Type.Missing); AddToDataGridView(CurrentAddress, SheetID.Name, ConnStr); //when match found get full Row details and populate it to datagridview } //empty ranges before looking for the next textbox values firstFind = null; currentFind = null; } //MessageBox.Show("Done control..." + cont.Name); //~test Purpose } //MessageBox.Show("Done...sheet"); //~test Purpoes } //MessageBox.Show("Done...wb"); //~test Purpose oWB.Close(false, Type.Missing, Type.Missing); oWB = null; oXL.Quit(); } ,该方法应该执行以下操作:

  • 使用where子句(以某种方式)获取完整的行详细信息{/ 1}}显示在附加图像
  • 填充此行并添加到AddToDataGridView()
另一种欢迎

的方法
rowid

enter image description here

示例

datagridview

假设 public void AddToDataGridView(string CurrentAddress, string SheetName, string ConnStr) { string cmdtxt = @"select * from [" + SheetName + "$" + CurrentAddress + "]Where ???? "; MessageBox.Show(cmdtxt); // test purpose using (OleDbConnection conn = new OleDbConnection(ConnStr)) { OleDbDataAdapter DA = new OleDbDataAdapter(cmdtxt, conn); DA.Fill(dt); DGV_Data.DataSource = dt; conn.Close(); } } 是我的搜索匹配值...我希望获得---------------------------------------------------------- # | A | B | C | D | ---------------------------------------------------------- 1 | A VALUE1 | B VALUE1 | C VALUE1 | D VALUE1 | ---------------------------------------------------------- 2 | A VALUE2 | B VALUE2 | C VALUE2 | D VALUE2 | ---------------------------------------------------------- 的rowid,在这种情况下,行B VALUE2的查询然后将其添加到{{1}怎么做?

非常感谢

2 个答案:

答案 0 :(得分:1)

没有完全理解,目标是什么。我总结的最好的方法是在Excel工作簿中搜索字符串。您希望搜索结果显示在DataGridView中。根据您的评论,您声明每个工作表可能具有不同的列结构,并且您希望在该行的至少一个单元格中匹配搜索字符串的“完整”行。因此,每个工作表搜索结果可能/将具有不同的列结构。我希望我能正确理解这一点。

如果是这种情况,那么我希望下面的代码可以提供帮助。它是一个简单的表单,有一个按钮可以打开/选择要搜索的Excel工作簿;一个文本框,允许用户输入搜索字符串;一个组合框,用于保存所选工作簿中工作表的名称;搜索按钮以启动搜索过程;用于调试的textLog文本框,最后是用于保存搜索结果的DataGridView。在工作簿“new293.xlsx”中搜索“John”之后,请输入几个标签,看起来像下面的内容。

enter image description here

右侧的文本框用作日志输出,以便在需要时进行测试。搜索结果返回后,用户可以使用组合框从每个工作表中选择结果。

代码可以简单地搜索给定工作簿中的字符串。当用户在搜索文本框中键入内容并单击搜索按钮时,代码将打开给定的工作簿,在每个工作表中搜索目标字符串,并创建DataTable以保存成功找到的行。创建DataSet以保存每个工作表创建的“不同”DataTable,因为每个工作表可能具有不同的列结构。

下面是更详细的说明,但是,我必须对可能出现的一些可能的Excel问题发表评论。

目前,代码将搜索目标字符串的任何子字符串。例如,如果您搜索“a”,则返回的结果将包含ANY字符串,其中包含“a”...“cat”“bat”等。您可能希望改进完成方式。 Excel“查找”方法可能不是最佳选择。 (更多下面)

使用Excel UsedRange属性时,应注意这可能会返回“显示”为空的单元格。在几乎所有情况下,当发生这种情况时,单元格中存在一些格式,但单元格为空并且不包含数据... UsedRange可能因格式化而包含该范围内的单元格。请注意这一点,并且不要太快宣称UsedRange在返回这些“显然”空单元格时有缺陷。我在另一个答案中有一个解决方案。

最后,关于Excel和interop ...当前代码使用与发布的代码相同的Excel Find方法。如果数据集不是很大,这应该没问题,但是,如果有大量数据(包含许多列和行的大型Excel工作表),这可能会成为性能问题。这是一个Excel和互操作问题。调用UsedRangeFind等方法在循环中使用时非常昂贵(我们是这样)。重点是,如果工作表很大,您可能需要考虑不同的实现而不使用互操作。我知道有更好的(免费)第三方Excel库。

话虽如此,下面是上述表格的代码。

表格中的全局变量:

DataSet ds来保存DataTables;不言自明的路径DefaultDirectory;一个Excel workbook来搜索,最后是Excel应用程序本身。加载后,Excel应用程序将启动并等待用户选择工作簿。

DataSet ds = new DataSet();  
string DefaultDirectory = @"D:\Test\";
Workbook workbook;
Excel.Application excelApp;

public Form3() {
  InitializeComponent();
}

private void Form3_Load(object sender, EventArgs e) {
  excelApp = new Excel.Application();
}

单击以打开/选择工作簿按钮以选择工作簿,使用OpenFileDialog允许用户选择要搜索的工作簿。选择后,全局变量workbook将打开,并可供其他方法使用。

private void btnSelectWorkbook_Click(object sender, EventArgs e) {
  DGV_Data.DataSource = null;
  tbSearch.Text = "";
  cbWorksheetNames.Items.Clear();
  textLog.Text = "";
  lblWorkbookName.Text = "";
  OpenFileDialog ofd = new OpenFileDialog();
  ofd.Filter = "Excel Files|*.xls;*.xlsx;*.xlsm";
  ofd.InitialDirectory = DefaultDirectory;
  if (ofd.ShowDialog() == DialogResult.OK) {
    string fileToOpen = ofd.FileName;
    workbook = excelApp.Workbooks.Open(fileToOpen);
    lblWorkbookName.Text = "Workbook: " + fileToOpen;
  }
}

用户选择要搜索的工作簿后,键入了一些要搜索的目标文本...用户单击“搜索”按钮。首先,进行两次检查以确保有一些要搜索的文本,并检查是否有可搜索的工作簿。如果没有要搜索的文本或工作簿未打开,则向用户显示一条消息并且不经搜索就返回。

如果有要搜索的打开的工作簿和文本,则初始化全局DataSet ds,然后通过调用{ds填充每个工作表的DataTable {1}}。 。填充GetRowsFromSearchStringFromAllWorksheets后(更多信息如下),DataSet的{​​{1}}设置为DataGridView中的第一个DataSource;组合框中填充了工作表名称,最后更新了一些标签。

DataTable

DatatSet方法(可能需要更好的名称)并没有做太多。它遍历工作簿中的每个工作表,从工作表创建private void btnSearch_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(tbSearch.Text)) { MessageBox.Show("Enter text for search"); return; } if (workbook == null) { MessageBox.Show("Select a workbook"); return; } ds = new DataSet(); try { ds = GetRowsFromSearchStringFromAllWorksheets(workbook, tbSearch.Text); DGV_Data.DataSource = ds.Tables[0]; FillComboBoxWithSheetNames(); cbWorksheetNames.SelectedIndex = 0; gbResults.Text = "Search Results for '" + tbSearch.Text + "'"; tbSearch.Text = ""; } catch (Exception ex) { MessageBox.Show("Error: " + ex.Message); } } ,通过调用GetRowsFromSearchStringFromAllWorksheets方法(下面)填充数据表,然后最后将DataTable添加到FillTableWithSearchResults 1}}。 DataTable方法(下面)根据工作表中标题行的内容创建DataSet。假设工作表中的第一行是标题行,并将它们用作GetDTColumnsFromWorksheet的列名。注意:目前,如果搜索返回时没有结果,则工作表DataTable仍然会添加到DataTable。如果您只想添加包含结果的工作表,我会离开调试代码进行修改。

DataTable

DataSet获取工作表并返回private DataSet GetRowsFromSearchStringFromAllWorksheets(Workbook wb, string searchString) { DataSet ds = new DataSet(); foreach (Worksheet currentWorksheet in wb.Worksheets) { System.Data.DataTable currentDT = GetDTColumnsFromWorksheet(currentWorksheet); //textLog.Text += "Searching in worksheet " + currentWorksheet.Name + Environment.NewLine; FillTableWithSearchResults(currentWorksheet.UsedRange, searchString, currentDT); if (currentDT.Rows.Count > 0) { textLog.Text += "Matches found in worksheet " + currentWorksheet.Name + Environment.NewLine; } else { textLog.Text += "No matches found in worksheet " + currentWorksheet.Name + Environment.NewLine; } ds.Tables.Add(currentDT); } return ds; } 。返回的数据表将具有与从工作表中的“UsedRange”返回的列数相同的列数。添加一个额外的列以显示工作表中找到单元格的位置。它在第一列中是RXXCXX格式。如果使用范围中的列中的单元格没有值,则将使用字符串“??? XX”。这适用于存在空列的情况。

GetDTColumnsFromWorksheet

DataTable方法需要搜索范围,要搜索的字符串以及最后添加成功搜索的数据表。传入的private System.Data.DataTable GetDTColumnsFromWorksheet(Worksheet ws) { // this assumes that row 1 of the worksheet contains a row header // we will use this to name the `DataTable` columns // this also assumes there are no "lingering" cells with values // that may not necessarily belong to the data table int missingColumnNameCount = 1; Range usedRange = ws.UsedRange; int numberOFColumns = usedRange.Columns.Count; System.Data.DataTable dt = new System.Data.DataTable(); dt.TableName = ws.Name; string currentColumnHeader = ""; Range row1; // add an extra column in the front // this column will show where (RXCX) the found item is in the worksheet dt.Columns.Add("CXRX", typeof(string)); for (int i = 1; i <= numberOFColumns; i++) { row1 = usedRange[1, i]; if (row1.Value2 != null) { currentColumnHeader = row1.Value2.ToString(); } else { // if the row has no value a default name and indexer to avoid duplicate column names currentColumnHeader = "???" + missingColumnNameCount; missingColumnNameCount++; } dt.Columns.Add(currentColumnHeader, typeof(string)); } return dt; } 已经创建,列已初始化。

我不确定这是否是处理Excel FillTableWithSearchResults方法的最佳方法。因此,我希望我有这个正确的。当第一次在范围上使用DataTable时,它将返回与搜索内容匹配的第一个找到的单元格。该重新调整的范围是“第一个”找到的项目的单元格地址。根据我的理解,Find/FindNext显然会返回NEXT找到的项目。问题是,当它找到最后一个项目并搜索下一个项目时,它只是从头开始。因此,循环的停止条件是当Find的单元地址与“第一个”FindNext的单元地址匹配时。这将要求我们保存“第一个”NextFind的单元格地址。以下是解决这一难题的一种方法。

创建两个范围:一个Find用于保存起始“第一个”Find,另一个startRange用于保存当前“已找到”范围。首先,进行检查以确保有搜索内容。如果要搜索至少一行,则Find将从“第一个”currentRange设置。这是我们在使用startRange时需要停止的单元格地址。如果找到至少一个项目,那么我们可以搜索下一个项目并输入Find循环。只需将FindNext设置为FindNext,将currentRange从“第一个”NextFind添加到数据表,然后最后输入startRange循环,以便它继续使用Find并向数据表添加新行,直到FindNext单元格地址FindNext currentRange单元格地址。这表示Equals已循环回到开头并且搜索已完成。 startingRange将找到的行添加到数据表(下方)。

注意:目前此代码允许在同一行中多个列中找到搜索字符串的情况下重复条目。对于行列中的每个找到的搜索字符串,网格中将有一个行条目。示例:如果第5行在第4,6和8列中搜索了字符串,则R5C4,R5C6和R5C8将有一行。我没有过滤这个以删除重复的行。

FindNext

AddExcelRowToDataTable方法使用一个已用范围来获取数据,范围private void FillTableWithSearchResults(Range usedRange, string searchString, System.Data.DataTable dt) { Range currentRange; if (usedRange.Rows.Count > 0) { Range startRange = usedRange.Find(searchString); if (startRange != null) { currentRange = usedRange.FindNext(startRange); AddExcelRowToDataTable(usedRange, startRange, dt); string startAddress = startRange.get_Address(true, true, XlReferenceStyle.xlR1C1); while (!currentRange.get_Address(true, true, XlReferenceStyle.xlR1C1).Equals(startAddress)) { AddExcelRowToDataTable(usedRange, currentRange, dt); currentRange = usedRange.FindNext(currentRange); } } } } 以添加到第三个给定参数AddExcelRowToDataTable。再次有点hacky,进行检查以确保使用范围中的列不比数据表中的列多。获取行索引以指示要添加到数据表的使用范围中的哪一行。从给定的row DataTable创建DataRow dr,以确保列模式相同。第一列将是我们之前添加的列,用于显示找到的项目的RXXCXX位置列。添加额外的列数据,然后遍历其余列,将工作表值添加到DataTable。添加完所有列值后,dt会添加到DataRow

DataRow

DataTable获取一个单元格范围,并从单元格字符串RXXCXX地址返回(int)行索引。

private void AddExcelRowToDataTable(Range usedRange, Range row, System.Data.DataTable dt) {
  if (usedRange.Columns.Count >= dt.Columns.Count - 1) {
    int rowIndex = GetRowIndexOfFoundItem(row);
    if (rowIndex >= 0) {
      DataRow dr = dt.NewRow();
      // add the CXRX data
      dr[0] = row.get_Address(true, true, XlReferenceStyle.xlR1C1);
      for (int i = 1; i <= usedRange.Columns.Count; i++) {
        dr[i] = usedRange.Cells[rowIndex, i].Value2;
      }
      dt.Rows.Add(dr);
    }
  }
}

搜索完成后用工作表名称填充组合框的方法。

GetRowIndexOfFoundItem

组合框private int GetRowIndexOfFoundItem(Range range) { // hacky ... the string is a format of RXXCX or RXXcXXX or RXXXXCXX. // we want the XXX after the R... split the string on 'C' // to get RXX..X, then remove the 'R' and parse the number string RCaddress = range.get_Address(true, true, XlReferenceStyle.xlR1C1); string[] split = RCaddress.Split('C'); RCaddress = split[0].Remove(0, 1); int rowIndex = 0; if (int.TryParse(RCaddress, out rowIndex)) { return rowIndex; } else { // not valid number return -1; } } 事件已连线,并使用所选的组合框索引来确定要在网格中显示的private void FillComboBoxWithSheetNames() { cbWorksheetNames.Items.Clear(); foreach (System.Data.DataTable dt in ds.Tables) { cbWorksheetNames.Items.Add(dt.TableName); } }

SelectedIndexChnged

最后进行一些资源清理。

DataTable

对于冗长的回答,我很抱歉,我希望有所帮助。

答案 1 :(得分:0)

首先,您只是在每张工作表中找到“第一”行,因为它在每张工作表中搜索名为TB_First_Name.Text的命名范围 - 每张工作表中最多只能有一个。

其次,您的代码中似乎存在拼写错误 - 我相信DataGridView gdv_data = new DataGridView();应为DataGridView DGV_data = new DataGridView();

最后,从其他答案(例如https://stackoverflow.com/a/46044387/3661120)看来,从excel填充datagridview行的最佳方法是这种方式:

var rowArray = oRng.Cells.Value2.Cast<object>().ToArray();
try { j = d.CurrentRow.Index; } catch { }
DataGridViewRow r = new DataGridViewRow();
r.CreateCells(d, rowArray);
DGV_Data.Rows.Insert(j, r);