Strange behavior from Excel.Worksheet.Cells[row, column] - c#

I have the following method:
public static object getValue(Excel.Worksheet sheet, int row, int col)
{
Excel.Range r = sheet.Cells[row, col] as Excel.Range;
//I've also tried using sheet.get_Range(cell, cell); here, with the same result
if (r.Row != row || r.Column != col)
{
//Why does this debug statement sometimes print?
Debug.Print("Tried to read (" + row.ToString() + ", " + col.ToString() + ") but got (" + r.Row.ToString() + ", " + r.Column.ToString() + ")");
}
return r.Value2;
}
Based on my understanding of Excel.Worksheet.Cells[row, column], my code should never enter the if statement. However, when I call getValue repeatedly to read multiple cells, every so often the row and column of the range it returns are different than the row and column I called Excel.Worksheet.Cells with.
Example output: "Tried to read (19, 1) but got (56, 5)"
Furthermore, if I break in the if statement, then rewind my execution point and run Excel.Worksheet.Cells again, I get the correct range.
Why could this be?

This isn't an answer to your direct question, but may solve the problem.
Is getValue in a tight loop? If so then I would approach it a different way. Since Accessing Cells and Value2 are COM-calls they are slower than normal array accessors. If you have a particular range you're looping over I would get the array of values for that range in memory and access the array directly rather than looping through the cells.
e.g. instead of
for(int r = 1; r <= 10; r++)
for(int c = 1; c <= 20; c++)
double val = sheet.Cells[r,c];
do
object[,] values = sheet.Range("A1:J20").Value
for(int r = 1; r <= 10; r++)
for(int c = 1; c <= 20; c++)
double val = values[r,c];
This may fix your problem by removing the COM access from tight loop and eliminating whatever problem is giving you the strange results.

Related

How to iterate on Excel.Range with an index in c#

I'm writing a c# Add in for excel that takes values from a column of an excel sheet and does some operations. This is a part of the code that I wrote:
Excel.Range aqRange = currentSheet.Range["C3:C" + cRowCount];
Excel.Range vcRange = currentSheet.Range["D3:D" + cRowCount];
Excel.Range k0R = currentSheet.Range["F2"];
double[] sgVett = new double[cRowCount];
foreach (Range aq in aqRange)
{
if (i == 0) sgVett[i] = k0R.Value + aq.Value;
else sgVett[i] = sgVett[i - 1] + aq.Value;
i++;
}
i = 0;
foreach (Range vc in vcRange)
{
if (i == 0) sgVett[i] -= vc.Value;
else sgVett[i] -= vc.Value;
i++;
}
I want to know if is there some way to do something like this:
for(int j = 0; j<cRowCount; j++) {
if (i == 0) sgVett[i] = k0R.Value + aqRange[i].Value;
else sgVett[i] = sgVett[i - 1] + aqRange[i].Value;
}
I know that I can't use a Excel.Range as a vector, but is there some way to use an index to scroll aqRange and vcRange or, at least, if it's possible to use just one for or one foreach instead of two (as I done).
You can use the Range's Cells property a bit like a 2 dimensional array.
It is important to note that although the syntax used is similar to a C# 2D array (Cells[RowIndex, ColIndex]), the Cells property accesses a COM object that uses 1 as it's start offset, rather than 0.
So you should be able to write something like :
for(int j = 1; j<=cRowCount; j++)
{
if (i == 0) sgVett[i] = k0R.Value + aqRange.Cells[j,1].Value;
}
Also see here : Fastest way to get an Excel Range of Rows & How do I get an Excel range using row and column numbers in VSTO / C#?

Adding a break to a bubble sort in case the array is already sorted

