Angelos Petropoulos' WebLog

Thoughts occasionally worth reading

SmartDocuments - Application Manifest Editor

clock September 1, 2007 18:04 by author angelosp

I am not sure how I managed to not post about it all this time I have been working with SmartDocuments, but this sample application manifest editor is invaluable. It allows you to open SmartDocument files and edit their ... well ... application manifest.

P.S. Can't find the link to download the code? It's right at the top of the page ...

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Extending Word with VSTO 2005 – Adding icons to toolbars and menus

clock September 1, 2007 18:03 by author angelosp

Both menus and toolbars are in fact CommandBar controls, and when you add child controls to a CommandBar you have the option to add the following type of controls:

 

  • MsoControlType.msoControlActiveX
  • MsoControlType.msoControlButton
  • MsoControlType.msoControlComboBox
  • MsoControlType.msoControlCustom
  • MsoControlType.msoControlDropdown
  • MsoControlType.msoControlEdit
  • MsoControlType.msoControlPopup

 

For both toolbar buttons and menu items you add child controls of type MsoControlType.msoControlButton using the following code

 

// Create the toolbar

Office.CommandBar toolbar = this.Application.CommandBars.Add(

    "Toolbar name", MsoBarPosition.msoBarTop, missing, false);

 

// Add a button to it

Office.CommandBarButton button = (Office.CommandBarButton)

    toolbar.Controls.Add(MsoControlType.msoControlButton, missing,

    missing, missing, false);

 

// Add the menu

Office.CommandBarPopup menu = (Office.CommandBarPopup)

    this.Application.CommandBars.ActiveMenuBar.Controls.Add(

    Office.MsoControlType.msoControlPopup, missing, missing, missing, false);

 

// Add a menu item

Office.CommandBarButton menuItem = (Office.CommandBarButton) menu.Controls.Add(

    Office.MsoControlType.msoControlButton, missing, missing, missing, false);

 

When you have created a msoControlButton, you have the option of using one of the following styles

 

  • MsoButtonStyle.msoButtonAutomatic
  • MsoButtonStyle.msoButtonCaption
  • MsoButtonStyle.msoButtonIcon
  • MsoButtonStyle.msoButtonIconAndCaption
  • MsoButtonStyle.msoButtonIconAndCaptionBelow
  • MsoButtonStyle.msoButtonIconAndWrapCaption
  • MsoButtonStyle.msoButtonIconAndWrapCaptionBelow
  • MsoButtonStyle.msoButtonWrapCaption

 

Anything style with “Icon” its name, obviously supports having an icon displayed. Let’s pick the following as an example

 

button.Style = Office.MsoButtonStyle.msoButtonIconAndCaption;

 

There are three ways of adding an icon and the first one is using the FaceId property. This property allows you to specify an index for the icons that are built into Word and Excel and there is around 3000 of them.

 

button.FaceId = 136;

 

The built in icons support transparency and are in fact the ones used by Word and Excel themselves. However, if the build in icons do not satisfy you, you can always create your own and use them instead. This brings us to the second way of adding icons which is using the PasteFace property. This property works in conjunction with the clipboard; you copy an icon into the clipboard and using PasteFace the button acquires the icon from the clipboard.

 

// Get the icon from the resource file

Icon icon = this.GetIconFromResource("example.ico");

 

// Copy the icon into the clipboard

Clipboard.SetDataObject(icon.ToBitmap(), true);

 

// Paste the icon into the button

button.PasteFace();

 

Unfortunately, this method does not support transparency, so if what you need is to add custom transparent icons, you need to go with option number three which uses STDOLE.

 

First you need to convert your image to IPictureDisp

 

Public stdole.IPictureDisp ConvertImage(Image image)

{

    //Returns a image to STDOLE format

    return (stdole.IPictureDisp)AxHost.GetIPictureDispFromPicture(image);

}

 

Now you can set the button’s Picture and Mask property. You have to set the Picture property with the IPictureDisp version of the icon you would like to appear on the button and the Mask property with the IPictureDisp version of the icon’s mask. The icon’s mask is just another icon which has all of the transparent areas of the original icon filled with the white and the rest of the icon filled with black.

 

// Get the icon and its mask from the resource file

Image icon = this.GetImageFromResource("example.ico");

Image iconMask = this.GetImageMaskFromResource("example_mask.ico");

 

