Sort for Excel worksheet using C# EPPLUS - c#

I am trying to sort an Excel worksheet using C# EPPLUS. I have reviewed the sort code method here
I am doing this
` var startRow = 11;
var startColumn= 1;
var endRow= 150;
var endColumn= 41;
var sortColumn = 1;
using (ExcelRange excelRange =
yourWorkSheet.Cells[startRow,startColumn, endRow, endColumn])
{
excelRange.Sort(sortColumn, true);
}
`
but that is not working.
The first two columns define what kind of record it is
ex. column one GSMO column 2 is Cost. There are 6 types of record for GSMO
ex GSMO Cost GSMO Profit GSMO LOSS etc.. Then it starts with another value in column 1 such as SESS etc all the way down to the final row
The columns from 3 to 41 are the dollar values associated with the row. So when I sort ascending on column one I want the rest of the columns (2-41) to stay with the same row. I hope this makes sense. Any help would be appreciated Thanks Pene

Related

How to get actual columns range Spreadsheetgear

I have an Excel worksheet which has 50 Rows and 38 columns "Filled" with actual data but when I try to get the .UsedRange I am getting More than 1025 Rows and 250 columns as Used range! Please let me know how can I get only actual Used ranges with filled data and retrive the actual range data using Spreadsheetgear lib? I tried something like, but when user added some column, it's does not work.
var workbook = Factory.GetWorkbook(filePath);
var worksheet = workbook.Worksheets[0];
var cells = worksheet.UsedRange;
var headerCount = 0;
// get columns size
for (var columnCount = 0; columnCount <= range.ColumnCount; ++columnCount)
{
if (columnCount > headers.Length && string.IsNullOrEmpty(range[HeaderRow, columnCount].Text))
{
headerCount = columnCount - 1;
break;
}
}
SpreadsheetGear (and Excel itself) will include cells into the UsedRange that are empty but have formatting of some kind. For instance, if cell ABC123 has a custom NumberFormat, Font color or similar, the UsedRange will include that cell, thereby potentially blowing up your UsedRange A1:ABC123 even if the actual "value-populated" or "filled" portion of the worksheet is much smaller.
If this is a problem and you need to only include the portion of a sheet that is actually populated with cell values, below is one possible routine you might be able to use to only include cells that have cell values of some kind. It is written as an extension method off of IWorksheet to make utilization of it easier, and includes a bool flag that you can pass in so as to return either the "normal" UsedRange (including cells with other types of formatting) or the "altered" UsedRange discussed here:
public static class SGExtensionMethods
{
public static IRange GetUsedRange(this IWorksheet worksheet, bool ignoreEmptyCells)
{
IRange usedRange = worksheet.UsedRange;
if (!ignoreEmptyCells)
return usedRange;
// Find last row in used range with a cell containing data.
IRange foundCell = usedRange.Find("*", usedRange[0, 0], FindLookIn.Formulas,
LookAt.Part, SearchOrder.ByRows, SearchDirection.Previous, false);
int lastRow = foundCell?.Row ?? 0;
// Find last column in used range with a cell containing data.
foundCell = usedRange.Find("*", usedRange[0, 0], FindLookIn.Formulas,
LookAt.Part, SearchOrder.ByColumns, SearchDirection.Previous, false);
int lastCol = foundCell?.Column ?? 0;
// Return a new used range that clips of any empty rows/cols.
return worksheet.Cells[worksheet.UsedRange.Row, worksheet.UsedRange.Column, lastRow, lastCol];
}
}
A couple additional notes about this approach. It does generally take into account hidden rows or columns--meaning hidden rows or columns that have cell values will be included in the UsedRange. One exception / edge-case to this inclusion of hidden rows or columns is if AutoFilters is enabled on the worksheet. This puts the worksheet in a special "mode" that excludes hidden rows from search results. So if AutoFilters is enabled (in such cases IWorksheet.AutoFilterMode will be true), you may not be able to rely on this approach if the last row(s) or column(s) of the AutoFiltered range could possibly have been filtered out.

Linking the Cells of a spreadsheet like excel to be able to perform formulas c#. Eg: A1 +C8

