Is there a way to read hidden text in Microsoft Word? - c#

I am trying to populate a word template in C#. The template contains a table with several cells. I need to be able to identify each cell based on a unique id. I do not find a way to store & read a unique id for each cell/text in word. My approach is to have the unique id as hidden text in each cell. And then format the cell (like changing the background color) based on this unique id.
I face the problem in reading this hidden text in each cell in C#?
Any suggestions would be of great help please!
Thanks!

Here it comes! You can iterate over a document and find a hidden text:
foreach (Microsoft.Office.Interop.Word.Range p in objDoc.Range().Words)
{
if (p.Font.Hidden != 0) //Hidden text found
{
// Do something
}
}
The values returned for p are:
0: Text visible
-1: Text Hidden
That's what I did for a Word Document, but if you are able to iterate over your cells' content, probably this information may help you.

To read hidden text in your code you just need to set
rangeObject.TextRetrievalMode.IncludeHiddenText = true

If you want to make them visible for example, you can iterate trough all words and check the Font.Hidden property, then set it visible:
Word.Document document = ThisAddIn.Instance.Application.ActiveDocument;
var rangeAll = document.Range();
rangeAll.TextRetrievalMode.IncludeHiddenText = true;
foreach (Microsoft.Office.Interop.Word.Range p in rangeAll.Words)
{
texts += p.Text;
if (p.Font.Hidden != 0) //Hidden text found
{
p.Font.Hidden = 0;
count++;
}
}

Related

How to delete conditional formats for a cell or range using EPPlus

