Copying cell range with images in Excel files - c#

I'm copying cells from one Excel sheet into another with GemBox.Spreadsheet. The cells are coming from a specific named range and I'm using CellRange.CopyTo method like this:
ExcelFile book = ExcelFile.Load("sv-data.xlsx");
ExcelWorksheet sheet1 = book.Worksheets[0];
CellRange range1 = sheet1.NamedRanges["SV"].Range;
ExcelWorksheet sheet2 = book.Worksheets.Add("Sheet2");
range1.CopyTo(sheet2, 14, 3);
This works great for all the cells' value and formatting, but it doesn't copy over the images.
Is this the intended behavior? How can I copy both data and images?

EDIT (2022-10-28):
In the current latest version of GemBox.Spreadsheet the CellRange.CopyTo() method is copying pictures, shapes, and charts.
Also, there is another set of CellRange.CopyTo() overload methods that accept the CopyOptions parameter with which you can specify what you want to copy.
For example:
range1.CopyTo(sheet2, row2, column2,
new CopyOptions() { CopyTypes = CopyTypes.Values | CopyTypes.Styles | CopyTypes.Drawings });
Also, see the second example on this page (it shows various options for copying and deleting cell ranges):
https://www.gemboxsoftware.com/spreadsheet/examples/excel-sheet-copy-delete/111
ORIGINAL:
Yes, it seems to be intended because images are not stored inside the cells, but rather inside a sheet. They are part of a separate collection, the ExcelWorksheet.Pictures.
So, perhaps you could iterate through that collection and copy the required elements.
For example, something like the following:
ExcelFile book = ExcelFile.Load("sv-data.xlsx");
ExcelWorksheet sheet1 = book.Worksheets[0];
CellRange range1 = sheet1.NamedRanges["SV"].Range;
ExcelWorksheet sheet2 = book.Worksheets.Add("Sheet2");
int row2 = 14;
int column2 = 3;
range1.CopyTo(sheet2, row2, column2);
int rowOffset = row2 - range1.FirstRowIndex;
int columnOffset = column2 - range1.FirstColumnIndex;
foreach (ExcelPicture picture1 in sheet1.Pictures)
{
ExcelDrawingPosition position1 = picture1.Position;
CellRange pictureRange1 = sheet1.Cells.GetSubrangeAbsolute(position1.From.Row.Index, position1.From.Column.Index, position1.To.Row.Index, position1.To.Column.Index);
if (range1.Overlaps(pictureRange1))
{
ExcelPicture picture2 = sheet2.Pictures.AddCopy(picture1);
ExcelDrawingPosition position2 = picture2.Position;
position2.From.Row = sheet2.Rows[position2.From.Row.Index + rowOffset];
position2.To.Row = sheet2.Rows[position2.To.Row.Index + rowOffset];
position2.From.Column = sheet2.Columns[position2.From.Column.Index + columnOffset];
position2.To.Column = sheet2.Columns[position2.To.Column.Index + columnOffset];
}
}
book.Save("output.xlsx");

Related

C#, ClosedXML. Taking cell addresses from an Excel sheet, and taking data from a data table, and use this addresses and datas to fill a template file

