In my tool, user can choose one configuration (through combobox->multiple datatable) and the respective table will reflect in the excel sheet as per below.Columns (data in rows will differ) that will remain the same for all configuration are Product Name, Serial Name and Length 1 and Total Length. Different configuration will have added columns such as Length 2, Length 3,Length 4 (user will add the data in these rows)etc.
I want to add conditional formatting formula in Total Length column where background cell will turn green if it is in range (minval to maxval) and red when it is out of range. I am stuck with my code without solution.It did not change any color when the user add the data in the excel. Help. Thanks!
Table
private void ManualFormatExcelandAddRules(ExcelWorksheet WS, DataTable DTtoFormat, int ColStartAddOfDT, int RowStartAddOfDT)
{
int colCountofDT = DTtoFormat.Columns.Count;
int rowCountofDT = DTtoFormat.Rows.Count;
double minval = 0;
double maxval = 0;
int flag = 0;
for (int Colno = ColStartAddOfDT; Colno < ColStartAddOfDT + colCountofDT; Colno++)
{
WS.Cells[RowStartAddOfDT, Colno].Style.Border.BorderAround(ExcelBorderStyle.Thin);
for (int RowNo = RowStartAddOfDT + 1; RowNo <= RowStartAddOfDT + rowCountofDT; RowNo++)
{ if (WS.Cells[RowNo, Colno].Text.Contains("to") && WS.Cells[RowNo, ColStartAddOfDT].Text.Contains("DRAM"))
{
string[] GuidelineVal = WS.Cells[RowNo, Colno].Text.Split("to".ToArray(), StringSplitOptions.RemoveEmptyEntries).ToArray();
if (GuidelineVal[0].Trim() != "NA" && GuidelineVal[1].Trim() != "NA")
{
minval = Convert.ToDouble(GuidelineVal[0].Trim());
maxval = Convert.ToDouble(GuidelineVal[1].Trim());
flag = 0;
}
else
flag = 1;
}
else if (WS.Cells[RowNo, Colno].Text == "" && WS.Cells[RowStartAddOfDT + 1, Colno].Text.Contains("to"))
{
if (flag == 0)
{
string _statement = "AND(Convert.ToDouble(WS.Cells[RowNo, Colno].Text) >= minval,Convert.ToDouble(WS.Cells[RowNo, Colno].Text) <= maxval)";
var _cond = WS.ConditionalFormatting.AddExpression(WS.Cells[RowNo, Colno]);
_cond.Formula = _statement;
_cond.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
_cond.Style.Fill.BackgroundColor.Color = Color.Green;
}
else
WS.Cells[RowNo, Colno].Style.Fill.BackgroundColor.SetColor(Color.Red);
WS.Cells[RowNo, Colno].Style.Border.BorderAround(ExcelBorderStyle.Thin);
}
}
}
The conditional formatting expression you use is wrong/contains syntax errors/uses functions that don't exist and that makes that Excel will ignore it as it doesn't understand what it needs to do.
Looking at your code you have 4 variables that make up that expression:
RowNo and ColNo to indicate the cell to apply the conditional formattig to
minval and maxval to be the lower and upper bound of the condition
The following code uses those variables to build up the correct expression:
string _statement = string.Format(
CultureInfo.InvariantCulture,
"AND({0}>={1},{0}<={2})",
new OfficeOpenXml.ExcelCellAddress(RowNo, ColNo).Address,
minval,
maxval );
var _cond = WS.ConditionalFormatting.AddExpression(WS.Cells[RowNo, ColNo]);
_cond.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
_cond.Style.Fill.BackgroundColor.Color = Color.Green;
_cond.Formula = _statement;
Notice that the expression uses only valid Excel functions. You can't mixin .Net statements like Convert.ToDouble. It is also important to use the InvariantCulture for the number conversion, otherwise the separators might get interpreted as an extra parameter in your function.
When you debug this _statement will contain this: AND(A2>=40.2,A2<=44.5) and when applied to the A2 cell, that works as advertised.
Related
I am computing in the large datatable. The table name is templetable.
I am trying to calculate the sum from the 4th column to the last column and save the results to another datatable. But I am getting errors as it says could not find the column "colPnow"
Any ideas?
DataView dv2 = new DataView(SP_dt);
for (int i = 0; i < LoadIDcount; i++)
{
string IDnow = LoadID[i, 0];
dv2.RowFilter = String.Format("Load_ID = '{0}'", IDnow);
DataTable temptable = dv2.ToTable();
for (int j = 0; j < 8760; j++)
{
string colPnow = SP_dt.Columns[j*2 + 4].ColumnName.ToString();
double ColP_sum= (double)temptable.Compute("Sum(colPnow)", String.Format("Load_ID = '{0}'", IDnow));
string colQnow = SP_dt.Columns[j*2 + 5].ColumnName.ToString();
double ColQ_sum = (double)temptable.Compute("Sum(colQnow)", String.Format("Load_ID = '{0}'", IDnow));
Load_dt.Rows[i][j * 2 + 2] = ColP_sum;
Load_dt.Rows[i][j * 2 + 3] = ColQ_sum;
}
}
You are not formatting your expression string correctly in the Compute method. Use another String.Format and it will work. Your Compute expression is currently literally equal to "Sum(colPnow)". You want the actual column name string that is stored in your colPnow variable.
Change this part of your code to this:
string colPnow = SP_dt.Columns[j*2 + 4].ColumnName.ToString();
double ColP_sum= (double)temptable.Compute(String.Format("Sum([{0}])", colPnow),
String.Format("Load_ID = '{0}'", IDnow));
The same should be done for your colQnow variable. I would also suggest you read how String.Format works so that you can grasp the string formatting concept.
here i am trying to get the comma separated values. In the following code "tbl"
is the table which contains the comma separated values..
private static DataTable PivotFieldData(DataTable tbl)
{
var tblPivot = new DataTable();
if (tbl.Columns.Count > 0)
{
tblPivot.Columns.Add(tbl.Columns[0].ColumnName, typeof(int));
for (int i = 0; i < tbl.Rows.Count; i++)
{
tblPivot.Columns.Add(Convert.ToString(tbl.Rows[i][1]), typeof(long));
}
var r = tblPivot.NewRow();
r[0] = tbl.Rows[0][0].ToString();
for (int col = 0; col < tbl.Rows.Count; col++)
{
r[col + 1] = tbl.Rows[col][2].ToString();
}
tblPivot.Rows.Add(r);
}
return tblPivot;
}
but i am getting an error as follow.
i have tried like
r[col + 1] = tbl.Rows[col][2].ToString().Trim() == "" ? 0 : Convert.ToInt64(tbl.Rows[col][2].ToString());
but i am getting an error that input string was not in correct format ....
please check it and help me... thank you..
The error message is pretty clear. You have a string value (i.e. -2312,77,845.00) and you're trying to store that into a column that is typeof(long). If that string value represents a number then strip out the commas and remove the ToString call inside your loop. If it represents a string (which it seems it does because the columns are in the wrong place for it to be a number) then change the column type.
I'm using SpreadsheetGear 2010 to draw column charts, and wish to loop through all data points, colouring the negative ones red... But I cannot see a way to get to the "value" of the point as I loop through.
The following (interpreting the data label string as the double value) works most of the time:
for (int i = 0; i < chart.SeriesCollection.Count; i++)
{
positiveColor = Color.FromArgb(79, 129, 189); // blue
negativeColor = Color.FromArgb(192, 80, 77); // red
chart.SeriesCollection[i].HasDataLabels = true;
for (int j = 0; j < chart.SeriesCollection[i].Points.Count; j++)
{
double pointValue;
// If the point is -0.004 but the number format is "0.00",
// label will be "0.00"
string label = chart.SeriesCollection[i].Points[j].DataLabel.Text;
chart.SeriesCollection[i].Points[j].Format.Fill.ForeColor.RGB =
(System.Double.TryParse(label, out pointValue) && pointValue >= 0)
? positiveColor
: negativeColor;
}
}
... but, if the value is slightly negative, the data label just shows zero, so pointValue >= 0 and I interpret the point as positive. This results in annoying small blue points hanging down from my X-axis.
SpreadsheetGear.Charts.IPoint does not appear to have any useful properties for retrieving the value that was used to draw the point.
chart.SeriesCollection[i].Values looks hopeful, but returns an object whose string interpretation is "=Data!$B$25:$B$44". I can't seem to cast this to anything useful, and can't find any relevant SpreadsheetGear documentation.
Any idea how I can get to the values used to draw the points?
This answer is not very elegant, but it should give you what you need. You already had the idea of using chart.SeriesCollection[i].Values. If the address contained in chart.SeriesCollection[i].Values is used, you can get the value from the same data that the chart is getting the value from to create the column.
Replace the line code where you define the string label.
string label = chart.SeriesCollection[i].Points[j].DataLabel.Text;
With this line.
string label = chart.Sheet.Workbook.Worksheets[chart.Sheet.Name].Cells[chart.SeriesCollection[i].Values.ToString().Replace("=" + chart.Sheet.Name + "!", "")][0, j].Value.ToString();
This way the value is not controlled by the way the label is formatted.
If the cell value is null, this will throw an exception when ToString() is added.
Here is another version with the changed line separated out more so it is less confusing. Also there is a check for null value before using ToString().
//if you do not have direct access to the worksheet object.
SpreadsheetGear.IWorksheet worksheet1 = chart.Sheet.Workbook.Worksheets[chart.Sheet.Name];
for (int i = 0; i < chart.SeriesCollection.Count; i++)
{
Color positiveColor = Color.FromArgb(79, 129, 189); // blue
Color negativeColor = Color.FromArgb(192, 80, 77); // red
chart.SeriesCollection[i].HasDataLabels = true;
//Get the address of the series
string address = chart.SeriesCollection[i].Values.ToString().Replace("=" + chart.Sheet.Name + "!", "");
for (int j = 0; j < chart.SeriesCollection[i].Points.Count; j++)
{
double pointValue;
//bool usePositiveValueColor = false;
// If the point is -0.004 but the number format is "0.00",
// label will be "0.00"
//string label = chart.SeriesCollection[i].Points[j].DataLabel.Text;
string label = (worksheet1.Cells[address][0, j].Value != null)
? worksheet1.Cells[address][0, j].Value.ToString() : "0";
chart.SeriesCollection[i].Points[j].Format.Fill.ForeColor.RGB =
(System.Double.TryParse(label, out pointValue) && pointValue >= 0)
? positiveColor
: negativeColor;
}
}
Assume that I have a DataGridView which is populated by a number of different strings (different length, numbers and plain text) in Cells.
Want I want to do is to copy and paste these strings, which could be any selection of Cells.
My approach to copy is:
if (e.Control && e.KeyCode == Keys.C)
{
// copy
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
Clipboard.SetDataObject(tmpCells);
}
Which is working properly.
My approach to paste is:
if (e.Control && e.KeyCode == Keys.V)
{
// paste
IDataObject dataInClipboard = Clipboard.GetDataObject();
string stringInClipboard = (string)dataInClipboard.GetData(DataFormats.Text);
char[] rowSplitter = { '\r', '\n' };
char[] columnSplitter = { '\t' };
string[] rowsInClipboard = stringInClipboard.Split(rowSplitter, StringSplitOptions.RemoveEmptyEntries);
int r1 = this.MyDataGridView.SelectedCells[0].RowIndex;
int c1 = this.MyDataGridView.SelectedCells[0].ColumnIndex;
int r2 = this.MyDataGridView.SelectedCells[this.MyDataGridView.SelectedCells.Count-1].RowIndex;
int c2 = this.MyDataGridView.SelectedCells[this.MyDataGridView.SelectedCells.Count-1].ColumnIndex;
int r = Math.Min(r1, r2); // Do not care if selection was taken by drag mouse up or down, always start from min
int c = Math.Min(c1, c2); // Do not care if selection was taken by drag mouse left or right, always start from min
for (int iRow = 0; iRow < rowsInClipboard.Length; ++iRow )
{
string[] valuesInRow = rowsInClipboard[iRow].Split(columnSplitter);
for (int iCol = 0; iCol < valuesInRow.Length; ++iCol )
{
if (this.MyDataGridView.ColumnCount-1 >= c + iCol)
{
DataGridViewCell DGVC = (this.MyDataGridView.Rows[r + iRow].Cells[c + iCol]);
DGVC.Value = valuesInRow[iCol];
}
}
}
}
}
Which works fine UNLESS the string itself DOES NOT contain any delimiter I specified with rowSplitter and columnSplitter. But this unfortunately is the case very often. It then separates the string and expands it to the next cell.
Example:
Cell[n] = {"This string contains a new line delimiter \n but should use only one cell."}
Will be pasted to:
Cell[n] = {"This string contains a new line delimiter"};
Cell[n+1] = {"but should use only one cell."}
So my question is: is it possible to restore the DataGridViewSelectedCellCollection as it was copied to the clipboard before? Just casting from object to DataGridViewSelectedCellCollection will not work:
DataGridViewSelectedCellCollection DGSCC = (DataGridViewSelectedCellCollection)dataInClipboard; // compiles, but throws exception at runtime
Do I have any other option but parsing each string by a defined formatting?
You will have to define own format for clipboard, which will do what default one can't do for you.
Simplest solution in this specific case is to convert multi-line breaks into \n and then convert back when you paste, but in any case it means no more
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
Clipboard.SetDataObject(tmpCells);
but
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
string result = "";
foreach(DataGridViewCell cell in tempCells)
// ... try to replicate what default clipboard text representation does
// change line breaks
Clipboard.SetDataObject(result.Replace("\xd\xa", "\\n"));
and paste will be:
IDataObject dataInClipboard = Clipboard.GetDataObject();
string stringInClipboard = dataInClipboard.GetData(DataFormats.Text).ToString().Replace("\\n", "\xd\xa");
I need help with a fast function to generate a range with the names of Excel columns, that receives as input the start and end indexes.
i.e. Generate(2, 4) -> B, C, D
Speed is important as I will need to generate ranges of up to 16,384 columns.
The basic algorithm for generating Excel column names has been explicated wonderfully by Graham over here: https://stackoverflow.com/a/182924/1644733
Putting it into the framework of the C# Generate() function, you could do the following:
static List<string> Generate(int start, int end) {
List<string> listOfColumns = new List<string>();
for (int i = start; i<=end; i++) {
listOfColumns.Add(NumberToColumn(i));
}
return listOfColumns;
}
static string NumberToColumn(int col) {
string retval = "";
// cannot be greater than 16384
if (col > 16384)
return "invalid";
int dividend = col;
while (dividend > 0)
{
int leftover = (dividend - 1) % 26;
char extraletter = (char) (65 + leftover);
retval = extraletter + retval;
dividend = (int)((dividend - leftover) / 26);
}
return retval;
}
In terms of speed: if you are going to be frequently generating many (thousands) of ranges of this sort, you may want to run this once at the beginning of the code, on the full range of 1-16384, and then just use the values in the resulting List directly, as needed.