Decimal separator comma but I want save data with point - c#

I have to read and write an excel file.
The problem is that when a PC is the decimal separator, (comma) also in the excel file numbers with the comma will be saved.
I would like to save the values for all modes with the point instead of a comma.
Here is a piece of code:
var wb = openWorkBook(filename);
var ws = wb.Worksheet("CNF");
IXLRow row = ws.Row(device.Ordinal - 1 + FirstRow);
for (int j = 0; j < MAXCOLS; ++j)
{
IXLCell cell = row.Cell(j + FirstCol);
cell.Value = Convert.ChangeType(device[j], m_column_type[j]);
}

Convert.ChangeType(object, Type) uses the thread's current culture for conversion. It sounds like you want the invariant culture, so you should use Convert.ChangeType(object, Type, IFormatProvider):
cell.Value = Convert.ChangeType(device[j], m_column_type[j],
CultureInfo.InvariantCulture);

Related

Apply format to many excel cells at once

I would like to format all added values in my excel file and i have a "small" and "fast" solution like this:
Item2 is a List<string>, Item3 is a List<List<string>>
if (chkWithValues.Checked && results.Item3.Any())
{
var rows = results.Item3.Count;
var cols = results.Item3.Max(x => x.Count);
object[,] values = new object[rows, cols];
object[,] format = new object[rows, cols];
//All returned items are inserted into the Excel file
//Item2 contains the database types, Item3 the Values
// pgMain shows the progress for the selected tables
for (int j = 0; j < results.Item3.Count(); j++)
{
int tmpNbr = 1;
foreach (string value in results.Item3[j])
{
values[j, tmpNbr - 1] = Converter.Convert(results.Item2[tmpNbr - 1], value).ToString().Replace("'", "");
format[j, tmpNbr - 1] = ExcelColumnTypes.ConvertToExcelTypes(results.Item2[tmpNbr - 1]);
tmpNbr++;
}
pgMain.Maximum = results.Item3.Count();
pgMain.PerformStep();
}
Excel.Range range = xlWorksheet.Range["A3", GetExcelColumnName(cols) + (rows + 2)];
range.Value = values;
range.NumberFormat = format;
}
To add the numberformat efficiently with a single assignment, I've found a solution with a 2d array, which contains all number formats that should be set.
The problem is that I get the following error message "Unable to set the NumberFormat property of the Range class" when I have more than (i think) 50.000 cells to format.
Does someone know a solution that is fast and can handle a large amount of cells without error?
update:
ExcelColumnTypes.ConvertToExcelTypes
public static string ConvertToExcelTypes(string databaseType)
{
if (DatabaseColumnTypes.DOUBLE.Contains(databaseType))
return DOUBLEPO1;
if (DatabaseColumnTypes.DATE.Contains(databaseType))
return DATE2;
if (DatabaseColumnTypes.INTEGER.Contains(databaseType))
return INT;
return TEXT;
}
The DatabaseColumnTypes are List with const or direct const.
Sample:
public const string VARBINARY = "varbinary";
public static List<string> STRING = new List<string>()
{
CHAR,
VARCHAR,
TEXT,
NTEXT,
NCHAR,
NVARCHAR,
BINARY,
VARBINARY
};
Please change the
range.NumberFormat = format;
range.NumberFormatLocal = format;
Then it should work

Get Formatted Cell Values efficiently