// Set its icon and its mask so that the transparency works correctly

// Use the ConvertImage method to get the IPictureDisp version of the the images

button.Picture = this.ConvertImage(icon);

button.Mask = this.ConvertImage(iconMask);

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Extending Word with VSTO 2005 - Manually generating a table of contents

clock September 1, 2007 17:57 by author angelosp

Word is quite flexible when it comes to Table of Contents (TOC) as it allows you to base its generation to either styles or table entry fields (Insert à Field à TC).

 

The first is the option that is most commonly used, as documents tend to be logically spit into distinct parts which have their own heading. Applying a specific style to those headings means that you can use all instances of this style to generate the TOC. This method stops working when the way the document is structured is not consistent with the way you want to TOC to be generated or when you want the TOC to include parts of the document that do not have a specific style applied to them.

 

This is where table entry fields come in. They can be placed anywhere in the document and they can contain any text that you like. They can even be associated with an outline level (i.e. which level in the hierarchy they belong to). In other words, it is like planting hidden headings within the document.

 

However, there are two main issues with table entry fields

 

  1. When “Show all formatting marks” are turned on the user can see them, which can be annoying to a power user that uses this option but is not interested in the table entry fields, and can also edit them either by mistake or on purpose. One way or the other, a lot of responsibility is placed on the hands of the user for keeping the table of entry fields intact.
  2. They are inline with text. If you place a table entry field between two words, selecting the two words and deleting them will also delete the table entry field. Clearly this can be an issue as it restricts the users’ ability to edit the document freely.

 

If you are in a situation where basing the TOC on styles or table entry fields is not an option, your only true alternative is the generating your own. By keeping track of the parts of the document that are of interest (e.g. using Smart Documents and XML tags to annotate the content of the document) you can generate a TOC of contents that looks and functions the same as well the TOC generated by Word, but this time you are not restricted to using styles or table entry fields.

 

Manually creating a TOC requires being able to do or having access to the following

 

  1. Titles that will be the entries of the TOC
  2. Creating hyperlinks so that users can CTRL+CLICK on a TOC entry and be taken to the part of the document the entry corresponds to
  3. The page number that a part of the document starts at
  4. Being able to align the page numbers differently than the titles and have some sort of spacing between them (e.g. white space, dots, etc.)

 

Titles that will be entries of the TOC

 

This one depends on the source of information that your TOC will be based upon, but should be quite easy. In the case of using Smart Documents and XML tags, you can have an XML attribute in each XML node that needs to be part of the TOC that has the value that will appear in the TOC. When the TOC is being generated, XPath can be used to retrieve all of the required attributes very efficiently.

 

Creating hyperlinks so that users can CTRL+CLICK on a TOC entry and be taken to the part of the document the entry corresponds to

 

This is not as straight forward as it sounds; it might not be technically very challenging, but it is not straight forward. First of all you need a bookmark to base the hyperlink on. Bookmarks can easily be added to a document using the following line of code

 

Globals.ThisDocument.Bookmarks.Add("BookmarkName", ref range);

 

However, bookmarks are visible within the document and since power users might also use them, the situation is not much different to using table entry fields. By prefixing the name of the bookmark with “_” the bookmark is added as “hidden”, meaning that it won’t be visible within the document and unless the user explicitly wants hidden bookmarks to be displayed they won’t even appear in the “Bookmarks” window (Insert à Bookmark).

 

Having the bookmark is half the battle; the rest is creating a hyperlink based on the bookmark. First you add the hyperlink to the document using the following code

 

object bookmarkRange = Globals.ThisDocument.Range(ref start, ref end);

object bookmarkSubAddress = SubsectionManager.GetBookmarkName("_BookmarkName");

object bookmarkScreenTip = "Mouse over text";

Globals.ThisDocument.Hyperlinks.Add(bookmarkRange, ref missing,

    ref bookmarkSubAddress, ref bookmarkScreenTip, ref missing, ref missing);

 

Then you need to style the TOC entry accordingly. You will probably have a style that you would like to apply to the range of the bookmark, but it is not as easy as that. Adding a hyperlink to the document will automatically apply the built-in “Hyperlink” style to the specified range. Applying your own style after adding the hyperlink will not override the hyperlink settings for font and its properties (i.e. blue colour, underline, etc.). The way around this is to create a style that is based on the built-in “Hyperlink” style but has the font properties you want the TOC entry to have. If it is still necessary you can apply your own style on top of the custom “Hyperlink” based style without issues, applying more formatting to the TOC entry.

 

