Is there a way of releasing memory and preventing the following code from crashing in MS-Word?
I get the following error message:
This method or property is not available because there is a memory or disk problem.
Sub vbaTest()
Dim doc As Document
Dim sty As Style
Dim s As Style
Dim readingOrder As WdReadingOrder
Dim i As Integer
Set doc = ActiveDocument
Set sty = doc.Styles(wdStyleNormal)
For i = 0 To 100
readingOrder = sty.ParagraphFormat.readingOrder
For Each s In doc.Styles
s.Font.SizeBi = s.Font.Size + 3
Next
Set s = Nothing
Next
Set sty = Nothing
End Sub
-- or --
public void CsharpRibbon_Click(O.IRibbonControl c)
{
var doc = app.ActiveDocument;
var style = doc.Styles[Wd.WdBuiltinStyle.wdStyleNormal];
for (int i = 0; i < 100; i++)
{
var readingOrder = style.ParagraphFormat.ReadingOrder;
foreach (Wd.Style s in doc.Styles)
s.Font.SizeBi = s.Font.Size + 3;
}
}
The code above doesn't really do anything helpful. I have a ribbon button that I noticed causes a crash on repeated button presses (around 5 or 6 times in a Word session). I stripped back the code and added the for loop to simulate multiple presses of the button.
I'm not sure if this is your error, because 100 iterations doesn't seem like enough to cause memory errors, but VSTO uses COM objects, which must be released after use. The simple way to do this is:
Paragraph para = Paragraphs[1];
// etc.
Marshal.ReleaseComObject(yourObject);
There's also VSTO Contrib which makes this a little easier. Instead of making a call to ReleaseComObject, you would do something like this:
using (var doc = Document.WithComCleanup())
using (var paragraphs = doc.Resource.Paragraphs.WithComCleanup())
{
int count = paragraphs.Resource.Count;
// etc.
}
Or, for collections:
foreach (Paragraph para in Paragraphs.ComLinq<Paragraph>())
{
int pageBreakBefore = para.PageBreakBefore;
// etc.
}
Related
I want to replace the text content of bookmarks without loosing the bookmark.
foreach(Bookmark b in document.Bookmarks)
{
b.Range.Text = "newtext"; // text is set in document but bookmark is gone
}
I tried to set the new Range of the bookmark before the Text setting but I still have the same problem.
I also tried to re-add the bookmark with document.Bookmarks.Add(name, range); but I can't create an instance of range.
I had to readd the bookmarks and save the range temporarily. I also had to add a list of processed items to evade an endless loop.
List<string> bookmarksProcessed = new List<string>();
foreach (Bookmark b in document.Bookmarks)
{
if (!bookmarksProcessed.Contains(b.Name))
{
string text = getTextFromBookmarkName(b.Name);
var newend = b.Range.Start + text.Length;
var name = b.Name;
Range rng = b.Range;
b.Range.Text = text;
rng.End = newend;
document.Bookmarks.Add(name, rng);
bookmarksProcessed.Add(name);
}
}
Looks like you solved your problem, but here is a cleaner way to do it:
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Word;
using System.Text.RegularExpressions;
using Word = Microsoft.Office.Interop.Word;
//declare and get the current document
Document extendedDocument = Globals.Factory.GetVstoObject(Globals.ThisAddIn.Application.ActiveDocument);
List<string> bookmarksProcessed = new List<string>();
foreach(Word.Bookmark oldBookmark in extendedDocument.Bookmarks)
{
if(bookmarksProcessed.Contains(oldBookmark.Name))
{
string newText = getTextFromBookmarkName(oldBookmark.Name)
Word.Range newRange = oldBookmark.Range;
newRange.End = newText.Length;
Word.Bookmark newBookmark = extendedDocument.Controls.AddBookmark(newRange, oldBookmark.Name);
newBookmark.Text = newText;
oldBookmark.Delete();
}
}
Code isn't tested but should work.
With the above approach, you still lose the bookmark before it is added back in. If you really need to preserve the bookmark, I find that you can create an inner bookmark that wraps around the text (a bookmark within bookmark). After having the inner bookmark, you simply need to do:
innerBookmark.Range.Text = newText;
After the text replacing, the inner bookmark is gone and the outer bookmark is preserved. No need to set range.End.
You can create the inner bookmark manually or programmatically depending on your situation.
I have been working successfully with the C# OpenXml SDK (Unofficial Microsoft Package 2.5 from NuGet) for some time now, but have recently noticed that the following line of code returns different results depending on what mood Microsoft Word appears to be in when the file gets saved:
var fields = document.Descendants<FieldCode>();
From what I can tell, when creating the document in the first place (using Word 2013 on Windows 8.1) if you use the Insert->QuickParts->Field and choose MergeField from the Field names left hand pane, and then provide a Field name in the field properties and click OK then the field code is correctly saved in the document as I would expect.
Then when using the aforementioned line of code I will receive a field code count of 1 field. If I subsequently edit this document (and even leave this field well alone) the subsequent saving could mean that this field code no longer is returned in my query.
Another case of the same curiousness is when I see the FieldCode nodes split across multiple items. So rather than seeing say:
" MERGEFIELD Author \\* MERGEFORMAT "
As the node name, I will see:
" MERGEFIELD Aut"
"hor \\* MERGEFORMAT"
Split as two FieldCode node values. I have no idea why this would be the case, but it certainly makes my ability to match nodes that much more exciting. Is this expected behaviour? A known bug? I don't really want to have to crack open the raw xml and edit this document to work until I understand what is going on. Many thanks all.
I came across this very problem myself, and found a solution that exists within OpenXML: a utility class called MarkupSimplifier which is part of the PowerTools for Open XML project. Using this class solved all the problems I was having that you describe.
The full article is located here.
Here are some pertinent exercepts :
Perhaps the most useful simplification that this performs is to merge adjacent runs with identical formatting.
It goes on to say:
Open XML applications, including Word, can arbitrarily split runs as necessary. If you, for instance, add a comment to a document, runs will be split at the location of the start and end of the comment. After MarkupSimplifier removes comments, it can merge runs, resulting in simpler markup.
An example of the utility class in use is:
SimplifyMarkupSettings settings = new SimplifyMarkupSettings
{
RemoveComments = true,
RemoveContentControls = true,
RemoveEndAndFootNotes = true,
RemoveFieldCodes = false,
RemoveLastRenderedPageBreak = true,
RemovePermissions = true,
RemoveProof = true,
RemoveRsidInfo = true,
RemoveSmartTags = true,
RemoveSoftHyphens = true,
ReplaceTabsWithSpaces = true,
};
MarkupSimplifier.SimplifyMarkup(wordDoc, settings);
I have used this many times with Word 2010 documents using VS2015 .Net Framework 4.5.2 and it has made my life much, much easier.
Update:
I have revisited this code and have found it clears upon runs on MERGEFIELDS but not IF FIELDS that reference mergefields e.g.
{if {MERGEFIELD When39} = "Y???" "Y" "N" }
I have no idea why this might be so, and examination of the underlying XML offers no hints.
Word will often split text runs with into multiple text runs for no reason I've ever understood. When searching, comparing, tidying etc. We preprocess the body with method which combines multiple runs into a single text run.
/// <summary>
/// Combines the identical runs.
/// </summary>
/// <param name="body">The body.</param>
public static void CombineIdenticalRuns(W.Body body)
{
List<W.Run> runsToRemove = new List<W.Run>();
foreach (W.Paragraph para in body.Descendants<W.Paragraph>())
{
List<W.Run> runs = para.Elements<W.Run>().ToList();
for (int i = runs.Count - 2; i >= 0; i--)
{
W.Text text1 = runs[i].GetFirstChild<W.Text>();
W.Text text2 = runs[i + 1].GetFirstChild<W.Text>();
if (text1 != null && text2 != null)
{
string rPr1 = "";
string rPr2 = "";
if (runs[i].RunProperties != null) rPr1 = runs[i].RunProperties.OuterXml;
if (runs[i + 1].RunProperties != null) rPr2 = runs[i + 1].RunProperties.OuterXml;
if (rPr1 == rPr2)
{
text1.Text += text2.Text;
runsToRemove.Add(runs[i + 1]);
}
}
}
}
foreach (W.Run run in runsToRemove)
{
run.Remove();
}
}
I tried to simplify the document with Powertools but the result was a corrupted word file. I make this routine for simplify only fieldcodes that has specifics names, works in all parts on the docs (maindocumentpart, headers and footers):
internal static void SimplifyFieldCodes(WordprocessingDocument document)
{
var masks = new string[] { Constants.VAR_MASK, Constants.INP_MASK, Constants.TBL_MASK, Constants.IMG_MASK, Constants.GRF_MASK };
SimplifyFieldCodesInElement(document.MainDocumentPart.RootElement, masks);
foreach (var headerPart in document.MainDocumentPart.HeaderParts)
{
SimplifyFieldCodesInElement(headerPart.Header, masks);
}
foreach (var footerPart in document.MainDocumentPart.FooterParts)
{
SimplifyFieldCodesInElement(footerPart.Footer, masks);
}
}
internal static void SimplifyFieldCodesInElement(OpenXmlElement element, string[] regexpMasks)
{
foreach (var run in element.Descendants<Run>()
.Select(item => (Run)item)
.ToList())
{
var fieldChar = run.Descendants<FieldChar>().FirstOrDefault();
if (fieldChar != null && fieldChar.FieldCharType == FieldCharValues.Begin)
{
string fieldContent = "";
List<Run> runsInFieldCode = new List<Run>();
var currentRun = run.NextSibling();
while ((currentRun is Run) && currentRun.Descendants<FieldCode>().FirstOrDefault() != null)
{
var currentRunFieldCode = currentRun.Descendants<FieldCode>().FirstOrDefault();
fieldContent += currentRunFieldCode.InnerText;
runsInFieldCode.Add((Run)currentRun);
currentRun = currentRun.NextSibling();
}
// If there is more than one Run for the FieldCode, and is one we must change, set the complete text in the first Run and remove the rest
if (runsInFieldCode.Count > 1)
{
// Check fielcode to know it's one that we must simplify (for not to change TOC, PAGEREF, etc.)
bool applyTransform = false;
foreach (string regexpMask in regexpMasks)
{
Regex regex = new Regex(regexpMask);
Match match = regex.Match(fieldContent);
if (match.Success)
{
applyTransform = true;
break;
}
}
if (applyTransform)
{
var currentRunFieldCode = runsInFieldCode[0].Descendants<FieldCode>().FirstOrDefault();
currentRunFieldCode.Text = fieldContent;
runsInFieldCode.RemoveAt(0);
foreach (Run runToRemove in runsInFieldCode)
{
runToRemove.Remove();
}
}
}
}
}
}
Hope this helps!!!
I'm creating an application to attach scanned documents at SAP documents, but I have some problems with that process. I'm using SAP BO 9 PL8 and found the next problems:
When I try to add a new attachment line in a existing attachment (using the attachments2 object) with the update method, the DI try to check older lines, and it's possible that the file not exists in the original source path. So, update method reports an error. I use the code below:
Attachments2 oAtt = oCompany.GetBusinessObject(BoObjectTypes.oAttachments2);
if (oAtt.GetByKey(doc.AttachmentEntry))
{
oAtt.Lines.Add();
oAtt.Lines.FileName = oAttNew.Lines.FileName;
oAtt.Lines.FileExtension = oAttNew.Lines.FileExtension;
oAtt.Lines.SourcePath = oAttNew.Lines.SourcePath;
oAtt.Lines.Override = BoYesNoEnum.tYES;
if (oAtt.Update() != 0)
throw new Exception(oCompany.GetLastErrorDescription());
}
There are some documents in SAP who have a attachment tab, but via DI is not possible access to this functionality. For example the item master data (oItems) or the stock transfer (oStockTransfer). They have a AttachmentEntry field like the Documents object, but the objects haven't a property to add a attachment, so I have to create an activity for this documents.
Documents doc = oCompany.GetBusinessObject(oType);
doc.GetByKey(int.Parse(docEntry));
doc.AttachmentEntry = oAtt.AbsoluteEntry;
StockTransfer oStock = .oCompany.GetBusinessObject(BoObjectTypes.oStockTransfer);
// oStock.AttachmentEntry = oAtt.AbsoluteEntry FAIL
When I modify the AttachmentEntry property in LandedCost object, the object fail when I try to update it. If the object have already an attachment (added manually), add a new attachment in a new line works. The error of the first case is: No matching records found (ODBC -2028). When I force a catch block I get this other information: "1320000126 - Incorrect update header field". I use the code below:
LandedCostsService service = oCompany.GetCompanyService().GetBusinessService(ServiceTypes.LandedCostsService);
LandedCostParams oParam = service.GetDataInterface(LandedCostsServiceDataInterfaces.lcsLandedCostParams);
LandedCost oLandedCost = service.GetDataInterface(LandedCostsServiceDataInterfaces.lcsLandedCost);
oParam.LandedCostNumber = int.Parse(docEntry);
oLandedCost = service.GetLandedCost(oParam);
if (oAtt.GetByKey(oLandedCost.AttachmentEntry)) {
// Code similar to first code block I posted
}
else
{
if (oAttNew.Add() != 0)
throw new Exception(oCompany.GetLastErrorDescription());
oAtt.GetByKey(int.Parse(oCompany.GetNewObjectKey()));
oLandedCost.AttachmentEntry = oAtt.AbsoluteEntry;
try
{
service.UpdateLandedCost(oLandedCost);
}
catch (Exception ex)
{
throw new Exception(ex.Message + oCompany.GetLastErrorDescription());
}
}
I need to know what I'm doing wrong or if I need to contact with SAP to inform about these DI issues. I hope you can help me. Thanks in advance.
Regards,
Pedro
I usually do this and it works!
Private Sub test_NonContinue_LandedCost()
'Sample code for the non-continuous inventory system
Dim svrLandedCost As SAPbobsCOM.LandedCostsService = oCompany.GetCompanyService().GetBusinessService(SAPbobsCOM.ServiceTypes.LandedCostsService)
Dim oLandedCost As SAPbobsCOM.LandedCost = svrLandedCost.GetDataInterface(SAPbobsCOM.LandedCostsServiceDataInterfaces.lcsLandedCost)
Dim oLandedCostEntry As Long = 0
Dim GRPOEntry As Integer = 15
'Landed cost document - item tab line 1
Dim oLandedCost_ItemLine As SAPbobsCOM.LandedCost_ItemLine
oLandedCost_ItemLine = oLandedCost.LandedCost_ItemLines.Add
oLandedCost_ItemLine.BaseDocumentType = SAPbobsCOM.LandedCostBaseDocumentTypeEnum.asGoodsReceiptPO
oLandedCost_ItemLine.BaseEntry = GRPOEntry
oLandedCost_ItemLine.BaseLine = 0
'Landed cost document - item tab line 2
oLandedCost_ItemLine = oLandedCost.LandedCost_ItemLines.Add()
oLandedCost_ItemLine.BaseDocumentType = SAPbobsCOM.LandedCostBaseDocumentTypeEnum.asGoodsReceiptPO
oLandedCost_ItemLine.BaseEntry = GRPOEntry
oLandedCost_ItemLine.BaseLine = 1
'Landed cost document - item tab line 3
'This is a split line –split from second line (BaseEntry = 13, BaseLine = 1)
oLandedCost_ItemLine = oLandedCost.LandedCost_ItemLines.Add()
oLandedCost_ItemLine.BaseDocumentType = SAPbobsCOM.LandedCostBaseDocumentTypeEnum.asGoodsReceiptPO
oLandedCost_ItemLine.BaseEntry = GRPOEntry
oLandedCost_ItemLine.BaseLine = 1
oLandedCost_ItemLine.Quantity = 2
oLandedCost_ItemLine.Warehouse = "02"
'Landed cost document - cost tab line 1
Dim oLandedCost_CostLine As SAPbobsCOM.LandedCost_CostLine
oLandedCost_CostLine = oLandedCost.LandedCost_CostLines.Add
oLandedCost_CostLine.LandedCostCode = "CB"
'Suppose the vendor currency is Foreign Currency, if in local currency should set 'oLandedCost_CostLine.amount
oLandedCost_CostLine.amount = 100
'oLandedCost_CostLine.AllocationBy = SAPbobsCOM.LandedCostAllocationByEnum.asCashValueAfterCustoms
'Landed cost document - cost tab line 2
oLandedCost_CostLine = oLandedCost.LandedCost_CostLines.Add
oLandedCost_CostLine.LandedCostCode = "EQ"
oLandedCost_CostLine.amount = 100
'oLandedCost_CostLine.AllocationBy = SAPbobsCOM.LandedCostAllocationByEnum.asCashValueAfterCustoms
'Landed cost document - cost tab line 3
oLandedCost_CostLine = oLandedCost.LandedCost_CostLines.Add
oLandedCost_CostLine.LandedCostCode = "EQ"
oLandedCost_CostLine.amount = 100
'oLandedCost_CostLine.AllocationBy = SAPbobsCOM.LandedCostAllocationByEnum.asCashValueAfterCustoms
oLandedCost_CostLine.CostType = SAPbobsCOM.LCCostTypeEnum.asVariableCosts
Dim oLandedCostParams As SAPbobsCOM.LandedCostParams = svrLandedCost.GetDataInterface(SAPbobsCOM.LandedCostsServiceDataInterfaces.lcsLandedCostParams)
'Add a landed cost
Try
oLandedCostParams = svrLandedCost.AddLandedCost(oLandedCost)
oLandedCostEntry = oLandedCostParams.LandedCostNumber
Catch ex As Exception
'exception process
MsgBox(ex.Message)
End Try
'Update a landed cost
Dim oLandedCostUpdateParams As SAPbobsCOM.LandedCostParams = svrLandedCost.GetDataInterface(LandedCostsServiceDataInterfaces.lcsLandedCostParams)
Dim oLandedCostUpdate As SAPbobsCOM.LandedCost = svrLandedCost.GetDataInterface(LandedCostsServiceDataInterfaces.lcsLandedCost)
'Operate on the landed cost
oLandedCostUpdateParams.LandedCostNumber = oLandedCostEntry
'Get the landed cost
Try
oLandedCostUpdate = svrLandedCost.GetLandedCost(oLandedCostUpdateParams)
Catch ex As Exception
'exception process
MsgBox(ex.Message)
End Try
'Split functionality, split line 1
Dim oLandedCostUpdate_ItemLine As SAPbobsCOM.LandedCost_ItemLine
oLandedCostUpdate_ItemLine = oLandedCostUpdate.LandedCost_ItemLines.Add()
oLandedCostUpdate_ItemLine.OriginLine = 1
oLandedCostUpdate_ItemLine.Quantity = 1
oLandedCostUpdate_ItemLine.Warehouse = "02"
Dim oLandedCostUpdate_CostLine As SAPbobsCOM.LandedCost_CostLine
oLandedCostUpdate_CostLine = oLandedCostUpdate.LandedCost_CostLines.Add()
oLandedCostUpdate_CostLine.LandedCostCode = "QA"
oLandedCostUpdate_CostLine.amount = 50
oLandedCostUpdate_CostLine.CostType = SAPbobsCOM.LCCostTypeEnum.asVariableCosts
oLandedCostUpdate_CostLine.AllocationBy = LandedCostAllocationByEnum.asQuantity
Try
svrLandedCost.UpdateLandedCost(oLandedCostUpdate)
Catch ex As Exception
'exception process
MsgBox(ex.Message)
End Try
End Sub
{
oAtt.Lines.Add(); <------ here i think you have a error
oAtt.Lines.FileName = oAttNew.Lines.FileName;
oAtt.Lines.FileExtension = oAttNew.Lines.FileExtension;
oAtt.Lines.SourcePath = oAttNew.Lines.SourcePath;
oAtt.Lines.Override = BoYesNoEnum.tYES;
if (oAtt.Update() != 0)
throw new Exception(oCompany.GetLastErrorDescription());
}
usually in the objects of sap the first line is by default, so you do not need to add a line unless you are going to add more than one line.
maybe it should be like this :
for(int i = 0 ;i< xxxx; i++)
{
oAtt.Lines.setCurrentLine(i);
if(i>0)
{
oAtt.Lines.add();
}
oAtt.Lines.FileName = oAttNew.Lines.FileName;
oAtt.Lines.FileExtension = oAttNew.Lines.FileExtension;
oAtt.Lines.SourcePath = oAttNew.Lines.SourcePath;
oAtt.Lines.Override = BoYesNoEnum.tYES;
}
if (oAtt.Update() != 0)
throw new Exception(oCompany.GetLastErrorDescription());
xxxx could be the number of lines you want to add
or the number of lines that the object has ( Att.lines.Count )
Blockquote
Using the Visual Studio C# Winforms Google Earth plugin, 4 placemarks have been added to the globe as can be seen in the picture below:
My goal is to be able to remove the linestring placemark. The steps would seem to be to get all the placemarks, find the linestring and remove it.
Here is the code being used to create the linestring placemarks (more or less from the API website)
var lineStringPlacemark = ge2.createPlacemark("Line_" + name);
// create the line string geometry
var lineString = ge2.createLineString("");
lineStringPlacemark.setGeometry(lineString);
// add the the points to the line string geometry
double dlat1 = Convert.ToDouble(lat1) / 100000;
double dlon1 = Convert.ToDouble(lon1) / 100000;
double dlat2 = Convert.ToDouble(lat2) / 100000;
double dlon2 = Convert.ToDouble(lon2) / 100000;
lineString.getCoordinates().pushLatLngAlt(dlat1, dlon1, 0);
lineString.getCoordinates().pushLatLngAlt(dlat2, dlon2, 0);
// Create a style and set width and color of line
lineStringPlacemark.setStyleSelector(ge2.createStyle(""));
var lineStyle = lineStringPlacemark.getStyleSelector().getLineStyle();
lineStyle.setWidth(5);
lineStyle.getColor().set("9900ffff"); // aabbggrr format
// Add the feature to Earth
ge2.getFeatures().appendChild(lineStringPlacemark);
And here is the code I ended up using to remove the line. Note that the GEHelpers.RemoveFeatureById(ge2, s); is commented out since it isn't working for me for some reason.
for (int i = 0; i < ge2.getFeatures().getChildNodes().getLength(); i++)
{
var kmlobject = ge2.getFeatures().getChildNodes().item[i];
string s = kmlobject.getId();
if (s.Contains("Line_"))
{
ge2.getFeatures().removeChild(kmlobject);
kmlobject.release();
//GEHelpers.RemoveFeatureById(ge2, s);
}
}
The line you have should work and remove all the currently loaded content.
GEHelpers.RemoveAllFeatures(ge); // removes all loaded features from 'ge'
If you wish to remove a specific placemark, or any other feature, simply specify its ID as the parameter to the RemoveFeatureById method.
GEHelpers.RemoveFeatureById(ge, 'foo'); // remove the feature with the id 'foo'
An ID can be set either when you create the feature via the api or when you define the feature in kml. e.g.
// api
ge.createPlacemark('foo');
//kml id
<Document id="foo">
</Document>
Edit:
You should not have to do anything other than...
for (int i = 0; i < ge2.getFeatures().getChildNodes().getLength(); i++)
{
var kmlobject = ge2.getFeatures().getChildNodes().item[i];
if (kmlobject.getId().Contains("Line_"))
{
ge2.getFeatures().removeChild(kmlobject);
}
}
I think that there is possibly something else going on with your set up, maybe to do with having multiple instances of the plugin running at the same time.
I am creating a OO Writer document with C#.
Any help would be appreciated - I no longer know whether I am coming or going, I have tried so many variations....
using C#, has anybody successfully got the following to work? I just have a simple table of 2 columns and want to set the column widths to different values (actual value at this stage immaterial - just not identical widths).
This code is adapted from various web sources given as examples of how to do column widths. I cannot get it to work....
//For OpenOffice....
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.uno;
using unoidl.com.sun.star.bridge;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.text;
using unoidl.com.sun.star.beans;
..............................
XTextTable odtTbl = (XTextTable) ((XMultiServiceFactory)oodt).createInstance("com.sun.star.text.TextTable");
odtTbl.initialize(10, 2);
XPropertySet xPS = (XPropertySet)odtTbl;
Object xObj = xPS.getPropertyValue("TableColumnSeparators")**; // << Runtime ERROR**
TableColumnSeparator[] xSeparators = (TableColumnSeparator[])xObj;
xSeparators[0].Position = 500;
xSeparators[1].Position = 5000;
xPS.setPropertyValue("TableColumnSeparators", new uno.Any(typeof(unoidl.com.sun.star.text.XTextTable),xSeparators));
// Runtime ERROR indicates the ; at the end of the Object line, with message of IllegalArgumentException
Now this is only one type of error out of all the combinations of attempts. Not many allowed execution at all, but the above code did actually run until the error.
What is the correct code for doing this in C# please?
In addition, what is the correct C# code to set an O'Writer heading to a particular style (such as "Heading 1") so that it looks and prints like that style in the document?
Thank you.
unoidl.com.sun.star.uno.XComponentContext localContext = uno.util.Bootstrap.bootstrap();
unoidl.com.sun.star.lang.XMultiServiceFactory multiServiceFactory = (unoidl.com.sun.star.lang.XMultiServiceFactory)localContext.getServiceManager();
XComponentLoader componentLoader =(XComponentLoader)multiServiceFactory.createInstance("com.sun.star.frame.Desktop");
XComponent xComponent = componentLoader.loadComponentFromURL(
"private:factory/swriter", //a blank writer document
"_blank", 0, //into a blank frame use no searchflag
new unoidl.com.sun.star.beans.PropertyValue[0]);//use no additional arguments.
//object odtTbl = null;
//odtTbl = ((XMultiServiceFactory)xComponent).createInstance("com.sun.star.text.TextTable");
XTextDocument xTextDocument = (unoidl.com.sun.star.text.XTextDocument)xComponent;
XText xText = xTextDocument.getText();
XTextCursor xTextCursor = xText.createTextCursor();
XPropertySet xTextCursorProps = (unoidl.com.sun.star.beans.XPropertySet) xTextCursor;
XSimpleText xSimpleText = (XSimpleText)xText;
XTextCursor xCursor = xSimpleText.createTextCursor();
object objTextTable = null;
objTextTable = ((XMultiServiceFactory)xComponent).createInstance("com.sun.star.text.TextTable");
XTextTable xTextTable = (XTextTable)objTextTable;
xTextTable.initialize(2,3);
xText.insertTextContent(xCursor, xTextTable, false);
XPropertySet xPS = (XPropertySet)objTextTable;
uno.Any xObj = xPS.getPropertyValue("TableColumnSeparators");
TableColumnSeparator[] xSeparators = (TableColumnSeparator[])xObj.Value; //!!!! xObj.Value
xSeparators[0].Position = 2000;
xSeparators[1].Position = 3000;
xPS.setPropertyValue("TableColumnSeparators", new uno.Any(typeof(TableColumnSeparator[]), xSeparators)); //!!!! TableColumnSeparator[]