Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional VSTO 2005 - Visual Studio 2005 Tools For Office (2006) [eng]

.pdf
Скачиваний:
121
Добавлен:
16.08.2013
Размер:
8.04 Mб
Скачать

Chapter 4

Finally, the user piece will be run on a single desktop, mitigating the risk of concurrent clipboard access during the time that the clipboard manipulation must occur. If Murphy’s law were to take effect at this time, the application can simply be closed and reopened to reinitialize the toolbar code.

Another issue is that the code that copies the image from the ImageList control to the clipboard assumes that the clipboard does not contain meaningful data and simply overwrites it. In a corporate environment, this is most likely not the case. End users typically copy and paste items while working on applications. The copy/paste combination implicitly uses the system clipboard. After our application runs, the end user would have lost the data. Or worse, the end user may find that a paste operation results in an MSN icon being inserted into a document. This is not necessarily disastrous, but it certainly is in poor taste.

One good approach is to clear the contents of the clipboard after the image has been copied. This will remedy the latter issue. The former issue is not so easy to fix. The code can either prompt the user to proceed with the operation, indicating that the clipboard data may be lost, or the code can internally save the contents of the clipboard to a local variable and restore it once the operation is complete. Finally, the developer must be aware that system clipboard manipulation requires that the appropriate permissions levels be set on the executing assembly. By default, a fully trusted assembly is able to manipulate the system clipboard. Assemblies with less than full trust may need to have the security policy edited in order to gain access to the system clipboard. While this is not a problem for Windows forms (Office assemblies must have full trust to be loaded), it may pose a problem for some server applications or

thin clients based on VSTO.

There must be an easier way to do this. The better approach is to use the picture property of the command bar object to insert an icon into the toolbar. With this approach, we sidestep all of the above issues. Listing 4-14 shows the code.

Visual Basic

Imports WordTools = Microsoft.Office.Tools.Word

Public Class ThisDocument

Public Class ConvertImage

Inherits System.Windows.Forms.AxHost

Public Sub New()

MyBase.New(“59EE46BA-677D-4d20-BF10-8D8067CB8B32”)

End Sub

Public Shared Function Convert(ByVal Image _

As System.Drawing.Image) As stdole.IPictureDisp

Convert = GetIPictureFromPicture(Image)

End Function

End Class

Dim NewCommandBar As Office.CommandBar

Dim NewCBarButton As Office.CommandBarButton

Private Sub ThisDocument_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup

NewCommandBar = Application.CommandBars.Add(“myButton”, Office.MsoBarPosition.msoBarTop)

NewCBarButton = DirectCast(NewCommandBar.Controls.Add(Office.MsoControlType.msoControlButton, Temporary:=False), Office.CommandBarButton)

128

Word Automation

NewCBarButton.Caption = “my new button”

NewCBarButton.Picture = GetImageResource()

NewCommandBar.Visible = True

End Sub

Private Function GetImageResource() As stdole.IPictureDisp

Dim tempImage As stdole.IPictureDisp = Nothing

Dim newIcon As Icon = New Icon(“C:\msn.ico”) Dim newImageList As New ImageList newImageList.Images.Add(newIcon)

return ConvertImage.Convert(newImageList.Images(0)) End Function

Private Sub ThisDocument_Shutdown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shutdown

End Sub

End Class

C#

using System; using System.Data;

using System.Drawing; using System.Windows.Forms;

using Microsoft.VisualStudio.Tools.Applications.Runtime; using Word = Microsoft.Office.Interop.Word;

using Office = Microsoft.Office.Core;

using WordTools = Microsoft.Office.Tools.Word;

namespace WordDocument1

