VSTO Word Add-In : Content Control Nesting - c#

I'm building an MS-Word Add-In for the company where I'm doing my internship.
I already created a new ribbon with lots of SplitButtons and Buttons.
Now what i want to do is when you click one of the buttons a content control will ba added to the word doc.
This works fine for Plain Content Controls. These content controls have tags like "sport/basketball/player/name" which is binded to an element in an XML file.
private void addSimpleContentControl(String tag, String placeholder)
{
try
{
contentControlPlain = Globals.ThisAddIn.Application.ActiveDocument.ContentControls.Add(Microsoft.Office.Interop.Word.WdContentControlType.wdContentControlText);
contentControlPlain.Tag = tag;
contentControlPlain.SetPlaceholderText(null, null, placeholder);
}
catch (COMException) { }
}
Now let's talk about my problem.
Some of my elements could be present for more then one time. So what i want to create is a Rich Content control which holds more than one Plain content control.
So i have a SplitButton "player" with buttons like "name","jersey number","position",.....
When one of the underlying buttons is clicked i first check if a rich text control with a specific name already exist.
If not than i make one and add one single Plain content control to it.
Rich content control-> plain text control -> end of Rich content control
So far so good, this all goes fine but from the moment i want to add another plain content control to the rich content control this pops up :
"Plain text controls cannot be inserted around other controls or XML elements"
here is my code to add a plain content control to a rich content control.
private void addContentControlToRich(String tag, String placeholder,String title)
{
Microsoft.Office.Interop.Word.Document doc = Globals.ThisAddIn.Application.ActiveDocument;
foreach (Microsoft.Office.Interop.Word.ContentControl cc in doc.ContentControls)
{
if (cc.Title == title && cc.Type == Microsoft.Office.Interop.Word.WdContentControlType.wdContentControlRichText)
{
try
{
Microsoft.Office.Interop.Word.Range rng = cc.Range;
object oRng = rng;
contentControlPlain = doc.ContentControls.Add(Microsoft.Office.Interop.Word.WdContentControlType.wdContentControlText, ref oRng);
contentControlPlain.Tag = tag;
contentControlPlain.SetPlaceholderText(null, null, placeholder);
contentControlPlain.LockContentControl = true;
break;
}
catch (COMException) { }
}
}
}