Addresses Excel
I have an issue with my c# code with ClosedXML, which takes cell addresses from another excel file, takes data from another excel table, and writes data to an excel file which is a template but while taking the addresses, it's sorting them, if the data table has a certain value, it's should write the value to a certain address. For example, if the C5 cell in the data table has "SPHERE", it's gonna write to value to K1 cell, if the C5 cell in the data table has "BODY", it's gonna write to value to K5 cell, if the C5 cell in the data table is empty, it's not gonna use that parameter and directly write the value to G3, this example can be multiplied by different parameters.
But the problem is when it takes data from a cell first, it fills the defined cell in the if and saves the file, when it comes to a second file, it fills the first cell with the value before, then fills the second cell. It iterates like this until the parameter has different value.
public void Read(string path, string directory, bool savewithdatetime)
{
// Open workbook and get worksheet at index 1
XLWorkbook excelBook = new XLWorkbook(path);
IXLWorksheet excelWorkSheet = excelBook.Worksheet(1);
excelWorkSheet.Row(1).Delete();
// Get number of rows with data
int excelRowCount = excelWorkSheet.RowsUsed().Count();
// Open data source and get the worksheet at index 1
XLWorkbook dataSource = new XLWorkbook(Application.StartupPath + "/dataSource.xlsx");
IXLWorksheet dataSourceWS = dataSource.Worksheet(1);
List<string> ifList = new List<string>();
List<string> parameterList = new List<string>();
List<string> fromList = new List<string>();
List<string> toList = new List<string>();
for (int i = 1; i <= dataSourceWS.RowsUsed().Count(); i++)
{
ifList.Add(dataSourceWS.Cell("A" + i).Value.ToString());
parameterList.Add(dataSourceWS.Cell("D" + i).Value.ToString());
fromList.Add(dataSourceWS.Cell("B" + i).Value.ToString());
toList.Add(dataSourceWS.Cell("C" + i).Value.ToString());
}
string time = TimeStamp(savewithdatetime);
// Iterate over each row in the excelWorkSheet
for (int row = 1; row <= excelRowCount; row++)
{
// Reset templateWS to the original template
templateWS = template.Worksheet(1);
// Iterate through the data source rows
for (int i = 0; i < fromList.Count; i++)
{
// Check if the cell at the given address in excelWorkSheet is empty
if (string.IsNullOrEmpty(excelWorkSheet.Cell(ifList[i]).Value.ToString()))
{
// If empty, skip to the next iteration
continue;
}
// Check if the value of the cell at the given address in excelWorkSheet matches the value of the cell at the corresponding address in parameterList
else if (parameterList[i] == excelWorkSheet.Cell(ifList[i]).Value.ToString())
{
// If the values match, set the value of the cell at the corresponding address in toList to the value of the cell at the corresponding address in fromList
templateWS.Cell(toList[i]).Value = excelWorkSheet.Cell(fromList[i]).Value;
}
}
// Save template workbook as new file with incremented index in the specified directory
template.SaveAs(directory + "/Test results" + time + "/Test result-[" + (row) + "].xlsx");
if (row == excelRowCount)
{
break;
}
// Delete the first row of the excelWorkSheet so that the next iteration will use the next row of data
excelWorkSheet.Row(1).Delete();
}
// Clear the lists and collect garbage to free up memory
ifList.Clear();
fromList.Clear();
parameterList.Clear();
toList.Clear();
GC.Collect();
}

Append data to excel sheet if created using ClosedXML in c#

I am writing an application in which I need to store data cell values into excel sheet. Everything is working fine but the problem is everytime I run the application, it overwrites the existing data.
So far the code I have taken from Github:
var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("Sample Sheet");
worksheet.Cell("A1").Value = this.textBox1.Text;
worksheet.Cell("B1").Value = this.textBox2.Text;
worksheet.Cell("C1").Value = this.textBox3.Text;
worksheet.Cell("D1").Value = col1;
worksheet.Cell("E1").Value = col2;
worksheet.Cell("F1").Value = this.textBox6.Text;
workbook.SaveAs("HelloWorld.xlsx");
Note: I don't want to save data using datatable or anything. I just want to get values from textboxes and append them to the existing sheet. I have visited many stackoverflow post but they doesn't helped me much.
Thanks in advance!
Hope this helps:
var wb = new XLWorkbook("Path to file");
IXLWorksheet Worksheet = wb.Worksheet("Tab name");
int NumberOfLastRow = Worksheet.LastRowUsed().RowNumber();
IXLCell CellForNewData = Worksheet.Cell(NumberOfLastRow + 1, 1);
CellForNewData.InsertData(your_data);
You are explicitly storing the values in row 1 of the spreadsheet. If you want to append the values, you'll have to increment the row number and store the values in the appropriate cells.

How to add a new excel column between two columns in a existing worksheet

