I want to select a range from the first to the last cell filled in the row or column. In VBA the code stays as below using xlDown or xlToRight.
Range(Selection, Selection.End(xlDown)).Select
Range(Selection, Selection.End(xlToRight)).Select
How could I do it the same way in C # using Epplus?
I will start from cell B139 and I must go to the last row and column
An important thing to know about the Cells object in an Worksheet in EPPlus is that it contains only references to cell that have data added to it. So with a little bit of LINQ you can get the address of every "Row" like this:
var lastRowCell1 = worksheet.Cells.Last(c => c.Start.Row == 1);
var lastRowCell2 = worksheet.Cells.Last(c => c.Start.Row == 2);
var lastColCell1 = worksheet.Cells.Last(c => c.Start.Column == 1);
var lastColCell2 = worksheet.Cells.Last(c => c.Start.Column == 2);
To get the last Cell's index you can use worksheet.Dimension:
int numCol = worksheet.Dimension.Rows;
int numCol = worksheet.Dimension.Columns;
If you want the last address of Column or Row you can use this:
String lastAddress = worksheet.Dimension.Address.Last().ToString();
Related
I am working on a Console application that simply grabs the value from a specified cell and displays that value to the Console. I would like to modify the code and get the value of the last cell's value in a column. I am able to get the value of cells I specify but I wish to only get the last filled cell(because the addressName may change when the sheet is updated with more rows ). I am currently using the code below to get the values by addressName. Can someone point me in the right direction or show an example. please and thank you.
using System;
using System.Linq;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
namespace ConsoleApp5
{
class Program
{
static void Main(string[] args)
{
string fileName = #"C:\Temp\myTempDoc\bigexcel.xlsx";
string sheetName = "sheet1";
string addressName = "B25";
var cellVall =GetCellValue(fileName, sheetName, addressName);
Console.WriteLine(cellVall);
Console.ReadKey();
}
public static string GetCellValue(string fileName,string sheetName,string addressName)
{
string value = null;
// Open the spreadsheet document for read-only access.
using (SpreadsheetDocument document =
SpreadsheetDocument.Open(fileName, false))
{
// Retrieve a reference to the workbook part.
WorkbookPart wbPart = document.WorkbookPart;
// Find the sheet with the supplied name, and then use that
// Sheet object to retrieve a reference to the first worksheet.
Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().
Where(s => s.Name == sheetName).FirstOrDefault();
// Throw an exception if there is no sheet.
if (theSheet == null)
{
throw new ArgumentException("sheetName");
}
// Retrieve a reference to the worksheet part.
WorksheetPart wsPart =
(WorksheetPart)(wbPart.GetPartById(theSheet.Id));
// Use its Worksheet property to get a reference to the cell
// whose address matches the address you supplied.
Cell theCell = wsPart.Worksheet.Descendants<Cell>().
Where(c => c.CellReference ==addressName ).FirstOrDefault();
// If the cell does not exist, return an empty string.
if (theCell != null)
{
value = theCell.InnerText;
// If the cell represents an integer number, you are done.
// For dates, this code returns the serialized value that
// represents the date. The code handles strings and
// Booleans individually. For shared strings, the code
// looks up the corresponding value in the shared string
// table. For Booleans, the code converts the value into
// the words TRUE or FALSE.
if (theCell.DataType != null)
{
switch (theCell.DataType.Value)
{
case CellValues.SharedString:
// For shared strings, look up the value in the
// shared strings table.
var stringTable =
wbPart.GetPartsOfType<SharedStringTablePart>()
.FirstOrDefault();
// If the shared string table is missing, something
// is wrong. Return the index that is in
// the cell. Otherwise, look up the correct text in
// the table.
if (stringTable != null)
{
value =
stringTable.SharedStringTable
.ElementAt(int.Parse(value)).InnerText;
}
break;
case CellValues.Boolean:
switch (value)
{
case "0":
value = "FALSE";
break;
default:
value = "TRUE";
break;
}
break;
}
}
}
}
return value;
}
}
}
I'm not quite clear whether you are after the last Cell in a Row of your choosing or the last Cell of the last Row. Both approaches are very similar though so I'll show how to do both.
The basic principal is to find the Row you are after first and then to grab the child Cells from that Row.
If you want the very last Cell of the sheet then we just want the last Row:
//grab the last row
Row row = wsPart.Worksheet.Descendants<Row>().LastOrDefault();
However, if you would like to be able to pass in a row number and grab the last Cell of that Row then something like this will do the trick (here the variable rowIndex denotes the index of the Row for which you want the last Cell):
//find the row that matches the rowNumber we're after
Row row = wsPart.Worksheet.Descendants<Row>()
.Where(r => r.RowIndex == rowIndex).FirstOrDefault();
Once you have the Row, it's just a case of grabbing the last Cell of that Row using similar code to the above:
Cell theCell = null;
//find the row that matches the rowNumber we're after
Row row = wsPart.Worksheet.Descendants<Row>().Where(r => r.RowIndex == rowIndex).FirstOrDefault();
if (row != null)
{
//now grab the last cell of that row
theCell = row.Descendants<Cell>().LastOrDefault();
}
// If the cell does not exist, return an empty string.
if (theCell != null)
...
I get the same issue and I found that an empty row of excel (OpenXml) has descendants = 1:
while(++rowIndex < rows.Count())
{
currentRow = rows.ElementAt(rowIndex);
int descendants = currentRow.Descendants<Cell>().Count();
if(descendants <= 1)
{
continue;
}
// your code
}
, so you can continue to read the rows and ignore empty rows until the last row.
I've tried to display the values of properties from a list of class on the table of MS Word like this:
public void CreateTable<T>(IList<T> list)
{
if (list != null)
{
var props = typeof(T).GetProperties();
Range tableLoc = WdDoc.Range();
var table = WdDoc.Tables.Add(tableLoc, list.Count, props.Length);
for (int i = 0; i < props.Length; i++)
{
table.Columns[i + 1].Select();
if (props[i].PropertyType == typeof(double))
{
WdApp.Selection.Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphRight;
}
else
{
WdApp.Selection.Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;
}
}
//...
}
}
I tried to automatically set the text alignment of columns according to fill-in property type.
However, only the first row of each column can be set as the correct alignment I assign.
Other rows will always be set as the latest alignment of for loop.
Why did this happen? Did I miss any snippet?
Also, what are the differences between
WdApp.Selection.Paragraphs.Format.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;
and
WdApp.Selection.Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;
Thanks!
The problem comes from using Selection.Range in this specific instance of working with the Word object model. When working with columns you need to use
WdApp.Selection.ParagraphFormat.Alignment
Background
Selection affects exactly what is selected - the entire column.
Selection.Range affects the Range of the selection. In the case of a Column, Selection.Range cannot be the entire column. A Range in Word must be a contiguous run of text, but a column is NOT contiguous. So Selection.Range returns only that part of the selection that is contiguous: the first cell (row). That explains why only the first row of the table is affected by your current code.
While a selected column appears to be contiguous this is just a convenience for the user. Under the covers, everything in the rows between the cells in one column and the next breaks up the text run.
If you're familiar with HTML think of how a table is defined in HTML. The way Word Open XML defines a table works on the same principle. The concept looks something like this:
<table><row><cell><cell></row><row><cell><cell><row></table>
The rows and their content are contiguous.
I need to write a list of objects to excel sheet as a table, in which each column represents object attributes or values. To the below method, Im passing column names in a separate List and data objects in a List, I managed to get the data displayed like below, but still I could not get the columns to display properly.
I referred the below documentation, but I could not find a way to get the titles display properly.
https://github.com/closedxml/closedxml/wiki/Inserting-Tables
Code
public string CreateExcelFile<T>(IEnumerable<T> list, string sheetName, string headerTitle, List<string> titles, string fileName, string savedPath)
{
var wb = new XLWorkbook();
var ws = wb.Worksheets.Add(sheetName);
ws.Cell(1, 1).Value = headerTitle; // sets excel sheet header
var rangeTitle = ws.Range(3, 1, 3, titles.Count); // range for row 3, column 1 to row 3, column titles.Count
rangeTitle.AddToNamed("Titles");
// Need to add columns names with in rangeTitle
//rangeTitle.InsertData(titles);
// write data from row 4 onwards
if (list != null && list.Any())
{
ws.Cell(4, 1).InsertData(list);
}
else
{
ws.Cell(4, 1).Value = "No data to show";
}
// styles
var titlesStyle = wb.Style;
titlesStyle.Font.Bold = true;
titlesStyle.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
titlesStyle.Fill.BackgroundColor = XLColor.Amber;
// style titles row
wb.NamedRanges.NamedRange("Titles").Ranges.Style = titlesStyle;
ws.Columns().AdjustToContents();
var filePath = savedPath + string.Format("{0}.xlsx", fileName);
wb.SaveAs(filePath);
return filePath;
}
Output excel
Output Im trying to get - I need to get values stored in titles in the Yellow highlighted row.
Can anyone help?
You could use InsertTable. The data is inserted as an Excel Table:
ws.Cell(1, 1).InsertTable(list.AsEnumerable());
I managed to get the columns to display by doing below.
// Need to add columns names with in rangeTitle
for (int i = 0; i < titles.Count; i++)
{
var columnNumber = i + 1;
ws.Cell(3, columnNumber).Value = titles[i];
}
This works for now. But, I wonder is there a better way to doing things (without manually assigning column names like above).
ws.Cell(3, 1).Value = new [] { titles };
If you set Value to an array, ClosedXML will write each object in the array to its own row, with one property of the object per column. (See https://github.com/ClosedXML/ClosedXML/wiki/Copying-IEnumerable-Collections)
In this case, the array we're passing in has only one object – an array of titles. That inner array gets written to the target row, and each item in the inner array gets written to a column in that row.
In my form I have this DataGridView (grid1), filled from a sqllite database.
I want select the column with the name "cap" and insert the column's values into an array.
How can I do?
I don't find a way to select the column cap by name, so I decided to indicate it with the index, but I don't like this way..
I don't know ho to insert these values into an array. there are a lot of columns/cell method but I don't figured out which one can help me!
I tryed to do this way (from an old answer here in the site) but it gives me error of "out of bounded matrix"
int[] wareCap = new int[grid1.Rows.Count];
...
//How I filled my dgv, if this can help
var context = new ForliCesenaEntities2();
BindingSource bi1 = new BindingSource();
bi1.DataSource = context.warehouses.ToList();
grid1.DataSource = bi1;
grid1.Refresh();
//How I try to insert the values into int[] wareCap
for (int i = 0; i<(grid1.Rows.Count-1); i++)
wareCap[i] = Convert.ToInt16(grid1.Rows[i].Cells[1].Value);
Thanks in advice
First you gridview has to be filled with values, then:
List<int> intValues = new List<int>();
foreach (DataGridViewRow row in grid.Rows)
{
intValues.Add(int.Parse(row.Cells["cap"].Value.ToString()));
}
int[] array = intValues.ToArray();
should do the trick.
Alternatively, you could use LINQ.
int[] intArray = dataGridView1.Rows
.Cast<DataGridViewRow>()
.Select(row => int.Parse(row.Cells["cap"].Value.ToString())).ToArray();
UPDATE:
The above solution might crash if the DataGridView's AllowUserToAddRows property is set to true. Because in that case your last row is the row for entering a new record, the Value is null, so invoking the ToString method on it causes a NullReferenceException.
I see two possibilities:
set your datagridview's AllowUserToAddRows property to false (but the user won't be able to add new rows),
use the following solution
Check if the row is the row for entering a new record:
int[] intArray = dataGridView1.Rows
.Cast<DataGridViewRow>()
.Where(row => !row.IsNewRow)
.Select(row => Convert.ToInt32(row.Cells["cap"].Value.ToString())).ToArray();
You already got all your values in a List, because you do sth. like this:
bi1.DataSource = context.warehouses.ToList();
You can easily store this in a reference:
var values = context.warehouses.ToList();
bi1.DataSource = values;
Your Grid is build based on Objekts in this List. So this should build your array:
int[] array = new int[values.Count];
int counter = 0;
foreach (var object in values)
{
array[counter++] = object.CAP;
}
int[] wareCap = new int[grid1.Rows.Count];
foreach(DataGridViewRow row in grid1.Rows)
{
wareCap.Add(Convert.ToInt32(cell["cap"].Value.ToString()));
}
I have a datagridview that contains list of subjects populated from Subject table from database.Columns include
Select(checkbox),
SubjectId,
SubjectName,
SubjectGroup.
Now I want if a user Selects on any of the desired rows, the corresponding SubjectId's should be added to a List. I have made and inserted into the desired table in the database.
The problem is that the new column of checkboxes I have added to this datagridview is not being detected.
My code is:
foreach (DataGridViewRow row in gvSubjectsOpted.Rows)
{
if (Convert.ToBoolean(gvSubjectsOpted.SelectedRows[0].Cells["SelectId"].Value=true))
{
olist.Add(gvSubjectsOpted.SelectedRows[0].Cells["SubjectId"].Value.ToString());
}
}
Late to the party. I had the same issue with trying to get the checkbox column by name, use the index instead. Here is a linq example assuming the checkbox is column 0 and the stored values for TrueValue and FalseVale are true and false respectively.
var checkedRows = from DataGridViewRow r in gvSubjectsOpted.Rows
where Convert.ToBoolean(r.Cells[0].Value) == true
select r;
foreach (var row in checkedRows)
{
olist.Add(row.Cells["SubjectId"].Value.ToString());
}
I realise this is an old post but I came across it and didn't think it was really answered in an efficient way so I thought I would add my method.
I have a similar block in my windows app. I read the values from the grid when the user clicks a button, and I want to know which rows they checked. As the checkboxes are in Cell 0 and the data I want is in Cell 1, I use the following code. Note the cast: it is important as it allows us the use the Where clause and therefore just a single line of code to get the collection of data. I could use the name of the cells instead of magic index numbers but then it would not fit your app so I put numbers instead (you should use names)
var checkedRows = dataGridView
.Rows
.Cast<DataGridViewRow>()
.Where(x => x.Cells[0].Value.ToString() == "1")
.Select(x => x.Cells[1]);
Note that this will give you an IEnumerable of type DataGridViewCell. If you want you can either add something like .Value.ToString() to the select or do this when you use your collection.
You question is similar to another SO question.
Check the answer of this Datagridview checkboxcolumn value and functionality.
Try this
foreach(GridViewRow r in gvSubjectsOpted.Rows)
{
GridViewCheckBoxColumn c = r.cells[0].Controls[0] as GridViewCheckBoxColumn;
if(c.Checked)
{
//Do something.
}
}
private void button1_Click(object sender, EventArgs e)
{
string subjId;
List<string> lines = new List<string>();
for (int i = 0; i < gvSubjectsList.Rows.Count; i++)
{
bool Ischecked =Convert.ToBoolean(gvSubjectsList.Rows[i].Cells["Select"].Value);
if (Ischecked == true)
{
subjId = gvSubjectsList.Rows[i].Cells["SubjectId"].Value.ToString();
lines.Add(subjId);
}
}
comboBox1.DataSource = lines;
}
//the most important thing is to set 'true' and 'false' values against newly added checkboxcolumn instead of '0' and '1'...that is,
CBColumn.FalseValue = "false";
CBColumn.TrueValue = "true";