{

public partial class ThisDocument

{

sealed public class ConvertImage : System.Windows.Forms.AxHost

{

private ConvertImage() : base(null)

{

}

public static stdole.IPictureDisp Convert (System.Drawing.Image image)

{

return (stdole.IPictureDisp)System. Windows.Forms.AxHost

.GetIPictureDispFromPicture(image);

}

}

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

{

object val = false;

129

Chapter 4

Office.CommandBar NewCommandBar = Application.CommandBars.Add(“myButton”, Office.MsoBarPosition.msoBarTop, false, false);

Office.CommandBarButton NewCBarButton = (Office.CommandBarButton)NewCommandBar.Controls.Add(Office.MsoControlType.msoContro lButton, missing, missing, missing, false);

NewCBarButton.Caption = “my new button”; NewCBarButton.Picture = GetImageResource(); NewCommandBar.Visible = true;

}

private stdole.IPictureDisp GetImageResource()

{

System.Drawing.Icon newIcon = new Icon(@”C:\msn.ico”); ImageList newImageList = new ImageList(); newImageList.Images.Add(newIcon);

return ConvertImage.Convert(newImageList.Images[0]);

}

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

{

}

}

}

Listing 4-14: Toolbar customization

The code in Listing 4-14 shouldn’t disturb you. We have already explained most of it in Listing 4-13. That portion of the code in the startup event handler simply creates a CommandBar button and places it on the toolbar. However, the last line of code is where the magic happens.

The call to GetImageResource returns an image that is used on the button’s surface. The image is returned by accessing a method on a special class convert. The reason for creating the class is that the call to GetIPictureDispFromPicture is protected. In order to call this method, we must derive a class based on the parent System.Windows.Forms.AxHost. The constructor of the class is necessary plumbing to cause the object to be created correctly.

Inside the body of GetImageResource, an icon object is constructed from a path that points to a .ico file. In my case, the path points to the msn icon file. The icon is then loaded into an ImageList control. The contents of the image list control are passed to the axhost library so that it can be converted into a picture. When the code is executed, the MSN image should appear as an icon on the toolbar along with an appropriate caption. See Figure 4-8.

Consider a better approach that sidesteps a lot of the thorny issues that plagued the code presented in Listing 4-9.

130

Word Automation

Figure 4-8

Menu Customization

If you’ve played with VSTO for a bit, you may have observed that the Word default menus are merged in with Visual Studio .NET menus. This is part of the grand design to ensure that both of these environments are seamlessly integrated.

The grand design also ensures that menus are easy to build into your application. In fact, the menu control applies to the VSTO suite as a whole and not just to one particular object in the tools suite. For instance, the code presented in Chapter 2 can be ported without reservation to wire up events to Word menus.

Let’s consider an example that builds a menu into the Office bar. Our menu is conceptually simple. We use a dialog box to display the information about the active document. In fact, the code recreates the built-in document statistics functionality that appears in Microsoft Office. The functionality is available on the Spelling and Grammar tab of the standard toolbar control in Office.

Listing 4-15 contains the code.

131

Chapter 4

Visual Basic

Dim menubar as Office.CommandBar

Dim cmdBarControl as Office.CommandBarPopup

Dim menuCommand as Office.CommandBarButton

‘ Add the menu.

menubar = DirectCast (Application.CommandBars.ActiveMenuBar, Office.CommandBar) Office.CommandBarPopup cmdBarControl = DirectCast( menubar.Controls.Add( Office.MsoControlType.msoControlPopup, Before:= menubar.Controls.Count, True), Office.CommandBarPopup)

If cmdBarControl IsNot Nothing Then

cmdBarControl.Caption = “&Refresh” ‘ Add the menu command.

menuCommand = DirectCast( cmdBarControl.Controls.Add( Office.MsoControlType.msoControlButton,Temporary:= True),

Office.CommandBarButton) menuCommand.Caption = “&Calculate” menuCommand.Tag = DateTime.Now.ToString() menuCommand.FaceId = 265

AddHandler menuCommand.Click, AddressOf menuCommand_Click End If

C#

Office.CommandBar menubar; Office.CommandBarPopup cmdBarControl; Office.CommandBarButton menuCommand;

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

{

// Add the menu.

menubar = (Office.CommandBar)Application.CommandBars.ActiveMenuBar; cmdBarControl = (Office.CommandBarPopup)menubar.Controls.Add( Office.MsoControlType.msoControlPopup, missing, missing, menubar.Controls.Count, true);

if (cmdBarControl != null)

{

cmdBarControl.Caption = “&Tools”; // Add the menu command.

menuCommand = (Office.CommandBarButton)cmdBarControl.Controls.Add(

Office.MsoControlType.msoControlButton, missing, missing, missing, true); menuCommand.Caption = “&Word Count...”;

menuCommand.Tag = DateTime.Now.ToString(); menuCommand.Click += new

Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler(menuCommand_Click);

}

}