In Our assignment we were given a task to create a new spreadsheet program with a 26 x 26 grid of textboxes without importing anything and make use of array (in my case I Used array of objects).
I created a 2 d array of objects containing size of grid (27x27). the reason of it being 27 instead of 26 is because the first row and column is to show the ABCDE etc and 12345 etc of the rows and columns.
now the rows indexation I had no problem because it is numeric. however, the letters I solved by creating a string array with alphabet from a to z and entering them through a for loop and it worked.
Now the next step is to be able to link the cells, however, I am a bit stumped, because people told me I have to use Ascii or smthin.
Can anyone help me plz on how to achieve the cell links?
I have been able to past the name of each cell using this code but I fear I just filled the .Text of the cells not the cells name per se:
for (int x = 1; x < TwoDArrayCells.GetLength(0); x++) //nested loop to create the grid of textboxes.
{
for (int y = 1; y < TwoDArrayCells.GetLength(1); y++)
{
Cells CellFields = new Cells(Name = $"{AlphabetRow[x]}{y}", x, y + 3); // called a new cell named CellFields.
CellFields.Text = Name;//Change to location if you wise to take
this.Controls.Add(CellFields);
}
}
The result I would like is to be able to after this be able to link cells. meaning if I insert in a text box A1 + A2 it knows that a1 is the first box in the first row of first column and the a2 is the box in the second row of the first column.
Assuming that Columns can be A-Z only (Capitals), You can extract the column index and row index as follows:
Given a cell, string cell = "A2";
- the column index: int column = cell[0] - 'A' + 1;
- the row index: int row = Convert.ToInt32(cell.Substring(1));
Explanation: for the column, take the first letter, subtract it from 'A' (the subtraction will be performed on the assci codes of the characters) and add 1 since your actual cells are stored starting from index 1. for the row, just parse the rest of the string to int.

Validate column with Excel formula increments the formula is expected behavior or bug?

I'm trying to create a spreadsheet where the first sheet ("Catalog") contains some pre-filled and some empty values in a column. I want the values to be in a drop down list that are restricted to values found in the second sheet ("Products").
I would expect that if I set the the Excel validation formula for cells "A1:A1048576" in the "Catalog" sheet to be a list validation of "Products!A1:A100" that every cell would only allow values from "Products!A1:A100". However, I'm finding that my formula gets incremented for every row in the "Catalog" sheet (i.e. In row 2 the formula becomes "Products!A2:A101", in row 3 the formula becomes "Products!A3:A102").
If version matters I'm using EPPlus.Core v1.5.4 from NuGet.
I'm not sure if this is a bug or if I'm going about applying my formula wrong?
I've already tried directly applying the validation to every cell in the column one cell at a time. I found that not only does it moderately increase the size of the resulting Excel file but more importantly it also exponentially increases the time taken to generate the Excel file. Even applying the validation one cell at a time on the first 2000 rows more than doubles the generation time.
ExcelPackage package = new ExcelPackage();
int catalogProductCount = 10;
int productCount = 100;
var catalogWorksheet = package.Workbook.Worksheets.Add($"Catalog");
for (int i = 1; i <= catalogProductCount; i++)
{
catalogWorksheet.Cells[i, 1].Value = $"Product {i}";
}
var productsWorksheet = package.Workbook.Worksheets.Add($"Products");
for (int i = 1; i <= productCount; i++)
{
productsWorksheet.Cells[i, 1].Value = $"Product {i}";
}
var productValidation = catalogWorksheet.DataValidations.AddListValidation($"A1:A1048576");
productValidation.ErrorStyle = ExcelDataValidationWarningStyle.stop;
productValidation.ErrorTitle = "An invalid product was entered";
productValidation.Error = "Select a product from the list";
productValidation.ShowErrorMessage = true;
productValidation.Formula.ExcelFormula = $"Products!A1:A{productCount}";
I guess I'm not that adept at Excel formulas.
Changing this line:
productValidation.Formula.ExcelFormula = $"Products!A1:A{productCount}";
to this:
productValidation.Formula.ExcelFormula = $"Products!$A$1:$A${productCount}";
stopped the auto increment issue. Hopefully this answer will save someone else some sanity as I wasted half a day on this issue myself.

Counting rows of filtered excel Range in C#

