How to fix 'Office.Interlop -2146822384: Cannot edit Range' in C# - c#

I am creating a quote generator that takes word doc templates and replaces key words in the template to generate a quote. Afterwards the document is returned as a PDF file. There are a few sections of the template that need to be removed if there is no data to fill them out.
Currently there is a table row that could have 2 different notes attached to it. If there are no notes to add, there is a lot of white space in the row if I don't delete these lines.
This is a Windows form App Using the .NET Framework in Visual studios. I have search for this error on Sack-overflow and the Microsoft.Office.Interop.Word API and have not been able to find a similar issue. Originally I was using the same range object to do the search and delete. So I tried making a new range object but I still get the same issue.
I have put some code here:
//If there is data to add
if (temp.DATAOBJECT != null){
FindAndReplace(_word, "<<Add-on Note PRODUCT>>", temp.DATAOBJECT, 1);
}else{
//////HERE IS WHERE THE FIRST RANGE IS DELETED. THIS ONE WORKS///////
range.Find.Execute("<<Add-on Note PRODUCT>>");
range.Expand(WdUnits.wdParagraph);
range.Delete();
}
//If there is data to add
if (temp.DATAOBJECT != null){
FindAndReplace(_word, "<<Add-on Note LINE>>", temp.DATAOBJECT, 1);
}else{
///HERE IS WHERE THE SECOND RANGE IS DELETED. THIS ONE DOESN'T WORK///
range2.Find.Execute("<<Add-on Note LINE>>");
range2.Expand(WdUnits.wdParagraph);
range2.Delete();
}
I have succeeded in deleting one of these line notes but when I attempt to delete the second line I get the error "Error -2146822384: Cannot edit Range."
I can not just make the line blank with "" because it leaves to much white space. It must be deleted.

Thanks to #CindyMeister in the comments We have found out why the error is happening.
The range in questions was selecting the end of a cell in a table which also selects the"\a" tag that signifies the end of the cell. We can not call Delete on this range if the \a tag is selected. Here is the updated code:
//If there is data to add
if (temp.DATAOBJECT != null){
FindAndReplace(_word, "<<Add-on Note LINE>>", temp.DATAOBJECT, 1);
}else{
range2.Find.Execute("<<Add-on Note LINE>>");
range2.Expand(WdUnits.wdParagraph);
range2.MoveEnd(WdUnits.wdCharacter, -1);
range2.Delete();
}
Hope this helps someone in the future

Related

How to extract a table with DocX?

I'm extracting text out of an MS Word document (.docx). I'm using the DocX C# library for this purpose, which works in general quit well. No, I want to be able to extract tables. The main problem is, that if I'm looping through the paragraphs, I can get whether I'm in a table cell with:
ParentContainer == Cell
but I do not get any information about how many rows and cells. Second possibility which I see is that there is a list with tables as property of the document object. There I can see, how many rows / columns and so on - but I do not know where they are.
Does anyone has an idea how to deal with tables correctly? Any other solution would be appreciated as well :)
I figured it out. The trick is, to check whether each paragraph is followed by a table. This can be done by
...
if (paragraph.FollowingTable != null)
{
tableId = paragraph.FollowingTable.Index;
}
...
The FollowingTable.Index will give you an index to the table, with which you can get all details about the table in the Document.Tables list.

In VSTO how can I delete rows from a Table in Excel

I have a table in an Excel worksheet where I need to programatically remove entire rows using VSTO. After a lot of searching here and everywhere else, I was unable to find the answer. Due to some unrelated code, I also cannot delete the first row of the table, but need to remove all other rows.
Here are the specific requirements:
One of the functions of this addin is to populate the table. This is done through a loop starting with the "root" named range in the left column of the first row of the table.
Whenever populating the table, I first need to delete all data from the table and then add the new data. I need to use the "root" to add the data, so I can't have it deleted.
I am using the Table for the automated formatting instead of formatting the table manually after adding each cell.
I never know how many rows will be added, but it will always be at least one.
After banging my head on this for a few hours, I slept on it and came at it refreshed this morning. After much trial and error, here is the code I came up with.
var deplTable = ThisSheet.Evaluate("DeploymentTable");
if (deplTable.ListObject.ListRows.Count > 1)
{
do deplTable.ListObject.ListRows[2].Delete();
while (deplTable.ListObject.ListRows.Count > 1);
}
NOTE: ThisSheet is set to the correct sheet earlier. The application works on multiple sheets, so it needs to be flexible.
I tried this a few ways before finally getting it to work. Looping through the rows gave unexpected results; possibly due to timing issues between Excel and VSTO.
Hope this helps other people!

