Word/Office Automation - How to retrieve selected value from a Drop-down form field - ms-word

I am trying to retrieve the value of all fields in a word document via office automation using c#. The code is shown below however if the field is a drop-down then the value of the range text is always empty even though I know it is populated. If it is a simple text field then I can see the range text. How do I get the selected drop down item? I feel there must be something quite simple that I'm doing wrong...
private void OpenWordDoc(string filename) {
Microsoft.Office.Interop.Word.Application app = new Microsoft.Office.Interop.Word.Application();
Document doc = app.Documents.Open(filename, ReadOnly: true, Visible: false);
foreach (Field f in doc.Fields) {
string bookmarkName = "??";
if (f.Code.Bookmarks.Count > 0) {
bookmarkName = f.Code.Bookmarks[1].Name; // have to start at 1 because it is vb style!
}
Debug.WriteLine(bookmarkName);
Debug.WriteLine(f.Result.Text); // This is empty when it is a drop down field
}
doc.Close();
app.Quit();
}

Aha - If I scan through FormFields instead of Fields then all is good...
foreach (FormField f in doc.FormFields) {
string bookmarkName = "??";
if (ff.Range.Bookmarks.Count > 0) {
bookmarkName = ff.Range.Bookmarks[1].Name; // have to start at 1 because it is vb style!
}
Debug.WriteLine(bookmarkName);
Debug.WriteLine(ff.Result); // This is empty when it is a drop down field
}
Problem solved. Phew.

Related

Insert multiple lines of text into a Rich Text content control with OpenXML

I'm having difficulty getting a content control to follow multi-line formatting. It seems to interpret everything I'm giving it literally. I am new to OpenXML and I feel like I must be missing something simple.
I am converting my multi-line string using this function.
private static void parseTextForOpenXML(Run run, string text)
{
string[] newLineArray = { Environment.NewLine, "<br/>", "<br />", "\r\n" };
string[] textArray = text.Split(newLineArray, StringSplitOptions.None);
bool first = true;
foreach (string line in textArray)
{
if (!first)
{
run.Append(new Break());
}
first = false;
Text txt = new Text { Text = line };
run.Append(txt);
}
}
I insert it into the control with this
public static WordprocessingDocument InsertText(this WordprocessingDocument doc, string contentControlTag, string text)
{
SdtElement element = doc.MainDocumentPart.Document.Body.Descendants<SdtElement>().FirstOrDefault(sdt => sdt.SdtProperties.GetFirstChild<Tag>().Val == contentControlTag);
if (element == null)
throw new ArgumentException("ContentControlTag " + contentControlTag + " doesn't exist.");
element.Descendants<Text>().First().Text = text;
element.Descendants<Text>().Skip(1).ToList().ForEach(t => t.Remove());
return doc;
}
I call it with something like...
doc.InsertText("Primary", primaryRun.InnerText);
Although I've tried InnerXML and OuterXML as well. The results look something like
Example AttnExample CompanyExample AddressNew York, NY 12345 or
<w:r xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:t>Example Attn</w:t><w:br /><w:t>Example Company</w:t><w:br /><w:t>Example Address</w:t><w:br /><w:t>New York, NY 12345</w:t></w:r>
The method works fine for simple text insertion. It's just when I need it to interpret the XML that it doesn't work for me.
I feel like I must be super close to getting what I need, but my fiddling is getting me nowhere. Any thoughts? Thank you.
I believe the way I was trying to do it was doomed to fail. Setting the Text attribute of an element is always going to be interpreted as text to be displayed it seems. I ended up having to take a slightly different tack. I created a new insert method.
public static WordprocessingDocument InsertText(this WordprocessingDocument doc, string contentControlTag, Paragraph paragraph)
{
SdtElement element = doc.MainDocumentPart.Document.Body.Descendants<SdtElement>().FirstOrDefault(sdt => sdt.SdtProperties.GetFirstChild<Tag>().Val == contentControlTag);
if (element == null)
throw new ArgumentException("ContentControlTag " + contentControlTag + " doesn't exist.");
OpenXmlElement cc = element.Descendants<Text>().First().Parent;
cc.RemoveAllChildren();
cc.Append(paragraph);
return doc;
}
It starts the same, and gets the Content Control by searching for it's Tag. But then I get it's parent, remove the Content Control elements that were there and just replace them with a paragraph element.
It's not exactly what I had envisioned, but it seems to work for my needs.

