Office 2003

Host an Interactive Visio Drawing Surface in .NET Custom Clients

Mai-lan Tomsen Bukovec and Blair Shaw

Code download available at:Visio2003.exe(325 KB)

This article assumes you're familiar with C#

Level of Difficulty123

SUMMARY

Microsoft Office Visio 2003 introduces a new drawing component that allows you to embed an interactive drawing surface into your application's user interface. You can drive the Visio drawing component from events in your host application or with data from a Web Service and an ADO.NET data adapter. The Visio drawing component supports the rich Visio application programming model, giving you control over how graphics are used and displayed on the drawing surface. This article explains how to embed the Visio drawing component into a C#-based Windows Forms client app that retrieves data from the Fabrikam 2.0 Web Service.

Contents

Drawing Component Fundamentals
The Visio Component API
The Visio Component at Work
Building the Application
Adding Shapes to the Visio Surface Using Events
Using Visio Events with the Host Application
Conclusion

Integrating programmable graphics into line-of-business applications makes them more user-friendly but requires considerable development time and graphics expertise. The Visio® drawing component, available in Microsoft® Office Visio 2003, lets you build upon the functionality of rich diagramming capabilities through Visio automation. The Visio component can be embedded in a number of containers, ranging from Office documents to custom applications built in Visual Studio® .NET, so it's quite versatile as well. In this article, we'll show you how to drive the Visio drawing component from a Microsoft .NET-based client application to give your users the ability to manipulate graphics.

Drawing Component Fundamentals

The Visio drawing component is essentially an ActiveX® wrapper for the main Visio library. Like any ActiveX component, it can be embedded in custom host applications developed using Visual Studio .NET 2003, Office 2003 containers (such as Microsoft Word), Microsoft Internet Explorer 5.5 and greater, and other ActiveX component containers. Although the component uses the ActiveX architecture, it is more of an embeddable application component than the typical ActiveX control found on a Web site. The Visio component provides the full functionality of the Visio application rather than a small subset of features. The Visio control differs from the Visio client application in two ways. First, the drawing control only supports single document interface (SDI) windows. The control architecture supports a single drawing in a single window. That means that all multiple document interface (MDI) windows in Visio are disabled in the control context. The Visio component allows access to neither the ShapeSheet window (although you can program against the ShapeSheet), nor the icon editor window. Second, the drawing control does not load Visual Basic® for Applications (VBA) or run VBA code. That means that any existing VBA code must be ported to host application code. It also means that users never see the Visio macro warning dialog when opening a Visio document in the control.

The Visio component in the Microsoft Office Visio 2003 release opens up a new variety of options for integrating programmable graphics in your application, such as:

  • "Live" Visio organization charts displayed in the Visio component embedded in a Word document, automatically updated from events in the Word 2002 or Word 2003 document itself.
  • The Visio component wrapped in a SharePoint™ Web Part, displaying timeline drawings driven from the Microsoft Project Server Web Service or another SharePoint Web Part.
  • Visio business process diagrams displayed in the Visio component embedded in a custom Visual Studio .NET container, or a Word Smart Document, updated with status changes in the workflow.

Because it essentially repackages the Visio application engine in a component, the Visio control is installed with the Visio application setup program and requires the Visio client application to initialize. If the user does not have the Visio client application installed on the machine, the control will fail to initialize in the host container. You can handle this event gracefully in your host application at initialization time by checking the registry to see if Microsoft Office Visio 2003 is installed. If the right version of Visio is present, you can provide the user with the full rich graphical interface. If not, you can downgrade to a less graphically rich user experience.

You have two options in deploying the component. You can either require Visio 2003 on the client machine (it can coexist with Visio 2002), or the user can install the Visio component through the minimal install option of the Visio setup program. You can chain the Visio installation before your app installation and set the INSTALLLEVEL to 40, which tells the Windows® Installer application in Visio to install Visio silently. That way, your users will not see the Visio setup UI while installing your application.

The Visio Component API

The drawing component has a thin API that contains a handful of properties, two of which, Document and Window, provide access to the full Visio programming model. See Figure 1 for a breakdown of the Visio component properties and their usage.

Figure 1 Visio Drawing Component Properties