I currently have a (somewhat messy) bubble sort of an object array called "sorted", the code is as follows
object storage = 0;
for (int i = 0; i < sorted.Length; i++)
{
for (int c = 0; c < sorted.Length - 1; c++)
{
if (sorted[c].ToString().CompareTo(sorted[c + 1].ToString()) > 0)
{
storage = sorted[c + 1];
sorted[c + 1] = sorted[c];
sorted[c] = storage;
}
}
return sorted;
Problem is that this function always loops through the array , no matter what. Hypothetically speaking the "sorted" array could be a large array and just so happen to be sorted already, in which case the function would still scan the array and work for some time, which I want to prevent.
So the question is, how do I stop the loop properly in case the array is sorted already?
A bubble sort bubbles the largest (smallest) element of an array towards the end of an array. This is what your inner loop does.
First of all you can take advantage of the knowledge that after n iterations, the last n elements are sorted already, which means that your inner loop doesn't need to check the last n elements in the (n+1)th iteration.
Secondly, if the inner loop doesn't change anything, the elements must be in sequence already, which is a good point for a break of the outer loop.
Since you're doing this as a practising exercise, I'll leave the coding up to you :-)
Why don't you use OrderBy instead of sorting it yourself?
sorted = sorted.OrderBy(s=> s).ToArray();
If you insist to use the bubble sort, you can do this:
bool changed;
for (int i = 0; i < sorted.Length; i++)
{
changed = false;
for (int c = 0; c < sorted.Length - 1; c++)
{
if (sorted[c].ToString().CompareTo(sorted[c + 1].ToString()) > 0)
{
changed = true;
storage = sorted[c + 1];
sorted[c + 1] = sorted[c];
sorted[c] = storage;
}
if(!changed) break;
}
I'm setting changed to false each time in the first loop. If there was no changes to the end, then the array is already sorted.
Try this:
object storage = 0;
for (int i = 0; i < sorted.Length; i++)
{
bool swapped = false;
for (int c = 0; c < sorted.Length - 1; c++)
{
if (sorted[c].ToString().CompareTo(sorted[c + 1].ToString()) > 0)
{
storage = sorted[c + 1];
sorted[c + 1] = sorted[c];
sorted[c] = storage;
swapped = true;
}
}
if (!swapped)
{
break;
}
}
If it gets through a pass without swapping then the array is ordered so it will break.

Displaying the values of a multidimensional array in a MessageBox

I am just now learning about multi-dimensional arrays and message boxes. I am currently have a issue in creating 2 columns in my message box. I can currently get it to print up the random numbers I need, but only in one column. Thanks for the help!
string msg = "";
Random numb = new Random();
int[,] thing = new int[ 10, 2 ];
thing[0, 0] = numb.Next(0,10);
thing[0, 1] = numb.Next(0,10);
thing[1, 0] = numb.Next(0,10);
thing[1, 1] = numb.Next(0,10);
thing[2, 0] = numb.Next(0,10);
thing[2, 1] = numb.Next(0,10);
thing[3, 0] = numb.Next(0,10);
thing[3, 1] = numb.Next(0,10);
thing[4, 0] = numb.Next(0,10);
thing[4, 1] = numb.Next(0,10);
thing[5, 0] = numb.Next(0,10);
thing[5, 1] = numb.Next(0,10);
thing[6, 0] = numb.Next(0,10);
thing[6, 1] = numb.Next(0,10);
thing[7, 0] = numb.Next(0,10);
thing[7, 1] = numb.Next(0,10);
thing[8, 0] = numb.Next(0,10);
thing[8, 1] = numb.Next(0,10);
thing[9, 0] = numb.Next(0,10);
thing[9, 1] = numb.Next(0,10);
foreach (int x in thing)
msg = msg + x + "\n";
MessageBox.Show(msg, "Table");
Change this:
foreach (int x in thing)
msg = msg + x + "\n";
MessageBox.Show(msg, "Table");
To :
for (int i = 0; i < thing.GetLength(0); i++)
msg += String.Format("{0} {1}\n", thing[i, 0], thing[i, 1]);
//msg += thing[i, 0] + " " + thing[i, 1] + "\n";
MessageBox.Show(msg, "Table");
Explanation:
Method GetLength(0) returns length of dimension for our x (it's basically like array.Length for simple array but with specific dimension) and we use i variable and for loop to iterate through it. In y we have only 2 elements so we can just use [0] and [1] indexes as we know there are only two elements there. This explains the line:
msg += thing[i, 0] + " " + thing[i, 1] + "\n";
Be careful with concatenating string like that, as if you don't add this space " " between your numbers compiler will just add them, so element like [33, 10] will be concatenated to string like : "43 \n" instead of "33 10 \n".
P.S.
So let's use String.Format method to make sure it's formatted well :
msg += String.Format("{0} {1}\n", thing[i, 0], thing[i, 1]);
I suspect that this question is a duplicate of an existing one, but I cannot find the appropriate precedent. So maybe we are in need of an answer explaining how to format a two-dimensional array for output. This is such an answer. :)
First thing to keep in mind is that the fact that you are displaying this using MessageBox is only barely relevant. What you are really asking is how to format a two-dimensional array as a string, where that string includes line-breaks between rows in your data, and the columns are lined up in the output. Once you have that string, you can display in any context that uses a fixed-space font (which is actually not the MessageBox…but it is not hard to create your own Form to use as a message box, where you can specify the font used to display the text) and have the output appear as you seem to be asking for.
For example, here's a console program that does what you want:
static void Main(string[] args)
{
StringBuilder sb = new StringBuilder();
Random random = new Random();
int[,] array = new int[10, 2];
for (int i = 0; i < array.GetLength(0); i++)
{
for (int j = 0; j < array.GetLength(1); j++)
{
array[i, j] = random.Next(10);
if (j > 0)
{
sb.Append(", ");
}
sb.Append(array[i, j]);
}
sb.AppendLine();
}
Console.WriteLine(sb);
}
Note that I have consolidated the initialization of the array and the formatting of the values into a single piece of code. I have also used nested for loops to index each individual element in the array; this allows two things:
To initialize the array using loops instead of explicitly writing out each element as you did in your example.
To make it easy to recognize the end of a row in the array so that a line-break can be added to the output.
In your example, your random numbers are only ever a single digit. But if you were to want to format numbers that could vary in their number of digits, you would want to pad the output with spaces for the shorter numbers, so that the numbers in the columns of your output lined up. You can do that by adding a width specifier to the output, like this:
sb.AppendFormat("{0,2}", array[i, j]);
In the above, rather than using the default formatting as the previous code example does, I am explicitly specifying a format (and calling AppendFormat() instead of Append() to do so), and in that explicit format, I have included the text ,2, to indicate that I want the output string to have a minimum width of two characters. The method will add spaces as necessary to ensure that.
I have also changed from simple string concatenation to the use of StringBuilder. In your original example, string concatenation is probably not horrible, but it's also not a good habit to get into. When you are repeatedly appending text in a loop, you should always use StringBuilder. Otherwise, an excessive number of objects may be created, and as well an excessive amount of time spent copying character data may be spent. Using StringBuilder allows the text to be concatenated more efficiently.

C# pincer for loop

Trying to implement a simple for loop that works 0 and the end at the same time, the problem i'm running into though is that it only works on even amounts of items. For odd numbered items it does'nt return the last item.
int x = 10;
for(int i=0; i!= x; i++)
{
Console.WriteLine(i + " " +x + " ");
x--;
}
In the code above, 5 won't be printed because at that time i is equal to x which violates the loop condition and exits the loop. Hence the value is not printed. Changing the loop condition from i != x to i<=x will fix the problem. This is shown below.
int x = 10;
for (int i = 0; i <= x; i++, x--)
{
Console.WriteLine(i + " " + x + " ");
}
Hope it helps :)

