Excel Interop - insert & add data by row - c#

Hey guys I need to insert empty rows below a certain row in an excel and then add data into those empty rows I inserted...
So far I am able to create empty rows but I am having a hell of a time trying to figure out how to set Range.Value to an array of type String
Method for inserting Rows:
private void shiftRows(int from, int numberof)
{
from++;
Range r = oXL.get_Range("A" + from.ToString(), "A" + from.ToString()).EntireRow;
for (int i = 0; i < numberof; i++)
r.Insert(Microsoft.Office.Interop.Excel.XlInsertShiftDirection.xlShiftDown);
}
// so this would shift the below rows by numberof times.
This method is currently what I am stuck on... which is inserting an array into the new rows one row at a time
public void inputRowData(string[] data, int rds)
{
int bestRow = getRowByRDS(rds);
string val = getValueOfCell(bestRow, 6);
if (val == null || val.Equals(""))
{
shiftRows(bestRow, data.Length);
string[] formatedData = formatOutput(bestRow, data);
for (int i = 0; i < formatedData.Length; i++)
{
Range r = oSheet.get_Range((bestRow + i).ToString() + ":" + (bestRow + i).ToString());
r.set_Value(formatedData[i].Split('\t'));
// have tried r.Value = formatedData[i].Split('\t')
// formatedData is an array of string which contains data for each cell seperated by a tab
}
}
else
{
Console.WriteLine("Line has some information already, skipping 1 more");
shiftRows(bestRow, data.Length + 1);
}
}

I strongly advise you:
NOT to insert rows but just write empty row instead (safety and performance)
to set a big array object and do only ONE write in excel (performance)
example (i kept the shiftrows but you should really get rid of it):
public void inputRowData(string[] data, int rds)
{
int bestRow = getRowByRDS(rds);
string val = getValueOfCell(bestRow, 6);
if (val == null || val.Equals(""))
{
shiftRows(bestRow, data.Length);
string[] formatedData = formatOutput(bestRow, data);
// transform formated data into string[,]
var string[][] splitedData = formatedData.Select(s => s.Split('\t')).ToArray();
var colCount = splitedData.Max(r => r.Lenght);
var excelData = new string[splitedData.Length, colCount]
for (int i = 0; i < splitedData.Length; i++)
{
for (int j = 0; j < splitedData[i].Length; j++)
{
excelData[i,j] = splitedData[i][j];
}
}
oSheet.get_Range("A" + bestRow.ToString()).Resize(splitedData.Length, colCount).Value = excelData;
}
else
{
Console.WriteLine("Line has some information already, skipping 1 more");
shiftRows(bestRow, data.Length + 1);
}
}

Related

How to programatically insert lines in csv file based on quantity of specific record

I have a program that runs a SQL query and returns data to a datagridview.
I then write the data to a csv file which works great.
However, I need to add a line in the csv for each item based on the quantity of the record.
The first record (highlighted in blue) has a quantity of 12, so I need to insert 12 lines containing the information from the first record, and so forth down the list.
This is what I would like to show :
Below is the code that I am using to create the csv file.
string strValue = string.Empty;
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
for (int j = 0; j < dataGridView1.Rows[i].Cells.Count; j++)
{
if (!string.IsNullOrEmpty(dataGridView1[j, i].Value.ToString()))
{
if (j > 0)
strValue = strValue + "," + dataGridView1[j, i].Value.ToString();
else
{
if (string.IsNullOrEmpty(strValue))
strValue = dataGridView1[j, i].Value.ToString();
else
strValue = strValue + Environment.NewLine + dataGridView1[j, i].Value.ToString();
}
}
}
}
string strFile = #"\\\\Path_To_CSV\\DynamicsPartsLabels.csv";
if (File.Exists(strFile) && !string.IsNullOrEmpty(strValue))
{
File.WriteAllText(strFile, strValue);
}
Is there a way to read the last column in the datagridview (Quantity) and create a line in a csv for each record based on the number in that column?
Try changing the code to:
StringBuilder rows = new StringBuilder();
var row = string.Empty;
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
row = string.Join(",", dataGridView1.Rows[i].Cells.Cast<DataGridViewCell>().Select(x => x.Value));
int.TryParse(dataGridView1.Rows[i].Cells["Quantity"].Value?.ToString(), out var quantity);
for (int q = 0; q < quantity; q++)
{
rows.AppendLine(row);
}
}
var strValue = rows.ToString();

