none
Issues Creating Index programmatically in Word using VSTO RRS feed

  • Question

  • Hello,

    Please let me give you the context and rundown of the steps I'm taking to hit the issues I'm facing.

    I'm developing a Application Level AddIn to Word 2007/2010 using interop (Microsoft.Office.Interop.Word Namespace). I have to create an index programmatically for which I'm using the Indexes Interface 

    Inside a foreach loop I call this function Indexes.MarkAllEntries Method for the list of words (List<Strings>) to create the index on.

     

    void MarkAllEntries(
    	Range Range,
    	ref Object Entry,
    	ref Object EntryAutoText,
    	ref Object CrossReference,
    	ref Object CrossReferenceAutoText,
    	ref Object BookmarkName,
    	ref Object Bold,
    	ref Object Italic
    )
    

     

    Then, after the foreach loop I call the Indexes.Add Method

     

    Index Add(
    	Range Range,
    	ref Object HeadingSeparator,
    	ref Object RightAlignPageNumbers,
    	ref Object Type,
    	ref Object NumberOfColumns,
    	ref Object AccentedLetters,
    	ref Object SortBy,
    	ref Object IndexLanguage
    )
    

    For the Range parameter the documentation reads 

    Range

    Type: Microsoft.Office.Interop.Word.Range

    Required Range object. The range where you want the index to appear. The index replaces the range, if the range isn't collapsed.

    Issue 1

    (The index replaces the range, if the range isn't collapsed.) Each time I click a button to generate the index by calling the Indexes.Add method my entire document content vanishes. For the range parameter I supply Word.ActiveDocument.Content as the argument which is a range object.

    I'm not aware of how to collapse the range (which in my case is my Word.ActiveDocument.Content).  I'm looking to place the index at the end or start of my active document. Can anyone guide me on this, how to collapse my range so that I get the index at the start or end of my active document?

     

    Issue 2

    (I get no index in my active document)  As I said earlier my entire document is cleared and the only thing that my document contains is this message "No index entries found". Can anyone help me understand why this is happening and how I can resolve it?

     

    Any help will be greatly appreciated.

    Thanks,

    P.S

    I've played around creating indexes by clicking on the References Tab --> Index Group --> Mark Entry --> Insert Index in the Office 2010 Ribbon. I've seen the VBA code that is generated by recording and editing a macro. 

     

    Thursday, December 23, 2010 6:18 AM

Answers

  • Hi Hamza Zia

    Solving Issue 1 should take care of Issue 2 :-)

    What you're seeing is expected behavior. As the Help says, if you put something into a Range without collapsing it first, you lose the content of the Range.

    There's a Collapse method for the Range object, but in order to use it you first need to declare a Range object and assign it to the Range. So, something like this:

    Word.Range rngIndex = wrdDoc.Content;
    object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
    rngIndex.Collapse(ref oCollapseEnd);


    Cindy Meister, VSTO/Word MVP
    Thursday, December 23, 2010 8:51 AM
    Moderator