Insert HTML in docx file

I have made an application that fills wordfiles with customxmlparts now I am trying to put text into a textfield, but it has HTML in it and I want it to show the styling of it. I tried converting it to rich text format but that just gets pasted in the word file. Here is an example of the code:
var taskId = Guid.NewGuid();
var tempFilePath = $"{Path.GetTempPath()}/{taskId}";
using (var templateStream = new FileStream($"{tempFilePath}.docx", FileMode.CreateNew))
{
templateStream.Write(template, 0, template.Length);
// 1. Fill template.
using (WordprocessingDocument doc = WordprocessingDocument.Open(templateStream, true))
{
MainDocumentPart mainDocument = doc.MainDocumentPart;
if (mainDocument.CustomXmlParts != null)
{
mainDocument.DeleteParts<CustomXmlPart>(mainDocument.CustomXmlParts);
}
CustomXmlPart cxp = mainDocument.AddCustomXmlPart(CustomXmlPartType.CustomXml);
foreach (var line in data.Lines)
{
if (line.MoreInfo != null && line.MoreInfo != " ") {
}
}
var xmlData = ObjectToXml(data);
using (var stream = GenerateStreamFromString(tempFilePath, xmlData))
{
cxp.FeedData(stream);
}
mainDocument.Document.Save();
}
}
You can't just write the HTML formatted text into a DOCX field, you would need to convert it into a WordprocessingML format.
However, there is another way that you could try and that is to insert an "AltChunk" element. That element represents a sort of like a placeholder which can reference a HTML file and then when the DOCX file is opened in MS Word, it will make that HTML to WordprocessingML conversion for you. For details see: How to Use altChunk for Document Assembly
Alternatively you could use some third party, like GemBox.Document, which can make that HTML to WordprocessingML conversion for you.
For example check this Set Content example:
// Set content using HTML tags
document.Sections[0].Blocks[4].Content.LoadText(
"Paragraph 5 <b>(part of this paragraph is bold)</b>", LoadOptions.HtmlDefault);

Custom properties are not updated for the word via openXML

I am trying to update custom properties of word document thru Open XML programming but it seems the updated properties are not getting saved properly for the word document. So when I opening document after successful execution of the update custom property code, I am getting the message box which is "This document contains field that may refer to other files; Do you want to update the fields in the Document?" If I am pressing 'NO' button then all the update properties would not be saved to the document. If we are going for yes option then it will update properties but I need to save the properties explicitly. Please suggest to save properties to the document without getting confirmation message or corrupting the document. :)
the code snippet is given as below,
public void SetCustomValue(
WordprocessingDocument document, string propname, string aValue)
{
CustomFilePropertiesPart oDocCustomProps = document.CustomFilePropertiesPart;
Properties props = oDocCustomProps.Properties;
if (props != null)
{
//logger.Debug("props is not null");
foreach (var prop in props.Elements<CustomDocumentProperty>())
{
if (prop != null && prop.Name == propname)
{
//logger.Debug("Setting Property: " + prop.Name + " to value: " + aValue);
prop.Remove();
var newProp = new CustomDocumentProperty();
newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
newProp.Name = prop.Name;
VTLPWSTR vTLPWSTR1 = new VTLPWSTR();
vTLPWSTR1.Text = aValue;
newProp.Append(vTLPWSTR1);
props.AppendChild(newProp);
props.Save();
}
}
int pid = 2;
foreach (CustomDocumentProperty item in props)
{
item.PropertyId = pid++;
}
props.Save();
}
}
I am using .Net framework 3.5 with Open XML SDK 2.0 and Office 2013.
Try this one
var CustomeProperties = xmlDOc.CustomFilePropertiesPart.Properties;
foreach (CustomDocumentProperty customeProperty in CustomeProperties)
{
if (customeProperty.Name == "DocumentName")
{
customeProperty.VTLPWSTR = new VTLPWSTR("My Custom Name");
}
else if (customeProperty.Name == "DocumentID")
{
customeProperty.VTLPWSTR = new VTLPWSTR("FNP.SMS.IQC");
}
else if (customeProperty.Name == "DocumentLastUpdate")
{
customeProperty.VTLPWSTR = new VTLPWSTR(DateTime.Now.ToShortDateString());
}
}
//Open Word Setting File
DocumentSettingsPart settingsPart = xmlDOc.MainDocumentPart.GetPartsOfType<DocumentSettingsPart>().First();
//Update Fields
UpdateFieldsOnOpen updateFields = new UpdateFieldsOnOpen();
updateFields.Val = new OnOffValue(true);
settingsPart.Settings.PrependChild<UpdateFieldsOnOpen>(updateFields);
settingsPart.Settings.Save();
you have to update your document fields on open.

