Excel-Protection from one sheet is set in another - c#

I created an excel file dynamicly using openXML. Inside this sheet there are multiple sheets. Inside each sheet there can be rows that are write-protected.
I use an excel file as template. In this template there are "normal" rows which allow editing and a row that does not. I grab the row and copy it to the places where I do not want the user to be able to edit the contents:
private Row CloneRow(Row sourceRow, uint index, bool? hidden = null)
{
var targetRow = (Row) sourceRow.CloneNode(true);
if (hidden.HasValue)
{
targetRow.Hidden = hidden;
}
foreach (Cell cell in targetRow.Elements<Cell>())
{
// Update the references for reserved cells.
string cellReference = cell.CellReference.Value;
cell.CellReference = new StringValue(cellReference.Replace(targetRow.RowIndex.Value.ToString(), index.ToString()));
cell.CellFormula = null;
}
// Update the row index.
targetRow.RowIndex = new UInt32Value(index);
return targetRow;
}
the parameter sourceRow is read from the template:
List<Row> rows = sheet.ChildElements.OfType<Row>().ToList();
rowChangeAllowed=rows.FirstOrDefault(rw=>rw.RowIndex==3);
rowNotChangeAllowed=rows.FirstOrDefault(rw=>rw.RowIndex==4);
Everything works as expected. But when I open the file in Excel, rows that should be proteced on ANY sheet are protected on ALL sheets.
Example:
Sheet 1: Row 4+5 should be protected
Sheet 2: Row 7 should be protected.
Now on sheet 1 rows 4,5 and 7 are protected
When I switch to the second sheet, suddenly everything works as needed: On Sheet 1, row 4+5 are still protected, but row 7 is not.
Because the behaviour is only wrong directly after opening the file, but is correct when I switch between the sheets: Is there an additional command I have to call to "refresh" the file after creating?
Additional Issue:
When I change a cell in sheet 1, it also is automaticly changed in sheet 2 (again: until I swap the sheets once manually)