All replies

  • Hi Hamza Zia

    Solving Issue 1 should take care of Issue 2 :-)

    What you're seeing is expected behavior. As the Help says, if you put something into a Range without collapsing it first, you lose the content of the Range.

    There's a Collapse method for the Range object, but in order to use it you first need to declare a Range object and assign it to the Range. So, something like this:

    Word.Range rngIndex = wrdDoc.Content;
    object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
    rngIndex.Collapse(ref oCollapseEnd);


    Cindy Meister, VSTO/Word MVP
    Thursday, December 23, 2010 8:51 AM
    Moderator
  • Hi Cindy,

     

    I really appreciate your quick response, tried your suggestion however Issue 2 remains. I'm pasting the code snippet for you to check if I'm doing something I'm not supposed to do or missing any steps.

     

      private void Indexbutton_Click(object sender, EventArgs e)
      {
       var entrylist = ContentrichTextBox.Text.Split(new[] {Environment.NewLine, "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries).ToList();
       foreach (var entry in entrylist)
        {ThisAddIn.ThisApplication.ActiveDocument.Indexes.MarkAllEntries(ThisAddIn.ThisApplication.ActiveDocument.Content, entry);
    }
       Range activeContent = ThisAddIn.ThisApplication.ActiveDocument.Content;
       object oCollapseEnd = WdCollapseDirection.wdCollapseEnd;
       activeContent.Collapse(ref oCollapseEnd);
       ThisAddIn.ThisApplication.ActiveDocument.Indexes.Add(ThisAddIn.ThisApplication.ActiveDocument.Content, WdHeadingSeparator.wdHeadingSeparatorLetter,
          true, WdIndexType.wdIndexIndent, 0, true, WdIndexSortBy.wdIndexSortBySyllable);
       ThisAddIn.ThisApplication.ActiveWindow.ActivePane.View.ShowAll = false;
      }
    
    

    Thanks,

    Hamza

     

    • Edited by Hamza Zia Thursday, December 23, 2010 11:01 AM Typographical Error
    Thursday, December 23, 2010 11:00 AM
  • Hi Hamza

    Instead of this in the Indexes.Add method: ThisAddIn.ThisApplication.ActiveDocument.Content

    Use this: activeContent


    Cindy Meister, VSTO/Word MVP
    • Marked as answer by Hamza Zia Friday, December 24, 2010 12:39 PM
    • Unmarked as answer by Hamza Zia Monday, December 27, 2010 8:33 AM
    Thursday, December 23, 2010 4:34 PM
    Moderator
  • Thanks Cindy,

    Works like a charm.

    Friday, December 24, 2010 12:40 PM
  • Hi Cindy,

    This post is in continuation of my index question therefore I haven't started a new thread.

    What I'm trying to do is give the user the ability to generate an index for the document by clicking a button and giving him options to place it at the end or start of the document. The user should be able to generate a new (fresh) index each time. To get this functionality programmatically I've written this code block

     

     

     private void Indexbutton_Click(object sender, EventArgs e)
      {
       object oCollapse = ((Button)sender).Name == "Startbutton"
             ? WdCollapseDirection.wdCollapseStart
             : WdCollapseDirection.wdCollapseEnd;
    
       foreach (Field field in ThisAddIn.ThisApplication.ActiveDocument.Fields)
       {
        if (field.Type == WdFieldType.wdFieldIndexEntry)
         field.Delete();
       }
    
       var entrylist = ContentrichTextBox.Text.Split(new[] { Environment.NewLine, "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries).ToList();
    
       for (int i = entrylist.Count - 1; i >= 0; i--)
        ThisAddIn.ThisApplication.ActiveDocument.Indexes.MarkAllEntries(ThisAddIn.ThisApplication.ActiveDocument.Content, entrylist[i]);
    
       Range activeContent = ThisAddIn.ThisApplication.ActiveDocument.Content;
       activeContent.Collapse(ref oCollapse);
    
    
       ThisAddIn.ThisApplication.ActiveDocument.Indexes.Add(activeContent, WdHeadingSeparator.wdHeadingSeparatorLetter,
          true, WdIndexType.wdIndexIndent, 0, true, WdIndexSortBy.wdIndexSortBySyllable);
    
       ThisAddIn.ThisApplication.ActiveWindow.ActivePane.View.ShowAll = false;
       Close();
      }

     

    Issue 1

    All the words/phrases (groups of words with spaces between them) do not appear in the index created. This happens when you comment out the lines. Also in subsequent clicks index entries from the previous invocation of the Index button remain. They do not get removed, new index entries are appended with the old ones.

     

      foreach (Field field in ThisAddIn.ThisApplication.ActiveDocument.Fields)
       {
        if (field.Type == WdFieldType.wdFieldIndexEntry)
         field.Delete();
       }
    

     

    Issue 2

    If you include (uncomment) the above lines in the IndexButton_Click function I observe Issue 1 and in addition the index is generated in the document only the first time. Subsequent button clicks to generate index returns "No index entries found". 

    What am I missing now? 

    Thanks,

     


    • Edited by Hamza Zia Monday, December 27, 2010 8:44 AM More detail
    Monday, December 27, 2010 8:32 AM
  • Hi Hamza

    (1a)
    Index entries that consist of more than one word must be within quotes. If you turn on the view of "hidden characters" in the document (the ¶ button in the ribbon) you'll see fields something like this:
    { XE Topic:subtopic 1 }

    but they need to look more like: { XE "Topic:subtopic 1" }

    So at some point in your process you need to add the quotes (ANSI 34)

    (1b)
    The problem is Field.Delete(). You're deleting the field code, but not the field result. Try Field.Result.Delete() (The Result property returns a RANGE, so you're deleting the range generated by the Index.) You'll want to check (Alt+F9), but I'm pretty sure that also deletes the field, itself. If it wouldn't, then do a Field.Delete afterwards.

    (2)
    I'm not sure about this one. Fix the issues 1a and 1b then test again to see what's left. FWIW deleting the Index does not remove the index markings ( XE fields ). So when a new index is generated it's going to look like the old one, plus anything new/different that may have been added.

    If you don't want these old index markings the XE fields need to be deleted, as well. You can do this with a "for...each", but it's also possible to remove them in one step using Range.Find with the FindText:="^d XE " and Replace set to wdReplaceAll.


    Cindy Meister, VSTO/Word MVP
    Monday, December 27, 2010 10:09 AM
    Moderator
  • Cindy,

    (1a)

    Have I done the correct thing in representing ANSI 34 like this in code

     

    const string quote = @"""";

     

    If yes, then I have modified the code like this

     for (int i = entrylist.Count - 1; i >= 0; i--)
                {
                    object entry = quote + entrylist[i] + quote;
                    ThisAddIn.ThisApplication.ActiveDocument.Indexes.MarkAllEntries(
                        ThisAddIn.ThisApplication.ActiveDocument.Content, entry);
                }

     

    Which unfortunately does not work and produces the output No index entries found". 

    If I'm incorrectly representing ANSI 34, what is the correct way?

    (1b) Haven't tried yet because (1a) does not work correctly

     

    (2)

    I found this  regarding deleting indexes via the replaceAll method. Under Trick1 please look at the Catch I have not tried your suggestion as yet, if the catch is true I will not use replaceAll method for obvious reasons.

     

     


    • Edited by Hamza Zia Wednesday, December 29, 2010 1:47 PM More specific
    Wednesday, December 29, 2010 1:46 PM
  • Hi Hamza

    "All the words/phrases (groups of words with spaces between them) do not appear in the index created."

    Could you please give me some sample information I can use to test reproducing the problem?

    1. A list of terms you want to be marked in the document

    2. Plus a corresponding list of terms for how the index entries should be marked

    3. Some sample text to run this on.

    It doesn't need to be a lot, just enough to repro...


    Cindy Meister, VSTO/Word MVP
    Thursday, December 30, 2010 8:12 AM
    Moderator
  • Hi Cindy,

    Please find the information to repro

    1. A list of terms you want to be marked in the document ( Change Current, Page Layout, Style Set )

    2. Plus a corresponding list of terms for how the index entries should be marked ( I use MarkAllEntries --> ThisAddIn.ThisApplication.ActiveDocument.Indexes.MarkAllEntries )

    3.  Some sample text to run this on ( Open up a new word document, go to the topleft corner (co-ordinate 0,0) type in this ---> =rand(8,9)

    When I run index programmatically i just get index for Style Set. Change Current and Page Layout do not show up.

    Thanks,

    Thursday, December 30, 2010 1:38 PM
  • Hi Hamza

    OK, I think I understand what's happening. I've never really looked closely at this part of the object model before. After reading the Help about 20 times, your code 10 and testing...

    The MarkAllEntries method's first parameter, the RANGE is the text that the method searches in the entire document and marks as an Index entry. I don't see anything in your code that's changing that Range.

    The second parameter is the text you want to see in the XE field (and that's the information I asked for in (2) that you didn't give me.

    So, in essence, you need to provide a RANGE for each term you want the method to put a XE field next to. That means you'd probably require Range.Find in your loop.

    But there is another method that might be more what you'd prefer to use. Take a look at the AutoMarkEntries method and look up the term "concordance file" in the end-user Help. Basically, you create a table of search term | index entry term. The method will put an XE next to every instance of the search term in the document. (This is what I thought you were doing with MarkAllEntries, until I read the Help.)
    Cindy Meister, VSTO/Word MVP
    Thursday, December 30, 2010 5:14 PM
    Moderator
  • Hi Cindy,

     

    Thank you for your help. Before I dive into coding again I would like to clarify somethings with you regarding MarkAllEntries parameters ...

    Parameter 1 (Range). I'm on the same page with you on this one (For my range I give the ActiveContent.Text since I want Word to MarkAllEntries in my entire document)

    Parameter 2 (Entry). I beg to differ, based on the following observations from the msdn link

    If you read what msdn has to offer it says The text you want to appear in the index, in the form MainEntry[:Subentry].

    If you go about building an index by highlighting phrases in the word document References Tab --> Index Group --> Mark Entry, in the popup you will find your highlighted phrase appearing in the MainEntry Textbox while the Subentry remains unpopulated. Once you select all the words/phrases you want to want to appear in the index you next click Insert Index.

    I have followed exactly the same procedure in my code. Therefore Parameter 2 should be The text (which in this case is Change Current, Page Layout, Style Set ) 

    I have not tried implementing your suggestion of Range.Find in my loop, I guess that would not be required.

     

    I was aware of AutoMarkEntries, I wanted to avoid it because of the overhead of creating a table and populating it (since I believe I will be working with lots of entries for indexing ) and I thought MarkAllEntries would perform as published in msdn.

    If we are not able to resolve MarkAllEntries functionality I would be forced to takeup AutoMarkEntries, but then again who knows what I'll face then. 

     

    • Edited by Hamza Zia Friday, December 31, 2010 10:04 AM More specific
    Friday, December 31, 2010 10:01 AM
  • Hi Hamza

    <<Parameter 1 (Range). I'm on the same page with you on this one (For my range I give the ActiveContent.Text since I want Word to MarkAllEntries in my entire document)>>

    Yes, this is what through me off and why it took me so long to figure out what the problem is.

    What this parameter actually wants is the Range that contains the text in the document next to which XE fields should appear.

    A lot of Word is constructed based on the UI - the user actions. When VBA replaced Word Basic, back in 1996/7, we were finally able to work with "objects" rather than the current selection. But anything that hasn't changed in the UI since that time is still very much linked to the UI.

    So, if you work as an end-user, when you create an index entry you first position the cursor where you want the XE field. You then open the dialog box and insert the entry ("Mark").

    If you first SELECT some text, then open the dialog box the "Mark All" button becomes active. When you click that, Word inserts the XE field and THEN searches the entire document for the selected text and inserts XE fields there, as well.

    The object model functions in a similar manner, except you don't have to select what you want to mark throughout the entire document. But you do have to specify a Range object that contains that text.

    What your current code is doing is passing the same range for each loop. And the feature won't mark the same range a second time.

    Conclusion: you need to change the Range for each loop. Since you want the same text in the XE field as is in the document, you can use Range.Find at the beginning of each loop to find an instance of the term, then MarkAllEntries.

    I know, it doesn't seem to make sense. But I did spend about an hour on this yesterday and that's how it works...


    Cindy Meister, VSTO/Word MVP
    Friday, December 31, 2010 10:12 AM
    Moderator
  • Hi Cindy,

    I have to juggle between different projects on disparate Microsoft technologies so I'm getting back to you on this late.

     

    First, Thank you so much for your wholehearted effort, yes I can generate index over all my entires, so 1a is solved with your guidance.

     

    However, 1b remains and I'll tell you what I've done over the last hour 

    Way 1

    I tried this

     

     if (ThisAddIn.ThisApplication.ActiveDocument.Fields != null)
       {
        foreach (
         Field field in
          ThisAddIn.ThisApplication.ActiveDocument.Fields.Cast<Field>().Where(
           field => field.Type == WdFieldType.wdFieldIndexEntry))
        {
         field.Result.Delete();
         field.Delete();
        }
       }
    

     

    However, I get this exception text on field.Result.Delete();

     

    System.Runtime.InteropServices.COMException (0x800A1703): That property is not available on that object.

       at Microsoft.Office.Interop.Word.Field.get_Result()

    Commenting out field.Result.Delete(); does not do the job, index entries from the last iteration remain

    Way 2

    I tried this

     

       Range fieldfindreplacerange = ThisAddIn.ThisApplication.ActiveDocument.Content;
       Find findrange = fieldfindreplacerange.Find;
       findrange.Text = "^d XE";
       findrange.Replacement.Text = "";
       findrange.MatchCase = false;
       findrange.Wrap = WdFindWrap.wdFindContinue;
       findrange.Forward = findrange.IgnorePunct = findrange.IgnoreSpace = true;
       findrange.ClearFormatting();
       findrange.Execute(Replace: WdReplace.wdReplaceAll);
    

    No success, index entries from previous iteration remain, new entries are appended. I ran a Macro to observe the VBA code while replacing all fields according to this link and noticed the range.Text had "^d" so I replaced findrange.Text = "^d", no luck still.

     

    Cindy, What intricate detail am I missing? 

    Regards,

     

     

     

    • Edited by Hamza Zia Thursday, January 6, 2011 4:58 AM Specificity
    Wednesday, January 5, 2011 1:52 PM
  • Hi Hamza

    <<field.Result.Delete();>>

    <argh- thud> Sorry about that. "Of course" the XE field has no "result". Try instead:

    field.Code.Delete();

    <<No success, index entries from previous iteration remain, new entries are appended. I ran a Macro to observe the VBA code while replacing all fields according to this link and noticed the range.Text had "^d" so I replaced findrange.Text = "^d", no luck still.>>

    That's strange. I don't see anything off-hand in your code snippet that should cause that. If you just do a single Find (no replace) then fieldfindreplacerange.Select(), what's selected?


    Cindy Meister, VSTO/Word MVP
    Thursday, January 6, 2011 6:14 PM
    Moderator
  • Hi Cindy,

    Thank you once again for your expert guidance, yes field.Code.Delete(); does work and I am getting the behavior in my application as expected. What I'm noticing is that even with field.Delete(); commented index generator works, so do I need it?

     

    >>>>That's strange. I don't see anything off-hand in your code snippet that should cause that. If you just do a single Find (no replace) then fieldfindreplacerange.Select(), what's selected?

    Entire contents of my word file are being selected.

     

    Cindy, can you please confirm whether the catch described at this link is true. If it is not true then I might implement that because I guess it will be faster.

    Regards,

     

     

    Friday, January 14, 2011 1:51 PM
  • Hi Hamza

    <<What I'm noticing is that even with field.Delete(); commented index generator works, so do I need it?>>

    I'd say, yes. It makes no sense to have duplicates and, if the information in the "search" file ever changes, you'd end up with unwanted entries.

    <<Cindy, can you please confirm whether the catch described at this link is true.>>

    This is the approach I suggested back on Dec. 27:

    "...but it's also possible to remove them in one step using Range.Find with the FindText:="^d XE " and Replace set to wdReplaceAll."

    If you follow the steps in the link, you should see that only the ^d shows up. This is the command to search the opening "character" (it's actually not a character, but a composite) for a field. When you also include a space plus the field name it will search and replace THAT kind of field, rather than any and every field.


    Cindy Meister, VSTO/Word MVP
    Sunday, January 16, 2011 8:09 AM
    Moderator