Find all elements by xpath attribute (aria-required = true) .//*[#aria-required='true']

I'm trying to get a list of IwebElements that contain the attribute aria-required. Reason why I'm trying to get this list, is so that i can check if all required fields are necessary for the user to fill out, before he can continue to the next page.
So far after 2 days of searching I'm still convinced that it shouldn't be that hard. I'm using the expression:
".//*[#aria-required='true']"
From my research that would mean that it will search for ALL the elements starting from the root of my webdriver.
[TestMethod]
public void CreateProjectWithoutRequiredFields()
{
GoToProjectPage();
tracking = CM.GoToNewlyCreatedFrameAfterClickButton("ftbNew", tracking, theDriver);
CM.Wait(2000);
bool succesSave = false;
CM.LogToFile("Create project whitout required fields", tracking);
foreach (IWebElement e in theDriver.FindElements(By.XPath(".//*[#aria-required='true']")))
{
FillDataInNewProjectPage();
e.Clear();
CM.GetDynamicElementById("btnSave", theDriver).Click();
try
{
CM.GetDynamicElementById("titel", theDriver).Click();
}
catch (Exception)
{
succesSave = true;
NUnit.Framework.Assert.IsFalse(succesSave, "The page is saved with succes, without entering text in the following required fields : " + e.GetAttribute("id").ToString());
}
CM.Wait(1000);
}
}
I will try to explain what i did here:
First i went to a overview page with all my existing projects. On this page i clicked the ftbNew button to create a new project. The driver is automatically switch to the correct frame (i now the right frame is selected because i used this line on other page's.)
then the line
foreach (IWebElement e in theDriver.FindElements(By.XPath(".//*[#aria-required='true']")))
{
should normaly find a the elements in my driver with an attribute "aria-required='true'"
Then it would fill in the page with data, clear the first element that is found from its data en try to save it.
if the element titel is found on the page, than we are still on the same page en the save action wasn't successful ( <- so this is good)
so next we again overwrite every field on the page and clear this time the second element that is found.
en so on...
What I'm guessing, that xpath has difficulty finding the 'old' aria-required attribute... When i try to validate my expression using firebug and other xpath checkers, the aria-required attribute isn't always present. Sometimes it finds the input field sometimes it doesn't.
source code page
As you can see in the firebug console not all attributes are loaded, account-manager has a aria-required attribute, but project leader doesn't. If i inspect the element again, but this time click project leader. The attribute will be loaded. Very strange....
Extra info: I am using frame's and i know a lot can go wrong if you are situated in the wrong frame, but i am sure that he is looking in the correct frame. Especially because i can't find the elements using firebug with the above expression. If i change my expression to .//input, it will find the elements but also selects input fields that aren't required.
In advance i want to thank everybody that want to look into my problem :)
Adriaan
Based on your description of the behavior on the page, you cannot rely on the aria-required attribute to indicate a required field. I think that you should go back to the developers of the site to ask them to give you a reliable handle. It may be as simple as looking for the "*" as the last character of the label associated with the input field, but that's kind of annoying to have to deal with.
As an aside, you should consider catching a more specific exception for your success case. Right now, if ANY exception happens, you'll declare success, but that may not be what you really want to do.

ListObject.Resize() makes DataBodyRange null when row count == 1

I am trying to reset the number of columns in an Excel ListObject. I know you can add and remove columns one-by-one, but I want to avoid unnecessary loops. I instead decided to resize the ListObject using the Resize method.
Here is the code that I am using (where OutputCasesTable is the ListObject):
OutputCasesTable.DataBodyRange.Value2 = "";
OutputCasesTable.Resize(OutputCasesTable.Range.Resize[ColumnSize: CaseCount]);
OutputCasesTable.DataBodyRange.Value2 = OutputCasesAray;
The above lines of code appear to work perfectly, however if the ListObject only contains 1 row of data, the DataBodyRange of the ListObject becomes null on the second line - producing an error when I try to change its cell's value. The row in excel still appears to be present.
The MSDN documentation says the following:
"The header must remain in the same row and the resulting list must overlap the original list. The list must contain a header row and at least one row of data."
Now I understand that "one row of data" implies that the row contains values - so the cause of the error here must be that the DataBodyRange cells all contain no value (""). However, a table with two data rows containing "" still doesn't have a row with data, does it?
I know there are many ways of accomplishing this task, but I want to understand why this happens.
Temporary Solution:
Replaced the code to only set the values to empty strings in columns that will be removed (columns above the new column count). All other columns will be replaced:
if(OutputCasesTable.ListColumns.Count - CaseCount > 0)
OutputCasesTable.DataBodyRange.Offset[ColumnOffset: CaseCount].Resize[ColumnSize: OutputCasesTable.ListColumns.Count - CaseCount].Value2 = "";
OutputCasesTable.Resize(OutputCasesTable.Range.Resize[ColumnSize: CaseCount]);
OutputCasesTable.DataBodyRange.Value2 = OutputCasesAray;
Personally I prefer looking at the first solution!
Is there anything I can do make it work with empty strings? Or do you have a better solution?
Best regards,
The Resize operation is the piece that kills the DataBodyRange, and clearly there's some internal logic that Resize uses, along the lines of "if there is only one row, and all the cells are empty, remove all the data rows. If there is more than one row, don't remove any".
I agree that this logic is a bit confounding. If your question is why did Microsoft implement it this way, I'd argue that although it's inconsistent, it's perhaps tidier in a way - it appears to the model that you're working with an empty table, and there's no way for the model to tell the difference graphically (it's not possible for a table to just have a header row).
When Resize turns up to do its work and finds a single-row blank table, it can't tell whether you have a zero-row table or a single-row table with empty strings. If it arrives and finds two empty rows, that's unambiguous (they must be meaningful rows).
For the workaround portion of your question, I'd suggest a tidier solution of just checking the ListRows.Count property, and adding one if necessary. Note that you can also use Clear instead of setting Value2 to blank; for me it reads as more self-explanatory.
OutputCasesTable.DataBodyRange.Clear();
OutputCasesTable.Resize(OutputCasesTable.Range.Resize[ColumnSize: CaseCount]);
if (OutputCasesTable.ListRows.Count == 0) OutputCasesTable.ListRows.Add();
OutputCasesTable.DataBodyRange.Value2 = OutputCasesAray;