object styleName = "Style TOC1 Hyperlink";

((Range)bookmarkRange).set_Style(ref styleName);

styleName = "Style TOC1";

((Range)bookmarkRange).set_Style(ref styleName);

 

The page number that a part of the document starts at

 

This might not be immediately obvious, but it is actually pretty simple. The object “Range” has a method called “get_Information()” that can take a number of parameters, one of which being the page number the specific range ends at. Since for the TOC entry the page number that a range starts are is required, a new range that starts and ends at the beginning of the original range can be used to get this information as shown below

 

object start = ((Range)bookmarkRange).Start;

Word.Range range = Globals.ThisDocument.Range(ref start, ref start);

range.get_Information(WdInformation.wdActiveEndAdjustedPageNumber);

 

When using page numbers that have been adjusted (e.g. the count starts from 2, the count actually starts from page 4 of the document, etc.) make sure you use WdInformation.wdActiveEndAdjustedPageNumber instead of WdInformation.wdActiveEndPageNumber.

 

Being able to align the page numbers differently than the titles and have some sort of spacing between them (e.g. white space, dots, etc.)

 

This part is probably the easiest one as it can be achieved as part of the style that is applied to the TOC entry. Each “paragraph” type style has a set of properties called “Tabs” which can be used to set the alignment that the text on the right of a “tab” will have, it’s position on the page and the spacing between the text on the right and the left of a “tab”.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Extending Word using VSTO 2005 - Track Changes is out to get you

clock September 1, 2007 17:51 by author angelosp

Word has had this great feature for a while now, called Track Changes. You can activate it through the menu "Tools" --> "Track Changes" and in summary what it does is keep a record of the changes made to the document since you enabled this option. It will monitor deletion and addition of text, change of styles, insertion of tables, etc.

When you are working with VSTO and Word, unless you are forbidding your users to have "Track Changes" enabled (as if they will listen to you anyway ...), you have to write your code with the following in mind:

When you remove something from the document with "Track Changes" enabled (e.g. you select a table and press the "Delete" key), until you "Accept" or "Reject" the change, whatever it is that you removed still exists in Word's object model.

When you add something new to the document with "Track Changes" enabled (e.g. you type some text), what ever it is you added exists in Word's object model immediately, before you "Accept" or "Reject" the change.

You will need a second to think about the implications and how it affects you. Do not dismiss it as something trivial until you have given it some good thought. Let me give you an example of how it affected a project I worked on, in a way not thought of until the system testers caught it.

  • The requirement was to have a textbox on a page, but for the textbox to exist only once on any given page. Code had been implemented that would detect the existence of this specific textbox on the page and not add a second one.
  • The textbox was typically positioned on the right hand side of the page, on top of the margin, with text floating around it. In these situations Word uses an anchor to associate the object with a specific paragraph on the page.
  • If the users needed the textbox to be moved to a different page, all they had to do was move the anchor to one of the paragraphs in the new page.

This was working quite well, until "Track Changes" got us! The scenario is simple, move the textbox's anchor to a different page when "Track Changes" is enabled. The result? When querying Word's object model the document now reported having two textboxes, one in the page the textbox was moved from and one in the page where the textbox was moved to. If the users attempted to add another textbox to the page from which they moved the original textbox from, our solution would complain that a textbox already existed and would not allow it.

The solution to this problem is to either educate users to "Accept" or "Reject" the change they have made (kind of tough as they have to know which specific pending change is causing the problem) or automatically "Accept" the change in code. We opted for the second option because we didn't think much of our users (only joking, it was not reasonable to request the users to do this manually) and this is the way we did it:

    // Go through each revision on the current page

    foreach (Word.Revision revision in pageRange.Revisions)

    {

        // Check if the revision is the deletion of the text box that

        // we have just found on the page

        if (revision.Type == Word.WdRevisionType.wdRevisionDelete &&

            revision.Range.Start <= currentTextbox.Anchor.Start &&

            revision.Range.End >= currentTextbox.Anchor.End)

        {

            // Accept the revision

            revision.Accept();

            break;

        }

    }