Property Action Description
Document Returns the underlying Visio application Document object. Gets access to the Visio application object model.
Window Returns the underlying Visio application Window object. Gets access to the Visio application object model.
HostID Returns or sets the string that defines where to get registry settings. Defaults to an empty string (uses the Visio setting). Char limit of 128. Set up a separate registry hive for the Visio component so that settings don't conflict with the Visio application.
SRC Returns or sets the string for the file to load. Loads an existing document, stencil or template, or a blank drawing.
PageSizingBehavior Indicates if the control should resize the page when the drawing is loaded into the control. Automatically resizes the drawing loaded in the control to fit the control's dimensions.
NegotiateMenus Indicates if the control does menu merging with the host container. Allows manipulation of the Visio menus through the Visio object model.
NegotiateToolbars Indicates if the control does toolbar merging with the host container. Allows manipulation of the Visio toolbars through the Visio object model.

If you embed the Visio component in your host container without any extra programming, you offer the Visio drawing surface to your users within the context of your application's user interface. Figure 2 shows what the control looks like embedded in a Windows Form with no code behind it.

Figure 2 Visio Control

Figure 2** Visio Control **

With no additional programming, the Visio component gives you a wide range of features. There are visual effects such as page tabs, scrollbars, rulers, and background grid on the Visio page. You can also turn these off (using Visio properties like Window.ShowRuler and Window.ShowTab) to generate a simple white background if you prefer.

There are right-click menus for shapes and pages, but no other Visio application toolbars or menus. You can turn on the Visio user interface toolbars programmatically, but it's better to listen for events on the drawing component and present your own custom application user interface instead.

Additionally, you have access to the current Visio application settings. You'll see that the background behind the Visio page is green rather than the default blue. That's because we have set the background color of our application to green. If we change the drawing component's HostID property (see Figure 1 for a description of HostID), the control will create a separate registry entry for Visio running as a component. As a result, changing the HostID property to the value of "MyNamespace.SampleApp" in design-time mode switches the background color for the control back to the default color of blue. Thereafter, when we run the Visio-based application, our page background will be green. When we run our form with the Visio component, the page background will remain the default blue (unless, for some reason, we decide to change it programmatically).

Finally, there is no stencil pane. You can use Visio automation to make the green stencil pane visible in the control so that users can drag and drop shapes onto the drawing page. In the control context, it's more likely that the shapes are displayed in a toolbar on the Windows Form or dynamically dropped onto the Visio drawing surface from data or events in the Space Plan application.

The Visio component gets a lot more interesting when you start to drive it using the Visio object model and events in your host application or data from a Web Service or database. The Visio object model is both robust and extensive. In fact, all Visio solutions that are shipped in the box (such as Organization Chart and Database Wizard) use the Visio automation layer. The vast majority of Visio component programming automation occurs using the Visio object model, which is well documented on MSDN® and also ships with the product.

Since the Visio component exposes the entire Visio object model, component programmers can use existing developer material as reference, including the Visio Web site on MSDN, which provides sample code, tutorials, articles, a Visio Software Development Kit, and the Visio automation reference.

The Visio Component at Work

To illustrate some of the key elements of Visio component programming, we'll walk through some of the code in our Space Plan sample application. The full application can be downloaded from the link at the top of this article and requires the .NET Framework version 1.1 and Visio 2003 to run. The goal of the sample application is to demonstrate the use of the Visio component API to access the Visio object model, the firing of events in the Space Plan sample application to trigger drawing creation in the component, and the trapping of events on the drawing component to drive the Space Plan sample application. We'll also show how to save Visio documents in the control context. In addition, we'll discuss some other aspects of the control that aren't explicitly demonstrated in the sample application code.

Figure 3 Space Plan

Figure 3** Space Plan **

As you can see in Figure 3, the Space Plan sample application architecture consists of a furniture costing sample form, product details form, and the Fabrikam Web Service 2.0 (a locally installed Web Service downloadable from MSDN) which is used to provide the furniture catalog for the sample application.

Let's spend a moment on the customer scenario for Space Plan. Administration staff typically handles the procurement of furniture or other building supplies. To complete the task of ordering furniture for a conference room, the administrative staff may switch contexts from print or online catalogs to Microsoft Excel for budget management, and then to Word or Excel to generate purchase orders. A tape measure would be very handy to measure the room and the furniture.

