Overview:
This sample is intended to expose several common tasks in VSTA v 1, including: declaring an object, using and manipulating a form, handling form events and event args, and generic event handling. The generic event handling in this sample is similar to Tom Quinn’s ShapeAppAdvancedCSharpGenerics sample available at http://blogs.msdn.com/tq/archive/2006/12/05/vsta-and-generics.aspx. This sample contains a very simple object, form, application with a basic VSTA integration, and an add-in, the EventHelper class found in the ShapeAppAdvancedCSharp is used for event handling.
To run the sample, extract the files to “C:\VSTA\Summit Software\Samples”. Open and build the EventSample solution, then open a Visual Studio 2005 Command Prompt and run the setup file included (using the command “cscript SetupEventSample.js” from the file’s directory). Next, open and build the add-in solution included in the Samples folder. Run the EventSample application, which should now automatically find and load the sample add-in included.
The EventSample application records button clicks and input from a text box in a list box. The sample add-in responds to mouse clicks on any of the buttons on the form, to adding input through the add button, clearing the display, and selecting the Open button. Event handling for the Save and Close buttons in the add-in is not implemented.
Included in this sample:
|
Item |
Description |
|
Form1 |
Main user form and empty code class. |
|
Program |
Starting point in the host application. |
|
MainApplication |
Main application class. All application specific code is here. |
|
MainObject |
Main object used by the MainApplication class. Consists of a string. |
|
EventHelper |
Class taken from ShapeAppAdvancedCSharp to manage events from the host application and add-ins. |
|
Extension |
Class based on ShapeAppBasicCSharp which uses add-in management API to load add-ins. |
|
HostItemProvider |
Class based on ShapeAppBasicCSharp which allows add-ins to access the host object model. |
|
EventSampleProxy |
Proxy file generated by ProxyGen and updated to allow generic event handling. |
|
EventSampleProject1 |
A sample add-in that interacts with the events of the host application. |
I) Declaring Objects in VSTA
VSTA cannot access constructors from the host application. The constructors included in the proxy file are for internal use only. To create an object in VSTA, a public method must be available in the add-in entry point, in this case the MainApplication class, which constructs the object, then returns it. The code below shows an object class, MainObject, the MainApplication class, and an add-in. In the MainApplication class, the method NewObject is used by the add-in to create a MainObject in the add-in.
//host application
namespace Sample
{
//simple object used by main application
public class MainObject
{
private string message;
public string Message
{
get
{
return message;
}
}
public MainObject(string messageIn)
{
message = messageIn;
}
}
//Main Application class
public partial class MainApplication
{
public MainObject NewObject(String inMessage)
{
MainObject thisObject = new MainObject(inMessage);
return thisObject;
}
}
}
//add-in
namespace SampleProject1
{
public partial class AddIn
{
private void AddIn_Startup(object sender, EventArgs e)
{
//add a message to the display box
EventSample.MainObject thisObject = this.NewObject("Add In- Object");
}
}
}
II) Using Forms in VSTA
Forms and other objects under System.Windows.Forms cannot be passed through ProxyGen because they are not serializable. To include a form, or any System.Windows.Forms object, make it an internal property in the main application. The code below demonstrates how to start a windows form based application. The starting point of the host application, the Program class, contains a MainApplication property. The MainApplication class contains a MyForm property which is run by the Program class.
//host application
namespace Sample
{
class Program
{
//Main Application variable and property
static private MainApplication myApplication;
static public MainApplication MyApplication
{
get
{
return myApplication;
}
}
//starting point of the host application
static void Main(string[] args)
{
//set up to use windows forms
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application. SetCompatibleTextRenderingDefault(false);
//create the application
myApplication = new MainApplication();
//run the applicaiton form
System.Windows.Forms.Application.Run(myApplication.MyForm);
}
}
//Main Application class
public partial class MainApplication
{
//form property
private Form1 myForm;
internal Form1 MyForm
{
get
{
return myForm;
}
}
public MainApplication ()
{
myForm = new Form1();
}
}
//form class
internal partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}
III) Manipulating a form in VSTA
With the form set as an internal property in the MainApplication class, VSTA cannot directly interact with the form or its controls. To interact with these, the AddInEntryPoint (in this case the MainApplication class) must contain public methods which handle these interactions. The code below shows the MainApplication’s addTextToListBox method which takes in a string and adds it to the list box lstDisplay contained on the form. The add-in calls this method and the string passed in from the add-in is displayed in the list box on the form.
//host application
namespace Sample
{
//Main Application class
public partial class MainApplication
{
public void addTextToListBox(string textToAdd)
{
//add the text to the list box
myForm.lstDisplay.Items.Add(textToAdd);
}
}
}
//add-in
namespace SampleProject1
{
public partial class AddIn
{
private void AddIn_Startup(object sender, EventArgs e)
{
//add a message to the listbox on the form
this.addTextToListBox("Text from Add-in");
}
}
}
IV) Form Events in VSTA
In order for an add-in to catch an event on a form, the host application must catch the event and fire a public event that VSTA can see. In the code below, the constructor of the MainApplication calls the method HookUpEvents which associates a method in the host application, DocOpen, with an event generated by the form, a button click. The method in the host application hooked up to this form event, DocOpen, fires the public event, DocOpenedEvent, which VSTA can see. The add-in hooks up a method, AddIn_DocOpen, with the event and displays a line of text on the form.
Note: When firing events accessible from VSTA it is important to use the host application (or object within the host application) as the sender, by specifying “this”. Using the original sender will not work.
//host application
namespace Sample
{
//Main Application class
public partial class MainApplication
{
//event that VSTA can access- fired but not used by host application
public event EventHandler DocOpenedEvent;
public MainApplication ()
{
myForm = new Form1();
//hook up the form events with event handlers
HookUpEvents();
}
private void HookUpEvents()
{
//add event handling for the form- not accessable from VSTA
//accesable events are raised in the methods called
myForm.cmdOpen.Click += new EventHandler(DocOpen);
}
//called when the button cmdOpen is clicked
internal void DocOpen(object sender, EventArgs e)
{
//Host application responds to the cmdOpen click
//fire the DocOpenedEvent event visible in VSTA
EventHelper.Invoke(DocOpenedEvent, this, EventArgs.Empty);
}
}
}
//add-in
namespace SampleProject1
{
public partial class AddIn
{
private void AddIn_Startup(object sender, EventArgs e)
{
//hook up the document open event with an add-in method
this.DocOpenedEvent += new EventHandler(AddIn_DocOpen);
}
void AddIn_DocOpen(object sender, EventArgs e)
{
//add a line to the display to show add-in awareness
this.addTextToListBox("Add-in caught Document Open event");
}
}
}
V) Generic Event Handling in VSTA
Generic event handling is using the type System.EventHandler or System.EventHandler<T> to declare events instead of first declaring a System.Delegate then declaring an event based on that delegate. For System.EventHandler events no additional steps are needed to use the events in VSTA. For System.EventHandler<T> events, which use custom event args, three changes to the proxy file are necessary. The first change in the proxy file is to the event declaration. Here, a syntax error produced by ProxyGen must be corrected and global added to the type. The event should be declared as below.
Auto-generated definition:
public event global::System.EventHandler`1[[Sample.CustomEventArgs, EventSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] CustomEvent
Corrected definition with the type EventHandler<T>:
public event global::System.EventHandler<global::Sample.CustomEventArgs> CustomEvent
The second change to the proxy file is in the declaration of the custom event args. Here the inheritance of System.EventArgs must be added.
Auto-generated definition:
public partial class CustomEventArgs : global::Microsoft.VisualStudio.Tools.Applications.IProxy
Corrected definition with System.EventArgs inherited:
public partial class CustomEventArgs : global::System.EventArgs, global::Microsoft.VisualStudio.Tools.Applications.IProxy
The third change is the addition of the type System.EventHandler<CustomEventArgs> to the type infrastructure manager in both the proxy file and the host application. While other types defined in the type infrastructure manager are mapped to specific instance of the type, this definition is not. The purpose of this definition is to allow the type System.EventHandler<CustomEventArgs> to pass between the host application and the add-in, not to map it to a specific instance of this type. Unlike the other type definition there is no “class name” or “key” to associate with this type; therefore, any unique non-empty string will do. Without this definition, add-ins that hook to events of the type System.EventHandler<CustomEventArgs> will fail.
Possible definition 1:
Proxy Side:
typeInfrastructureManager.CanonicalNameToTypeMap.Add("Sample, Sample.CustomEventHandler", typeof(DelegateProxy <global::System.EventHandler<global::EventSample.CustomEventArgs>>));
typeInfrastructureManager.TypeToCanonicalNameMap.Add(typeof(global::System.EventHandler<global::Sample.CustomEventArgs>), "Sample, EventSample.CustomEventHandler");
Host Side:
hostType = typeof(global::System.EventHandler<global:: Sample.CustomEventArgs>);
proxyType = typeof(DelegateProxy<global::System.EventHandler<global:: Sample.CustomEventArgs>>);
typeInfrastructureManager.CanonicalNameToTypeMap.Add("Sample, Sample.CustomEventHandler", proxyType);
typeInfrastructureManager.TypeToCanonicalNameMap.Add(hostType, "Sample, Sample.CustomEventHandler");
Possible definition 2:
Proxy Side:
typeInfrastructureManager.CanonicalNameToTypeMap.Add("x", typeof(DelegateProxy<global::System.EventHandler<global::Sample.CustomEventArgs>>));
typeInfrastructureManager.TypeToCanonicalNameMap.Add( typeof(global::System.EventHandler<global::Sample.CustomEventArgs>), "x");
Host Side:
hostType = typeof(global::System.EventHandler<global:: Sample.CustomEventArgs>);
proxyType = typeof(DelegateProxy<global::System.EventHandler<global:: Sample.CustomEventArgs>>);
typeInfrastructureManager.CanonicalNameToTypeMap.Add("x", proxyType);
typeInfrastructureManager.TypeToCanonicalNameMap.Add(hostType, "x");
VI) Using EventArgs in VSTA
Like forms, EventArgs based on the System.Windows.Forms class cannot be passed between the host application and add-ins because they are not serializable. One work around for this is to use custom event args. The custom event args should have serializable public properties to hold information from the event args based on System.Windows.Forms. Below is the CustomMouseEventArgs class is used in place of System.Windows.Forms.MouseEventArgs to pass information about button clicks on the form to the add-ins.
//host application
namespace Sample
{
//Custom Event Args to hold info from MouseEventArgs
public class CustomMouseEventArgs : EventArgs
{
//name of the button on the form
private String buttonName;
public String ButtonName
{
get
{
return buttonName;
}
}
//which button on the mouse was used
private String mouseButton;
public String MouseButton
{
get
{
return mouseButton;
}
}
public CustomMouseEventArgs(object sender, System.Windows.Forms.MouseEventArgs e)
{
//in this method the sender is the button
System.Windows.Forms.Button button= (System.Windows.Forms.Button) sender;
buttonName = button.Name;
mouseButton = e.Button.ToString();
}
}
public partial class MainApplication
{
//event that VSTA can access- fired but not used by host application
public event EventHandler<CustomMouseEventArgs> ButtonClickEvent;
public MainApplication ()
{
myForm = new Form1();
//hook up the form events with event handlers
HookUpEvents();
}
private void HookUpEvents()
{
// ButtonClickEvent is visible in VSTA and called in ButtonClick
myForm.cmdOpen.Click += new EventHandler(ButtonClick);
}
internal void ButtonClick(object sender, EventArgs e)
{
//If the event args are from a mouse click
if (e is System.Windows.Forms.MouseEventArgs)
{
//raise the event visible to VSTA
EventHelper.Invoke(ButtonClickEvent, this, new CustomMouseEventArgs(sender, (System.Windows.Forms.MouseEventArgs)e));
}
}
}
}
//add-in
namespace SampleProject1
{
public partial class AddIn
{
private void AddIn_Startup(object sender, EventArgs e)
{
//hook up the button click event with an add-in method
this.ButtonClickEvent += new EventHandler<EventSample.CustomMouseEventArgs>(AddIn_ButtonClick);
}
void AddIn_ButtonClick(object sender, EventSample.CustomMouseEventArgs e)
{
//add a line to the display to show add-in awareness
this.addTextToListBox("Add-in caught button click event.");
}
}
}