Read Last line from First column using Aspose.Words v13.1.0

I have a word document with two column layout.
How to read last line from first column and read first line from second column in a word document.If first column last line text is in specific format, then move one line down which would automaticaly moves the text to next column(second).
Please let me know how to achieve this in .net using Aspose.Words V13.1.0
First you need to download offline samples pack from this link to use the following code example. Then extract the archive and from the DocumentLayoutHelper example, include the RenderedDocument and LayoutEntities source files in to his application.
Here is the sample code:
Document doc = new Document(dataDir + "Test.docx");
// This sample introduces the RenderedDocument class and other related classes which provide an API wrapper for
// the LayoutEnumerator. This allows you to access the layout entities of a document using a DOM style API.
// Create a new RenderedDocument class from a Document object.
RenderedDocument layoutDoc = new RenderedDocument(doc);
// Loop through the layout info of each page
foreach (RenderedPage page in layoutDoc.Pages)
{
if (page.Columns.Count > 1)
{
// Find the last line in the first column on the page.
RenderedLine lastLine = page.Columns.First.Lines.Last;
// This is the pargraph which belongs to the last line on the page, implement required logic and checks here.
Paragraph para = lastLine.Paragraph;
if (para.ParagraphFormat.StyleIdentifier == StyleIdentifier.Heading1)
{
// Insert a blank paragraph before the last paragraph in the column.
para.ParentNode.InsertBefore(new Paragraph(doc), para);
}
// This is how to get the first line in the second column and the related paragraph.
// Use this if it is required.
RenderedLine secondColumnLine = page.Columns[1].Lines.First;
Paragraph secondColumnPara = lastLine.Paragraph;
}
}
PS, Hope that above example can fulfil your requirements. My name is Nayyer and I am Support/Engangelist developer at Aspose.

Categories

Resources