Currently rated 4.5 by 2 people

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Extending Word with VSTO 2005 – Adding a toolbar

clock September 1, 2007 17:50 by author angelosp

I have been working on a VSTO 2005 project in the past few weeks and although the progress made since VSTO 2003 is substantial in terms of productivity and freedom, there are still a few gotchas. In the next few days I will be posting information on some of the issues I have come across and any helpful tips I have discovered. The project was a Word Template one, so some of the posts will be specific to that, but most of the code will apply to VSTO 2005 in general.

 

Adding a toolbar is quite a common task and it is not that difficult, but there are a few little details that you need to be aware of. Let’s have a look at some code first

 

private Office.CommandBar AddToolbar()

{

    // Create the toolbar

    Office.CommandBar toolbar = this.Application.CommandBars.Add(

        "Toolbar name", MsoBarPosition.msoBarTop, missing, false);

   

    // Add a button to it

    Office.CommandBarButton button = (Office.CommandBarButton)

        toolbar.Controls.Add(MsoControlType.msoControlButton, missing,

        missing, missing, false);

 

    // Set the button's style

    button.Style = Office.MsoButtonStyle.msoButtonCaption;

 

    // Set the button's caption

    button.Caption = "Click me";

 

    // Assign a tag value and the Click event will be fired once

    // for every document that is open

    //button.Tag = “a unique value”;

 

    // Ensure the toolbar is visible, by default it won't be

    toolbar.Visible = true;

 

    // Return the newly created toolbar

    return toolbar;

}

 

Let’s start with the first line of code that actually creates the CommandBar object. If the code is placed in the ThisDocument.cs file then you have access to the Word application through the homonymous property; otherwise you can use Globals.ThisDocument.Application. First you pass in the name of the new toolbar, then where you want it (top, bottom, floating, etc.), then whether you would like to replace the currently active menu bar or not (it is a Boolean parameter, but you can use missing which defaults to false) and lastly and most interestingly whether you would like to add the new toolbar as a temporary or a permanent one. The difference between the two is that a permanent toolbar is saved to the template and will be available next time a document is created from the template without having to execute this code. A temporary one is not saved in the template and it will have to be added every time you create a document from the template. Unless there is a reason to do otherwise, I prefer to add the toolbar as permanent to save myself some time during the creation of a new document.

 

The second line of code adds a control to the toolbar. You specify what type of control you would like (drop-down, popup, combo, etc.), in this case a button, then you have some parameters that apply to built in controls which we are not interested in here and finally again whether the control should be temporary or permanent. After that, the style of the button is set (caption, image, etc.) and since we chose a caption button we give it a caption. The line of code that is commented out line refers to the Tag property of the button which when given a value, the Click event of the button will be fired in every open document that was based on the template; otherwise the event will be fired only in the currently active one. This is quite handy if you need to perform an action across multiple open documents. The rest is for making the toolbar visible, which is the same as right clicking on Word’s toolbar pane and selecting a toolbar by name.

 

As you may have noticed, the code above does not subscribe to the button’s Click event. This is not an omission; there is a very good reason for it. If you remember, a toolbar can be added either as permanent or not and here we are adding it as a permanent one. This means that after the fist time, any document that gets created from the template, the toolbar will already exist and will not be necessary to add it again. In that case, the above does not need to and should not be executed. If the code that listens to the Click event of the button was added here, the button for any document created from the template after the first one would not work. Don’t get ahead of yourself and think that if the toolbar is added as a temporary one, then this is not an issue. The toolbar, regardless of whether it is a temporary one or not, is added to a Word application (i.e. process) and therefore if another document is created from the template whilst the first one is still open, by default, it will be created within the same Word application and the toolbar will therefore already exist. So, now that I have convinced you that you should not subscribe to the buttons’ Click event during creation of the toolbar, let’s move on to the code that takes care of this issue

 

private void SubscribeToToolbarEvents(Office.CommandBar toolbar)

{

    // Go through each control in the toolbar

    foreach (Office.CommandBarControl control in toolbar.Controls)

    {

        // If the control is a button

        if (control is Office.CommandBarButton)

        {

            // Cast the control to a button

            Office.CommandBarButton button = (Office.CommandBarButton)control;

           

            // Subscribe to the button's Click event

            button.Click += new Office._CommandBarButtonEvents_ClickEventHandler(

                ThisDocument_Click);

 

            // Add the button to a class scope collection of buttons

            this.toolbarButtons.Add(button);

        }

    }

}

 