Figure 4 Space Plan Sample App

Figure 4** Space Plan Sample App **

The Space Plan sample application that we built simplifies this process by consolidating all of these actions in a single C#-based Windows Forms application (Figure 4). It enables you to review furniture options, select and the arrange furniture, and itemize when creating the purchase order.

Building the Application

When the sample application runs, the first thing we do is make an asynchronous call to the Fabrikam Web Service. We can also work with a locally cached data set if the user is not able to connect to the Web Service. The DataGrid displays details such as the retail and wholesale price of furniture available in the furniture catalog.

For simplicity, we've only implemented three furniture buttons, each representing a different product in the DataGrid. The button faces are bitmaps of the shapes in Visio that represent each piece of furniture. When the user clicks the furniture buttons, several things happen. As you can see in Figure 5, the corresponding furniture shapes drop onto the blank Visio drawing surface, where the user can rearrange them as desired. Since the furniture is scaled to the room (as represented by the component's drawing), the user can accurately select the proper quantity of furniture for the space.

Figure 5 Furniture Costing Form

Figure 5** Furniture Costing Form **

On the shape add and shape delete events, the running totals for retail and wholesale price increment accordingly. Also, the quantity column in the DataGrid updates to reflect the shapes that are in the Visio component.

To get started with programming the Visio component, add the Visio drawing control to the Visual Studio .NET Toolbox, which adds a reference to the Visio 11.0 Type Library and Visio 11.0 Drawing component Type Library. If you're developing a managed code application like our C# sample, you'll also need to declare the Visio Primary Interop Assemblies (PIA) namespaces. These PIAs allow managed code applications to access the Visio unmanaged type libraries:

using Visio = Microsoft.Office.Interop.Visio; using VisOcx = Microsoft.Office.Interop.VisOcx;

To access Visio objects from our C# application, we use the Visio component Window and Document properties cast to Visio application objects since C# is strongly typed:

VisWindow = (Visio.Window) axDrawingControl1.Window;

Using the Visio Window object, we can set the ShowRulers, ShowPageTabs, ShowScrollBars, and ShowGrid properties to false, which presents the Visio component as a white rectangle. We also set the window's Zoom property value to 1.00 (which maps to 100 percent) so that the blank white page fills the window. The Visio component itself has one zooming property which, when set, allows the control to resize the drawing to fit the control's window. The Visio-based application, however, has finer-grained drawing zoom that gives you better control. It includes the ability to lock the zooming of the window against any user modification.

Adding Shapes to the Visio Surface Using Events

The Visio component gives you the ability to integrate the drawing surface with the host application using events. Let's take a close look at how the button click event in the Windows Form adds (or drops) shapes in the Visio component.

The first thing we do in our sample application is define where the shapes should appear on the Visio drawing surface. We use an array of structures called ShapeLocation to store the PinX and PinY values for each of the three shapes. The ShapeLocation structure is simple, containing just two doubles (X and Y). Visio, like many drawing applications, is based on two-dimensional geometry. A shape's geometry is essentially a collection of horizontal and vertical locations (vertices). A shape's pin, or center of rotation, determines its location. The pin consists of X and Y coordinates relative to the shape's parent, which can be a page or a group. These coordinates dictate exactly where the shape is positioned on the Visio page. Each ShapeLocation structure defines X and Y coordinates for the location of the shape. We'll need these structures to place the shapes appropriately on the window when the user clicks the shape button on the form.

Figure 6 Chair Coordinates on a Page in Visio

Figure 6** Chair Coordinates on a Page in Visio **

When populating the values in the ShapeLocation structs in the array, we use inches, which is the default unit of measure for Visio. The point of origin (0, 0) lies in the bottom-left corner of the Visio page. Figure 6 populates the struct for the OfficeChair shape type with the X and Y coordinate values:

ShapeLocations[(int)ShapeType.OfficeChair].X = .6495; ShapeLocations[(int)ShapeType.OfficeChair].Y = 2.4271;

