I'm trying to insert multiple tables into a word document using c#, but when I add another block of code to add a table in I am getting an error and the second table is not being inserted. How do I move the range down to the bottom of the page and then add another table? I tried creating a new range using the end of doc reference but this doesn't seem to work, can anyone give me some help?
Word._Application objApp;
Word._Document objDoc;
try
{
object objMiss = System.Reflection.Missing.Value;
object objEndOfDocFlag = "\\endofdoc"; /* \endofdoc is a predefined bookmark */
//Start Word and create a new document.
objApp = new Word.Application();
objApp.Visible = true;
objDoc = objApp.Documents.Add(ref objMiss, ref objMiss,
ref objMiss, ref objMiss);
//Insert a paragraph at the end of the document.
Word.Paragraph objPara2; //define paragraph object
object oRng = objDoc.Bookmarks.get_Item(ref objEndOfDocFlag).Range; //go to end of the page
objPara2 = objDoc.Content.Paragraphs.Add(ref oRng); //add paragraph at end of document
objPara2.Range.Text = "Test Table Caption"; //add some text in paragraph
objPara2.Format.SpaceAfter = 10; //defind some style
objPara2.Range.InsertParagraphAfter(); //insert paragraph
//Insert a table
Word.Table objTab1; //create table object
Word.Range objWordRng = objDoc.Bookmarks.get_Item(ref objEndOfDocFlag).Range; //go to end of document
objTab1 = objDoc.Tables.Add(objWordRng, 9, 2, ref objMiss, ref objMiss); //add table object in word document
objTab1.Range.ParagraphFormat.SpaceAfter = 6;
objTab1.Range.Borders[Word.WdBorderType.wdBorderBottom].LineStyle = Word.WdLineStyle.wdLineStyleThickThinLargeGap;
objTab1.Range.Borders[Word.WdBorderType.wdBorderHorizontal].LineStyle = Word.WdLineStyle.wdLineStyleDouble;
objTab1.Range.Borders[Word.WdBorderType.wdBorderTop].LineStyle = Word.WdLineStyle.wdLineStyleDouble;
objTab1.Range.Borders[Word.WdBorderType.wdBorderLeft].LineStyle = Word.WdLineStyle.wdLineStyleDouble;
objTab1.Range.Borders[Word.WdBorderType.wdBorderRight].LineStyle = Word.WdLineStyle.wdLineStyleDouble;
objTab1.Columns.Borders[Word.WdBorderType.wdBorderVertical].LineStyle = Word.WdLineStyle.wdLineStyleDouble;
objTab1.Columns[1].Shading.BackgroundPatternColor = Word.WdColor.wdColorGray20;
objTab1.Columns[1].Width = objApp.CentimetersToPoints(3.63f);
objTab1.Columns[2].Width = objApp.CentimetersToPoints(13.11f);
int iRow, iCols;
string[] col = new string[9];
col[0] = "Row1";
col[1] = "row2";
col[2] = "Row3";
col[3] = "row4";
col[4] = "row5";
col[5] = "row6";
col[6] = "row7";
col[7] = "row8";
col[8] = "tow9";
for (iRow = 1; iRow <= 9; iRow++)
{
objTab1.Rows[iRow].Range.Font.Bold = 1;
for (int i = 0; i <= col.Length; i++)
{
string s = col[i];
objTab1.Rows[iRow++].Range.Text = s;
objTab1.Rows[iRow].Range.Font.Bold = 1;
}
}
objApp.Selection.TypeParagraph();
//Insert a paragraph at the end of the document.
Word.Paragraph objPara3; //define paragraph object
object oRng2 = objDoc.Bookmarks.get_Item(ref objEndOfDocFlag).Range; //go to end of the page
objPara3 = objDoc.Content.Paragraphs.Add(ref oRng2); //add paragraph at end of document
objPara3.Range.Text = "hello"; //add some text in paragraph
objPara3.Format.SpaceAfter = 10; //defind some style
objPara3.Range.InsertParagraphAfter(); //insert paragraph
//Insert a 2 x 2 table, (table with 2 row and 2 column)
Word.Table objTab2; //create table object
Word.Range objWordRng2 = objDoc.Bookmarks.get_Item(ref objEndOfDocFlag).Range; //go to end of document
objTab2 = objDoc.Tables.Add(objWordRng2, 9, 2, ref objMiss, ref objMiss); //add table object in word document
objTab2.Range.ParagraphFormat.SpaceAfter = 6;
object stylename2 = "Table Grid";
I get the following exception "the requested member of the collection does not exist"
Without fully following how you want the layout to appear. There are a couple of issues with the posted code. First in the for loops where you are adding the text to the first table, I am not sure what you are doing with the following lines:
objTab1.Rows[iRow++].Range.Text = s;
objTab1.Rows[iRow].Range.Font.Bold = 1;
The iRow++ increment in the first line is going to throw off where the row is in the table. I am guessing you may want:
objTab1.Rows[iRow].Range.Font.Bold = 1;
objTab1.Rows[iRow].Range.Text = s;
iRow++;
The other issue is how the code is getting the last paragraph like below:
object oRng2 = objDoc.Bookmarks.get_Item(ref objEndOfDocFlag).Range;
objPara3 = objDoc.Content.Paragraphs.Add(ref oRng);
The oRng2 range is the end of doc range however, the next line uses oRng which is the top of the document. Changing the add paragraphs to the proper range should fix this.
objPara3 = objDoc.Content.Paragraphs.Add(ref oRng2);
Hope this helps.
Related
So in using Microsoft.Office.Interop.Word to generate letters automatically, the issue I am having is the header section. Whenever I run the code below it just replaces the first Item in the header on all pages instead of having both the client name and client address on the header of all pages on different lines, I just get the client address
foreach (Section section in document.Sections)
{
//Get the header range and add the header details.
var headerRange = section.Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
var headerRange1 = section.Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
headerRange.Fields.Add(headerRange, WdFieldType.wdFieldPage);
headerRange1.Fields.Add(headerRange, WdFieldType.wdFieldPage);
headerRange.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphLeft;
headerRange1.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphLeft;
headerRange.Font.ColorIndex = WdColorIndex.wdBlack;
headerRange1.Font.ColorIndex = WdColorIndex.wdBlack;
headerRange.Font.Size = 12;
headerRange1.Font.Size = 12;
headerRange.Font.Name = "Arial";
headerRange1.Font.Name = "Arial";
headerRange.Font.Bold = 1;
headerRange1.Font.Bold = 1;
headerRange.Text = ClientNameBox.Text;
headerRange.InsertParagraphAfter();
headerRange1.Text = ClientsAddressBox.Text;
headerRange.ParagraphFormat.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphLeft;
headerRange1.ParagraphFormat.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphLeft;
}
Think of a Range like an invisible selection: if you type something when content is selected, what you type replaces what was selected. So if you assign something to a Range that has content, what you assign replaces the content. It doesn't matter whether you assign the same content to two separate Range objects - since they both encompass the same start and end points, changing the one changes the other.
The trick with a Range, as with a Selection, is to "collapse" it. For a selection, you press an arrow key; for a Range there is a Collapse method, where you specify the direction: either to the Start or to the End.
foreach (Section section in document.Sections)
{
//Get the header range and add the header details.
var headerRange = section.Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
headerRange.Fields.Add(headerRange, WdFieldType.wdFieldPage);
headerRange.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphLeft;
headerRange.Font.ColorIndex = WdColorIndex.wdBlack;
headerRange.Font.Size = 12;
headerRange.Font.Name = "Arial";
headerRange.Font.Bold = 1;
headerRange.Text = ClientNameBox.Text;
headerRange.InsertParagraphAfter();
object oCollapseEnd = WdCollapseDirection.wdCollapseEnd;
headerRange.Collapse(ref oCollapseEnd);
headerRange.Text = ClientsAddressBox.Text;
headerRange.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphLeft;
}
I have a problem like this: I need to use advanced find to find a specific text (base on Font of the text, the text string, wildcard,...), and write down to a notepad file which page did I find that text.
I see that in C# .Net has this Find.Execute method, but I don't know if it's possible to do this, I have googled around but no hope.
But my idea is like this code
using Microsoft.Office.Core;
using Word = Microsoft.Office.Interop.Word;
using System.Reflection;
...
Word.Application oWord;
Word._Document oDoc;
oWord = new Word.Application();
oWord.Visible = false;
oDoc = oWord.Documents.Open(strPath, ReadOnly: true);
Word.Range findRange;
Word.Range resultRange;
int nPage;
//Get the range of the whole word document
int nEnd;
nEnd = oDoc.Paragraphs.Last.Range.Sentences.First.End;
findRange = oDoc.Range(0, nEnd);
//Setup find condition
//The color of the found text: RGB(243,99, 195) . Please help!
//Execute find --> Loop until not found anymore
{
//findRange.Find.Execute... Please help!
//Get the range of the found text
//resultRange = ... Please help!
//Get page of the result range
nPage = resultRange.get_Information(Word.WdInformation.wdActiveEndPageNumber);
//Do anything you like with nPage
}
//Close the process
oDoc.Close(Word.WdSaveOptions.wdDoNotSaveChanges);
((Word._Application)oWord).Quit(Word.WdSaveOptions.wdDoNotSaveChanges);
Thank you in advance.
Thank God, I found my solution.
After I read:
this article to find out how to loop the find next feature.
This article to find out that must use Find.Font.Color instead of Find.Font.TextColor.RGB
This article to get the page range (the code is pretty unclean, but usable)
Ok, here it goes
Word.Application oWord;
Word._Document oDoc;
oWord = new Word.Application();
oWord.Visible = false;
oDoc = oWord.Documents.Open(strWorkingPath, ReadOnly: true);
//===================Excute===================
/*Word 2013*/
oWord.ActiveWindow.View.ReadingLayout = false;
// Get pages count
Word.WdStatistic PagesCountStat = Word.WdStatistic.wdStatisticPages;
int nTotalPage = oDoc.ComputeStatistics(PagesCountStat);
int nEndOfTheDoc = oDoc.Paragraphs.Last.Range.Sentences.First.End;
int nStart = 0;
int nEnd = nEndOfTheDoc;
List<int> lstPage = new List<int>();
int color = 696969;//The color you can get by read the Font.Color of the Range in Debug view
Word.Range findRange;
object What = Microsoft.Office.Interop.Word.WdGoToItem.wdGoToPage;
object Which = Microsoft.Office.Interop.Word.WdGoToDirection.wdGoToAbsolute;
object nCrtPage;
object nNextPage;
bool bPageIsIn = false;
/*Loop the pages*/
for (int i = 1; i <= nTotalPage; i++)
{
/*Get the start and end position of the current page*/
nCrtPage = i;
nNextPage = i + 1;
nStart = oWord.Selection.GoTo(ref What,
ref Which, ref nCrtPage).Start;
nEnd = oWord.Selection.GoTo(ref What,
ref Which, ref nNextPage).End;
/*The last page: nStart will equal nEnd*/
if(nStart == nEnd)
{
/*Set nEnd for the last page*/
nEnd = nEndOfTheDoc;
}
/*Set default for Count page trigger*/
bPageIsIn = false;
/*Set the find range is the current page range*/
findRange = oDoc.Range(nStart, nEnd);
/*Set up find condition*/
findRange.Find.Font.Color = (Word.WdColor)color;
findRange.Find.Format = true;
findRange.Find.Text = "^?";
do
{
/*Loop find next*/
findRange.Find.Execute();
/*If found*/
if (findRange.Find.Found)
{
/*If found data is still in the page*/
if (findRange.End <= nEnd)
{
/*If found data is visible by human eyes*/
if (!string.IsNullOrWhiteSpace(findRange.Text))
{
/*Ok, count this page*/
bPageIsIn = true;
break;/*no need to find anymore for this page*/
}
}
}
else
break;/*no need to find anymore for this page*/
}while (findRange.End < nEnd);/*Make sure it is in that page only*/
if (bPageIsIn)
lstPage.Add(i);
}
//===================Close===================
oDoc.Close(Word.WdSaveOptions.wdDoNotSaveChanges);
((Word._Application)oWord).Quit(Word.WdSaveOptions.wdDoNotSaveChanges);
foreach (var item in lstPage)
{
builder.AppendLine(item.ToString());//Do anything you like with the list page
}
An hour ago I been searching for a code that split word document into separate pages I found this question
Using the code in the thread
static class PagesExtension {
public static IEnumerable<Range> Pages(this Document doc) {
int pageCount = doc.Range().Information[WdInformation.wdNumberOfPagesInDocument];
int pageStart = 0;
for (int currentPageIndex = 1; currentPageIndex <= pageCount; currentPageIndex++) {
var page = doc.Range(
pageStart
);
if (currentPageIndex < pageCount) {
//page.GoTo returns a new Range object, leaving the page object unaffected
page.End = page.GoTo(
What: WdGoToItem.wdGoToPage,
Which: WdGoToDirection.wdGoToAbsolute,
Count: currentPageIndex+1
).Start-1;
} else {
page.End = doc.Range().End;
}
pageStart = page.End + 1;
yield return page;
}
yield break;
}
}
I call the code above using this code
var app = new Microsoft.Office.Interop.Word.Application();
object missObj = System.Reflection.Missing.Value;
app.Visible = false;
var doc = app.Documents.Open(fileLocation);
int pageNumber = 1;
foreach (var page in doc.Pages())
{
Microsoft.Office.Interop.Word.Document newDoc = app.Documents.Add(ref missObj, ref missObj, ref missObj, ref missObj);
page.Copy();
var doc2 = app.Documents.Add();
doc2.Range().Paste();
object newDocName = pageNumber.ToString() + ".docx";
Console.WriteLine(newDocName);
doc2.SaveAs2(newDocName, Microsoft.Office.Interop.Word.WdSaveFormat.wdFormatXMLDocument,
CompatibilityMode: Microsoft.Office.Interop.Word.WdCompatibilityMode.wdWord2010);
pageNumber++;
}
app.ActiveDocument.Close();
app.Quit();
But I'm getting an error in a specific document and here is the error
This method or property is not available because no text is selected.
What is the reason for it? i checked the document and found out that the document contains lots of spaces before the next page. How can I solve this?
And using the code above it didn't copy the header and footer. Thank you
Update: Error
This method or property is not available because no text is selected.
at Microsoft.Office.Interop.Word.Range.Copy()
at retrieveObjects(String location) in Document.cs:line 31
and this is the line
page.Copy();
I'm using epplus to create excel in my program!
I need insert a column chart.
This is my code:
//Add the chart to the sheet
var chart = sheet.Drawings.AddChart(chartTitle, eChartType.ColumnStacked3D);
chart.SetPosition(positionRow, 2, positionCol, 2);
chart.Title.Text = chartTitle;
chart.Title.Font.Bold = true;
chart.Title.Font.Size = 18;
chart.SetSize(width, height);
//Set the data range
chart.Series.Add("D17:D22", "B17:B22");
chart.Series.Add("P17:P22", "B17:B22");
And I get result:
But I want result as:
After I created excel file from program, I open it and change the chart:
Right click in the chart/Select data/Switch row/column.
How can I Switch row/column in my code? Or how to insert the chart like the below picture?
Sorry for not good in English
Thank you very much!
That button in excel just switches the data and rebuilds the chart. Rather then try to mimic it better to build the chart the right way from the start.
What I mean is your original chart is treating the data as 2 data series but what you really want is 6 series.
The only problem is values in the x axis - there is no direct way with Epplus it seems to get to the category (horizontal) axis labels of the series. So you have to do it through XML manipulation as below.
So change your code to this:
//Set the data range
//chart.Series.Add("D17:D22", "B17:B22");
//chart.Series.Add("P17:P22", "B17:B22");
for (var i = 0; i < opt.Count; i++)
{
var datarange = sheet.Cells[$"Bar!D{17 + i},Bar!P{17 + i}"];
var ser = chart.Series.Add(datarange.Address, $"B{17 + i}:B{17 + i}");
ser.HeaderAddress = sheet.Cells[$"$B{17 + i}"];
}
//have to remove cat nodes from each series so excel autonums 1 and 2 in xaxis
var chartXml = chart.ChartXml;
var nsm = new XmlNamespaceManager(chartXml.NameTable);
var nsuri = chartXml.DocumentElement.NamespaceURI;
nsm.AddNamespace("c", nsuri);
//Get the Series ref and its cat
var serNodes = chartXml.SelectNodes("c:chartSpace/c:chart/c:plotArea/c:bar3DChart/c:ser", nsm);
foreach (XmlNode serNode in serNodes)
{
//Cell any cell reference and replace it with a string literal list
var catNode = serNode.SelectSingleNode("c:cat", nsm);
catNode.RemoveAll();
//Create the string list elements
var ptCountNode = chartXml.CreateElement("c:ptCount", nsuri);
ptCountNode.Attributes.Append(chartXml.CreateAttribute("val", nsuri));
ptCountNode.Attributes[0].Value = "2";
var v0Node = chartXml.CreateElement("c:v", nsuri);
v0Node.InnerText = "opening";
var pt0Node = chartXml.CreateElement("c:pt", nsuri);
pt0Node.AppendChild(v0Node);
pt0Node.Attributes.Append(chartXml.CreateAttribute("idx", nsuri));
pt0Node.Attributes[0].Value = "0";
var v1Node = chartXml.CreateElement("c:v", nsuri);
v1Node.InnerText = "closing";
var pt1Node = chartXml.CreateElement("c:pt", nsuri);
pt1Node.AppendChild(v1Node);
pt1Node.Attributes.Append(chartXml.CreateAttribute("idx", nsuri));
pt1Node.Attributes[0].Value = "1";
//Create the string list node
var strLitNode = chartXml.CreateElement("c:strLit", nsuri);
strLitNode.AppendChild(ptCountNode);
strLitNode.AppendChild(pt0Node);
strLitNode.AppendChild(pt1Node);
catNode.AppendChild(strLitNode);
}
pck.Save();
Which gives this as the output in my unit test (made up the numbers):
I can create two tables easy enough but I am having trouble getting them to appear side by side like this:
I am unsure how to achieve this using the Open XML SDK. I'm guessing it will either be a TableProperty or a trick with paragraphs but using the Productivity Tool I couldn't work it out. Code snippet:
int LeftWidth = 2000;
int RightWidth = 2000;
int NumberOfCols = 2;
Table leftTable = StartTable(NumberOfCols, LeftWidth); // Create a basic table
Table rightTable = StartTable(NumberOfCols, RightWidth);
body.Append(leftTable);
/// Do something to right table properties here?
body.Append(rightTable);
I am open to different methods, although ideally the idea would be transferable to three tables side by side too.
In the end I realised there are two main ways to achieve this is Word - either click a table and drag it's top left crosshair to where you want it or split the page into two columns and place a column break between the tables.
Floating Tables Method
int LeftWidth = 2000;
int RightWidth = 2000;
int NumberOfCols = 2;
Table leftTable = StartTable(NumberOfCols, LeftWidth); // Create a basic table
Table rightTable = StartTable(NumberOfCols, RightWidth);
/// Add table position properties and place table in top left
TableProperties tblProps = leftTable.Descendants<TableProperties>().First();
TablePositionProperties tblPos = new TablePositionProperties() { VerticalAnchor = VerticalAnchorValues.Text, TablePositionY = 1 };
TableOverlap overlap = new TableOverlap() { Val = TableOverlapValues.Overlap };
tblProps.Append(tblPos, overlap);
body.Append(leftTable);
/// Add position property to right table, set 8700 and 2400 to where you want the table
TableProperties tblProps2 = rightTable.Descendants<TableProperties>().First();
TablePositionProperties tblPos2 = new TablePositionProperties() { HorizontalAnchor = HorizontalAnchorValues.Page, VerticalAnchor = VerticalAnchorValues.Page, TablePositionX = 8700, TablePositionY = 2400 };
TableOverlap overlap2 = new TableOverlap() { Val = TableOverlapValues.Overlap };
tblProps2.Append(tblPos2, overlap2);
body.Append(rightTable);
Columns Method
Before the tables place this code to keep the prior content in one column:
/// Make sure everything else stays at one column
Paragraph oneColPara = body.AppendChild(new Paragraph());
/// Adjust current doc properties to keep things like landscape
SectionProperties sectionOneProps = null;
if (body.Descendants<SectionProperties>().Count() > 0)
sectionOneProps = (SectionProperties)body.Descendants<SectionProperties>().First().CloneNode(true);
else
sectionOneProps = new SectionProperties();
sectionOneProps.RemoveAllChildren<Columns>();
sectionOneProps.RemoveAllChildren<DocGrid>();
sectionOneProps.Append(new Columns(){ Space = "708" }, new DocGrid(){ LinePitch = 360 }, new SectionType { Val = SectionMarkValues.Continuous } );
oneColPara.Append(new ParagraphProperties(sectionOneProps));
Then do this in the tables part:
int LeftWidth = 2000;
int RightWidth = 2000;
int NumberOfCols = 2;
Table leftTable = StartTable(NumberOfCols, LeftWidth); // Create a basic table
Table rightTable = StartTable(NumberOfCols, RightWidth);
/// Need this blank para to line tables up
body.Append(new Paragraph());
body.Append(leftTable);
/// Place the tables side by side
body.Append(new Paragraph(new Run(new Break() { Type = BreakValues.Column })));
body.Append(rightTable);
/// Make section have 2 columns
Paragraph twoColPara = body.AppendChild(new Paragraph());
/// Adjust current doc properties to keep things like landscape
SectionProperties sectionTwoProps = null;
if (body.Descendants<SectionProperties>().Count() > 0)
sectionTwoProps = (SectionProperties)body.Descendants<SectionProperties>().First().CloneNode(true);
else
sectionTwoProps = new SectionProperties();
sectionTwoProps.RemoveAllChildren<Columns>();
sectionTwoProps.RemoveAllChildren<DocGrid>();
sectionTwoProps.Append(new Columns() { Space = "284", ColumnCount = 2 }, new DocGrid() { LinePitch = 360 }, new SectionType { Val = SectionMarkValues.Continuous });
twoColPara.Append(new ParagraphProperties(sectionTwoProps));