I would like to be able to efficiently retrieve a multi-dimensional array of formatted cell values from Excel. When I say formatted values, I mean I would like to get them exactly as they appear in Excel with all the cell NumberFormat applied.
The Range.Value and Range.Value2 properties work great for retrieving the cell values of a large number of cells into a multi-dimensional array. But those are the actual cell values (well at least with Range.Value2 is, I'm not quite sure what Range.Value is doing with respect to some of the values).
If I want to retrieve the actual text that is displayed in the cells, I can use the Range.Text property. This has some caveats. First, you need to AutoFit the cells or else you may get something like #### if not all the text is visible with the current cell width. Secondly, Range.Text does not work for more than one cell at a time so you would have to loop through all of the cells in the range and this can be extremely slow for large data sets.
The other method that I tried is to copy the range into the clipboard and then parse the clipboard text as a tab-separated data stream and transfer it into a multi-dimensional array. This seems to work great, although it is slower than getting Range.Value2, it is much faster for large datasets than getting Range.Text. However, I don't like the idea of using the system clipboard. If this was a really long operation that takes 60 seconds and while that operation is running, the user may decide to switch to another application and would be very unhappy to find that their clipboard either doesn't work or has mysterious data in it.
Is there a way that I can retrieve the formatted cell values to a multi-dimensional array efficiently?
I have added some sample code that is run from a couple ribbon buttons in a VSTO app. The first set some good test values and number formats and the second button will display what they look like when retrieved using one of these methods in a MessageBox.
The sample output on my system is(It could be different on yours due to Regional Settings):
Output using Range.Value
1/25/2008 3:19:32 PM 5.12345
2008-01-25 15:19:32 0.456
Output using Range.Value2
39472.6385648148 5.12345
2008-01-25 15:19:32 0.456
Output using Clipboard Copy
1/25/2008 15:19 5.12
2008-01-25 15:19:32 45.60%
Output using Range.Text and Autofit
1/25/2008 15:19 5.12
2008-01-25 15:19:32 45.60%
The Range.Text and Clipboard methods produce the correct output, but as explained above they both have problems: Range.Text is slow and Clipboard is bad practice.
private void SetSampleValues()
{
var sheet = (Microsoft.Office.Interop.Excel.Worksheet) Globals.ThisAddIn.Application.ActiveSheet;
sheet.Cells.ClearContents();
sheet.Cells.ClearFormats();
var range = sheet.Range["A1"];
range.NumberFormat = "General";
range.Value2 = "2008-01-25 15:19:32";
range = sheet.Range["A2"];
range.NumberFormat = "#";
range.Value2 = "2008-01-25 15:19:32";
range = sheet.Range["B1"];
range.NumberFormat = "0.00";
range.Value2 = "5.12345";
range = sheet.Range["B2"];
range.NumberFormat = "0.00%";
range.Value2 = ".456";
}
private string ArrayToString(ref object[,] vals)
{
int dim1Start = vals.GetLowerBound(0); //Excel Interop will return index-1 based arrays instead of index-0 based
int dim1End = vals.GetUpperBound(0);
int dim2Start = vals.GetLowerBound(1);
int dim2End = vals.GetUpperBound(1);
var sb = new StringBuilder();
for (int i = dim1Start; i <= dim1End; i++)
{
for (int j = dim2Start; j <= dim2End; j++)
{
sb.Append(vals[i, j]);
if (j != dim2End)
sb.Append("\t");
}
sb.Append("\n");
}
return sb.ToString();
}
private void GetCellValues()
{
var sheet = (Microsoft.Office.Interop.Excel.Worksheet)Globals.ThisAddIn.Application.ActiveSheet;
var usedRange = sheet.UsedRange;
var sb = new StringBuilder();
sb.Append("Output using Range.Value\n");
var vals = (object [,]) usedRange.Value; //1-based array
sb.Append(ArrayToString(ref vals));
sb.Append("\nOutput using Range.Value2\n");
vals = (object[,])usedRange.Value2; //1-based array
sb.Append(ArrayToString(ref vals));
sb.Append("\nOutput using Clipboard Copy\n");
string previousClipboardText = Clipboard.GetText();
usedRange.Copy();
string clipboardText = Clipboard.GetText();
Clipboard.SetText(previousClipboardText);
vals = new object[usedRange.Rows.Count, usedRange.Columns.Count]; //0-based array
ParseClipboard(clipboardText,ref vals);
sb.Append(ArrayToString(ref vals));
sb.Append("\nOutput using Range.Text and Autofit\n");
//if you dont autofit, Range.Text may give you something like #####
usedRange.Columns.AutoFit();
usedRange.Rows.AutoFit();
vals = new object[usedRange.Rows.Count, usedRange.Columns.Count];
int startRow = usedRange.Row;
int endRow = usedRange.Row + usedRange.Rows.Count - 1;
int startCol = usedRange.Column;
int endCol = usedRange.Column + usedRange.Columns.Count - 1;
for (int r = startRow; r <= endRow; r++)
{
for (int c = startCol; c <= endCol; c++)
{
vals[r - startRow, c - startCol] = sheet.Cells[r, c].Text;
}
}
sb.Append(ArrayToString(ref vals));
MessageBox.Show(sb.ToString());
}
//requires reference to Microsoft.VisualBasic to get TextFieldParser
private void ParseClipboard(string text, ref object[,] vals)
{
using (var tabReader = new TextFieldParser(new StringReader(text)))
{
tabReader.SetDelimiters("\t");
tabReader.HasFieldsEnclosedInQuotes = true;
int row = 0;
while (!tabReader.EndOfData)
{
var fields = tabReader.ReadFields();
for (int i = 0; i < fields.Length; i++)
vals[row, i] = fields[i];
row++;
}
}
}
private void button1_Click(object sender, RibbonControlEventArgs e)
{
SetSampleValues();
}
private void button2_Click(object sender, RibbonControlEventArgs e)
{
GetCellValues();
}
I've found a partial solution. Apply the NumberFormat value to the parsed double of Value2. This only works for single cells as returning an array for NumberFormat with different formats in the array returns System.DBNull.
double.Parse(o.Value2.ToString()).ToString(o.NumberFormat.ToString())
The dates don't work with this though. If you know which columns contains certain things, like a formatted date, you can use DateTime.FromOADate on the double and then value.ToString(format) with the NumberFormat. The code below gets close but is not complete.
<snip>
sb.Append("\nOutput using Range.Value2\n");
vals = (object[,])usedRange.Value2; //1-based array
var format = GetFormat(usedRange);
sb.Append(ArrayToString(ref vals, format));
</snip>
private static object[,] GetFormat(Microsoft.Office.Interop.Excel.Range range)
{
var rows = range.Rows.Count;
var cols = range.Columns.Count;
object[,] vals = new object[rows, cols];
for (int r = 1; r <= rows; ++r)
{
for (int c = 1; c <= cols; ++c)
{
vals[r-1, c-1] = range[r, c].NumberFormat;
}
}
return vals;
}
private static string ArrayToString(ref object[,] vals, object[,] numberformat = null)
{
int dim1Start = vals.GetLowerBound(0); //Excel Interop will return index-1 based arrays instead of index-0 based
int dim1End = vals.GetUpperBound(0);
int dim2Start = vals.GetLowerBound(1);
int dim2End = vals.GetUpperBound(1);
var sb = new StringBuilder();
for (int i = dim1Start; i <= dim1End; i++)
{
for (int j = dim2Start; j <= dim2End; j++)
{
if (numberformat != null)
{
var format = numberformat[i-1, j-1].ToString();
double v;
if (double.TryParse(vals[i, j].ToString(), out v))
{
if (format.Contains(#"/") || format.Contains(":"))
{// parse a date
var date = DateTime.FromOADate(v);
sb.Append(date.ToString(format));
}
else
{
sb.Append(v.ToString(format));
}
}
else
{
sb.Append(vals[i, j].ToString());
}
}
else
{
sb.Append(vals[i, j]);
}
if (j != dim2End)
sb.Append("\t");
}
sb.Append("\n");
}
return sb.ToString();
}
One solution to your problem is to use:
Range(XYZ).Value(11) = Range(ABC).Value(11)
As described here, this will:
Returns the recordset representation of the specified Range object in an XML format.
Assuming that your excel is configured in OpenXML format, this will copy the value/formula AND the formatting of range ABC and inject it into range XYZ.
Additionally, this answer explains the difference between Value and Value2.
.Value2 gives you the underlying value of the cell (could be empty, string, error, number (double) or boolean)
.Value gives you the same as .Value2 except if the cell was formatted as currency or date it gives you a VBA currency (which may truncate decimal places) or VBA date.

Using Excel Double Value in C#

I'm trying to get double values from Excel to my C# project. But, when I try that, Visual Studio automatically erases the comma/period, and shows the number without them.
For Example:
Excel: 192,168 or 192.168
C#: 192168,00
How can I prevent this from happening?
Excel Get-Value Code:
for (int i = 0; i < dataGridView1.ColumnCount; i++)
{
for (int j = 0; j < dataGridView1.Rows.Count - 1; j++)
{
tsp.addArray(Convert.ToDouble(ds.Tables["Deneme"].Rows[i][j].ToString()), i, j);
}
}
Replace is good way to do it i guess.
Just try like that.
string oldString = ds.Tables["Deneme"].Rows[i][j].ToString(); // Old string in addArray.
string convertedString = oldString.Replace(".",","); // Replacer for dots.
double convertedDouble = Convert.ToDouble(convertedString); // Convert double now.
I think your culture is tr-TR that's why your NumberDecimalSeparator is , not .
That's why when you write 192.168 in your program, it read its as hundred and ninety-two thousand ... but when you write 192,168 hundred and ninety-two ...
If your excel cell is like 192,168, there is no problem. Convert.ToDouble works exactly as you want;
string s = "192,168";
double d = Convert.ToDouble(s, CultureInfo.GetCultureInfo("tr-TR"));
Console.WriteLine(d); //192,168
But if your excel cell is like 192.168, then you need to use n0 format like;
string s = "192.168";
double d = 0;
if(s.Contains("."))
{
d = Convert.ToDouble(s, CultureInfo.GetCultureInfo("tr-TR"));
}
Console.WriteLine(d.ToString("n0")); //192.168

C# parse value from string

I would like to parse string value like this :
.02234
-.23455
-1.23345
2.
.3
but i get an FormatException
for (int i = 1; i < 7; i++)
{
var item = Double.Parse(reader.ReadLine(44).Substring(8 * i, 8));
richTextBox1.Text += item.ToString() + "\n";
}
the problem that i should convert this numbers like "0.2" or "-.0541" to double or any value type to work with it !!!
Since you've made a comment that , is the decimal separator in your locale, there is a better option than doing a string-replace of . to ,; tell the Double.Parse() method to use a different number format.
See the MSDN doc for Parse(String s). Especially, note the following:
The s parameter is interpreted using
the formatting information in a
NumberFormatInfo object that is
initialized for the current thread
culture. For more information, see
CurrentInfo. To parse a string using
the formatting information of some
other culture, call the
Double.Parse(String, IFormatProvider)
or Double.Parse(String, NumberStyles,
IFormatProvider) method.
Assuming your current thread culture is using a number format that considers , to be the decimal separator (French/France fr-FR, for example), you must pass an IFormatProvider to the Parse() method that defines . as the decimal separator. Conveniently, the "no culture in particular" culture, CultureInfo.InvariantCulture, does just this.
So this code should parse successfully:
for (int i = 1; i < 7; i++)
{
// Assume the substring of ReadLine() contains "-.23455", for example
var item = Double.Parse(reader.ReadLine(44).Substring(8 * i, 8), CultureInfo.InvariantCulture);
richTextBox1.Text += item.ToString() + "\n";
}
You're passing a string that isn't a double, something like 1.a or 1. .3 (1 string representing 2 numbers)
You can use Double.TryParse() and it will not throw an exception but return true/false if it was successful or not. It might make the flow easer.
reader.ReadLine(44).Substring(8 * i, 8).ToString()
You won't need the .ToString(). Verify that this actually returns the value you're looking for.
The format exception is throw because the value it's trying to parse is not valid!
My first guess is that the argument you're passing is not in fact a double.
Might try to split up your calls, and toss in a breakpoint to see what's actually going on.
for (int i = 1; i < 7; i++)
{
string textNum = reader.ReadLine(44).Substring(8 * i, 8);
// add a breakpoint on the next line, then look at textNum. I bet it's not what you hoped.
double item = double.Parse(textNum);
richTextBox1.Text += string.Format("{0}\n", item);
}
Use Double.TryParse() and test to see if the value was successfully parsed into your local variable like this:
for (int i = 1; i < 7; i++)
{
double value = 0;
string input = reader.ReadLine(44).Substring(8 * i, 8);
if (Double.TryParse(input, out value))
{
richTextBox1.Text += value.ToString() + "\n";
}
else
{
richTextBox1.Text = "Invalid double entered.";
}
}
i just find a solution to the problem ( replace to dot with comma ) :
public Double[] GridValues(int fromline)
{
Double[] values = new Double[7];
for (int i = 1; i < 7; i++)
{
string input = ReadLine(fromline).Substring(8 * i, 8).Replace(".", ",");
values[i-1] = double.Parse(input);
}
return values;
}

What is the c# equivalent of Java DecimalFormat?

How would I convert the following code to C#
DecimalFormat form
String pattern = "";
for (int i = 0; i < nPlaces - nDec - 2; i++) {
pattern += "#";
}
pattern += "0.";
for (int i = nPlaces - nDec; i < nPlaces; i++) {
pattern += "0";
}
form = (DecimalFormat) NumberFormat.getInstance();
DecimalFormatSymbols symbols = form.getDecimalFormatSymbols();
symbols.setDecimalSeparator('.');
form.setDecimalFormatSymbols(symbols);
form.setMaximumIntegerDigits(nPlaces - nDec - 1);
form.applyPattern(pattern);
EDIT The particular problem is that I do not wish the decimal separator to change with Locale (e.g. some Locales would use ',').
For decimal separator you can set it in a NumberFormatInfo instance and use it with ToString:
NumberFormatInfo nfi = new NumberFormatInfo();
nfi.NumberDecimalSeparator = ".";
//** test **
NumberFormatInfo nfi = new NumberFormatInfo();
decimal d = 125501.0235648m;
nfi.NumberDecimalSeparator = "?";
s = d.ToString(nfi); //--> 125501?0235648
to have the result of your java version, use the ToString() function version with Custom Numeric Format Strings (i.e.: what you called pattern):
s = d.ToString("# ### ##0.0000", nfi);// 1245124587.23 --> 1245 124 587?2300
// 24587.235215 --> 24 587?2352
System.Globalization.NumberFormatInfo
In C#, decimal numbers are stored in the decimal type, with an internal representation that allows you to perform decimal math without rounding errors.
Once you have the number, you can format it using Decimal.ToString() for output purposes. This formatting is locale-specific; it respects your current culture setting.

Categories