I would like to add a column that already contains cells values between two columns (or at the end) of a worksheet of an existing workbook that I load.
So I have a function that sets that "column values" I need :
private static Workbook SetIndicatorsWorkbook()
{
var workbook = new Workbook(WorkbookFormat.Excel2007MacroEnabled);
var worksheet = workbook.Worksheets.Add("Unit & Integration Tests");
//Don't worry about team and jenkinsBuilTeams variables
foreach (var team in jenkinsBuildTeams)
{
worksheet.Rows[posX].Cells[0].Value = lastnbUnitTests + lastnbIntegrationTests;
posX += 1;
}
return workbook;
}
And then in main function I want to add this column (which is workbook.worksheets[0].Columns[0] ) in a loaded workbook :
private static void Main()
{
//The workbook I need to update
Workbook workbook = Workbook.Load("file.xlsx");
Workbook temp = SetIndicatorsWorkbook();
WorksheetColumn wc = temp.Worksheets[0].Columns[0];
//The issue is that Worksheet's Columns collection has no "Insert" property
workbook.Save("file.xlsx");
}
The Columns collection of the Worksheet has an Insert method that will shift data/formatting just as would happen in Excel. This was added in the 2014 volume 2 version. You can read more about that in the help topic or the api documentation. Note I've linked to the WPF version help but the Insert method is available in the other platforms as well.

Add Columns to Existing Excel 2007 workbook using Open Xml

I have a predefined Excel workbook with all sheets in place and I need to write content to it. I succesfully write to cells.
The problem is in a particular worksheet that i need to add three columns to it. In the code bellow, first i'm grabbing the Worksheet and then i proceed to add columns. This code runs fine, i mean, no exception is thrown, but then I get an error when I try to open the Excel file, stating that there are some content that cannot be read and all the content of this particular worksheet is cleared.
I know that the problem is with this operation because if I comment out those lines that add columns, the workbook opens just fine with all the cells values I write from code in place.
This is the relevant code, for testing purpose I'm trying to add 3 columns:
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)){
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single( s => s.Name == "Miscellaneous Credit" );
Worksheet workSheet2 = ( (WorksheetPart)document.WorkbookPart.GetPartById( sheet2.Id ) ).Worksheet;
Columns cs = new Columns();
for ( var y = 1; y <= 3; y++ ) {
Column c = new Column()
{
Min = (UInt32Value)1U,
Max = (UInt32Value)1U,
Width = 44.33203125D,
CustomWidth = true
};
cs.Append( c );
}
workSheet2.Append( cs );
}
EDIT : As per Chris's explanation about columns's concept
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)){
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single( s => s.Name == "Miscellaneous Credit" );
Worksheet workSheet2 = ( (WorksheetPart)document.WorkbookPart.GetPartById( sheet2.Id ) ).Worksheet;
// Check if the column collection exists
Columns cs = workSheet2.Elements<Columns>().FirstOrDefault();
if ( ( cs == null ) ) {
// If Columns appended to worksheet after sheetdata Excel will throw an error.
SheetData sd = workSheet2.Elements<SheetData>().FirstOrDefault();
if ( ( sd != null ) ) {
cs = workSheet2.InsertBefore( new Columns(), sd );
}
else {
cs = new Columns();
workSheet2.Append( cs );
}
}
//create a column object to define the width of columns 1 to 3
Column c = new Column
{
Min = (UInt32Value)1U,
Max = (UInt32Value)3U,
Width = 44.33203125,
CustomWidth = true
};
cs.Append( c );
}
This first part of answer deals about how to set columns width (based on the initial sample code, I was thinking that you wanted only define the width of the columns).
First, it seems you misunderstood what are Min and Max properties of the Column object. They represent respectively First and Last column affected by this 'column info' record. So if you have a set of contiguous columns with the same width, you can set that width using one Column class. In your snippet you define 3 times the width of the same column (Index 1).
Then, you presume Columns collection doesn't exist yet...
And finally, the main point is that if the Columns collection is appended after SheetData, Excel will throw error.
Final code that work for me (Open XML SDK 2.0)
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)) {
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single(s => s.Name == "Your sheet name");
Worksheet workSheet2 = ((WorksheetPart)document.WorkbookPart.GetPartById(sheet2.Id)).Worksheet;
// Check if the column collection exists
Columns cs = workSheet2.Elements<Columns>().FirstOrDefault();
if ((cs == null)) {
// If Columns appended to worksheet after sheetdata Excel will throw an error.
SheetData sd = workSheet2.Elements<SheetData>().FirstOrDefault();
if ((sd != null)) {
cs = workSheet2.InsertBefore(new Columns(), sd);
} else {
cs = new Columns();
workSheet2.Append(cs);
}
}
//create a column object to define the width of columns 1 to 3
Column c = new Column {
Min = (UInt32Value)1U,
Max = (UInt32Value)3U,
Width = 44.33203125,
CustomWidth = true
};
cs.Append(c);
}
I'm still confused on how to perform column insert. Says I have
columns A, B and C, I want to insert three columns between B and C,
ending up with columns A,B,C,D,E,F. How can i achieve it?
The Columns object in OpenXml SDK is here to store styles and width informations for the columns. Inserting a Column in the collection won't "insert" a column in the sheet.
"Inserting" a column like you mean is a very large and complex task with OpenXmlSDK.
From my understanding of the problem, it means you will have to find all cells and shift them by changing their reference (ex. a cell with ref "B1" would become "F1" after inserting 3 columns, etc ...). And it means you will have to change a lot of other things (reference of cell in formulas for example).
This kind of task could be easily done with Office.Interop or probably with libraries like EEPlus or ClosedXml.

