How to insert specific excel row into datagridview using excel rowindex

佐手、 提交于 2019-12-24 19:17:14

问题


I am performing search within excel workbook using small c# application using below method

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

now when match found I am calling this method AddToDataGridView() which should do the following:

  • Get full row details by using where clause (in somehow) the rowid shown at attached image
  • populate this row and add to datagridview

another way to this are welcomed

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

example

----------------------------------------------------------
 # |     A     |      B      |      C      |      D      |
----------------------------------------------------------
 1 | A VALUE1  |   B VALUE1  |   C VALUE1  |   D VALUE1  |
----------------------------------------------------------
 2 | A VALUE2  |   B VALUE2  |   C VALUE2  |   D VALUE2  |
----------------------------------------------------------

suppose that B VALUE2 is my searching match value... I want to get the rowid which is 2 in that case the query that row select * from [sheet_Name$] where rowid=2 then add it to datagridview how to do that ?

thanks alot


回答1:


Without fully understanding, what the goal is here. The best I can sum up is to search for a string in an Excel workbook. You want the results of the search to be displayed in a DataGridView. From your comments, you stated that each worksheet may have a different column structure and you want the “complete” row that matched the search string in at least one of the cells in that row. Therefore, each worksheet search result may/will have a different column structure. I hope I understand this correctly.

If this is the case then I am hoping the code below may help. It is a simple form, that has a button to open/select an Excel workbook to search; a text box to allow the user to type the search string into; a combo box to hold the names of the worksheets in the selected workbook; a search button to initiate the search process; a textLog text box for debugging and finally a DataGridView to hold the search results. Throw in a few labels and it may look something like below after a search for “John” in the workbook “new293.xlsx”.

The text box on the right is used as a log output for testing if needed. After the search results come back, the user can use the combo box to select the results from each worksheet.

The code does a simple search for a string in a given workbook. When the user types something into the search text box and clicks the search button, the code opens the given workbook, searches for the target string in each worksheet and creates a DataTable to hold the successfully found rows. A DataSet is created to hold the “different” DataTable’s created by each worksheet since each worksheet may have a different column structure.

Below is a more detail description, however, I must comment about some possible Excel issues that may arise.

Currently the code will search for any substring of the target string. Example, if you searched for “a”, the returned results will contain ANY string with an “a” in it... “cat” “bat” etc. You may want to refine how this is done. Excels “Find” method may not be the best option. (more below)

When using Excels UsedRange property, it should be noted that this may return cells that will “appear” empty. In almost all cases when this happens, there is some formatting in the cell but the cell is empty and contains no data… UsedRange may include that cell in the range because of the formatting. Please be aware of this and do not be too quick to claim UsedRange is flawed when it returns these “apparently” empty cells. I have a solution for this in another answer.

Lastly, about Excel and interop… the current code uses the same Excel Find method as your posted code does. This should be fine if the dataset is not to large, however, if there is a large amount of data (large Excel worksheets with many columns and rows), this may become a performance problem. This is an Excel and interop issue. Calling methods like UsedRange, Find and others is VERY EXPENSIVE when used in a loop (which we are). Point being, if the worksheets are large, you may want to consider a different implementation without using interop. I understand there are better (free) third party Excel libraries.

With that said, below is the code to the form above.

Global variables in the form:

A DataSet ds to hold the DataTables; self-explanatory path DefaultDirectory; an Excel workbook to search and finally the Excel application itself. Upon load, the Excel app is started and waits for the user to select a workbook.

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

The open/select workbook button click to select a workbook uses an OpenFileDialog to allow the user to select the workbook to search. Once selected, the global variable workbook is open and available to other methods.

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

After the user selects a workbook to search, and has typed some target text to search for… the user clicks the “Search” button. First, two checks are made to make sure there is some text to search for and also check if there is a workbook open to search in. If there is no text to search for or a workbook is not open, display a message to the user and return without searching.

If there is an open workbook and text to search for, the global DataSet ds is initialized, then ds is filled with each worksheet’s DataTable by calling the GetRowsFromSearchStringFromAllWorksheets. . After the DataSet has been filled (more below) the DataGridView’s DataSource is set to the first DataTable in the DatatSet; the combo box is filled with the worksheet names and finally some labels are updated.

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

