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

How to insert specific excel row into datagridview using excel rowindex

我正在使用小型 C# 应用程序在 excel 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();
}

现在,当找到匹配项时,我正在调用此方法 AddToDataGridView(),它应该执行以下操作:

欢迎使用另一种方式

    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  |
----------------------------------------------------------

假设 B VALUE2 是我的搜索匹配值...我想获取 2 的 rowid 在这种情况下,查询那行 select * from [sheet_Name$] where rowid=2 然后将其添加到 datagridview 怎么做?

非常感谢

首先,您只能在每个 sheet 中找到 "first" 行,因为它正在每个 sheet 中搜索名为 TB_First_Name.Text 的命名范围 - 只能最多是每个 sheet.

中的一个

其次,您的代码中似乎有错字 - 我认为 DataGridView gdv_data = new DataGridView(); 应该是 DataGridView DGV_data = new DataGridView();

最后,从其他答案(例如 )看来,从 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);

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

如果是这种情况,那么我希望下面的代码可能有所帮助。这是一个简单的表格,有一个按钮可以 open/select 一个要搜索的 Excel 工作簿;允许用户输入搜索字符串的文本框;一个组合框,用于保存 selected 工作簿中工作表的名称;用于启动搜索过程的搜索按钮;一个用于调试的 textLog 文本框,最后是一个 DataGridView 来保存搜索结果。添加一些标签,在工作簿“new293.xlsx”中搜索“John”后,它可能如下所示。

右边的文本框作为日志输出,需要时可以进行测试。搜索结果返回后,用户可以使用组合框 select 每个工作表的结果。

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

下面是更详细的描述,但是,我必须评论一些可能出现的Excel问题。

目前代码将搜索目标字符串的任何子字符串。例如,如果您搜索“a”,returned 结果将包含任何带有“a”的字符串...“cat”“bat”等。您可能想要改进其完成方式。 Excel的“查找”方法可能不是最佳选择。 (更多内容在下方)

当使用Excels UsedRange 属性时,需要注意的是,这可能会return单元格“显得”为空。在几乎所有发生这种情况的情况下,单元格中都有一些格式,但单元格为空且不包含任何数据...... UsedRange 可能由于格式原因将单元格包含在范围内。请注意这一点,当 return 是这些“明显”的空单元格时,不要过快地声称 UsedRange 有缺陷。我在另一个答案中有解决方案。

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

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

表格中的全局变量:

A DataSet ds 持有DataTables; self-explanatory路径DefaultDirectory;一个要搜索的 Excel workbook,最后是 Excel 应用程序本身。加载后,Excel 应用程序启动并等待用户 select 工作簿。

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();
}

open/select 工作簿按钮单击 select 工作簿使用 OpenFileDialog 允许用户 select 工作簿进行搜索。一旦 selected,全局变量 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;
  }
}

在用户 select 找到要搜索的工作簿并键入一些要搜索的目标文本后……用户单击“搜索”按钮。首先,进行两项检查以确保有一些文本可供搜索,并检查是否有打开的工作簿可供搜索。如果没有要搜索的文本或工作簿未打开,则向用户显示一条消息并且 return 无需搜索。

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

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 方法(如下)填充数据 table,最后添加 DataTableDataSetGetDTColumnsFromWorksheet 方法(下面根据工作表中 header 行的内容创建 DataTable。工作表中的第一行假定为 header 行,并将它们用作 DataTable 的列名称。注意:目前,如果搜索 return 没有结果,工作表 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;
}

GetDTColumnsFromWorksheet 需要一个工作表并且 return 是一个 DataTable。数据 table returned 的列数与工作表中“UsedRange”的 returned 的列数相同。添加了一个额外的列以显示在工作表中找到该单元格的位置。它在第一列中采用 RXXCXX 格式。如果使用范围内的列中的单元格没有值,将使用字符串“???XX”。这适用于有空列的情况。

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;
}

FillTableWithSearchResults 方法需要一个范围进行搜索,一个字符串进行搜索,最后一个数据 table 添加成功的搜索。传入的DataTable已经创建,列也已经初始化。

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

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

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

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 方法使用一个已用范围从中获取数据,一个范围 row 添加到第三个给定参数 DataTable。再次有点 hacky,进行检查以确保使用范围内的列数不超过数据 table 中的列数。得到一个row Index,表示将使用范围内的哪一行添加到数据中table。 DataRow dr 是根据给定的 DataTable dt 创建的,以确保列架构相同。第一列将是我们之前添加的列,用于显示找到的项目的 RXXCXX 位置列。添加额外的列数据,然后遍历其余列,将工作表值添加到 DataRow。添加所有列值后,DataRow 将添加到 DataTable

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 从单元格字符串 RXXCXX 地址获取单元格范围和 returns (int) 行索引。

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 事件被连接起来并使用组合框 selected 索引来确定要在网格中显示哪个 DataTable

private void cbWorksheetNames_SelectedIndexChanged(object sender, EventArgs e) {
  DGV_Data.DataSource = ds.Tables[cbWorksheetNames.SelectedIndex];
}

最后一些资源清理。

private void Form3_FormClosing(object sender, FormClosingEventArgs e) {
  try {
    if (workbook != null) {
      workbook.Close();
      Marshal.ReleaseComObject(workbook);
    }
    excelApp.Quit();
    Marshal.ReleaseComObject(excelApp);
  }
  catch (Exception ex) {
    MessageBox.Show("Error: " + ex.Message);
  }
}

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