void menuCommand_Click(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault)

{

Word.Dialog dlg = Application.Dialogs[Word.WdWordDialog.wdDialogDocumentStatistics]; object timeOut = 0;

dlg.Show(ref timeOut);

}

Listing 4-15: Menu customization

132

Word Automation

The code is straightforward and the general approach has been explained in Chapter 2. However, we’ll examine the important parts briefly. First, a reference is obtained for the ActiveMenuBar object. Next, a menu item is added using the reference obtained earlier. Once the menu item has been added successfully, the code subscribes to the click event so that menu click notifications invoke the registered handler, menuCommand_Click.

Notice too that the Caption and FaceId are purely cosmetic. However, the Tag property is not. As explained in Chapter 2, the Tag property needs to be unique in order for the event hook up to fire successfully. The assignment to the current time satisfies the unique condition.

The point of the exercise is to show that Microsoft Office contains a slew of built-in components that provide much needed document functionality that would be expensive and tedious to create otherwise. More so, because of the flexibility in the VSTO architecture, these components can be used outside of an Office document application.

Consider a custom application developed with Windows forms that needs to provide some sort of document statistics functionality to the end user. For that type of application, it is easier to hook into the VSTO Word document statistics dialog and let that component provide the raw statistics as opposed to writing custom code to provide that type of functionality. To harness the functionality, the application can simply maintain a hidden document and import the custom data into the hidden document. Then, the code can create the appropriate Word dialog to generate the necessary document statistics.

But there are some drawbacks that you should be aware of. Word dialogs are not intrinsically attached to the document. They become aware of the document only at runtime when the dialog is created. At this time, and only at this time, the dialog box discovers the applicable properties of the document.

As an example, consider the code that needs to set the PageWidth of a document. You cannot simply create a dialog box object and set its PageWidth property in C#. You will need to use reflection to obtain the available properties and methods in C#. For Visual Basic, you will need to turn off Option strict or use VB’s CallByName function. Listing 4-16 shows some sample code to demonstrate the process.

Visual Basic

Dim dlgType as Word.Dialog =

Application.Dialogs(Word.WdWordDialog.wdDialogFilePageSetup)

‘this will not compile dlg.PageWidth = 10

‘this will work at run-time CallByName(dlgType,’PageWidth’,CallType.Set,10) ‘ Display the dialog box.

Dlg.Execute()

C#

Word.Dialog dlgType = Application.Dialogs[Word.WdWordDialog.wdDialogFilePageSetup]; //this will not compile

dlgType.PageWidth = 10; //this will work at run-time

dlgType.GetType().InvokeMember(“PageWidth”, System.Reflection.BindingFlags.SetProperty |

System.Reflection.BindingFlags.Public |

133

Chapter 4

System.Reflection.BindingFlags.Instance, null, dlgType, new object[] {10},

System.Globalization.CultureInfo.InvariantCulture); // Display the dialog box.

dlgType .Execute();

Listing 4-16: Sample late binding code

Hopefully, the ugly C# code should scare you into using Visual Basic. If it doesn’t, consider wrapping the eyesore in a method that takes the appropriate parameters and calls the InvokeMember.

Working with Other Office Applications

Under the crust, all Microsoft Office Applications are COM servers. Any application that requires interoperation with these servers must be able to interoperate with COM. VSTO is no exception. Fortunately, the interaction between managed code and a COM server occurs seamlessly, so there is no need to explicitly write ugly Interop code. Since every Office API exposes a well-defined interface for automation purposes, let’s consider an automation example.

A PowerPoint Automation Example

Listing 4-17 shows an example of automation with PowerPoint.

Visual Basic

Imports System

Imports System.Data

Imports System.Drawing

Imports System.Windows.Forms

Imports Microsoft.VisualStudio.Tools.Applications.Runtime

Imports Word = Microsoft.Office.Interop.Word