How to convert nested for loops with if condition to LINQ

I have a horrible method that extracts data from a DataTable and converts it to a desirable formatted DataTable. I'm sure there is a much nicer way to do this in LinQ but I'm not really experienced with it. I would appreciate if somebody could show me a nicer solution.
Heres the code
private static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
// The number of Locale colums included in the excel file.
for (int x = 0; x < languages; x++)
{
// The total number of friendlynames-keys / language included in the excel.
for (int j = 0; j < dtExtracted.Rows.Count; j++)
{
var row = dtExtracted.Rows[j];
DataRow tempRow = importDt.NewRow();
// Filling in the 3 columns. (FriendlyName - LocaleID - Text)
for (int i = 0; i <= 2; i++)
{
if (i == 0)
{
tempRow[i] = row[i]; // Friendly names: This is always going to be column 1 [0].
}
else if (i == 1)
{
tempRow[i] = Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
}
else
{
tempRow[i] = row[x + 1];
}
}
importDt.Rows.Add(tempRow);
}
}
}
i would rewrite inner for loops
for (int x = 0; x < languages; x++)
foreach (DataRow row in dtExtracted.Rows)
importDt.Rows.Add
(
row[0],
Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
row[x + 1]
);
DataTable.Rows collection has overload of Add method, which accept an array of objects: Add
I am not quite sure if LINQ could be of any help here, but apart from rewriting the entire mapping logic I would at least split this method into two:
private static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
// The number of Locale colums included in the excel file.
for (int x = 0; x < languages; x++)
{
// The total number of friendlynames-keys / language included in the excel.
for (int j = 0; j < dtExtracted.Rows.Count; j++)
{
AddRow(importDt, dtExtracted, dtExtracted.Rows[j], x+1);
}
}
}
private static void AddRow(DataTable table, DataTable dtExtracted, DataRow originalRow, int language)
{
var row = table.NewRow();
row[0] = originalRow[0];
row[1] = Regex.Match(dtExtracted.Columns[language].ToString(), #"\d+").Value;
row[2] = originalRow[language];
table.Rows.Add(row);
}
You can write something like this but in this case is more like abuse of Linq but if you do it for educational purposes ..The main power of LINQ is when you want to enumerate or filter for example collections not in cases like this.
public static void ExtractImportLayoutFromExcelDt(DataTable importDt, DataTable dtExtracted, int languages)
{
Enumerable.Range(0, languages)
.ToList().ForEach(x =>
{
Enumerable.Range(0, dtExtracted.Rows.Count)
.ToList().ForEach(j =>
{
var row = dtExtracted.Rows[j];
DataRow tempRow = importDt.NewRow();
AddRow(importDt, dtExtracted, x, row, tempRow);
});
});
}
private static void AddRow(DataTable importDt, DataTable dtExtracted, int x, DataRow row, DataRow tempRow)
{
for (int i = 0; i <= 2; i++)
{
if (i == 0)
{
tempRow[i] = row[i]; // Friendly names: This is always going to be column 1 [0].
}
else if (i == 1)
{
tempRow[i] = Regex.Match(dtExtracted.Columns[x + 1].ToString(), #"\d+").Value; // LocaleIDs: Getting rid of non numeric characters from this column.
}
else
{
tempRow[i] = row[x + 1];
}
}
importDt.Rows.Add(tempRow);
}

Excel data entry from List<[]> is slow, is there a better algorithm design for this?

I have an algorithm that takes a list of arrays and enters them into an Excel file, however it is very slow. Is there a better design for this algorithm?
public void WriteToExcel(List<string[]> parsedData, string path, string fileName)
{
// Get the Excel application object.
Excel.Application xlApp = new Excel.Application();
// Make Excel visible.
xlApp.Visible = true;
Excel.Workbook workbook = xlApp.Workbooks.Add(Excel.XlWBATemplate.xlWBATWorksheet);
Excel.Worksheet sheet = (Excel.Worksheet)xlApp.Worksheets[1];
sheet.Select(Type.Missing);
//Loop through arrays in parsedData list.
for (var lstElement=0;lstElement<parsedData.Count;lstElement++)
{
//Loop through array.
for(var arryElement = 0; arryElement<parsedData[lstElement].Count(); arryElement++)
{
sheet.Cells[lstElement + 1, arryElement + 1] = parsedData[lstElement][arryElement];
}
}
// Save the changes and close the workbook.
workbook.Close(true, fileName, Type.Missing);
// Close the Excel server.
xlApp.Quit();
}
When working with Office interop, the slowest part are the inter process calls which happen anytime you access some property or method of the automation class/interface.
So the optimization goal should be to minimize the roundtrips (inter process calls).
In your particular use case, instead of setting values cell by cell (i.e. doing a lot of calls), there is fortunately a way to set values of a whole Excel range with one call by passing array of values. Depending of how many columns contains your data, the following modification should give you a significant speedup.
The significant part:
//Loop through arrays in parsedData list.
int row = 1, column = 1;
object[] values = null; // buffer - see below. Avoids unnecessary allocations.
for (var lstElement = 0; lstElement < parsedData.Count; lstElement++)
{
var data = parsedData[lstElement];
if (data == null || data.Length == 0) continue;
if (data.Length == 1)
{
// Single cell
sheet.Cells[row, column] = data[0];
}
else
{
// Cell range
var range = sheet.Range[CellName(row, column), CellName(row, column + data.Length - 1)];
// We can pass the data array directly, but since it's a string[], Excel will treat them as text.
// The trick is to to pass them via object[].
if (values == null || values.Length != data.Length)
values = new object[data.Length];
for (int i = 0; i < data.Length; i++)
values[i] = data[i];
// Set all values in a single roundtrip
range.Value2 = values;
}
row++;
}
Helpers used:
static string CellName(int row, int column)
{
return ColumnName(column) + row;
}
static string ColumnName(int column)
{
const int StartLetter = 'A', EndLetter = 'Z', LetterCount = EndLetter - StartLetter + 1;
int index = column - 1;
var letter = (char)(StartLetter + (index % LetterCount));
if (index < LetterCount) return letter.ToString();
var firstLetter = (char)(StartLetter + index / LetterCount - 1);
return new string(new [] { firstLetter, letter });
}
Once you get the idea, you can get even better performance by extending the above to handle multi row ranges like this (the most important thing in this case is to use 2d array for values):
const int MaxCells = 1 * 1024 * 1024; // Arbitrary
var maxColumns = parsedData.Max(data => data.Length);
var maxRows = Math.Min(parsedData.Count, MaxCells / maxColumns);
object[,] values = null;
int row = 1, column = 1;
for (int lstElement = 0; lstElement < parsedData.Count; )
{
int rowCount = Math.Min(maxRows, parsedData.Count - lstElement);
if (values == null || values.GetLength(0) != rowCount)
values = new object[rowCount, maxColumns];
for (int r = 0; r < rowCount; r++)
{
var data = parsedData[lstElement++];
for (int c = 0; c < data.Length; c++)
values[r, c] = data[c];
}
var range = sheet.Range[CellName(row, column), CellName(row + rowCount - 1, column + maxColumns - 1)];
range.Value2 = values;
row += rowCount;
}

Excel Interop Efficiency get row number with value in a column

Currently I am using the below segement of code to get a row with a value int in the 3rd column.
private int getRowByRDS(int id)
{
int bestfit = -1;
Boolean foundOne = false;
for (int i = 2; i < oSheet.Rows.Count; i++)
{
string val = getValueOfCell(i, 3);
if (val == null)
continue;
int rds = int.Parse(val);
if (rds == id)
{
bestfit = i;
foundOne = true;
}
else
if (foundOne)
return bestfit;
}
return bestfit;
}
The issues is that this is pathetically show at large amount of rows.
Can someone suggest a better way of searching col 3 for a int and returning the last row # that it is in.
For Joe:
public void inputRowData(string[] data, int rds)
{
int bestRow = getRowByRDS_a(rds);
string[] formatedData = formatOutput(bestRow, data);
string val = getValueOfCell(bestRow, 6);
if (val != null)
{
shiftRows(bestRow, data.Length);
bestRow++;
}
else
shiftRows(bestRow, data.Length - 1);
// transform formated data into string[,]
string[][] splitedData = formatedData.Select(s => s.Split('\t')).ToArray();
var colCount = splitedData.Max(r => r.Length);
var excelData = new string[splitedData.Length, colCount];
for (int i = 0; i < splitedData.Length; i++)
{
for (int j = 0; j < splitedData[i].Length; j++)
{
excelData[i, j] = splitedData[i][j];
}
}
oSheet.get_Range("A" + bestRow.ToString()).Resize[splitedData.Length, colCount].Value = excelData;
MainWindow.mainWindowDispacter.BeginInvoke(new System.Action(() => MainWindow.mainWindow.debugTextBox.AppendText("Done with " + rds + " input!" + Environment.NewLine)));
}
private void shiftRows(int from, int numberof)
{
from++;
Range r = oXL.get_Range("A" + from.ToString(), "A" + from.ToString()).EntireRow;
for (int i = 0; i < numberof; i++)
r.Insert(Microsoft.Office.Interop.Excel.XlInsertShiftDirection.xlShiftDown);
}
Here's how I'd do it:
Get a Range corresponding to the column you're interested in
Get the UsedRange of the sheet you're interested in
Get a Range that is the intersection of the above two ranges
Get the value of this Range, which will be an array of values from the column you're interested in
You can then iterate through this array to find the value you want, then use its index to derive the row number.
The number of calls to the Excel is O(1) with the above method, as opposed to O(n) in your version.
You could also use the Find method of the Excel.Range object. This method returns a Excel.Range object if a match is found or null. Follow the first three steps that Joe described to create the search range. If the Find method returns a valid Range, you can then use it's Row Property.
P.S.: Sorry Joe I wanted to upvote your answer, but I am not allowed to do so yet.

Is it possible to populate a DataGridView with alternating vertical columns?

I need to display data that will be hybrid "hard-coded" strings and data from a database. Specifically, every even-numbered column contains string values that are NOT from the database, and every odd-numbered column contains data. So column 1, for example, will contain values 1 through 12 from the database, so that the first two columns look something like this (and the same pattern repeats several times):
00:00 BoundVal1
00:15 BoundVal2
. . .
02:45 BoundVal12
Is this possible?
Right now I'm using a TableLayoutPanel for this, but the coding for that is a little pretzly (not a Steely Dan reference).
You could just build a DataTable object and set it as DataGridView's DataSource.
I finally got this working, based on code from Mohamed Mansour at http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/ca89a857-6e17-44a7-8cdb-90d64a0a7bbe
int RowCount = 12;
Dictionary<int, string> PlatypusPairs;
. . .
private void button3_Click(object sender, EventArgs e) {
// Retrieve data as dictionary
PlatypusPairs = InterpSchedData.GetAvailableForPlatypusAndDate(oracleConnectionTestForm, 42, DateTime.Today.Date);
int ColumnCount = 16;
// Add the needed columns
for (int i = 0; i < ColumnCount; i++) {
string colName = string.Format("Column{0}", i + 1);
dataGridView1.Columns.Add(colName, colName);
}
for (int row = 0; row < RowCount; row++) {
// Save each row as an array
string[] currentRowContents = new string[ColumnCount];
// Add each column to the currentColumn
for (int col = 0; col < ColumnCount; col++) {
currentRowContents[col] = GetValForCell(row, col);
}
// Add the row to the DGV
dataGridView1.Rows.Add(currentRowContents);
}
}
private string GetValForCell(int Row, int Col) {
string retVal;
if (Col % 2 == 0) {
retVal = GetTimeStringForCell(Row, Col);
} else {
retVal = GetPlatypusStringForCell(Row, Col);
}
return retVal;
}
private string GetTimeStringForCell(int Row, int Col) {
const int TIME_INCREMENT_STEP = 15;
var dt = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);
dt = dt.AddMinutes(((Col * (RowCount / 2)) + Row) * TIME_INCREMENT_STEP);
return dt.ToString("HH:mm");
}
private string GetPlatypusStringForCell(int Row, int Col) {
int multiplicand = Col / 2;
string val = string.Empty;
int ValToSearchFor = (multiplicand * RowCount) + (Row + 1);
if (PlatypusPairs.ContainsKey(ValToSearchFor)) {
PlatypusPairs.TryGetValue(ValToSearchFor, out val);
if (val.Equals(0)) {
val = string.Empty;
}
}
return val;
}

Categories

Resources