I've been working in a c# project and what I'm trying to accomplish is counting the visible rows of a filtered range in C#.
First, I create a range that I'm going to filter.
Range usedRange = sheet.Range[sheet.Cells[7, 1], sheet.UsedRange.SpecialCells(XlCellType.xlCellTypeLastCell)];
Then, I apply 2 filters:
usedRange.AutoFilter(20, "<>RAW", XlAutoFilterOperator.xlFilterValues, "<>AV", true);
usedRange.AutoFilter(2, "ARG", XlAutoFilterOperator.xlFilterValues, Type.Missing, true);
And last, I create another range, using the "XlCellType.xlCellTypeVisible" and try to count the rows, but the count returns 1
argFiltered = usedRange.SpecialCells(XlCellType.xlCellTypeVisible);
Console.WriteLine("Number of rows: "+ argFiltered.Rows.Count) // the count returns 1.
If I loop through the Range I Can get the count. But with a big amount of data this can slow the process a lot:
//this is not effective
foreach (Range area in argFiltered.Areas) {
foreach (Range areaRow in area) {
count++;
}
}
My question is: is there any way to get the number of the visible filtered rows "directly"?
Thank you all!
Note:
If I select the range, it selects the exact number of rows that should be displayed in the count!.
argFiltered.Select();
Not ideal but this will be somewhat better:
foreach (Range area in argFiltered.Areas) {
count += area.Rows.Count;
}
Can you afford to put a formula in some unused cell that counts the rows? If you use the Subtotal function with code 3 it won't count the rows hidden by a filter. Put a formula in a cell and read its value, e.g.
sheet.Cells[1, 255].Formula = "=subtotal(3, A1:A16383)"; //Count all rows that have a non-blank column A and aren't hidden
var count = sheet.Cells[1, 255].Value;
sheet.Cells[1, 255].ClearContents();

datagridview multiplying cell value

I have a problem with DataGridView. I want to multiply two cells and display the result in a third cell.
I have two DataGridViews. The first DataGridView gets data from database. The second DataGridView gets values after I select row from first DataGridView and adds that row to second DataGridView. Column A gets from first DataGridView (from database); in column B the user inserts values manually.
example of second datagridview:
Price | Amount | Total
10.50 | 2 | 21.00
5.20 | 4 | 20.80
7.30 | 5 | 36.50
After that I want to sum column C and display the sum in a text box.
Column A is type decimal; Column B is integer; Column C should be decimal too.
This is solution that one guy gave me on internet but it works only for DataGridView that gets data manually, it doesn't work if data is from database:
decimal sum = 0.0m;
for (int i = 0; i < gridProizvodi.Rows.Count; i++)
{
DataGridViewRow row = gridProizvodi.Rows[i];
if (row.IsNewRow) break;
decimal product = Convert.ToDecimal(row.Cells[1].Value)
* Convert.ToInt32(row.Cells[2].Value);
sum += product;
row.Cells[3].Value = product;
}
txtCijena.Text= sum.ToString();
I get an argument out of range exception was unhandled error in this line
decimal product = Convert.ToDecimal(row.Cells[1].Value)
* Convert.ToInt32(row.Cells[2].Value);
Can someone help me find a solution?
I am not sure if you are using forms or webapp... The folloing option is ususally aplicable to webapps, I don't know about forms.
This usually happens when the GridView columns are generated automatically, i.e. gridview.AutoGenerateColumns = true;. It does not know that there are any columns, therefore gives you anout of range exception. You would need to set it to false and tell it what columns to display. In the template field for the third column, you can simply change the binding property to a multiplicated value.
Another oprion would be to return three columns from the database, third column being your multiplied values.
Hope this helps...
if error happened on the line as you sad. Then faster solution will be to use a column names.
As #Andres mentioned it is better practice then using of indexes.
From form designer check a columns name and then use like this:
decimal product = Convert.ToDecimal(row.Cells[datagridviewTextBoxColumn1.Name].Value)
* Convert.ToInt32(row.Cells[datagridviewTextBoxColumn2.Name].Value);
Or much better will be to used custom names of columns.
Have your second datargidview a predefined columns or it created programmatically?
if they have a predefined names-> then use them
if they not -> then, because you have always same columns in second datagridview, then will be better to create columns once in designer for example
How if you change it like this
decimal sum = 0.0m;
decimal product = 0;
for (int i = 0; i < gridProizvodi.Rows.Count; i++)
{
DataGridViewRow row = gridProizvodi.Rows[i];
if (row.IsNewRow) break;
product = row.item("Price") * row.item("Amount");
'--- if your price and amount field is numeric type, you dont have to convert it
sum += product;
row.item("Total") = product;
}
If you want to do it in VB.NET then......
Dim sum As Decimal = 0D
For i As Integer = 0 To DataGridView1.Rows.Count - 1
Dim row As DataGridViewRow = DataGridView1.Rows(i)
If row.IsNewRow Then
Exit For
End If
Dim product As Decimal = Convert.ToDecimal(row.Cells("Column1").Value) * _
Convert.ToDecimal(row.Cells("Column2").Value)
'sum += product
row.Cells("Column3").Value = product
Next
Thank You!!

Categories