Imports Office = Microsoft.Office.Core

Imports PowerPoint = Microsoft.Office.Interop.PowerPoint

Imports Graph = Microsoft.Office.Interop.Graph

Imports System.Runtime.InteropServices

Namespace PowerPoint

Public partial Class ThisDocument

Private Sub SlideShow()

Dim background As String = “C:\Program Files\Microsoft Office\Templates\Presentation Designs\Blends.pot”

Dim stringStyle As String = “C:\Windows\Blue Lace 16.bmp”

Dim objApp As PowerPoInteger.Application = New PowerPoInteger.Application() objApp.Visible = Microsoft.Office.Core.MsoTriState.msoTrue

Dim presentContext As PowerPoInteger.Presentations = objApp.Presentations Dim presenter As PowerPoint._Presentation =

presentContext.Open(background,Microsoft.Office.Core.MsoTriState.msoFalse,Microsoft

.Office.Core.MsoTriState.msoTrue,Microsoft.Office.Core.MsoTriState.msoTrue) Dim objSlides As PowerPoInteger.Slides = presenter.Slides

‘Build slide #1:

Dim name As PowerPoInteger.Slide = objSlides.Add(1,PowerPoInteger.PpSlideLay.ppLayoutTitleOnly)

134

Word Automation

Dim textSlide As PowerPoInteger.TextRange = name.Shapes(1).TextFrame.TextRange textSlide.Text = “Programming VSTO 2005”

textSlide.Font.Size = 20

name.Shapes.AddPicture(stringStyle, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue, 150, 150, 500, 350)

name = objSlides.Add(2, PowerPoInteger.PpSlideLay.ppLayoutBlank) name.FollowMasterBackground = Microsoft.Office.Core.MsoTriState.msoFalse Dim objShapes As PowerPoInteger.Shapes = name.Shapes

Dim objShape as PowerPoint.Shape = objShapes.AddTextEffect(Microsoft.Office.Core.MsoPresetTextEffect.msoTextEffect27, “Buy Now!!!”, “Arial”, 90, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoFalse, 200, 200)

Dim counter() As Integer =

New Integer(2) {}

Dim i As Integer

 

For i = 0 To 2- 1 Step

1

counter(i) = i + 1

 

Next

 

Dim objSldRng As PowerPoInteger.SlideRange = objSlides.Range(counter)

Dim objSST As PowerPoInteger.SlideShowTransition = objSldRng.SlideShowTransition

objSST.AdvanceOnTime = Microsoft.Office.Core.MsoTriState.msoTrue objSST.AdvanceTime = 6

objSST.EnTryEffect = PowerPoInteger.PpEnTryEffect.ppEffectBlindsVertical ‘Run the Slide

Dim objSSS As PowerPoInteger.SlideShowSettings = presenter.SlideShowSettings objSSS.StartingSlide = 1

objSSS.EndingSlide = 2 objSSS.Run() presenter.Close() objApp.Quit()

End Sub

 

Private

Sub New_Startup(ByVal sender As Object, ByVal e As System.EventArgs)

SlideShow()

End Sub

 

Private

Sub New_Shutdown(ByVal sender As Object, ByVal e As System.EventArgs)

GC.Collect()

End Sub

End Class

End Namespace

C#

using System; using System.Data;

using System.Drawing; using System.Windows.Forms;

using Microsoft.VisualStudio.Tools.Applications.Runtime; using Word = Microsoft.Office.Interop.Word;

using Office = Microsoft.Office.Core; using PowerPoint;

using Graph = Microsoft.Office.Interop.Graph; using System.Runtime.InteropServices; namespace PowerPoint