Accumulating row values c#

I am looking to accumulate the value of each cell and display this in a "total column" at the end of the grid in flex cell. Which is the best way of going about this?
I have following code so far which I don't think is correct!
int total = 0;
for (int B = 3; B < 27; B++)
{
total = total + int.Parse(this.grid2.Cell(0, B).ToString());
this.grid2.Cell(0, 27).Text = total.ToString();
}
Your code seems correct to me, but it can be improved:
int total = 0;
for (int B = 3; B < 27; B++)
{
total = total + Convert.ToInt32(this.grid2.Cell(0, B));
}
this.grid2.Cell(0, 27).Text = total.ToString();
Only put things that need to be repeated inside the for-loop. If it only needs to be done once, put it after (or before if possible) the loop.
Also, try to use more meaningfull names for your variables. I would change 'B' to either 'i' if you don't want to give it a long name, or to 'column', so you (and other developers, like us) know what it stands for.
BTW, the code calculates the sum for one row (the first one). If you want to do it for every row, then you will need a double for-loop:
for(int row = 0;row < numRows; row++){
int total = 0;
for (int column = 3; column < 27; column++)
{
total = total + Convert.ToInt32(this.grid2.Cell(row, column));
}
this.grid2.Cell(row, 27).Text = total.ToString();
}

Categories

Resources