Remove Content controls after adding text using open xml

By the help of some very kind community members here I managed to programatically create a function to replace text inside content controls in a Word document using open xml. After the document is generated it removes the formatting of the text after I replace the text.
Any ideas on how I can still keep the formatting in word and remove the content control tags ?
This is my code:
using (var wordDoc = WordprocessingDocument.Open(mem, true))
{
var mainPart = wordDoc.MainDocumentPart;
ReplaceTags(mainPart, "FirstName", _firstName);
ReplaceTags(mainPart, "LastName", _lastName);
ReplaceTags(mainPart, "WorkPhoe", _workPhone);
ReplaceTags(mainPart, "JobTitle", _jobTitle);
mainPart.Document.Save();
SaveFile(mem);
}
private static void ReplaceTags(MainDocumentPart mainPart, string tagName, string tagValue)
{
//grab all the tag fields
IEnumerable<SdtBlock> tagFields = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == tagName);
foreach (var field in tagFields)
{
//remove all paragraphs from the content block
field.SdtContentBlock.RemoveAllChildren<Paragraph>();
//create a new paragraph containing a run and a text element
Paragraph newParagraph = new Paragraph();
Run newRun = new Run();
Text newText = new Text(tagValue);
newRun.Append(newText);
newParagraph.Append(newRun);
//add the new paragraph to the content block
field.SdtContentBlock.Append(newParagraph);
}
}
Keeping the style is a tricky problem as there could be more than one style applied to the text you are trying to replace. What should you do in that scenario?
Assuming a simple case of one style (but potentially over many Paragraphs, Runs and Texts) you could keep the first Text element you come across per SdtBlock and place your required value in that element then delete any further Text elements from the SdtBlock. The formatting from the first Text element will then be maintained. Obviously you can apply this theory to any of the Text blocks; you don't have to necessarily use the first. The following code should show what I mean:
private static void ReplaceTags(MainDocumentPart mainPart, string tagName, string tagValue)
{
IEnumerable<SdtBlock> tagFields = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == tagName);
foreach (var field in tagFields)
{
IEnumerable<Text> texts = field.SdtContentBlock.Descendants<Text>();
for (int i = 0; i < texts.Count(); i++)
{
Text text = texts.ElementAt(i);
if (i == 0)
{
text.Text = tagValue;
}
else
{
text.Remove();
}
}
}
}

Populate select in jqgrid filter toolbar

I've tried to populate a dropdownlist with values from my database. I've got the following code in my .js file:
function getDropdowndata() {
var sHTML;
var filter;
var url = "dropdown.json";
jQuery.getJSON(url, function (dddata) {
if (dddata.rows.length > 0) {
sHTML = "";
for (x = 0; x < dddata.rows.length; x++) {
sHTML += (dddata.rows[x].Type + ":" + dddata.rows[x].Type + ";");
}
filter = sHTML.substring(0, sHTML.length - 1);
}
});
return filter;
}
And in my Jqgrid list I've got the following:
editoptions: { value: ":All;" + getDropdowndata() }
The problem I've got with this code is that it seems that the function is being executed too early and because of that the dropdownlist contains nothing.
The reason for my assumption is that if I put an alert inside of the javascript function before the return, the dropdownlist is filled with the values and everything seems to work.
Any suggestions?
Instead of getting the data with a custom function using JSON, you might want to try using the built-in functionality for dynamic select fields (see documentation: select edittype ). All you do is specify a url where the code for the select element is generated.
colModel:[
{name:'colName',
editable:true,
edittype:'select',
formatter:'select',
editoptions:{dataUrl:'/path/to/generated/html/select'}
]
Then you just need to make sure that /path/to/generated/html/select returns all the right HTML code for a select element.