These coordinates map to a location close to the point of origin (0,0) in the lower-left corner of the page. In Figure 6, I've added X and Y axes to illustrate how Visio uses the coordinates.

With the shape locations defined, we can obtain a reference to the master shape in Visio we want to drop as part of the button click event handler on the form. A master shape can be a single shape, multiple shapes, a group of shapes, or an object from another application (like a bitmap) that is stored on a stencil. When you drop a master shape onto a page, you create an instance of that shape, which inherits all of the properties and behaviors of the master shape.

For our sample application, we have a Visio stencil called Space Plan.vss that contains the three master shapes of furniture. We'll have to open the stencil first using the Visio OpenEx method to access the masters. Figure 5 illustrates how to drop a shape onto the Visio drawing surface using the Drop method on the Page object. The arguments of the following Drop method specify the master (visMaster) and the X and Y coordinates that we defined in the ShapeLocation structure.

visShape = VisPage.Drop( visMaster, ShapeLocations[shapetype].X, ShapeLocations[shapetype].Y);

If the user clicks one of the buttons more than once, we want to make sure that shapes do not overlap one another. In order to avoid this overlapping, we add subsequent shapes at a .1250-inch offset of the PinX and PinY values in the ShapeLocation struct.

Using Visio Events with the Host Application

Now that we've shown how a form event adds shapes to the Visio component, let's look at how to use Visio events with the host application. In our sample, Visio events trigger two actions in the host app: they update the Retail and Wholesale price counters when a shape is added to the Visio drawing surface, and they display a custom Windows Form on a shape's click event.

In our sample, we implement IVisEventProc, a Visio event procedure interface, in our event sink to handle notifications of event objects that we create using the Visio AddAdvise method. This method has better performance than IDispatch when handling Visio events. We'll create a new event list first and call the Visio AddAdvise method to add the ShapeAdded event, as shown in the code snippet in Figure 7.

Figure 7 eventList

Visio.EventList eventList = VisApp.EventList; Visio.EventList eventListDoc = VisioDoc.EventList; // for each event you want to receive a notification add an event handler // here eventSinkHandler = new EventSinkHandler( frm); // ShapeAdded visEvtAdd+visEvtShape &H8040. Note the use of the unchecked // statement to handle numeric overflow with visEvtAdd and visEvtShape. resultEvent = eventListDoc.AddAdvise((unchecked( (short) _ Visio.VisEventCodes.visEvtAdd ) + (short)_ Visio.VisEventCodes.visEvtShape), (Visio.IVisEventProc)_ eventSinkHandler, "", "" );

Our application uses the Visio ShapeAdded and ShapeDeleted events to increment and decrement the Retail and Wholesale counters. The implementation uses each of these event codes (documented in the Visio automation reference) to determine how to handle the event in our IVisEventProc implementation. Figure 8 shows how we've implemented the IVisEventProc interface to handle the ShapeAdded event, the NoEventsPending event, and the ShapeDeleted event. Figure 9 describes how we are handling each one of these events upon receiving notification through the IVisEventProc interface.

Figure 9 Sample Events Using IVisEventProc

Event Handler Action
ShapeAdded Adds the new Action shape to a queue, which we're using to store a first-in, first-out collection of shape objects. The x method takes the shapes from the queue and looks up the cost of that piece of furniture from the DataGrid to increment the Retail and Wholesale price counters.
ShapeDeleted Removes the shape from the queue. The x method takes the shapes from the queue and looks up the cost of that piece of furniture from the DataGrid to decrement the Retail and Wholesale price counters.
NoEventsPending Visio fires this event after the application is done processing events in the Visio queue. Once the sample application hears the NoEventsPending event, it processes the items from its own queue.

Figure 8 Implementing IVisEventProc