The GetRowsFromSearchStringFromAllWorksheets method (which probably needs a better name) is not doing much. It loops through each worksheet in the workbook, creates a DataTable from the worksheet, fills the data table by calling the FillTableWithSearchResults method (below), then finally adds the DataTable to the DataSet. The GetDTColumnsFromWorksheet method (below) creates a DataTable based on what the header rows are in the worksheet. The first row in the worksheet is assumed to be header rows and uses these as column names for the DataTable. Note: currently, if the search returns with no results, the worksheet DataTable is STILL added to the DataSet. I left the debug code to modify if you only want to add worksheets that contained results.

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

The GetDTColumnsFromWorksheet takes a worksheet and returns a DataTable. The data table returned will have the same number of columns as there are returned from “UsedRange` from the worksheet. An extra column is added to display where in the worksheet the cell is found. It is in an RXXCXX format in the first column. If a cell in a column in the used range has no value, the string “???XX” will be used. This is for cases where there is an empty column.

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

The FillTableWithSearchResults method takes a range to search through, a string to search for and finally a data table to add the successful searches to. The DataTable passed in has already been created and the columns have been initialized.

I am not sure if this is the best way to handle Excels Find/FindNext methods. Therefore, I hope I have this correct. When the Find is used on a range for the first time, it returns the first found cell that matches what it is searching for. This retuned range is a cell address of the “first” found item. From my understanding, the FindNext will obviously return the NEXT found item. The issue is that when it finds the LAST item and searches for the next one, it simply starts over at the beginning. Therefore, the stopping condition for the loop would be when the cell address of the NextFind matches the cell address of the “first” Find. This will require that we save the “first” Find’s cell address. Below is one approach to this conundrum.

Create two ranges: one startRange to hold the starting “first” Find, another currentRange to hold the currently “found” range. First, a check is made to make sure there is something to search in. If there is at least one row to search in, the startRange is set from the “first” Find. This is the cell address we need to stop at when using FindNext. If there is at least one item found, then we can search for the next one and enter the FindNext loop. Simply set currentRange to the NextFind add the startRange from the “first” Findto the data table then finally enter the FindNext loop such that it will continue using FindNext and adding new rows to the data table until the currentRange cell address Equals the startingRange cell address. This indicates that FindNext has looped back to the beginning and the search is complete. The AddExcelRowToDataTable adds the found row to the data table (below).

Note: currently this code allows duplicate entries in cases where the searched for string is found in more than one column in the same row. There will be one row entry in the grid for every found search string in the rows columns. Example: if row 5 has the searched for string in columns 4, 6 and 8, there will be a row for R5C4, R5C6 and R5C8. I did not filter this to remove duplicate rows.

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

The AddExcelRowToDataTable method takes a used range to get the data from, a range row to add to the third given parameter DataTable. Again a little hacky, a check is made to make sure that there are not more columns in the used range than there are in the data table. A row Index is obtained to indicate which row in the used range to add to the data table. A DataRow dr is created from the given DataTable dt to make sure the column schemas are the same. The first column is going to be the column we added earlier to display the RXXCXX location column of the found item. Add the extra column data then loop through the rest of the columns adding the worksheet values to the DataRow. After the all the column values have been added, the DataRow is added to the 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);
    }
  }
}

The GetRowIndexOfFoundItem takes a cell range and returns the (int) row index from the cells string RXXCXX address.

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

Method to fill the combo box with worksheet names after the search has finished.

private void FillComboBoxWithSheetNames() {
  cbWorksheetNames.Items.Clear();
  foreach (System.Data.DataTable dt in ds.Tables) {
    cbWorksheetNames.Items.Add(dt.TableName);
  }
}

The combo boxes SelectedIndexChnged event is wired up and uses the combo boxes selected index to determine which DataTable to display in the grid.

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

Finally some resource cleanup.

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

Sorry for the long winded answer, I hope it helps.




回答2:


First, you are only finding the "first" row in each sheet because it is searching for a named range called TB_First_Name.Text in each sheet - there can only be at most one of those in each sheet.

Secondly, there seems to be a typo in your code - I believe DataGridView gdv_data = new DataGridView(); should be DataGridView DGV_data = new DataGridView();

Finally, seems from other answers (e.g. https://stackoverflow.com/a/46044387/3661120) the best way to populate a datagridview row from excel would be this kind of way:

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


来源:https://stackoverflow.com/questions/48871143/how-to-insert-specific-excel-row-into-datagridview-using-excel-rowindex

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!