Select range in aspose

Do you know an equivalent to VBA code:
Range(Selection, Selection.End(xlToRight)).Select
In Aspose.Cells. It seems that its only possible to select the last cell in the entire row:
public Aspose.Cells.Cell EndCellInRow ( Int32 rowIndex )
Or the last cell on the right within a range:
public Aspose.Cells.Cell EndCellInRow ( Int32 startRow, Int32 endRow, Int32 startColumn, Int32 endColumn )
but then you must know more or less how big your table is going to be.
I found this from 2009: http://www.aspose.com/community/forums/permalink/196519/196405/showthread.aspx but that will not resolve my problem as I may have many tables in a sheet both horizontally and vertiacally. And I can't predict where they are going to be.
Edit1:
Sorry if this is dumb question, but ctrl+shift+arrow is such a common operation that I can't believe it would be not implemented so I'm making sure I really have to re-invent the wheel.
Aspose.Cells provides the list of tables in a worksheet using property named 'Worksheet.ListObjects'. 'ListObjects' is a colloection of 'ListObject' type which represents a Table in an excel sheet. That means if one has more than one Tables in a worksheet, the ListObjects collection will give access to every table in the worksheet very conveniently. Each 'ListObject' in turn contains a property named 'DataRange' which specifies all the cells inside a Table. For the sake of convenience DataRange can be used for following operations on a Table:
To apply styles/formatting on the cells in Table
To get the data values
Merge or move the cells in Range
Export contents
To get enumerator to traverse through Table cells
To make selection of cells from DataRange, you can traverse using DataRange to get all the cells in a Row (This could also be done for a column)
Applying any operation on Table cells like after selecting cells using Ctrl+Shift+Arrow, could be performed using a workbook object as follows:
Workbook workbook = new Workbook(new FileStream("book1.xls", FileMode.Open));
if (workbook.Worksheets[0].ListObjects.Count > 0)
{
foreach (ListObject table in workbook.Worksheets[0].ListObjects)
{
Style st = new Style();
st.BackgroundColor = System.Drawing.Color.Aqua;
st.ForegroundColor = System.Drawing.Color.Black;
st.Font.Name = "Agency FB";
st.Font.Size = 16;
st.Font.Color = System.Drawing.Color.DarkRed;
StyleFlag stFlag = new StyleFlag();
stFlag.All = true;
table.DataRange.ApplyStyle(st, stFlag);
}
}
workbook.Save("output.xls");
There is also some worthy information available in Aspose docs about Table styles and applying formatting on a ListObject. For getting last Table cell in a certain row or column, I am sure this will help:
int iFirstRowIndex = table.DataRange.FirstRow;
int iFirstColumnIndex = table.DataRange.FirstColumn;
int iLastRowIndex = table.DataRange.RowCount + iFirstRowIndex;
int iLastColumnIndex = table.DataRange.ColumnCount + iFirstColumnIndex;
for (int rowIndex = 0; rowIndex < table.DataRange.RowCount; rowIndex++)
{
//Get last cell in every row of table
Cell cell = worksheet.Cells.EndCellInColumn(rowIndex + iFirstRowIndex, rowIndex + iFirstRowIndex, (short)iFirstColumnIndex, (short)(iLastColumnIndex - 1));
//display cell value
System.Console.WriteLine(cell.Value);
}

Categories

Resources