This piece of code is pretty straight forward, given a toolbar you go through its buttons and subscribe to its Click event. There is a small detail, however, which might go unnoticed but is of great importance; each button is added to a class scope collection. If this step is not taken, after the first pass of the Garbage Collector the Click event of the buttons will stop firing. This might appear very strange at first, but if you consider that what we are dealing with here is COM Interop, it actually makes sense. The instances of CommandBarButton that we get from accessing a toolbar’s Controls property, are not the actual objects stored in the toolbar; they are just wrappers. As a result, unless you keep a reference to them by adding them to a class scope collection, nothing else has a reference to them and they get garbage collected. The API needs to be as lightweight as possible, so the VSTO team has decided to not keep the wrapper objects around unless they are required; otherwise for every unmanaged object in Word exposed through VSTO would have a managed counterpart making Word’s memory consumption almost double as soon as VSTO is used.

 

Alright, now that we have gone through each individual issue, let’s put the code together

 

private void ThisDocument_Startup(object sender, System.EventArgs e)

{

    // See if the toolbar already exists

    Office.CommandBar toolbar = this.GetToolbar(TOOLBAR_NAME);

 

    // If it does not exist

    if (toolbar == null)

    {

        // Add the toolbar

        toolbar = this.AddToolbar();

    }

 

    // Start listening to the events fired by the toolbar and its buttons

    this.SubscribeToToolbarEvents(toolbar);

}

 

 

private Office.CommandBar GetToolbar(string toolbarName)

{

    // Go through each toolbar in the application

    foreach (Office.CommandBar toolbar in this.Application.CommandBars)

    {

        // Iff the toolbar has the specified name

        if (toolbar.Name.CompareTo(toolbarName) == 0)

        {

            // Return it

            return toolbar;

        }

    }

 

    // A toolbar with the specified name was not found, return null

    return null;

}

 

private Office.CommandBar AddToolbar()

{

    // Create the toolbar

    Office.CommandBar toolbar = this.Application.CommandBars.Add(

        "Toolbar name", MsoBarPosition.msoBarTop, missing, false);

 

    // Add a button to it

    Office.CommandBarButton button = (Office.CommandBarButton)

        toolbar.Controls.Add(MsoControlType.msoControlButton, missing,

        missing, missing, false);

 

    // Set the button's style

    button.Style = Office.MsoButtonStyle.msoButtonCaption;

 

    // Set the button's caption

    button.Caption = "Click me";

 

    // Assign a tag value and the Click event will be fired once

    // for every document that is open

    //button.Tag = Guid.NewGuid().ToString();

 

    // Ensure the toolbar is visible, by default it won't be

    toolbar.Visible = true;

 

    // Return the newly created toolbar

    return toolbar;

}

 

private void SubscribeToToolbarEvents(Office.CommandBar toolbar)

{

    // Go through each control in the toolbar

    foreach (Office.CommandBarControl control in toolbar.Controls)

    {

        // If the control is a button

        if (control is Office.CommandBarButton)

        {

            // Cast the control to a button

            Office.CommandBarButton button = (Office.CommandBarButton)control;

           

            // Subscribe to the button's Click event

            button.Click += new Office._CommandBarButtonEvents_ClickEventHandler(

                ThisDocument_Click);

 

            // Add the button to a class scope collection of buttons

            this.toolbarButtons.Add(button);

        }

    }

}

 

Those who pay close attention to detail might have noticed that the variable for the toolbar is not at class scope. The reason is that I have come across problems doing that It appears that there is some issue with the wrapper object and the underlying actual toolbar and after a while using the wrapper to access the toolbar causes issues. The one I have come across is with toolbar.Delete() during document shutdown, which will throw an exception if used on a wrapper object held at class scope, but will work correctly if the toolbar wrapper is created again using the GetToolbar method. I have made it my standard practice now to always use this method instead of keeping a variable at class scope for the toolbar.

 

And there you have it, how to add a toolbar in VSTO 2005.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Subscribe

Syndicate
AddThis Feed Button
AddThis Social Bookmark Button

Search

Calendar

<<  August 2008  >>
SuMoTuWeThFrSa
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

Archive

Tags

Categories


Blogroll

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2008

Sign in