The problem was having to many views in the sheet. The following code solved the issue:
//There can only be one sheet that has focus
SheetViews views = worksheetPart.Worksheet.GetFirstChild<SheetViews>();
if (views != null)
{
views.Remove();
worksheetPart.Worksheet.Save();
}
(got it from http://blogs.msdn.com/b/brian_jones/archive/2009/02/19/how-to-copy-a-worksheet-within-a-workbook.aspx)

Related

Creating multiple header rows in an excel file with EPPlus.Core

I will be using EPPlus.Core to generate excel reports in my .Net Core project. So after digging Google to find some sample code snippets I found the following blog:
Create Excel Files in C#
I can create an excel file with a single header with the following code:
using (ExcelPackage excel = new ExcelPackage())
{
excel.Workbook.Worksheets.Add("Worksheet1");
excel.Workbook.Worksheets.Add("Worksheet2");
excel.Workbook.Worksheets.Add("Worksheet3");
var headerRow = new List<string[]>()
{
new string[] { "ID", "First Name", "Last Name", "DOB" }
};
// Determine the header range (e.g. A1:D1)
string headerRange = "A1:" + Char.ConvertFromUtf32(headerRow[0].Length + 64) + "1";
// Target a worksheet
var worksheet = excel.Workbook.Worksheets["Worksheet1"];
// Popular header row data
worksheet.Cells[headerRange].LoadFromArrays(headerRow);
FileInfo excelFile = new FileInfo(#"C:\Users\amir\Desktop\test.xlsx");
excel.SaveAs(excelFile);
}
But I couldn't find out how to add multiple header rows on a single worksheet. For instance, the first table has ID, Name, Price headers and the second table has Notes and Price headers. Is it possible to add multiple tables into a worksheet with EPPlus.Core? The code snippet above adds only a single header row from A1 to D1. Let's say I wanna add another header row from A4 to J4. Any suggestions?
And yet I found the answer. Hope this will help others to save their time. It is not the best of best approach but this is what I need. I defined the coordinates on the Cells array by adding "A1:J1" and "A3:E3".
var worksheet = excel.Workbook.Worksheets["Worksheet1"];
worksheet.Cells["A1:J1"].LoadFromArrays(header1Data);
worksheet.Cells["A1:J1"].Style.Font.Bold = true;
worksheet.Cells[2, 1].LoadFromArrays(array1Data);
worksheet.Cells["A3:E3"].LoadFromArrays(header2Data);
worksheet.Cells["A3:E3"].Style.Font.Bold = true;
worksheet.Cells[4, 1].LoadFromArrays(array2Data);

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.

Get names of tabs in Excel workbook and use just one

I've got an Excel workbook, and needed to read data from the third (and final) worksheet in that workbook. I used old code that scanned through all worksheets, but modified it to jump to the third worksheet:
foreach (var worksheet in Workbook.Worksheets(excelInput).skip(2))
This took me to the third worksheet, my code would execute, and everything was fine.
Now, the client is sending either a three-tabbed Workbook with the sheet I need elsewhere (either 1st or 2nd tab), or they're sending a four-tabbed Workbook, and the sheet I need could be anywhere.
Is there a way to choose the one-and-only sheet that I need by either finding the name of the tab, or the text in cell A1, and using that worksheet and that worksheet only?
Thanks very much in advance!
If you do not know “where” the worksheet is located in the workbook, you can loop through each sheet and check either the sheet name, or as you say “text in cellA1”.
Checking the text in cell A1… I am not sure how you would know if the sheet was the proper one by looking at the first cell. What if all sheets have a value in the first cell? And what would that cell contain? Would the cell contain text that says “this is the correct sheet”? So I am assuming that this workbook only has one sheet with data, and the other sheets are empty.
Looping through the sheets in the workbook is relatively easy, however if you do not know what the name of the sheet is, then it may be difficult to identify the proper sheet. Using my assumption (above) of only one sheet with data, then you can check each sheet to see how many rows of data each sheet has. Then the one with more than 1 row of data is the sheet you are looking for. If the other sheets have data also, then this won’t work and you are going to have to identify the target sheet some other way.
If you DO KNOW the name of the worksheet, then you are good to go. In your case you stated that the worksheet was “Sheet3” but may not be in the 3rd index of the worksheets in the workbook. In this case you can reference the sheet by name as you asked with:
Worksheet target = workbook.Sheets[“Sheet3”];
If target returns null, then the worksheet named “Sheet3” does not exist. If target is not null, then you have found the worksheet and it can be referenced by the target Worksheet variable.
From what you described, if the sheet is always named ”Sheet3” then simply use:
if (SheetExist(workbook, "Sheet3"))
{
Worksheet myWS = workbook.Sheets["Sheet3"];
if (myWS.Cells[1,1].Value != null)
Console.WriteLine("\nCell[1,1] of sheet3: " + myWS.Cells[1, 1].Value.ToString() + "\n");
else
Console.WriteLine("\nCell[1,1] of sheet3 is Null/has no value");
}
else
{
//Console.WriteLine("Worksheet NOT found!");
// sheet named "Sheet3" does not exist in the workbook
}
Below is a method that you can use to see if a "worksheet" with the supplied name “sheetName” exist in the given ”workbook.” If you need the index of the worksheet you could change this to return the index or -1 if it does not exist.
private static bool SheetExist(Excel.Workbook workbook, string sheetName)
{
Worksheet target = workbook.Sheets[sheetName];
if (target != null)
{
//Console.WriteLine("Worksheet found!");
return true;
}
else
{
//Console.WriteLine("Worksheet NOT found!");
return false;
}
A method that loops through all the sheets and checks the number of rows with data using the “UsedRange” function. This also identifies the index of the worksheet in the workbook.
private static void LoopThruAllSheets(Workbook workbook)
{
Sheets allWorkSheets = workbook.Worksheets;
foreach (Worksheet curWS in allWorkSheets)
{
Console.WriteLine("Worksheet : at index " + curWS.Index + " name: " + curWS.Name.ToString() + " UsedRange.Rows.Count: " + curWS.UsedRange.Rows.Count.ToString());
}
}
Hope this helps

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