{

public partial class ThisDocument

{

private void SlideShow()

135

Chapter 4

{

string background = @”C:\Program Files\Microsoft Office\Templates\Presentation Designs\Blends.pot”;

string stringStyle = @”C:\Windows\Blue Lace 16.bmp”; PowerPoint.Application objApp = new PowerPoint.Application(); objApp.Visible = Microsoft.Office.Core.MsoTriState.msoTrue; PowerPoint.Presentations presentContext = objApp.Presentations; PowerPoint._Presentation presenter = presentContext.Open(background, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue, Microsoft.Office.Core.MsoTriState.msoTrue);

PowerPoint.Slides objSlides = presenter.Slides; //Build slide #1:

PowerPoint.Slide name = objSlides.Add(1, PowerPoint.PpSlideLayout.ppLayoutTitleOnly);

PowerPoint.TextRange textSlide = name.Shapes[1].TextFrame.TextRange; textSlide.Text = “Programming VSTO 2005”;

textSlide.Font.Size = 20;

name.Shapes.AddPicture(stringStyle, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue, 150, 150, 500, 350);

name = objSlides.Add(2, PowerPoint.PpSlideLayout.ppLayoutBlank); name.FollowMasterBackground = Microsoft.Office.Core.MsoTriState.msoFalse; PowerPoint.Shapes objShapes = name.Shapes;

PowerPoint.Shape objShape = objShapes.AddTextEffect(Microsoft.Office.Core.MsoPresetTextEffect.msoTextEffect27, “Buy Now!!!”, “Arial”, 90, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoFalse, 200, 200);

int[] counter = new int[2]; for (int i = 0; i < 2; i++) counter[i] = i + 1;

PowerPoint.SlideRange objSldRng = objSlides.Range(counter); PowerPoint.SlideShowTransition objSST = objSldRng.SlideShowTransition; objSST.AdvanceOnTime = Microsoft.Office.Core.MsoTriState.msoTrue; objSST.AdvanceTime = 6;

objSST.EntryEffect = PowerPoint.PpEntryEffect.ppEffectBlindsVertical; //Run the Slide

PowerPoint.SlideShowSettings objSSS = presenter.SlideShowSettings; objSSS.StartingSlide = 1;

objSSS.EndingSlide = 2; objSSS.Run(); presenter.Close(); objApp.Quit();

}

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

{

SlideShow();

}

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

{

GC.Collect();

}

}

}

Listing 4-17: PowerPoint automation

136

Word Automation

PowerPoint slides are made up of individual slides that are displayed on screen in sequential fashion till the end. With that in mind, the most challenging task is to create the individual slides. In Listing 4-17, the code sets up the background and picture styles for each slide. By default, Microsoft Office installs some default backgrounds that are convenient for demonstration purposes.

Next, the required objects are created. Automating other Microsoft Office applications has some key ingredients, namely creating an instance of the automation server. In Listing 4-17, an instance of the PowerPoint object is created and some basic object plumbing is set up.

objApp.Visible = Microsoft.Office.Core.MsoTriState.msoTrue objPresSet = objApp.Presentations

objPres = objPresSet.Open(strTemplate, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue, Microsoft.Office.Core.MsoTriState.msoTrue) objSlides = objPres.Slides

Then, the PowerPoint harness is initialized using the Open method call. Each slide that forms part of the PowerPoint presentation is then created using the following code.

‘Build Slide #1:

‘Add text to the slide, change the font and insert/position a ‘picture on the first slide.

objSlide = objSlides.Add(1, PowerPoInteger.PpSlideLay.ppLayoutTitleOnly) objTextRng = objSlide.Shapes(1).TextFrame.TextRange

objTextRng.Text = “My Sample Presentation” objTextRng.Font.Name = “Comic Sans MS” objTextRng.Font.Size = 48

objSlide.Shapes.AddPicture(strPic, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue,

150, 150, 500, 350)

Finally, the code displays the slides that form part of the PowerPoint application. Other Office applications may be automated using this same approach. First, import the required namespace and then create an instance of the application. Then, make the appropriate method calls to automate the object. The default location for most of the type libraries is C:\Program Files\Microsoft Office\Office[version]. The garbage collector call is provided as a safe guard to ensure that a collection is performed deterministically.

An Office Automation Executable Example

The material presented so far in this chapter focused on automating Office examples from inside VSTO. The example in Listing 4-18 demonstrates how to automate Office applications from a console application.

Visual Basic

Imports System

Imports System.Runtime.InteropServices

Imports Word = Microsoft.Office.Interop.Word

Imports Office = Microsoft.Office.Core

namespaceConsoleAutomation

137