I am having an issue getting the array "whyWontYouWork" to populate with a value. In the following example, the value of rangeNames[j] is "$A$1:$A$10".
the string "group" will fill in correctly as "$A$1:$A$10" but the line above it shows up as "The name 'whyWontYouWork' does not exist in this context", so I am at a loss since it works once, and when I try to split the string, I get nothing. Any ideas?
private void CutStates(string[] sheetNames,string[] rangeNames, string[] idNums)
{
Excel.Application xlApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
Excel.Workbook wkbk = null;
wkbk = xlApp.ActiveWorkbook;
for (int i = 0; i < idNums.Length; i++)
{
string stateId = idNums[i];
for (int j = 0; j < sheetNames.Length; j++)
{
string[] sheet = sheetNames[j].Split('!');
List<string> rowsToDelete = new List<string>();
List<string> reverseDelete = new List<string>();
string tabName = sheet[0];
string[] whyWontYouWork = rangeNames[j].Split(':');
string group = rangeNames[j];
Excel.Range range = wkbk.Sheets[tabName].Range[group];
foreach (Excel.Range cell in range)
{
string val2 = cell.Value.Substring(0, 2);
string cellAdd = cell.Address.ToString();
if (val2 != stateId)
{
string delCell = cell.Address.ToString();
rowsToDelete.Add(delCell);
}
}
reverseDelete = rowsToDelete.ToList();
reverseDelete.Reverse();
foreach (string item in reverseDelete)
{
Excel.Range delete = wkbk.Sheets[tabName].Range[item];
delete.Delete();
}
}//j
}//i
}
I plan on using the first part ($A$1) as the starting point of a group to delete from the top down, and the second part ($A$10) to be the starting point from the bottom up to delete.
I want to iterate through the cells in "group" one at a time and if the first two characters don't match the two character stateId. At that point I move to the next until I find one that matches, move back one row grab that address and then grab the start of the list ($A$1) and select from there to the last row that doesn't match, and delete the block. I will do the same for the row after the last match to the end ($A$10). I would do this line by line but I do this over 15K rows so one at a time is terribly slow. I hope that makes more sense.
From code it seems you are iterating through sheets (for (int j = 0; j < sheetNames.Length; j++)) so it is possible that for first sheet you have rangeNames[j] value ("$A$1:$A$10") and on other sheets you don't.
From what I remember Excel by default creates 3 sheets, so that's probably the problem.
Thanks to A few of you pointing out that the variable wasn't used, I checked my settings and it was optimizing the variable away. I thought I had changed that. Adding a simple console.writeline (instead of using the Watch) pushed the variable into use and made it stick.
Related
I'm looking some help with my code for writing data to an Excel worksheet.
I'm currently writing data to a single cell at a time which is taking around three seconds per cell. As you can imagine, this time really starts to add up when you start to get into hundreds of cells.
I'm aware that there is a method of saving to a range of cells but not entirely sure how to do this.
The document I am writing to is a questionnaire style spread sheet with columns: Number, Question, Yes, No, NA.
I loop through a list of user controls which hold the users selected answer from my form and then write it to the cell.
Any help appreciated, thanks.
if (QuestionList.Count > 0)
{
//loop round rows
for (int i = 0; i < QuestionList.Count; i++)
{
//check that index is not null
if (!String.IsNullOrEmpty(QuestionList[i].Index))
{
//if the char in index is a letter
if (Char.IsLetter(QuestionList[i].Index[0]))
{
worksheet2.Cells[i + 1, 4].Value2 = QuestionList[i].Yes;
worksheet2.Cells[i + 1, 5].Value2 = QuestionList[i].No;
worksheet2.Cells[i + 1, 6].Value2 = QuestionList[i].NA;
}
}
}
}
The simple way to read multiple cell values in one go is to create a new Excel range, specifying the range of cells that you want to read.
You can then take the values within the range and put them into a multidimensional object array.
Then simply refer back to the cell position within the array to get values.
This will do one single read to get multiple cell values rather than a single read for each cell.
string name;
string dob;
string address;
//excel range
Excel.Range range = (Excel.Range)xlWorksheet1.Range["A1", "I10"];
//range array
object[,] rangeValueArray = range.Value2;
//cell 1A
if (rangeValueArray[1,1] != null)
{
name = rangeValueArray[1, 1].ToString();
}
//cell 1B
if (rangeValueArray[1, 2] != null)
{
dob = rangeValueArray[1, 2].ToString();
}
//cell 1C
if (rangeValueArray[1, 3] != null)
{
address = rangeValueArray[1, 3].ToString();
}
I know how to read through all lines of a file and replace a selected line when a certain sequence of characters is found. The issue that I'm having at the moment is that I'm stuck with a structure that has no unique string to search for except for the main class name. So for example I'd know that the name of the class is "List_of_boats" and the structure tells me that 11 lines underneath that line is the value "items=2;" which I need to change to a certain value, depending on the amount of items I want to insert there.
Is there a way to use the foreach function or something to do this? I have provided some code that I've already got so far but I'm kind of stuck now.
var lines = File.ReadAllLines(fileToMerge);
var linID = 0;
foreach (var line in lines) {
if (line.Contains("ace_arsenal_saved_loadouts")) {
var newlinID = linID + 11; //go from ace_arsenal_saved_loadouts to "items=x;" to change number of items.
}
linID = linID + 1;
}
Convert the enumerable to an array, and loop through it by index:
var lines = File.ReadAllLines(fileToMerge).ToArray();
for (var linID = 0; linID < lines.Length; linID++) {
var line = lines[linID];
if (line.Contains("ace_arsenal_saved_loadouts")) {
var newlinID = linID + 11; //go from ace_arsenal_saved_loadouts to "items=x;" to change number of items.
}
}
I have the following code:
string result = "";
for(int i=2; i<=excelRange.Rows.Count; i++)
{
result += excelRange.Cells[i, 2].Value2;
}
For an excel file with a couple hundred entries this takes 5 seconds. Is there a more efficient way, perhaps? I only want the values from B2 to Bn.
Yes, there is a more efficient way.
Create a range that exactly matches the cells that you really need.
Get the Value2 property of this range. The result will be an array type.
Iterate through the array
The problem with your approach is the large number of inter-process requests between your application and Excel. Your approach requires two or three requests per cell. The proposed approach is much faster because it requires a few requests up-front but not additional requests per cell.
Note that this works up to about 4000 cells. If you need to process more cells, you will need to split it into several ranges, each one containing less than 4000 cells.
Update
Assuming Excel is already running, it would look something like this (the correct number of rows in the B column is automatically selected):
var excelApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
Excel._Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet;
var range = (Excel.Range)workSheet.Range[workSheet.Range["B2"],
workSheet.Range["B2"].End[Excel.XlDirection.xlDown]];
var cellData = (Object[,])range.Value2;
string result = "";
foreach (var cell in cellData) {
result += cell.ToString();
}
The basic skeleton is the following:
var xlApp = new Excel.Application { Visible = true };
var xlBook = xlApp.Workbooks.Open(#"C:\Temp\Results.xlsx");
var xlSheet = xlBook.Sheets[1] as Excel.Worksheet;
var arr = (object[,])xlSheet.Range["B2:B100000"].Value;
var sb = new StringBuilder();
for (int x = 1; x <= arr.GetUpperBound(0); ++x)
{
sb.Append(arr[x, 1]);
}
var final_string = sb.ToString();
// Close workbook, close Excel...
my error is on the very last line, saying my index is out of range. Not sure what the problem is. I would like to continue using a list of lists or lists. I am trying to read a line of a csv file and separate that line into groups if one of the words in that line repeats; for example:
"hey how are you hey whats up"
hey how are you would be in one group and then hey whats up would be in the other group.
string[] ReadDirectory = Directory.GetFiles("C:\\Users\\-------", "*.csv");
List<List<List<string>>> myList = new List<List<List<string>>>();
List<string> CSVlist = new List<string>();
foreach (string file in ReadDirectory)
{
using (StreamReader readFile = new StreamReader(file))
{
int groupIndex = 0;
string line = readFile.ReadLine();
string[] headers = line.Split(',');
Array.Reverse(headers);
CSVlist.Add(headers[headers.Length - 1]);
myList.Add(new List<List<string>>());
for (int i = 0; i < headers.Length; i++)
{
if (headers[i].Contains("repeats") && headers[i + 1].Contains("repeats"))
{
myList.Add(new List<List<string>>());
groupIndex++;
}
myList[0][groupIndex].Add(headers[i]);
}
}
}
the problem resides when i =headers.Length-1, then headers[i + 1] is out of bounds. try:
for (int i = 0; i < headers.Length; i++)
{
if (i<headers.Length-1)
{
if (headers[i].Contains("repeats") && headers[i + 1].Contains("repeats"))
{
myList.Add(new List<List<string>>());
groupIndex++;
}
myList[0][groupIndex].Add(headers[i]);
}
}
Looking at the code, I'm not sure it'll do what you want it too (eg. if headers contains the exact word 'repeats', but this may just be example code so I'll ignore that) - but I'll focus on the error reported.
The exact error you reported is caused by this line:
myList[0][groupIndex].Add(headers[i]);
When you first add a nested list to myList, you don't add a nested list to that first nested list - so when the if statement is false, it tries to add the header into myList[0][0] where the second index is out of range because there is no inner list at myList[0].
Changing
myList.Add(new List<List<string>>());
to something like
var innerGroupList = new List<string>();
var groupList = new List<List<string>>();
groupList.Add(innerGroupList);
myList.Add(groupList);
will resolve the issue, but you won't get your expected outcome from the example data as the word 'repeats' is not there, you would need to do something like save each word in a Hashset, and check each word against that. If it already exists in the dictionary, split it into another group.
I need to build an excel sheet from a list of test-cases in a specific format in order to upload it it to the server.
I've trubles to populate the two dimensional range of "expected" and "actual" in the file.
I use the same methods in order to populate the headers, which is a one-dimensional array, and the steps (which is two-dims).
The flow is:
Defunding the TestCase range (some headers + steps). Let's say: A1 to E14 for the 1st iteration.
Depunding a sub (local) range within the testCase range for the headers (e.g: A1 to C1).
Depunding another sub (local) range within the testCase range for the headers (in my case: D1 to E14).
Populate the two sub-ranges with a test-case values (headers and steps).
Repeat by defunding the next spreadsheet range (A14 to E28 in my case) with same local ranges (steps 2-3), and populate them, and so on...
The source value is a Dictionary which represents the test-case's steps (key = expected and value = actual).
Here is the code I use:
public class TestCase
{
public Dictionary<string, string> steps;
}
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
Workbooks workBooks = excelApp.Workbooks;
Workbook workbook = workBooks.Add(XlWBATemplate.xlWBATWorksheet);
Worksheet worksheet = (Worksheet)workbook.Worksheets[1];
excelApp.Visible = true;
foreach (TestCase testCase in TestCaseList.Items.GetList())
{
Range worksheetRange = GetRangeForTestCase(worksheet);
//The step above is equivalent to:
// Range worksheetRange = worksheet.get_Range("A1", "E14");
//for the first foreach iteration.
Range stepsRange = worksheetRange.get_Range("D1", "E14");
//This is a local range within the worksheetRange, not the worksheet,
//so it is always have to be between D1 to E14 for a 14th steps test case.
//Anyway, it should work at least for the 1st iteration.
//for test evaluation only. All cells between D1 to E14 are filled with "ccc"
test.Value = "ccc";
//This list of pairs which are converted to Array[,] is about to be converted to a two dimensional array
list = new List<object>();
foreach (KeyValuePair<string, string> item in testCase.Steps)
{
//Here I build the inside Array[,]
object[] tempArgs = {item.Key, item.Value};
list.Add(tempArgs);
}
object[] args = { Type.Missing, list.ToArray() };
test.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, test, args);
//Now, all the "ccc" within the Excel worksheet are disapeared, so the Range is correct, but the value of args[] are not set!!
}
The actual results of running this code is that the range is defined (probably correctly) but its values are set to null,
although - I can see the correct values in the args array in run time.
I've also tried to set a wider range and populate it with range.Value = "Pake value" and saw that, after running my peace of code, the correct range of steps become blank!
So, the range is correct, the array is filled with my values, the InvokeMember method is correctly invoked :)
But, all values are set to null..
Help...
One cell or 1dim array can be set via one of the following:
Range range = SetRange();//Let's say range is set between A1 to D1
object[] args = {1, 2, 3, 4 };
//Directly
range.Value = args;
//By Reflection
range.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, range, args);
A 2dim array cannot be directly set, so one has to use the reflection flow to set a matrix of values. This matrix has to be built before the set, like this:
Range range = SetRange();//Let's say range is set between A1 to C5
int rows = 5;
int columns = 3;
object[,] data = new object[rows, columns];
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
//Here I build the inside Array[,]
string uniqueValue = (i + j).ToString();
data[i, j] = "Insert your string value here, e.g: " + uniqueValue;
}
}
object[] args = { data };
range.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, range, args);
As for your issue, all the range set to null, I think this is due to wrong arguments.
Indeed why the Type.Missing in the arguments list?
Hence this should be a step in the right direction:
object[] args = { list.ToArray() };
test.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, test, args);
Moreover list.ToArray will only generate an array of arrays not a matrix, so you should build your matrix differently, e.g.:
object[,] data = new object[14, 2];
int row = 0;
foreach (KeyValuePair<string, string> item in testCase.Steps)
{
//Here I build the inside Array[,]
data[row, 0] = item.Key;
data[row, 1] = item.Value;
++row;
}
object[] args = { data };
And what's the rational behind the use of InvokeMember instead of a simpler:
test.Value = data;
?
Hope this helps...