object Visio.IVisEventProc.VisEventProc( short eventCode, object source, int eventID, int eventSeqNum, object subject, object moreInfo) { Visio.IVApplication app = null; Visio.IVDocument doc = null; Visio.IVShape shape; try { if ( source is Visio.IVApplication) { app = (Visio.Application) source; } else if (source is Visio.IVDocument ) { doc = (Visio.Document) source; } switch (eventCode) { case unchecked( (short) Visio.VisEventCodes.visEvtAdd) + (short) Visio.VisEventCodes.visEvtShape: shape = (Visio.Shape) subject; HandleShapeAdd( doc, shape ); break; case (short) Visio.VisEventCodes.visEvtApp + (short)Visio.VisEventCodes.visEvtNonePending: HandleNonePending( app); break; case (short) Visio.VisEventCodes.visEvtDel + (short) Visio.VisEventCodes.visEvtShape: shape = (Visio.Shape) subject; HandleShapeDelete( doc, shape ); break; default: break; } } catch (Exception err) { MessageBox.Show("Exception in IVisEventProc.VisEventProc: " + err.Message); }

Once the furniture shapes are added to the Visio drawing surface, the user can move the furniture around using the mouse or keyboard for any preferred arrangement. To lock down the Visio drawing surface to create more of a runtime mode, simply intercept all Visio mouse and keystroke events and throw them away. If the Visio component doesn't hear any events from the user interface, the net effect to the user will be a read-only drawing that can only be driven through the host application user interface.

In our sample application, if the user right-clicks a shape on the drawing surface, we present a custom form (see Figure 10).

Figure 10 Custom Form

Figure 10** Custom Form **

The mouse and keyboard events take place on the Visio Document or Window object, not on the shape itself. To find out the actual shape that the Click event occurred on, you have to use the Visio SpatialSearch property. As demonstrated in Figure 6, SpatialSearch takes the X and Y coordinates for the Click event on the document or page, a constant that indicates that a shape can be contained within another shape, tolerance (typically a very small number since it's the area that you want to search around the Click event), and a flag that indicates that the shapes should be stored front-to-back. The property returns a Selection object that contains the shape that corresponds to the Click event:

Selection visSelection = visioPage.get_SpatialSearch(x, y, (short) VisSpatialRelationCodes.visSpatialContainedIn, tolerance,(short) VisSpatialRelationFlags.visSpatialFrontToBack);

In order to get the custom Windows Form to display properly, we'll also have to call a simple helper routine that translates the Visio coordinates (lower-left corner point of origin) to the Windows coordinate system (upper-right corner point of origin). This lets us position the form to the right of the shape being clicked using the correct scale.

In our app, the custom properties shown on the Product Detail form are stored in the Fabrikam dataset in the host application. The shape itself only stores one custom property: FabrikamProductID. On the mouse click event, the Visio component passes the cached dataset, FabrikamProductID, and metafile image of the product to a custom constructor for the Product Detail form. The Product Detail form is then populated by the data from the Fabrikam records. (The form also displays a metafile of the Visio shape, which is generated using Visio automation.) You can store data with Visio shapes using either shape custom properties or SolutionXML, which creates a user-defined XML data island on a shape or document. However, when using the Visio component, it's much more elegant to consolidate the data manipulation in the host application and just use the drawing surface for reacting to and triggering events.

The end result is a custom properties form that integrates seamlessly with the host application user interface. We could have used the standard Visio custom properties dialog (accessible through the context menu), but we wouldn't have been able to modify the appearance of the Visio dialog. In our Space Plan application, we don't display any Visio user interface menus or toolbars. We've turned off the default context menu for the Visio shape and replaced it with our custom Product Detail form. (Note that the context menu for the page in Visio is still accessible, in case you want to see what the standard dialog looks like in the context of the sample application.)

The best practice when using the Visio component is to intercept Visio events and display your own custom user interface. This allows you to integrate the Visio component more deeply into your application. We do provide the NegotiateToolbars and NegotiateMenus properties on the drawing component. Set these two properties to True and you can expose the Visio toolbars and menus in the drawing component using the application's CommandBar object or UI object. However, negotiating menus and toolbars can be finicky depending on your host container.

Menu and toolbar merging will only work if the container supports OLE 2.0 menu merging. If your container does not support it (the Visual Studio .NET Windows Form editor is one example), you can use IOleCommandTarget to run Visio commands through the IOleCommandTarget interface. Menu and toolbar merging gets especially tricky when you're using multiple instances of the control in a single application. You can embed the Visio control multiple times in a single application, but the instances share a single underlying Visio application object. As you can imagine, this can result in unexpected behavior if you are trying to do menu merging with multiple active instances of the control.

If you absolutely need to use the existing Visio menus in your UI, check with the Visio Developer Center on MSDN for technical articles for Visio component programmers—you'll be able to get more details on sample code and implementation guidelines. Typically, it's a far better idea to replace the Visio user interface with your own. The simple user interface of our Space Plan app presents a cohesive appearance due to the consistency of user interface elements throughout the application.

Because you have full access to the Visio programming model, you can drive Visio functionality through the menus of your host application. For example, our host application's File menu provides three options: New, Open, and Save. The New item resets the existing drawing in the Visio component by explicitly setting the SRC property to load an existing blank Visio drawing. The Open method allows the user to open an existing Visio drawing in the Visio component. It's important to note that SRC loads a copy of the file, rather than the file itself. As a result, you can't save changes to the physical file unless you use the Visio SaveAs method.

When the control reinitializes, it will reload the file from the SRC property and wipe out any changes made to the drawing previously. If you want to persist the changed document in-stream, reset the SRC property to an empty string after you've initialized the SRC property with an existing file. The next time someone uses the control, they will see the changes in the in-stream version of the document. Note that this in-memory document will not be saved to disk unless you explicitly use the Visio SaveAs method.

In our sample application, we use the SaveAs method for the File | Save menu item and filter the options to save the file as a Visio drawing and template extension (see Figure 11) instead of the full range of options available through the Visio application.

Figure 11 Saving Component Doc to Filtered Extensions

private void MenuFileSave_Click(object sender, System.EventArgs e) { SaveFileDialog sfd = new SaveFileDialog(); sfd.Title = "Save Visio Document (*.vsd; *.vsx)"; sfd.Filter = "Drawing (*.vsd;*.vdx)|*.vsd;*.vdx"; sfd.FilterIndex = 1; sfd.InitialDirectory = Application.StartupPath; if (sfd.ShowDialog() == DialogResult.OK) { try { VisDocument.SaveAs( sfd.FileName ); } catch ( Exception ComExption) { Debug.WriteLine( "Exception Saving Document " + exp.Message); throw exp; } } }

For the New menu item, we've set the SRC property to load a blank drawing as the new drawing. This is the only way to start with a newly initialized Document object. The following code demonstrates how we can select all the existing shapes in the window and delete them:

VisWindow.SelectAll(); VisWindow.Delete(); ZeroQty();

This allows us to preserve any custom styles or formatting for our drawing, but does mean that you continue to use the existing Document object. When the control is first initialized in our application, we set the SRC property to an empty string to load a blank document. Note that resetting the SRC property to an empty string will not wipe the contents of the drawing surface. It simply retains an in-memory version of the copy of the original SRC document, with any changes made in the component. To reset the Visio drawing surface, you can choose between the following options: delete all the shapes in the drawing, dynamically destroy and recreate the component, or load an existing blank drawing.

Conclusion

The Visio component, as demonstrated in our sample application, lets you easily integrate programmable graphics with your user interface. In the Space Plan application, we were able to use the drawing surface to graphically represent the shapes in a furniture purchase order, allow the user to rearrange furniture images on a scaled drawing surface, and view the price and quantity impact of furniture selection in the drawing surface.

Because the Visio component provides access to the full Visio automation model, there is extensive support for event handling from the host application as well as for firing events for the host application. You can drive the Visio drawing component from data as well as from events in custom and application containers. More importantly, you can use the Visio component in-process and in context of your host application's user interface.

The Visio drawing component gives you the ability to build upon Visio automation so you can get the benefits of an intuitive, graphical interface without implementing the complexities of a graphical application.

For related articles see:
Programming with the Microsoft Office Visio 2003 ActiveX Control
Integrating Microsoft Office Visio 2003 with Microsoft SharePoint Products and Technologies

Mai-lan Tomsen Bukovec is a Lead Program Manager for Visio at Microsoft. She has written articles for Microsoft Systems Journal and Microsoft Internet Developer and two books for technical business managers for Addison Wesley Longman and Cambridge University Press.

Blair Shaw is an Application Development Consultant working with Microsoft partners. For the past six years, he has worked closely with ISVs and a variety of Microsoft product groups including Visio. You can reach him at blairsh@microsoft.com.