Is there a way to remove Conditional Formatting from a cell or range with EPPlus? I have tried Clear, Reset, delete rows, insert rows, copy a cell without formatting, but nothing works. There are ways to remove all the conditional formatting from the sheet (worksheet.RemoveAll or RemoveAt) but not from a cell or range that I have been able to find anywhere.
After some more research I found a way to inspect, change or delete a conditional format of a cell or range looking at the openxml specs. The conditional format is stored in the worksheet, with the range under the attribute sqref. So you can edit that range or add a new.
For example:
DIM p As New ExcelPackage(New FileInfo(ExlReportPath), True)
Dim ws As ExcelWorksheet = p.Workbook.Worksheets(ExlSheetName)
'--Find Node "worksheet" (1 in my case) , Find all Child Nodes "conditionalFormatting" (5 to 11 in my test)
Print.Debug(ws.WorksheetXml.ChildNodes(1).ChildNodes(5).Name)
'--You get: conditionalFormatting
'--Now you can inspect the range:
Print.Debug(ws.WorksheetXml.ChildNodes(1).ChildNodes(5).Attributes("sqref").Value)
'--Will give you the cell address that this formatting applies to example: "D11:D15"
'--you can change delete or add new range if you want, below I add F11:F15
ws.WorksheetXml.ChildNodes(1).ChildNodes(5).Attributes("sqref").Value="D11:D15 F11:F15"
'--You can inspect the rule itself in the InnerXml also...
If you need more details of the markup, google Wouter van Vugt, "Open XML The markup explained". I found it useful and the full document was online (free).
If you find an easier way please post it.
Regards
You could set the Address property of each rule to a new ExcelAddress object with an address that omits the cell or sub-range in question, e.g. ---
var cfRules = worksheet.ConditionalFormatting;
var cfToModify = cfRules.Where(cf => ofInterest(cf.Address));
foreach (var cf in cfToModify)
{
// Determine a new address for the rule so that it no longer
// affects the cell or sub-range you're interested in.
var newRangeAddress = "something else"; // (Just some filler, won't work...)
cf.Address = new ExcelAddress(newRangeAddress);
}
--- with ofInterest defined however you want, e.g.:
static bool ofInterest(ExcelAddress addr)
{
var result = false; // Determine as you see fit.
return result;
}
(Note that the ExcelAddress.Address property is read-only, so you have to set the rule's Address property to a new ExcelAddress object.)

OpenXML Spreadsheet update value in SharedStringTable

I am using OpenXML Spreadsheet in order to generate .xlsx file with a given template and a variable dictionary. But I have a question when updating the value in SharedStringTable since the InnerText of the SharedStringItem is read only.
My template excel file is a file with .xlsx. The variables I need to replace has prefix "$$$". For example, "$$$abc", then in the dictionary I may have <"abc", "Payson"> pair (if the dictionary does not contain the key "abc", just leave the "$$$abc" there.
What I have done is something like this
private void UpdateValue(WorkbookPart wbPart, Dictionary<string, string> dictionary)
{
var stringTablePart = wbPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
if (stringTablePart == null)
{
stringTablePart = wbPart.AddNewPart<SharedStringTablePart>();
}
var stringTable = stringTablePart.SharedStringTable;
if (stringTable == null)
{
stringTable = new SharedStringTable();
}
//iterate through all the items in the SharedStingTable
// if there is any text starts with $$$, find out the name of the string
// look for the string in the dictionary
// replace it if found, or keep it if not.
foreach (SharedStringItem item in stringTable.Elements<SharedStringItem>())
{
if (item.InnerText.StartsWith("$$$"))
{
string variableName = item.InnerText.Substring(3);
if (!string.IsNullOrEmpty(variableName) && dictionary.containsKey(variableName))
{
// The problem is here since InnerText is read only.
item.InnerText = dictionary[variableName];
}
}
}
}
The document is here http://msdn.microsoft.com/en-us/library/documentformat.openxml.openxmlcompositeelement.innertext(v=office.14).aspx
Even the document mentioned that innertext can be set, however, there is no set accessor.
Does anyone know how to set the InnterText. Since I may have many cells with the same variable name "$$$abc", and I would like to replace all of them with "Payson".
It appears that you need to replace the InnerXml, then save the SharedStringTable. See Set text value using SharedStringTable with xml sdk in .net.
I've tried to edit the Text and it works.
Text newText = new Text();
newText.Text = dictionary[variableName];
item.Text = newText;
stringTable.Save();

converting cells into text boxes where check box is checked

I am using mysql and asp.net with c#. I have a grid view which will display dynamically selected table data. I am able to display the data of selected table. In the first column i have added a check box and a Button outside Grid view. When user selects Check box and clicks on button, the selected rows must turn into text boxes. I am able to find the seleted check box, but i'm unable to convert the cells into text boxes. Here's my code:
int n = GridView1.HeaderRow.Cells.Count;
for( int i=0; i < GridView1.Rows.Count;i++)
{
GridViewRow row = GridView1.Rows[i];
bool isChecked = ((CheckBox)row.FindControl("CheckBox1")).Checked;
{
for( int j=0;j<n;j++)
{
TextBox txt = ((TextBox)GridView1.Rows[i].Cells[j]).Text;
}
}
}
At this line: TextBox txt = ((TextBox)GridView1.Rows[i].Cells[j]).Text;
i get a warning :
cannot convert 'System.Web.UI.Controls.TableCell' to type 'System.Web.UI.Controls.TextBox
I am unable to resolve this. Please help. Thank you
Try this.
You can remove one or few lines based on your hands-on with C#.
Concept is, you should create a TextBox, assign cell text that textbox and then Add that newly created textbox to child controls of Grid Cell of particular row.
Mark this solution if you found useful.
bool isChecked = ((CheckBox)row.FindControl("CheckBox1")).Checked;
if(isChecked)
{
for( int j=0;j<n;j++)
{
TextBox tbForCell = new TextBox();
tbForCell.Text = GridView1.Rows[i].Cells[j].Text;
GridView1.Rows[i].Cells[j].Text = "";
GridView1.Rows[i].Cells[j].Controls.Add(tbForCell);
}
}
If you want to avoid the TextBox to appear in CheckBox Column please initialise loop variable j with 1 instead of 0.
for( int j=1;j<n;j++)
Your method call is finding the cell. The Textbox is a control contained within the cell. Try something like this instead:
TextBox txt = ((TextBox) GridView1.Rows[i].Cells[j].FindControl("textbox name")).Text;
The FindControl method is documented here.
Also take #Bartdude's advice about using editable gridviews. If one will work at least as well as what you're trying to hand-roll, it's worth the time learning how to use it.

Get text above table MS Word

This one is probably a little stupid, but I really need it. I have document with 5 tables each table has a heading. heading is a regular text with no special styling, nothing. I need to extract data from those tables + plus header.
Currently, using MS interop I was able to iterate through each cell of each table using something like this:
app.Tables[1].Cell(2, 2).Range.Text;
But now I'm struggling on trying to figure out how to get the text right above the table.
Here's a screenshot:
For the first table I need to get "I NEED THIS TEXT" and for secnd table i need to get: "And this one also please"
So, basically I need last paragraph before each table. Any suggestions on how to do this?
Mellamokb in his answer gave me a hint and a good example of how to search in paragraphs. While implementing his solution I came across function "Previous" that does exactly what we need. Here's how to use it:
wd.Tables[1].Cell(1, 1).Range.Previous(WdUnits.wdParagraph, 2).Text;
Previous accepts two parameters. First - Unit you want to find from this list: http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.wdunits.aspx
and second parameter is how many units you want to count back. In my case 2 worked. It looked like it should be because it is right before the table, but with one, I got strange special character: ♀ which looks like female indicator.
You might try something along the lines of this. I compare the paragraphs to the first cell of the table, and when there's a match, grab the previous paragraph as the table header. Of course this only works if the first cell of the table contains a unique paragraph that would not be found in another place in the document:
var tIndex = 1;
var tCount = oDoc.Tables.Count;
var tblData = oDoc.Tables[tIndex].Cell(1, 1).Range.Text;
var pCount = oDoc.Paragraphs.Count;
var prevPara = "";
for (var i = 1; i <= pCount; i++) {
var para = oDoc.Paragraphs[i];
var paraData = para.Range.Text;
if (paraData == tblData) {
// this paragraph is at the beginning of the table, so grab previous paragraph
Console.WriteLine("Header: " + prevPara);
tIndex++;
if (tIndex <= tCount)
tblData = oDoc.Tables[tIndex].Cell(1, 1).Range.Text;
else
break;
}
prevPara = paraData;
}
Sample Output:
Header: I NEED THIS TEXT
Header: AND THIS ONE also please

C# Form Controls

I have a form containing many picture boxes named pb_a1,pb_a2,pb_a3... and so on..
I have a String array containing the picture box names. What I need to do is access each of these and specify an image for it.
Instead of hard coding, I would like to know if there is any way by which I can write a loop which gives me commands like
> Form1.pb_a1.Image=<some Image>;
>
> Form1.pb_a2.Image=<some Image>;
>
> Form1.pb_a3.Image=<some Image>;
>
> Form1.pb_a4.Image=<some Image>;
Can you use the ControlCollection.Find( ) method on the forms Controls property?
if you only have the name of the picture controls and not a reference to them ( which I think you could have kept in a dictionary with name and reference when you created those controls earlier in the form... ), the only way you have I think is to search in the Form.Controls collection until you find the one with the name you are looking for and which is of the picture box type.
You're better off storing the picture boxes in a picture box array, rather than a string array.
PictureBox[] myPictures = {pictureBox1, pictureBox2, pictureBox3, pictureBox4};
foreach (PictureBox picture in myPictures)
{
picture.Image = <some Image>;
}
If you have to have it as a string, the following code may help you out. Please note that I haven't included any error checking incase the element doesn't exist. You're likely to just get an empty element in that part of the array. You may want to encase it in try/catch as well.
string[] myPicturesString = {"pictureBox1", "pictureBox2", "pictureBox3", "pictureBox4"};
PictureBox[] myPictures = new PictureBox[myPicturesString.Length];
for (int i = 0; i < myPictures.Length; i++)
{
foreach (Control c in this.Controls)
{
if (c.Name == myPicturesString[i])
{
myPictures[i] = (PictureBox) c;
}
}
}
MessageBox.Show(myPictures[1].Name);
Assuming that these picturebox are fields of your form, you can reflect (System.Reflection) on the form class, retrieve a reference to the picturebox fields and store them into a dictionary. Do that during form creation (after InitializeComponent).
Next time you have to access a picture box by its name, just use:
myDictionary["pictureboxname"].Image = blabla;

Categories