instead of
contentControlPlain = doc.ContentControls.Add(Microsoft.Office.Interop.Word.WdContentControlType.wdContentControlText, ref oRng);
use
contentControlPlain = richTextControl.Range.ContentControls.Add(Microsoft.Office.Interop.Word.WdContentControlType.wdContentControlText, ref oRng);
before using above code use code below
Application.Selection.Start = lastControlinRichTextControl.Range.End+1;
and set `oRng = Application.Selection.Range

As per the message, your code is trying to wrap a plain text control around everything in the rich text control (ie an existing plain text control). Fix your range object so that it doesn't do that eg collapse it to just a point inside the rich text control.

Related

Get Text of Node(Item) Treeview in SelectedItemChange Event IN WPF

First of all I am from Iran and I can't speak English very well, sorry for this.
I made something like OpenFileDialog in WinForms and it works correctly.
After that for better User Interface I'm try to make it in WPF.
I use TreeView and other controls to make it work in both platforms (Winforms and WPF).
In WPF I want to get the text of Treeview item for comparison, in Winform I could do this with below code:
private void Folder_FileTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
if(e.Node.Text=="Desktop")
{
//Do something
}
}
in WPF I added text with and image next to each other using this method:
public object Node(string NodeIMGUri, string NodeText)
{
Image IMG = new Image() { Source = new System.Windows.Media.Imaging.BitmapImage(new Uri(NodeIMGUri, UriKind.RelativeOrAbsolute)) };
TextBlock Text = new TextBlock() { Text = NodeText };
StackPanel CustomStackPanel = new StackPanel();
TreeViewItem TVItem = new TreeViewItem();
IMG.Height = 50;
IMG.Width = 50;
CustomStackPanel.Orientation = Orientation.Horizontal;
CustomStackPanel.Children.Add(IMG);
CustomStackPanel.Children.Add(Text);
TVItem.Header = CustomStackPanel;
return TVItem;
}
But when in SelectedItemChanged (or ItemChanged) event of TreeView how can I get the text of the item clicked?
If anyone can help me to complete this dll, I can send it free to all programmers.
This dll supports most languages like german, france, china, hindi, bengali, indonesian, persian, japanese, korean, arabic, portuguese, latin, swede, english
The way you are currently doing things, you would need to go through the children of your item to find the TextBlock and get the Text property from that. But this isn't the proper or recommended way of doing things in WPF.
Instead of manually creating TreeViewItems, you shoudl be using TreeView.ItemsSource and TreeView.ItemTemplate. If you're not familiar with how to use DataTemplates in WPF, you should really read up on it. Here is a good place to start.
Basically you would define a class, let's say Folder, then you would have a collection of Folder objects (e.g. List<Folder>), and you would bind that to TreeView.ItemsSource. You would then use a DataTempalte to declare the visual representation of how a Folder object should look in the TreeView. Then, when the selected item is changed, you can use TreeViewItem.DataContext to get the Folder object that is being selected, which would probably have a property such as Path.
So what I can see there you put into your TreeViewItem's header a panel with two items - Image and TextBlock with Text you want to get. The TextBlock is stored as the second item in the panel's collection (Children). All you have to do is this:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
CustomStackPanel panel = (CustomStackPanel)((TreeViewItem) e.NewValue).Header;
TextBlock textBlock = (TextBlock)panel.Children[1];
string text = textBlock.Text; //Your text
}
Hope it helps.

How to set ContentControl.Range to the current ContentControl I am working from?

I am not finding a way to set the ContentControl.Range.Text from where the C# is executing from (inside the content control). Perhaps I should be looking at it from a completely different perspective.
Currently I have a content control that produces a set of text with some text between [] square brackets and I want to select text and format the colour by setting the start and end of the range of characters between the []. I am stuck on trying to set the initial range to the contentcontrol I am currently using.
Most of what I have managed/found/patched together below.
object word;
Microsoft.Office.Interop.Word.Document _PWdDoc;
try
{
word = System.Runtime.InteropServices.Marshal.GetActiveObject("Word.Application");
//If there is a running Word instance, it gets saved into the word variable
}
catch (Exception ex)
{
//If there is no running instance, it creates a new one
Type type = Type.GetTypeFromProgID("Word.Application");
word = System.Activator.CreateInstance(type);
}
Microsoft.Office.Interop.Word.Application oWord = (Microsoft.Office.Interop.Word.Application) word;
_PWdDoc = oWord.ActiveDocument;
System.Collections.IEnumerator ContentX = _PWdDoc.ContentControls.GetEnumerator();
//Microsoft.Office.Interop.Word.ContentControl ContentX = Microsoft.Office.Interop.Word.ContentControls.Item[];
//Microsoft.Office.Interop.Word.Range rng = Microsoft.Office.Interop.Word.ContentControl.Range.Duplicate(ref ContentX);
//var rngX = Microsoft.Office.Interop.Word.ContentControl.Range(ContentX);
//Microsoft.Office.Interop.Word.ContentControl cc1 = ContentX;
Excuse the coding mess but it's all I can come up with with the minimal experience I have with this.
Now I have gotten the IEnumerator fo the Content Control(I think) I have no idea how to use it besides from what I have read, they say to iterate through the IEnumerables accessing each of them. That's not what I want to do. I want 1 content control. The current one that I am working in. I want to find it's range and assign it to a value. Then in that range's "text" I want to do some [fancy] highlighting.
Determining whether the current selection or a specific Range is in a content control and doing something with that content control is not a trivial matter. Most other Word objects will return something that they're "in"; content controls do not.
So the approach I use is to
create a Range that reaches from the current selection (or a specific Range) back to the beginning of the document
count the number of content controls in that range
then check whether the current selection is in the same range as the last content control of the extended range.
if it is, then I know the selection is within a content control and I can access the content control.
Here's some sample code. The snippet that calls the function I use to return the information:
Word.Range rng = null;
//Substitute a specific Range object if working with a Range, rather than a Selection
Word.ContentControl cc = IsSelectionInCC(wdApp.Selection.Range);
if ( cc != null)
{
rng = cc.Range;
rng.HighlightColorIndex = Word.WdColorIndex.wdYellow;
}
The function:
private Word.ContentControl IsSelectionInCC(Word.Range sel)
{
Word.Range rng = sel.Range;
Word.Document doc = (Word.Document) rng.Parent;
rng.Start = doc.Content.Start;
int nrCC = rng.ContentControls.Count;
Word.ContentControl cc = null;
bool InCC = false;
rng.Start = doc.Content.Start;
if (nrCC > 0)
{
if (sel.InRange(doc.ContentControls[nrCC].Range))
{
InCC = true; //Debug.Print ("Sel in cc")
cc = doc.ContentControls[nrCC];
}
else
{
sel.MoveEnd(Word.WdUnits.wdCharacter, 1);
if (sel.Text == null)
{
//Debug.Print ("Sel at end of cc")
InCC = true;
cc = doc.ContentControls[nrCC];
}
}
}
return cc;
}
Assuming you mean that the insertion point is inside a Content Control, and your Word Application object is called oWord, then you can get the range of that content control using e.g.
Microsoft.Office.Interop.Word.Range r = oWord.Selection.Range.ParentContentControl.Range
If you have nested controls You can verify that the insertion point is in a Content Control (Word 2013 and later, I think) by checking the value of inCC as follows:
Boolean inCC = (Boolean)oWord.Selection.Information[Microsoft.Office.Interop.Word.WdInformation.wdInContentControl]
However, when dealing with content controls, be aware that selecting a content control in the UI is different from selecting the "range of the content control". Programmatically, it's obvious how to select the Range - not so obvious how to select the control. If you select the Range, the ParentContentControl should be the control whose range you've selected. If you (or the user) selected the control, OTTOMH I am not so sure.

Replace text in Word with text from C# form

I'm trying to make an application in C#. When pressing a radio button, I'd like to open a Microsoft Word document (an invoice) and replace some text with text from my Form. The Word documents also contains some textboxes with text.
I've tried to implement the code written in this link Word Automation Find and Replace not including Text Boxes but when I press the radio button, a window appears asking for "the encoding that makes the document readable" and then the Word document opens and it's full of black triangles and other things instead of my initial template for the invoice.
How my invoice looks after:
Here is what I've tried:
string documentLocation = #"C:\\Documents\\Visual Studio 2015\\Project\\Invoice.doc";
private void yes_radioBtn_CheckedChanged(object sender, EventArgs e)
{
FindReplace(documentLocation, "HotelName", "MyHotelName");
Process process = new Process();
process.StartInfo.FileName = documentLocation;
process.Start();
}
private void FindReplace(string documentLocation, string findText, string replaceText)
{
var app = new Microsoft.Office.Interop.Word.Application();
var doc = app.Documents.Open(documentLocation);
var range = doc.Range();
range.Find.Execute(FindText: findText, Replace: WdReplace.wdReplaceAll, ReplaceWith: replaceText);
var shapes = doc.Shapes;
foreach (Shape shape in shapes)
{
var initialText = shape.TextFrame.TextRange.Text;
var resultingText = initialText.Replace(findText, replaceText);
shape.TextFrame.TextRange.Text = resultingText;
}
doc.Save();
doc.Close();
Marshal.ReleaseComObject(app);
}
So if your word template is the same each time you essentially
Copy The Template
Work On The Template
Save In Desired Format
Delete Template Copy
Each of the sections that you are replacing within your word document you have to insert a bookmark for that location (easiest way to input text in an area).
I always create a function to accomplish this, and I end up passing in the path - as well as all of the text to replace my in-document bookmarks. The function call can get long sometimes, but it works for me.
Application app = new Application();
Document doc = app.Documents.Open("sDocumentCopyPath.docx");
if (doc.Bookmarks.Exists("bookmark_1"))
{
object oBookMark = "bookmark_1";
doc.Bookmarks.get_Item(ref oBookMark).Range.Text = My Text To Replace bookmark_1;
}
if (doc.Bookmarks.Exists("bookmark_2"))
{
object oBookMark = "bookmark_2";
doc.Bookmarks.get_Item(ref oBookMark).Range.Text = My Text To Replace bookmark_2;
}
doc.ExportAsFixedFormat("myNewPdf.pdf", WdExportFormat.wdExportFormatPDF);
((_Document)doc).Close();
((_Application)app).Quit();
This code should get you up and running unless you want to pass in all the values into a function.
EDIT: If you need more examples I'm working on a blog post as well, so I have a lot more detail if this wasn't clear enough for your use case.

C# - RichTextBoxPrintCtrl and tabcontrol

Hello (and sorry for my English), I have a problem with Printing RTF using RichTextBoxPrintCtrl and TabControl.
1) The tabcontrol got no tabs on Design, when the Form Load, it will get a tab with the method AddTab(Title). (Don't mind about other variables).
private void AddTab(string Name = "Nuova Nota*")
{
RichTextBox Body = new RichTextBox();
Body.Name = "Body";
Body.Dock = DockStyle.Fill;
Body.ContextMenuStrip = contextMenuStrip1;
TabPage NewPage = new TabPage();
string DocumentText = Nome;
if (Nome == "Nuova Nota*")
{
TabCount += 1;
DocumentText = Nome + TabCount;
}
NewPage.Name = DocumentText;
NewPage.Text = DocumentText;
tabControl1.Visible = true;
NewPage.Controls.Add(Body);
tabControl1.TabPages.Add(NewPage);
tabControl1.SelectedTab = NewPage;
Nomi_Files.Add(NewPage.Text);
Path_Files.Add("");
}
2) Once the tab is created, you can start to write, change colors, fonts, etc...
To get access on the document that you are making, i use a GetCurrentDocument that return the "Body" of the selected tab:
private RichTextBox GetCurrentDocument
{
get { return (RichTextBox)tabControl1.SelectedTab.Controls["Body"];}
}
Now, all the functions (save, open, fonts, colors...) Works Fine, i wanted to print my document and keep the style, so i Googled and i found this: How to print the content of a RichTextBox control by using Visual C#
I made the RichTextBoxPrintCtrl.dll, added the resource on my project, added the item inside the toolbox, but i can't change the RichTextBox that i create from Code, with RichTextBoxPrintCtrl.
The error that i get is:
Error 1 'RichTextBoxPrintCtrl' is a 'namespace' but is used like a
'type'
How i can use that RichTextBoxPrintCtrl without drag and drop it inside the design form?
Ok, i figured out how to solve it:
instead of declaring like:
RichTextBoxPrintCtrl NameControl = new RichTextBoxPrintCtrl();
we need to declare the namespace so:
RichTextBoxPrintCtrl.RichTextBoxPrintCtrl NameControl = new RichTextBoxPrintCtrl.RichTextBoxPrintCtrl();
Everything works fine :) thanks;

Remove Content Control when Text is edited programmatically

As per MSDN (emphasis mine):
If the Temporary property is true, the ContentControl is automatically deleted when the user types in the control, or when the text in the control is changed programmatically. When the ContentControl is automatically deleted from the document, the text in the control remains in the document.
It's working for "when the user types in the control" using Word Editor, but not when I change Text in (C#) code. My code is as below.
Make a tag Temporary
static void MakeTagsTemporary(List<Tag> tagList)
{
tagList.ForEach(x => x.Parent.Append(new TemporarySdt() { Val = true }));
} mainPart.Document.Save();
I am saving specifically to accept changes of making the Content Control Temporary, but with no effect.
Edit the text
static void ApplyProductGrid(MainDocumentPart mainPart, Plan pl, List<Tag> tagList)
{
foreach (Tag tagitem in tagList)
{
string GridValue = pl.FormattedTags.Where(x => x.Key == tagitem.Val).Select(x => x.Value).FirstOrDefault();
tagitem.Parent.Parent.Descendants<Text>().FirstOrDefault().Text = GridValue;
}
}
The line tagitem.Parent.Parent.Descendants<Text>().FirstOrDefault().Text = GridValue; is doing its job, its updating the value but Content Control is not removed.
When I edit something from word, Content Control is getting deleted.

Categories