I am creating an xlsx file using openXML. I want to add comments to some row cells. Is there any way to add comments to the cell. Or should I use Microsoft.Office.Interop to add comments to the excel cells ?
I used the Open XML SDK Productivity Tool for Microsoft Office which lets you load an Excel or Word file and then emits the C# code to produce that exact file.
Then I created a file with a comment. First I tried with two comments but the amount of code it generates makes it hard to tell what's what.
The result is not pretty to look at. You could reduce a lot of it and probably eliminate some. But it's a way to find out what's going on under the hood.
You can also skip all of this and just use EPPlus, which after looking at what comes next you can imagine why someone felt the need to make this easier.
The key steps are:
Create a new Comments
Create a new CommentsList (?!!!)
Create a new Comment which has a Reference property indicating which cell it belongs to
Create a new CommentText
Create a new Run
Append the Run to the CommentText
Append the CommentText to the Comment
Append the Comment to the CommentsList
Append the CommentsList to the Comments
Set the WorksheetCommentsPart.Comments property to the Comments.
private void GenerateWorksheetCommentsPart1Content(WorksheetCommentsPart worksheetCommentsPart1)
{
Comments comments1 = new Comments(){ MCAttributes = new MarkupCompatibilityAttributes(){ Ignorable = "xr" } };
comments1.AddNamespaceDeclaration("mc", "http://schemas.openxmlformats.org/markup-compatibility/2006");
comments1.AddNamespaceDeclaration("xr", "http://schemas.microsoft.com/office/spreadsheetml/2014/revision");
Authors authors1 = new Authors();
Author author1 = new Author();
author1.Text = "Hannen, Scott";
authors1.Append(author1);
CommentList commentList1 = new CommentList();
Comment comment1 = new Comment(){ Reference = "B3", AuthorId = (UInt32Value)0U, ShapeId = (UInt32Value)0U };
comment1.SetAttribute(new OpenXmlAttribute("xr", "uid", "http://schemas.microsoft.com/office/spreadsheetml/2014/revision", "{811649EF-4CB5-4311-BE14-228133003BE4}"));
CommentText commentText1 = new CommentText();
Run run1 = new Run();
RunProperties runProperties1 = new RunProperties();
FontSize fontSize3 = new FontSize(){ Val = 9D };
Color color3 = new Color(){ Indexed = (UInt32Value)81U };
RunFont runFont1 = new RunFont(){ Val = "Tahoma" };
RunPropertyCharSet runPropertyCharSet1 = new RunPropertyCharSet(){ Val = 1 };
runProperties1.Append(fontSize3);
runProperties1.Append(color3);
runProperties1.Append(runFont1);
runProperties1.Append(runPropertyCharSet1);
Text text1 = new Text(){ Space = SpaceProcessingModeValues.Preserve };
text1.Text = "This is my comment!\nThis is line 2!\n";
run1.Append(runProperties1);
run1.Append(text1);
commentText1.Append(run1);
comment1.Append(commentText1);
commentList1.Append(comment1);
comments1.Append(authors1);
comments1.Append(commentList1);
worksheetCommentsPart1.Comments = comments1;
}
Related
I am generating a Word document with Open XML. The document includes a table, and some of the cells contain a JSON string.
Before inserting the JSON string I beautify it with the following lines:
Newtonsoft.Json.Linq.JToken parsedJson =
Newtonsoft.Json.Linq.JToken.JToken.Parse(unformattedJsonString);
string formattedJsonString = parsedJson.ToString(Formatting.Indented);
For my OpenXML code I started with the "Reflect Code" from the OpenXML SDK 2.5. It looks like this:
TableCell tableCell2 = new TableCell();
TableCellProperties tableCellProperties2 = new TableCellProperties();
TableCellWidth tableCellWidth2 = new TableCellWidth() { Width = "8640", Type = TableWidthUnitValues.Dxa };
Shading shading2 = new Shading() { Val = ShadingPatternValues.Clear, Color = "auto", Fill = "auto" };
tableCellProperties2.Append(tableCellWidth2);
tableCellProperties2.Append(shading2);
Paragraph paragraph2 = new Paragraph() { RsidParagraphAddition = "00006AF4", RsidParagraphProperties = "00006AF4", RsidRunAdditionDefault = "00006AF4" };
ParagraphProperties paragraphProperties2 = new ParagraphProperties();
SpacingBetweenLines spacingBetweenLines2 = new SpacingBetweenLines() { After = "0" };
paragraphProperties2.Append(spacingBetweenLines2);
Run run2 = new Run();
Text textSecondColumn = new Text();
textSecondColumn.Text = formattedJsonString; // see the snippet above
run2.Append(textSecondColumn);
paragraph2.Append(paragraphProperties2);
paragraph2.Append(run2);
tableCell2.Append(tableCellProperties2);
tableCell2.Append(paragraph2);
tableRow.Append(tablePropertyExceptions);
tableRow.Append(tableCell1);
tableRow.Append(tableCell2);
So here's the problem - even though the string "formattedJsonString" is definitely beautified, all the line breaks and indents are lost in the final document.
I used to do this in Word Interop and it worked fine. Any idea for how to handle this with Open XML?
By the way - The SDK will not open a document.xml that has formatted/beautified Json text.
Amendment: It just occurred to me that the formatting adds line breaks - and that perhaps each line needs to be a separate paragraph. Gonna try that and will amend this post again if needed.
You're on the right track. You do need to convert the line breaks, but I don't think each line needs to be a separate paragraph (although I suppose you could do it that way if you wanted to). I would instead just create a Run for each line, inserting a Break element before the Text element in each one except the first. Also be sure to set the Space property on each Text element to SpaceProcessingModeValues.Preserve to ensure the leading whitespace isn't lost.
Add all the Runs to the Paragraph inside the TableCell and that should do it.
I think this is what you are looking for:
static void AddJsonToParagraph(string json, Paragraph paragraph)
{
string[] lines = json.Split(Environment.NewLine);
bool isFirstLine = true;
foreach (var line in lines)
{
Run run = new Run();
if (!isFirstLine)
{
run.Append(new Break());
}
isFirstLine = false;
Text text = new Text
{
Space = SpaceProcessingModeValues.Preserve,
Text = line
};
run.Append(text);
paragraph.Append(run);
}
}
In your code, replace the line that appends run2 to paragraph2 with this:
AddJsonToParagraph(formattedJsonString, paragraph2);
I am completely new to Excel automation in C#
Actually I am came across some of API for Excel generation in C# .net like CLOSED XML , EEPLUS , and spreadsheetlight by vincent ,Open XML by Microsoft , Interop excel by Microsoft
According My Study
CLOSED XML -- No charts supported
EEPLUS - Charts supported
Spread light- Very easy to use and Charts also supported
Open XML- complex hard to work
I was completely fine with Spread light light was good API , but i am not able to find a solution how to add label inside a Chart
I hope any one in stack overflow came across with same problem.
I need to add label like text inside chart like for example Company in chart.
Please let me know how to find solution any one this Free API
Thanks
Ranjith
You can add at title via Epplus but positioning will require XML editing:
[TestMethod]
public void Chart_Manual_Title_Test()
{
//http://stackoverflow.com/questions/37304860/how-to-add-to-text-label-for-excel-charts-using-open-xml-or-epplus
//Throw in some data
var datatable = new DataTable("tblData");
datatable.Columns.AddRange(new[] { new DataColumn("Col1", typeof(int)), new DataColumn("Col2", typeof(int)), new DataColumn("Col3", typeof(object)) });
for (var i = 0; i < 10; i++)
{
var row = datatable.NewRow();
row[0] = i;
row[1] = i * 10;
row[2] = Path.GetRandomFileName();
datatable.Rows.Add(row);
}
//Create a test file
var fileInfo = new FileInfo(#"c:\temp\Chart_Manual_Title_Test.xlsx");
if (fileInfo.Exists)
fileInfo.Delete();
using (var pck = new ExcelPackage(fileInfo))
{
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.Add("Sheet1");
worksheet.Cells.LoadFromDataTable(datatable, true);
var chart = worksheet.Drawings.AddChart("chart test", eChartType.XYScatter);
var series = chart.Series.Add(worksheet.Cells["B2:B11"], worksheet.Cells["A2:A11"]);
chart.Title.Text = "XYZ Corp";
//Add custom layout
var chartXml = chart.ChartXml;
var nsm = new XmlNamespaceManager(chartXml.NameTable);
var nsuri = chartXml.DocumentElement.NamespaceURI;
nsm.AddNamespace("c", nsuri);
nsm.AddNamespace("a", "http://schemas.openxmlformats.org/drawingml/2006/main");
//Set the title overlay
var overlayNode = chartXml.SelectSingleNode("c:chartSpace/c:chart/c:title/c:overlay", nsm);
overlayNode.Attributes["val"].Value = "1";
//Set the font size
var defRPrNode = chartXml.SelectSingleNode("c:chartSpace/c:chart/c:title/c:tx/c:rich/a:p/a:pPr/a:defRPr", nsm);
defRPrNode.Attributes["sz"].Value = "1200";
//Get the title layout and add the manual section
var layoutNode = chartXml.SelectSingleNode("c:chartSpace/c:chart/c:title/c:layout", nsm);
var manualLayoutNode = chartXml.CreateElement("c:manualLayout", nsuri);
layoutNode.AppendChild(manualLayoutNode);
//Add coordinates
var xModeNode = chartXml.CreateElement("c:xMode", nsuri);
var attrib = chartXml.CreateAttribute("val");
attrib.Value = "edge";
xModeNode.Attributes.Append(attrib);
manualLayoutNode.AppendChild(xModeNode);
var yModeNode = chartXml.CreateElement("c:yMode", nsuri);
attrib = chartXml.CreateAttribute("val");
attrib.Value = "edge";
yModeNode.Attributes.Append(attrib);
manualLayoutNode.AppendChild(yModeNode);
var xNode = chartXml.CreateElement("c:x", nsuri);
attrib = chartXml.CreateAttribute("val");
attrib.Value = "0.9";
xNode.Attributes.Append(attrib);
manualLayoutNode.AppendChild(xNode);
var yNode = chartXml.CreateElement("c:y", nsuri);
attrib = chartXml.CreateAttribute("val");
attrib.Value = "0.95";
yNode.Attributes.Append(attrib);
manualLayoutNode.AppendChild(yNode);
pck.Save();
}
}
Which gives you this in the output:
RESPONSE TO COMMENTS
Ok, thats a little tougher. The right way would be to use a relSizeAnchor which can be placed inside the chart and moved/sized with it. But that you would have to do from scratch (or at best another library). If you activate a chart in excel and do an Insert > Text Box to see what that looks like.
Another option would be to fake it by using an unused title like say an Axis Title and moving it similar to how I did the chart title.
But the easiest option would be to simply add a shape. The draw back is if you move the chart it will not move with it:
var tb1 = worksheet.Drawings.AddShape("tb1", eShapeStyle.Rect);
tb1.Text = "ABC Company";
tb1.SetPosition(1, 0, 2, 0);
tb1.SetSize(200, 20);
tb1.Font.Color = Color.Black;
tb1.TextAlignment = eTextAlignment.Center;
tb1.Fill.Color = Color.LightYellow;
tb1.Fill.Style = eFillStyle.SolidFill;
tb1.Border.Fill.Color = Color.Red;
gives this as the output when combined with above:
Essential XlsIO can add textboxes to Excel charts.
Example code
//Accessing the chart of the worksheet
IChartShape shape = workbook.Worksheets[0].Charts[0];
//Adding textbox to chart shape
shape.TextBoxes.AddTextBox(1,1, 100,200);
//Setting position for textbox
shape.TextBoxes[0].Top = 900;
shape.TextBoxes[0].Left = 750;
//Adding text to textbox
shape.TextBoxes[0].Text = "New textbox";
The whole suite of controls is available for free (commercial applications also) through the community license program if you qualify (less than 1 million US Dollars in revenue). The community license is the full product with no limitations or watermarks.
Note: I work for Syncfusion.
If the chart already exists in your template and you are just adding data, then you can read the contents of the box from a cell on the page. Then from C# you just need to write to the cell. This lets you do any formatting or positioning you want in Excel.
My end aim is to programatically set Excel's "Print Titles" value of Page Setup for all worksheets in my document.
Initially I tried looking at using the SpreadsheetPrintingParts object (based on this question) - however, that requires generating a base 64 string, which seemingly has to come from an exisiting file. (I am generating my spreadsheet from scratch.)
This post then taught me that I could set "Print_Titles" as a defined name on the row I needed instead. I've been attempting to do that programatically, but this seems to corrupt all my files.
My code:
var definedNamesCol = new DefinedNames(); //Create the collection
var definedName = new DefinedName() { Name = "_xlnm.Print_Titles", Text = "\'SheetName\'!$2:$2", LocalSheetId = (UInt32) (_nextSheetId - 1) }; // Create a new range
definedNamesCol.Append(definedName); // Add it to the collection
_workbookPart.Workbook.Append(definedNamesCol);
I've also looked with the OpenXML productivity tool which suggests: (Essentially identical)
DefinedNames definedNames1 = new DefinedNames();
DefinedName definedName1 = new DefinedName(){ Name = "_xlnm.Print_Titles", LocalSheetId = (UInt32Value)0U };
definedName1.Text = "\'SheetName\'!$2:$2";
definedNames1.Append(definedName1)
I've also tried setting the Xlm property on DefinedName but the file then opens with an error that it contains Macro's in a Macro-Free file, which isn't what I think I want to do.
A (simplified) version of what I'm generating in workbook.xml:
<?xml version="1.0" encoding="utf-8"?>
<x:workbook xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<x:sheets>
<x:sheet name="ABBEY" sheetId="1" r:id="R2f5447238bc94fa4" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" />
</x:sheets>
<x:definedNames>
<x:definedName name="_xlnm.Print_Titles" localSheetId="0">'SheetName'!$2:$2</x:definedName>
</x:definedNames>
</x:workbook>
Is there a better way to approach the problem? Or is my intention right, and it's a misunderstanding of the method somewhere else?
The above code was within a CreateWorksheet method, so was being called for each sheet. In the resulting workbook.xml file, this was then creating multiple definedNames objects, when there should only be a single definedNames object containing multiple definedNames.
I resolved the problem using this code:
var definedName = new DefinedName() { Name = "_xlnm.Print_Titles", Text = "\'Sheet Name\'!$2:$2", LocalSheetId = (UInt32) (_nextSheetId - 1) }; // Create a new range
if (_workbookPart.Workbook.DefinedNames == null)
{
var definedNamesCol = new DefinedNames();
_workbookPart.Workbook.Append(definedNamesCol);
}
_workbookPart.Workbook.DefinedNames.Append(definedName); // Add it to the collection
I have a word document with a table on the first page. It is the first table on the document and only one of the first page. I need to set the "Indent from left" to 0" on this table. I'm already appending some text to the document:
using (WordprocessingDocument oDocument = WordprocessingDocument.Open(_ms, true))
{
//Set paragraph, run, and runproperties objects.
Paragraph para = oDocument.MainDocumentPart.Document.Descendants<Paragraph>().First();
Run run = para.AppendChild(new Run());
RunProperties runPro = new RunProperties();
Run lineBreak = new Run(new Break());
//Set the text color and text value
Color color = new Color() { Val = "FFFFFF" };
Text text1 = new Text();
text1.Text = "text";
//Add the text to the body
runPro.Append(color);
run.Append(runPro);
run.Append(text1, lineBreak);
//Close the handle
oDocument.Close();
}
I've read a bit about the TableIndentation class, but haven't found any examples of it being used. Does anybody have experience with this?
Thanks
Have you tried using the Open XML SDK Productivity Tool (note that there are multiple downloads on this page) to inspect a document that has what you need? If not, do so. It will answer your question.
You can specify the table indentation in the table properties class like this.
TableProperties tPr = new TableProperties();
tPr.TableIndentation = new TableIndentation() { Type = indentationType, Width = indentationWidth };
use this and provide width in DXA(1"=1440 something)
props.TableIndentation = new TableIndentation() { Type = TableWidthUnitValues.Dxa, Width=-997 };
I'm converting one of our in-house apps to use OpenOffice/LibreOffice as well as MsOffice.
In an Excel spreedsheet with C#/.NET it is fairly simple to set only parts of text within a cell to bold. It looks like this:
sheet.Range["A1"].Characters[startIndex, length].Font.Bold = true;
How do you do the equivalent in OO/LO?
Have you look at XML File format ?
http://www.openoffice.org/xml/xml_specification.pdf
I am not sure about open Excel but in MS-Excel you can define the font and Append it .
var fonts = new Fonts();
var font = new DocumentFormat.OpenXml.Spreadsheet.Font();
var fontName = new FontName {Val = StringValue.FromString("Arial")};
var fontSize = new FontSize {Val = DoubleValue.FromDouble(11)};
font.FontName = fontName;
font.FontSize = fontSize;
fonts.Append(font);
var cellFormats = new CellFormats();
cellFormats.Append(fonts);
I'm not sure if this is the most efficient way to do it, but it works. The basic idea is to use the example code for inserting text into a cell and then create a cursor on the desired subrange and set the CharWeight property on the subrange.
var xCellText = (unoidl.com.sun.star.text.XText)cell;
var xTextCursor = xCellText.createTextCursor();
xCellText.insertString(xTextCursor, newLine, false);
xTextCursor.gotoStart(false);
xTextCursor.goRight((short)boldStartIndex, false);
xTextCursor.goRight((short)boldLength, true);
var xPropSet = (XPropertySet)xTextCursor;
xPropSet.setPropertyValue("CharWeight", new uno.Any(unoidl.com.sun.star.awt.FontWeight.BOLD));