8.

 

Capturing Events

 

Before we proceed with the explanation of the above program, we call upon you to copy the following code.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect()

{

MessageBox.Show("Connect");

}

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

DTE applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

OutputWindowPane op; 

op = oo.Add("Vijay Mukhi");

op.OutputString("Sonal is my wife");

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

}

public void OnAddInsUpdate(ref System.Array custom)

{

}

public void OnStartupComplete(ref System.Array custom)

{

}

public void OnBeginShutdown(ref System.Array custom)

{

}

}

}

The last four functions of the interface would remain empty more often than not. Nonetheless, their presence in the code is imperative, since they belong to the interface.

 

Build the solution. You must ensure that the Output window is projected on the screen. If it is not complied with, then click on the View menu, followed by the Other Windows menu option, and select Output Window, as demonstrated in screen 8.1.

 

Screen 8.1

Screen 8.2

 

Thereafter, activate the Add-in. The Output Window would display a screen, as is evident in screen 8.2. Vijay Mukhi is sighted as the sub-title of the Output Window, which also contains some text in it. Thus, we have devised an Add-in that writes to the Output window, provided that the Output window is visible.

 

This Add-in does not activate the Output window, but it writes to this window. The View menu can be exploited to activate it at any point of time. As always, the real action lies in the OnConnection function. Firstly, a handle to the DTE object is acquired, and then, the windows present in the Visual Studio.Net are accessed.

 

The Windows class is a compendium of the numerous windows that inhabit the Visual Studio. It is an interface that is derived from IDispatch. Since it is not the entire collection of windows but a single window that has caught our fancy, the indexer stands out as the most favorable option. However, in this situation, the powers to be insist on the usage of a method called Item to access a single window. The parameter to this function is of type object, consequently any data type may be availed.

 

The Item function is then assigned an enum named Constants from the EnvDTE namespace, and also a value vsWindowKindOutput, which solicits a handle to the Output Window type. The return value is stored in an object ww of type Window.

 

This Window object ww contains a property object, which returns a window. It is this particular window that can be accessed and used at run time. This signifies the fact that the Window object is incapable of being used directly. It can only be reached indirectly using the Object property.

 

The Output window has an interface OutputWindow, which is also derived from IDispatch. The task of the Output window is to display the text output of the various tools, such as the C# compiler. Since a sizeable number of tools exist, the Output window is divided into panes, which can be selected from a drop-down listbox. Each tool is competent enough to create its own pane and write into it.

 

Tools that are written in the appropriate manner should only write into their own panes. Thus, there is a need to create a pane first. Towards this end, the property OutputWindowPanes is employed. It returns an OutputWindowPanes object, which represents a collection of panes in the Output window.

 

Using the Add function, an additional pane named Vijay Mukhi is added to the Output window. Concurrently, the newly created OutputWindowPane object is stored in the variable op. Then, the OutputString function is called to write the desired text in the window. Thus, the text can be written into any window in Visual Studio.Net with effortless ease.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect()

{

MessageBox.Show("Connect");

}

OutputWindowPane op; 

WindowEvents we;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

DTE applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

we = e.get_WindowEvents(null);

we.WindowClosing += new _dispWindowEvents_WindowClosingEventHandler(wClosing);

we.WindowActivated += new _dispWindowEvents_WindowActivatedEventHandler(wActivated);

we.WindowCreated += new _dispWindowEvents_WindowCreatedEventHandler(wCreated);

we.WindowMoved += new _dispWindowEvents_WindowMovedEventHandler(wMoved);

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

op.OutputString("In Connection\n");

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

we.WindowClosing -= new _dispWindowEvents_WindowClosingEventHandler(wClosing);

we.WindowActivated -= new _dispWindowEvents_WindowActivatedEventHandler(wActivated);

we.WindowCreated -= new _dispWindowEvents_WindowCreatedEventHandler(wCreated);

we.WindowMoved -= new _dispWindowEvents_WindowMovedEventHandler(wMoved);

}

public void OnAddInsUpdate(ref System.Array custom)

{

}

public void OnStartupComplete(ref System.Array custom)

{

}

public void OnBeginShutdown(ref System.Array custom)

{

}

public void wClosing(EnvDTE.Window a)

{

op.OutputString("Window Closing Name: " + a.Caption + "\n");

}

public void wActivated(EnvDTE.Window a, EnvDTE.Window b)

{

op.OutputString("Window Activated Name " + a.Caption + " Deactivated " + b.Caption + "\n");

}

public void wCreated(EnvDTE.Window a)

{

op.OutputString("Window Created " + a.Caption + "\n");

}

public void wMoved(Window a, int top, int left, int width, int height)

{

op.OutputString("Window Moved " + a.Caption + " " + top.ToString() + " " + left.ToString() + "\n");

}

}

}

 

The aforementioned instance captures Windows events as and when they occur, and then, it displays them in the Output window. The previous example had brought out the technique of writing text in a window. Hence, we have refrained from laying too much emphasis on this aspect here.

 

The Events property returns an Events collection, which is a cluster of all events that could ever crop up. Since our prime concern is only the Windows events, the property WindowEvents is taken cognizance of. It takes a parameter whose value is null at this instance. This can be a Window object if the events to a single window are to be taken into consideration. Thus, this parameter acts as a filter, where the value of null is specified at the time when all the events have to be captured.

 

The class WindowsEvents has four events, and each of them is associated with a function through a delegate. Thus, whenever an event is triggered, the automation model calls the relevant function. The programmer enjoys the license to enter the code in these functions.

 

We shall now shed sufficient light upon each and every event, and shall also elucidate the parameters with which the functions get called.

 

The first event is WindowClosing, which calls the function wClosing each time a window is closed. To activate this event, simply close a window. On doing so, the function wClosing gets called. This function is furnished with a Window object of the window that is being closed. The crux of the matter is that this function gets called before the window actually closes.

 

In all the Window events, only the Caption of the window is displayed. However, you are at liberty to display all the properties, if you so desire.

 

The screen 8.3 exhibits the additional line in the Output window. The event of WindowActivated occurs whenever a window obtains focus or gets activated.

 

Screen 8.3

 

This event occurs only after the environment is absolutely ready or initialized. Moreover, the first window that receives focus does not trigger this event.

 

The rationale behind it is that the event calls a function with two parameters. The first parameter is a Window object that receives focus. The second parameter is the Window object that has just lost focus. Thus, to activate this event, click on any window and observe the Output Window.

 

The third event is WindowCreated, which occurs whenever a window is created. A window can be created by merely opting for the menu option Window, followed by the option New Window. The function that is called, is passed the handle of the newly created window.

 

Finally, the last event of WindowMoved occurs whenever a window is moved. To move a window, click on the title bar of the window, and without releasing the mouse, drag the window around.

 

The first parameter is the Window object being moved and the next four parameters indicate the new location of the window. The four points refer to the top and left position, along with the width and the height. The program displays the top and left parameters. You may also display the others if you so desire.

 

Thus, whenever any program or a user activates either of the four abovementioned events, the functions that are associated with the events, are called to first identify the affected window.

 

This is all there is to the automation world. Believe us, there is no information kept under wraps! Therefore, it becomes a simple task to activate a user-defined code whenever a Windows event occurs.

 

The function OnDisconnection gets executed whenever the Add-in is downloaded. So, basic courtesy demands that you clean up your act before marching forward. If this is not done, then no code would be available to handle the events. The -= syntax is used to displace the function from the event.

 

We wish to enlighten those of you who are still uninitiated, that functions can be added using the += syntax. 

 

public void wMoved(Window a, int top, int left, int width, int height)

{

op.OutputString("Window Moved " + a.Caption + " " + top.ToString() + " " + left.ToString() + "\n");

a.Caption = "Sonal Mukhi";

}

 

A minor modification has been introduced in the above program, whereby a single line of code has been added to the wMoved function.

 

The caption of the window can be accessed using the Caption property, which is a read-write property. Here, it is changed to 'Sonal Mukhi'. Thus, each time the window is moved, the caption of the window changes to 'Sonal Mukhi'. Screen 8.4 flaunts the Output Window with its new caption.

 

Screen 8.4

 

Thus, the properties of the window can now be changed effortlessly, whenever a specific Window event occurs.

 

The ensuing example tackles the events present in the Events collection. The basic structure of the program remains unaltered. There are a large number of events to trap. So, without further ado, we launch into writing separate programs for each of the events.

 

namespace vij25  {

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect()

{

MessageBox.Show("Connect");

}

OutputWindowPane op; 

TextEditorEvents texte;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

DTE applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

texte = e.get_TextEditorEvents(null);

texte.LineChanged += new _dispTextEditorEvents_LineChangedEventHandler(tLineChanged);

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

texte.LineChanged -= new _dispTextEditorEvents_LineChangedEventHandler(tLineChanged);

}

public void OnAddInsUpdate(ref System.Array custom) { }

public void OnStartupComplete(ref System.Array custom) { }

public void OnBeginShutdown(ref System.Array custom) { }

public void tLineChanged(TextPoint s, TextPoint e, int h)

{

vsTextChanged t = (vsTextChanged)h;

op.OutputString("TextEditor " + s.DisplayColumn.ToString() + " " + e.DisplayColumn.ToString() + " " + e.Line.ToString());

op.OutputString(" Name: " + s.Parent.Parent.Name);

op.OutputString(" hint: " + t.ToString() + "\n");

}

}

}

 

The next event to be captured is the TextEditorEvents. This is achieved by using the property TextEditorEvents and assigning it a TextDocument object type, which currently holds a value of null. The events are to be monitored for the parameter, which is the TextDocument object. Fortunately, the TextEditorEvents contains only a solitary event called LineChanged.

 

This event gets fired whenever any changes are incorporated in a line in the Code Editor, and the insertion point is moved. The texte variable of type TextEditorEvents is used to initialize the LineChanged event, thus resulting in a call to the coupled function tLineChanged.

 

Then, the above function is eliminated from the OnDisconnection function. The function tLineChanged gets called with three parameters. The first two are TextPoint objects, which represent the start and end points of the line that is being modified. Basically, a TextPoint object represents a location in the Code Editor. Thus, this object can be used to determine any location or point in the text document.

 

To understand the exact course of things around here, move to any line in the Code Editor and press the Enter key. The first TextPoint object 's' indicates the start column number, as seen in screen 8.5. The property DisplayColumn displays this value.

 

Screen 8.5

 

The end TextPoint parameter evidently prints a value of 1, since the Enter key moves the cursor to the beginning of a new line. The Line property denotes the line number where the cursor is currently blinking, while the Parent property provides the name of the file. The last parameter is not an int, but an enum or a constant named vsChanged.

 

This constant can assume one of the following values in order to communicate the change that has occurred in the text document:-

     A value of 1 signifies that multiple lines have been affected, which happens when a great deal of text is inserted.

     A value of 2 implies that a line was committed to disk by saving the file.

     A value of 4 signifies that the insertion point has been moved.

     A value of 8 represents the 'replace all' operation.

     A value of 16 indicates that a new line was created.

     A value of 20 means that the Enter key was pressed, since it denotes that the cursor or insertion point was moved and a new line was created.

     A value of 32 symbolizes the 'find all' operation.

 

This is how the changes that have transpired in the Text Editor, can be identified.

 

public void tLineChanged(TextPoint s, TextPoint e, int h)

{

vsTextChanged t = (vsTextChanged)h;

op.OutputString("TextEditor " + s.DisplayColumn.ToString() + " " + e.DisplayColumn.ToString() + " " + e.Line.ToString());

op.OutputString(" Name: " + s.Parent.Parent.Name);

op.OutputString(" hint: " + t.ToString() + "\n");

EditPoint ee = s.CreateEditPoint();

ee.Insert("Sonal Mukhi");

ee.ChangeCase(20,vsCaseOptions.vsCaseOptionsUppercase);

}

 

Now, add three lines to the function tLineChanged. An EndPoint class allows text to be manipulated either in the Code Editor or in the text document. An EndPoint is created by employing the end TextPoint object e and the function CreateEndPoint.

 

After introducing the above Add-in, move the cursor to the comma that is located after the first parameter on the line containing the tLineChanged function. Press the Enter key at this position. The changes that occur in the editor are reflected in screen 8.6.

 

Screen 8.6

 

Sonal Mukhi gets inserted at that position. This is a fallout of the Insert function. Thereafter, the remaining characters are converted to upper case by the ChangeCase function. The ChangeCase function takes the remaining 20 characters as the first parameter value, and converts them to the case specified in the second parameter.

 

The second parameter is an enum that can assume any one of three different values.

     The value of vsCaseOptionsUppercase converts all the characters to capitals.

     The second option converts all the characters to lower case.

     The third option capitalizes only the first character of every word.

 

The keystone here is that each and every character can be manipulated within the Code Editor.

 

namespace vij25 {

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect() {

MessageBox.Show("Connect");

}

OutputWindowPane op;

TaskListEvents taske;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

DTE applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

taske = e.get_TaskListEvents("");

taske.TaskAdded += new _dispTaskListEvents_TaskAddedEventHandler(tAdded);

taske.TaskModified += new _dispTaskListEvents_TaskModifiedEventHandler(tModified);

taske.TaskNavigated += new _dispTaskListEvents_TaskNavigatedEventHandler(tNavigated);

taske.TaskRemoved += new _dispTaskListEvents_TaskRemovedEventHandler(tRemoved);

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

taske.TaskAdded -= new _dispTaskListEvents_TaskAddedEventHandler(tAdded);

taske.TaskModified -= new _dispTaskListEvents_TaskModifiedEventHandler(tModified);

taske.TaskNavigated -= new _dispTaskListEvents_TaskNavigatedEventHandler(tNavigated);

taske.TaskRemoved -= new _dispTaskListEvents_TaskRemovedEventHandler(tRemoved);

}

public void tAdded(TaskItem t)

{

op.OutputString("Added " + t.Description + "\n");

}

public void tModified(TaskItem t, vsTaskListColumn c)

{

op.OutputString("Modified  " + t.Description + "\n");

}

public void tNavigated(TaskItem t, ref bool h)

{

op.OutputString("Navigated " + t.Description + "\n");

}

public void tRemoved(TaskItem t)

{

op.OutputString("Removed  " + t.Description + "\n");

}

public void OnAddInsUpdate(ref System.Array custom) { }

public void OnStartupComplete(ref System.Array custom) { }

public void OnBeginShutdown(ref System.Array custom) { }

}

}

 

The above Add-in examines the events that deal with Tasks. So, an instance variable named taske of type TaskListEvents is created. Then, it is initialized using the property TaskListEvents from the Events table. This property takes a string parameter, which is the name of the category whose tasks need to be filtered.

 

The TaskListEvents class has only four events that get triggered. Likewise, four functions are associated with these events, as was illustrated earlier. Then, at disconnection time, they are eliminated. Now, it is left for you to decipher the code that reiterates itself.

 

After the above Add-in has been built and activated, the Task Window has to be activated in order to view the output. This is achieved by clicking on the menu View, followed by the option Other Windows, and then, by selecting the Task List, as is evident in screen 8.7.

 

Screen 8.7

 

The Task Window displays the line 'Click here to add a new task'. When the Task Window is clicked on, a new line gets added in the Task Window, where the text vijay is entered and the Enter key is pressed, (Screen 8.8).

 

Screen 8.8

 

On pressing the Enter key, the function tAdded gets called. This phenomenon is an outcome of the event named TaskAdded. The function is passed a parameter of type TaskItem object. The Description property is then displayed, which exposes the text vijay.

 

Double clicking on the task vijay now activates the event TaskNavigated. The first parameter is the navigated task, while the second parameter is a bool value. The second parameter is passed by ref.

 

Thus, in the eventuality of it being changed, these modifications get reflected in the main code. Now, click on the checkbox in front of Vijay. The screen 8.9 amply substantiates the fact that the task item was being modified, which triggered off the event TaskModified.

 

Screen 8.9

Screen 8.10

 

The Delete key on the keyboard is used to delete the task Vijay. This action triggers off the event TaskRemoved. The Output Window pane Vijay Mukhi displays the events being called and the functions being executed.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect()

{

MessageBox.Show("Connect");

}

OutputWindowPane op;

SolutionEvents se;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

DTE applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

se = e.SolutionEvents;

se.AfterClosing += new _dispSolutionEvents_AfterClosingEventHandler(sAfterClosing);

se.BeforeClosing += new _dispSolutionEvents_BeforeClosingEventHandler(sBeforeClosing);

se.Opened += new _dispSolutionEvents_OpenedEventHandler(sOpened);

se.ProjectAdded += new _dispSolutionEvents_ProjectAddedEventHandler(sProjectAdded);

se.ProjectRemoved += new _dispSolutionEvents_ProjectRemovedEventHandler(sProjectRemoved);

se.ProjectRenamed += new _dispSolutionEvents_ProjectRenamedEventHandler(sProjectRenamed);

se.QueryCloseSolution += new _dispSolutionEvents_QueryCloseSolutionEventHandler(sQueryCloseSolution);

se.Renamed += new _dispSolutionEvents_RenamedEventHandler(sRenamed);

}

 

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

se.AfterClosing -= new _dispSolutionEvents_AfterClosingEventHandler(sAfterClosing);

se.BeforeClosing -= new _dispSolutionEvents_BeforeClosingEventHandler(sBeforeClosing);

se.Opened -= new _dispSolutionEvents_OpenedEventHandler(sOpened);

se.ProjectAdded -= new _dispSolutionEvents_ProjectAddedEventHandler(sProjectAdded);

se.ProjectRemoved -= new _dispSolutionEvents_ProjectRemovedEventHandler(sProjectRemoved);

se.ProjectRenamed -= new _dispSolutionEvents_ProjectRenamedEventHandler(sProjectRenamed);

se.QueryCloseSolution -= new _dispSolutionEvents_QueryCloseSolutionEventHandler(sQueryCloseSolution);

se.Renamed -= new _dispSolutionEvents_RenamedEventHandler(sRenamed);

}

public void sAfterClosing()

{

op.OutputString("AfterClosing\n");

}

public void sBeforeClosing()

{

op.OutputString("BeforeClosing\n");

}

public void sOpened()

{

op.OutputString("Opened\n");

}

public void sProjectAdded(Project p)

{

op.OutputString("ProjectAdded " + p.FullName + "\n");

}

public void sProjectRemoved(Project p)

{

op.OutputString("ProjectRemoved " + p.FullName + "\n");

}

public void sProjectRenamed(Project p, string o)

{

op.OutputString("ProjectRenamed " + p.FullName + " " + o + "\n");

}

public void sQueryCloseSolution(ref bool c)

{

op.OutputString("QueryCloseSolution " + c + "\n");

}

public void sRenamed(string o)

{

op.OutputString("Renamed " + o + "\n");

}

public void OnAddInsUpdate(ref System.Array custom)

{

}

public void OnStartupComplete(ref System.Array custom)

{

}

public void OnBeginShutdown(ref System.Array custom) { }

}

}

 

This Add-in handles events associated with the Solution Explorer. An object se of type SolutionEvents is created and initialized, using the SolutionEvents property from the Events class. This class houses a total of eight events, which are skimmed over one at a time.

 

At first, the Solution Events created an Add-in project. The wizard creates two projects in a single solution.

 

The project attends to the task of saving the details of the projects, while the solution merely stores the projects that it encompasses. The details are stored in ASCII files. A solution uses an sln extension, while a project utilizes a csproj extension. These configuration files are pure XML files.

 

When the project name is right clicked on, a menu unfolds itself. The selection of the menu option of Remove triggers off the event ProjectRemoved. The function associated with this event is called with a single parameter, which is the project earmarked for elimination. The code in this function merely displays the full path of the project. 

 

In case the Rename option had been selected in lieu of the Remove option, the event ProjectRenamed would have been  triggered off. The function accepts two parameters, viz. a handle to the project that is currently selected and a string that contains the old name. The Output Window projects both, the new as well as the old name.

 

One of the menu options that presents itself on right clicking the Project is Add, which leads to the menu option New Project. A click on this option brings forth the familiar Project dialog box, from where a Project template type can be selected. This activates the event ProjectAdded, and also displays the full name of the new project that has been added.

 

This thrashes out the issue of events that handle Projects in a solution. Now, let us converge our attention onto the Solution Events.

 

When the solution name is clicked on with the right-mouse button and the Rename option is selected, the Renamed event gets fired. The event gets triggered only when a new word is specified, followed by the Enter key. The function is supplied with a string containing the old name.

 

Open a solution that had been stored earlier, while the existing one is still open. On doing so, the QueryCloseSolution event gets triggered, with a boolean value of false. This event is followed by the BeforeClosing event, which in turn is followed by the AfterClosing event, since the solution gets closed. Then, the Opened event of the project that is being opened, gets triggered off. This explicates all the five solution events that get executed. There is a minor aspect that still needs to be expounded.

 

public void sQueryCloseSolution (ref bool c)

{

op.OutputString("QueryCloseSolution " + c + "\n");

c = true;

}

 

The event QueryCloseSolution gets called just prior to the closing of a solution. A ref parameter is passed, which has a default value of false. Set it to true, and then, build the solution. Activate the Add-in. Once this has been accomplished, attempt at opening any new solution. Amazingly, no solution opens up.

 

This stems from the fact that the Event Manager checks the value of the bool parameter. If any of the functions sets its value to true, then the earlier solution does not close at all. It is for this reason that the event begins with the word Query. The Add-ins are asked whether they want the existing solution to be closed or not.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect()

{

MessageBox.Show("Connect");

}

OutputWindowPane op;

SelectionEvents se;

DTE applicationObject;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

se = e.SelectionEvents;

se.OnChange += new _dispSelectionEvents_OnChangeEventHandler(sOnChange);

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

se.OnChange -= new _dispSelectionEvents_OnChangeEventHandler(sOnChange);

}

public void sOnChange()

{

SelectedItems s = applicationObject.SelectedItems;

int c = s.Count;

op.OutputString("Selection Number " + c.ToString() + "\n");

for (int i = 1 ; i <= c ; i++)

{

SelectedItem ss = s.Item(i);

op.OutputString("Name " + ss.Name + "\n");

}

}

public void OnAddInsUpdate(ref System.Array custom) { }

public void OnStartupComplete(ref System.Array custom) { }

public void OnBeginShutdown(ref System.Array custom) { }

}

}

 

The events property returns a SelectionEvents object when the property SelectionEvents is pressed into action. Fortunately, there is a solitary event called OnChange, which is linked with the function sOnChange that accepts no parameters.

 

In case it has slipped your attention, the applicationObject or DTE object has been made an instance variable. The event OnChange gets fired whenever the user makes some selection, or when the selection model changes.

 

The DTE object has a property called SelectedItems that returns a SelectedItems object. This object collects all the changes. All collection objects have a property called Count, which gives the total count on the items that are present.

 

In the above code, the Count property is displayed in function sOnChange, and all the items in the collection are examined using a 'for' loop. The Item function is like an indexer, which facilitates the retrieval of each and every item that is present. For the Item function, the count starts from 1, whereas the indexer for the Item function begins its count with the value of 0. Then, the name of the selection is displayed in the Output window.

 

Build and activate the Add-in, and then, keep selecting varied items in the project. The OnChange event gets triggered, thus displaying the names of the individual project files that are clicked on.

 

Next, click on the solution vij25 on the Start page. This will beget a change in the selection model. This triggers off the event OnChange multiple times, and prompts the display of a list of files that constitute the solution.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect()

{

MessageBox.Show("Connect");

}

OutputWindowPane op;

OutputWindowEvents oe;

DTE applicationObject;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

oe = e.get_OutputWindowEvents ("");

oe.PaneAdded += new _dispOutputWindowEvents_PaneAddedEventHandler(sPaneAdded);

oe.PaneClearing += new _dispOutputWindowEvents_PaneClearingEventHandler(sPaneClearing);

oe.PaneUpdated += new _dispOutputWindowEvents_PaneUpdatedEventHandler(sPaneUpdated);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

op.OutputString("Started\n");

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

oe.PaneAdded -= new _dispOutputWindowEvents_PaneAddedEventHandler(sPaneAdded);

oe.PaneClearing -= new _dispOutputWindowEvents_PaneClearingEventHandler(sPaneClearing);

oe.PaneUpdated -= new _dispOutputWindowEvents_PaneUpdatedEventHandler(sPaneUpdated);

}

public void sPaneAdded(OutputWindowPane p)

{

op.OutputString("PaneAdded " + p.Name + "\n");

}

public void sPaneClearing(OutputWindowPane p)

{

op.OutputString("PaneClearing " + p.Name + "\n");

}

public void sPaneUpdated(OutputWindowPane p)

{

System.Windows.Forms.MessageBox.Show("PaneUpdated");

}

public void OnAddInsUpdate(ref System.Array custom) { }

public void OnStartupComplete(ref System.Array custom) { }

public void OnBeginShutdown(ref System.Array custom) { }

}

}

 

The above Add-in deals with events related to the Output window. Here, we need to proceed cautiously, since the program also writes to the Output window. The property OutputWindowEvents gives an OutputWindowEvents object; and as usual, the parameter that is passed is the name of the pane in a string form. A value of null traps all the events for all the Output Window panes. There are three events in this section.

 

At first, the activated Add-in displays the text ‘Srarted’  in the Output Window.

 

The message box that emerges, signifies that an event PaneUpdated has been fired. The function OutputString has not been used in the function sPaneUpdated, since it would result in an indefinite recursion. It is because the event gets triggered whenever something is written in the pane. The screen 8.11 displays the relevant message box.

 

Screen 8.11

Screen 8.12

 

Rebuild the solution. The screen 8.12 appears because the PaneClearing event gets called. This occurs because Visual Studio.Net actually builds the pane before writing into it. In the above events, it is merely the name of the pane that is displayed.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect() {

MessageBox.Show("Connect");

}

OutputWindowPane op;

FindEvents fe;

DTE applicationObject;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom) {

applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

EnvDTE.Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

fe = e.FindEvents;

fe.FindDone += new _dispFindEvents_FindDoneEventHandler(fFindDone);

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

fe.FindDone -= new _dispFindEvents_FindDoneEventHandler(fFindDone);

}

public void fFindDone(vsFindResult r, bool c)

{

op.OutputString(" " + r.ToString() + " " + c.ToString() + "\n");

}

public void OnAddInsUpdate(ref System.Array custom) { }

public void OnStartupComplete(ref System.Array custom) { }

public void OnBeginShutdown(ref System.Array custom) { }

}

}

The above Add-in traps the events that get generated when the Find in Files option is used. This option is in the Edit menu, located after the menu options of Find and Replace. Enter a search word and click on Find. The Find Results 1 window shows-up the results of the Find in Files operation, but the the FindDone event does not get called. This can be verified in the output window.

 

Now for the option “Look in”  in the Find in Files Dialog Box change the value “Current Document” to a Specific file name “C:\v2\vij25\Connect.cs” as in screen 8.13.

 

Screen 8.13

Screen 8.14

 

The Add-in obtains access to the FindEvents object in the Events class by using the FindEvents property. The function fFindDone gets called whenever the Find in Files operation completes with a specified file name, as depicted in screen 8.14.

 

Here, the function fFindDone gets called, together with the Find Results Output window containing the results of the Find in Files operation. The fFindDone function gets called with two parameters; one is an enum named vsFindResult, while the other is a bool having the result value of false.

 

The enum vsFindResult can assume any one of seven possible values, conferring the outcome of the search. They are sequentially numbered from 0 to 6.

     The first enum value is vsFindResultNotFound or 0, which indicates that the search item has not been found.

     In our case, the enum value is vsFindResultFound or 1, since the key word has been located in the file.

     The third enum value is vsFindResultReplaceAndNotFound or 2, which indicates that the search word for the replace operation was not found.

     The fourth enum value is vsFindResultReplaceAndFound or 3. It informs us that the Replace word that was being sought, has been found.

     The fifth enum value is vsFindResultReplaced or 4, which indicates that the search item was successfully replaced.

     The sixth enum value is vsFindResultPending or 5, which signifies that the search is still pending.

     The seventh enum value and the final enum value is vsFindResultError or 6, which indicates that the search has resulted in some error.

 

It is a great experience working with Add-ins that have only one event.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect()

{

MessageBox.Show("Connect");

}

OutputWindowPane op;

DTE applicationObject;

DTEEvents de;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

de = e.DTEEvents;

de.ModeChanged += new _dispDTEEvents_ModeChangedEventHandler(dModeChanged);

de.OnBeginShutdown += new _dispDTEEvents_OnBeginShutdownEventHandler(dOnBeginShutdown);

de.OnMacrosRuntimeReset += new _dispDTEEvents_OnMacrosRuntimeResetEventHandler(dOnMacrosRuntimeReset);

de.OnStartupComplete += new _dispDTEEvents_OnStartupCompleteEventHandler(dOnStartupComplete);

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

de.ModeChanged -= new _dispDTEEvents_ModeChangedEventHandler(dModeChanged);

de.OnBeginShutdown -= new _dispDTEEvents_OnBeginShutdownEventHandler(dOnBeginShutdown);

de.OnMacrosRuntimeReset -= new _dispDTEEvents_OnMacrosRuntimeResetEventHandler(dOnMacrosRuntimeReset);

de.OnStartupComplete -= new _dispDTEEvents_OnStartupCompleteEventHandler(dOnStartupComplete);

}

public void dModeChanged(vsIDEMode l)

{

op.OutputString("ModeChanged " + l.ToString() + "\n");

}

public void dOnBeginShutdown()

{

MessageBox.Show("OnBeginShutdown\n");

}

public void dOnMacrosRuntimeReset() {

MessageBox.Show("OnMacrosRuntimeReset\n");

}

public void dOnStartupComplete() {

MessageBox.Show("OnStartupComplete\n");

}

public void OnAddInsUpdate(ref System.Array custom) { }

public void OnStartupComplete(ref System.Array custom) { }

public void OnBeginShutdown(ref System.Array custom) { }

}

}

 

The DTE events relate to the state that the environment is in. Therefore, the DTEEvents property from the events object is used to attain a new DTEEvents object.

 

This class has four events. The first one is the ModeChanged event. This event gets fired whenever the mode of the environment endures a change. For instance, this occurs when the Windows Application project is opened by clicking on the menu option of Debug - Start. Although it projects an empty window, it also simultaneously triggers off the event ModeChanged.

 

The event is passed one parameter, which is an enum of type vsIDEMode that takes only two values. The value of vsIDEModeDesign indicates that the environment is in Design Mode. On closing the form, the event gets called again. However, this time, the parameter has a value of vsIDEModeDebug. This suggests that the environment is in Debug Mode.

 

On closing Visual Studio.Net, the event OnBeginShutDown gets called while Visual Studio.Net is in the process of shutting down or quitting out. After Visual Studio.Net starts up and has been completely launched, then and only then, the OnStartupComplete event gets called. All the User Interface features and the resources provided by Visual Studio.Net, can be put to use from this stage onwards.

 

Now, click on the menu option Tools, and then on Macros. Thereafter, select New Macro Project. The dialog box that comes in sight, is shown in screen 8.15.

 

Screen 8.15

Screen 8.16

Screen 8.17

 

Select the existing option, and then, click on the OK button. Screen 8.17 exhibits a MessageBox, thus suggesting that the OnMacrosRuntimeReset event has been called. This event gets called when the macro runtime execution engine resets itself, which occurs during the creation of a new Macro project. This act clears up all global variable data and eliminates all the event connections.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

public Connect()

{

MessageBox.Show("Connect");

}

OutputWindowPane op;

DTE applicationObject;

DocumentEvents de;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

de = e.get_DocumentEvents(null);

de.DocumentClosing += new _dispDocumentEvents_DocumentClosingEventHandler(dDocumentClosing);

de.DocumentOpened += new _dispDocumentEvents_DocumentOpenedEventHandler(dDocumentOpened);

de.DocumentOpening += new _dispDocumentEvents_DocumentOpeningEventHandler(dDocumentOpening);

de.DocumentSaved += new _dispDocumentEvents_DocumentSavedEventHandler(dDocumentSaved);

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

de.DocumentClosing -= new _dispDocumentEvents_DocumentClosingEventHandler(dDocumentClosing);

de.DocumentOpened -= new _dispDocumentEvents_DocumentOpenedEventHandler(dDocumentOpened);

de.DocumentOpening -= new _dispDocumentEvents_DocumentOpeningEventHandler(dDocumentOpening);

de.DocumentSaved -= new _dispDocumentEvents_DocumentSavedEventHandler(dDocumentSaved);

}

public void dDocumentClosing(Document d)

{

op.OutputString("DocumentClosing " + d.Name + "\n");

}

public void dDocumentOpened(Document d)

{

op.OutputString("DocumentOpened: " + d.Name + "\n");

}

public void dDocumentOpening(string d, bool t)

{

op.OutputString("DocumentOpening  " + d + " " + t.ToString() + "\n");

}

public void dDocumentSaved(Document d)

{

op.OutputString("DocumentSaved " + d.Name + "\n");

}

public void OnAddInsUpdate(ref System.Array custom) {

}

public void OnStartupComplete(ref System.Array custom) {  }

public void OnBeginShutdown(ref System.Array custom) { }

}

}

 

The above Add-in deals with Document events. The property DocumentEvents provides a Document object. The parameter supplied is a null value, by reason of which, the  DocumentEvents object gets triggered for all documents. There are four events that get triggered.

 

Firstly, ensure that the solution vij30 is active. Then, activate the Add-in and select the project vij20 from the start page (Help - Show Startpage).

 

Screen 8.18

 

The following output is visible in the Output window:-

 

DocumentClosing Form1.cs

DocumentClosing Form1.resx

DocumentOpening  c:\v1\vij20\Class1.cs False

DocumentOpened: Class1.cs

DocumentSaved Class1.cs

 

The activation of a new project is preceded by the closing of all open documents. Thus, the framework closes the two documents that comprise the vij30 project, viz. Form1.cs and Form1.resx. This triggers off the event DocumentClosing twice. This event is passed a Document object. The Name property is printed out.

Then, as a new solution is being opened, and since the solution is made up of a single file named Class1.cs, the event DocumentOpening is triggered off, and it is assigned two parameters. The first one is the Document object whose Name property is displayed, and the second is a bool that conveys whether the document is being opened in read-only mode or not.

 

Since the document Class1.cs permits changes, the bool variable holds a value of false. This event is immediately followed by the event DocumentOpened with a similar document parameter. The document is saved after integrating a few changes to the file Class1.cs, using the menu option File - Save Class1.cs. Thereafter, the event DocumentSaved gets triggered off.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("00271586-C6A9-44EF-A342-2E63FFD4D384"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

OutputWindowPane op;

DTE applicationObject;

CommandEvents ce;

public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom)

{

applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

ce = e.get_CommandEvents("{00000000-0000-0000-0000-000000000000}", 0);

ce.AfterExecute += new _dispCommandEvents_AfterExecuteEventHandler(cAfterExecute);

ce.BeforeExecute += new _dispCommandEvents_BeforeExecuteEventHandler(cBeforeExecute);

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)

{

ce.AfterExecute -= new _dispCommandEvents_AfterExecuteEventHandler(cAfterExecute);

ce.BeforeExecute -= new _dispCommandEvents_BeforeExecuteEventHandler(cBeforeExecute);

}

public void cAfterExecute(string g, int i, object c, object c1)

{

string s= "";

s = applicationObject.Commands.Item(g,i).Name;

op.OutputString("AfterExecute " + s + " " + g + " " + i.ToString() + "\n");

}

public void cBeforeExecute(string g, int i, object c, object c1, ref bool d)

{

string s= "";

s = applicationObject.Commands.Item(g,i).Name;

op.OutputString("BeforeExecute " + s + " " + g + " " + i.ToString() + " " + d.ToString() + "\n");

}

public void OnAddInsUpdate(ref System.Array custom)

{

}

public void OnStartupComplete(ref System.Array custom)

{

}

public void OnBeginShutdown(ref System.Array custom)

{

}

}

}

 

The Add-in furnished above, examines the CommandEvents. Yet again, the CommandEvents object is retrieved from the Events object by resorting to the property CommandEvents. This property necessitates two parameters.

 

The first parameter is a string that represents a GUID. This string is in the form of 8 digits, followed by three sets of four digits, and ends with 12 digits to form a 16 byte number. The GUID for the command group can be supplied. A value of null signifies that all the command groups are included.

 

The second parameter is an int or an ID, which serves as an index into the command in the command group. Each command has an index and a GUID.

 

Screen 8.19

 

This CommandEvents object has only two events, viz. BeforeExecute and AfterExecute. When the Add-in is activated by clicking on the OK button, the event AfterExecute gets called, as illustrated below.

 

AfterExecute Tools.AddinManager {5EFC7975-14BC-11CF-9B2B-00AA00573819} 297

 

This event takes four parameters. The first parameter is the GUID of the command, which consists of the textual name of the command, plus a GUID. The second parameter with a value of 297, is the ID of the command. The last two parameters are custom arguments, which are passed to the command and to the next event handler.

 

Now, select the menu option View, Solution Explorer. This adds the following lines in the Output window:-

 

BeforeExecute View.SolutionExplorer {5EFC7975-14BC-11CF-9B2B-00AA00573819} 234 False

AfterExecute View.SolutionExplorer {5EFC7975-14BC-11CF-9B2B-00AA00573819} 234

 

The BeforeExecute event gets called prior to the execution of the command. The text output clearly postulates that the menu option of Solution Explorer had been selected.

 

Copy the above text from the Output window. On pressing Ctrl-C, the following lines get displayed:-

 

BeforeExecute Edit.Copy {5EFC7975-14BC-11CF-9B2B-00AA00573819} 15 False

AfterExecute Edit.Copy {5EFC7975-14BC-11CF-9B2B-00AA00573819} 15

 

Screen 8.20

 

This sample elucidates the fact that although the Solution Explorer and Copy may inhabit different menus, they share the same command group. The ID is used to differentiate between the distinct commands within a command group.

 

public void cBeforeExecute(string g, int i, object c, object c1, ref bool d)

{

string s= "";

s = applicationObject.Commands.Item(g,i).Name;

op.OutputString("BeforeExecute " + s + " " + g + " " + i.ToString() + " " + d.ToString() + "\n");

d = true;

}

 

Effect a minor alteration in the cBeforeExecute function, by setting the last ref bool parameter that is passed a value of true. By default, this variable has a value of false. Now, build the Add-in. As before, the AfterExecute event gets called. Also, selecting any menu option or shortcut henceforth, would prove to be futile, since none of them would work. Try to activate any menu option or button from the toolbox, including shortcuts such as Ctrl-Shift-B. Take our word, nothing will work!

 

The Output window lucidly illustrates the fact that although the BeforeExecute and AfterExecute events get triggered, code in the Visual Studio framework does not get called. The Visual Studio.Net product has been built by employing the same code that we have implemented here.

 

When we click on the menu option View, Solution Explorer, either some code gets executed, or a listener gets called. There can be innumerable such listeners registered for each command. Every listener gets called in turn. If any listener changes the value of the last ref parameter to true, as has been done by us, no other listener is expected to execute code for that command, since some routing is handling the events. This effectively disables all the code in Visual Studio.Net. Therefore, returning a value of true apprises the framework of the fact that the program would handle the request, and hence, it would not be delegated.

 

The last Add-in in this chapter deals with the set of debugging events.

 

namespace vij25

{

using System;

using Microsoft.Office.Core;

using Extensibility;

using System.Runtime.InteropServices;

using EnvDTE;

using System.Windows.Forms;

[GuidAttribute("81BA7553-4DE7-4A73-A0CD-48F644052262"), ProgId("vij25.Connect")]

public class Connect : Object, Extensibility.IDTExtensibility2

{

OutputWindowPane op;

DTE applicationObject;

DebuggerEvents de;

public Connect()

{

MessageBox.Show("Connect");

}

public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)

{

applicationObject = (DTE)application;

Windows w = applicationObject.Windows;

Events e = applicationObject.Events;

Window ww = w.Item(Constants.vsWindowKindOutput);

OutputWindow o = (OutputWindow)ww.Object;

OutputWindowPanes oo =  o.OutputWindowPanes;

op = oo.Add("Vijay Mukhi");

de = e.DebuggerEvents;

de.OnContextChanged += new _dispDebuggerEvents_OnContextChangedEventHandler(dOnContextChanged);

de.OnEnterBreakMode += new _dispDebuggerEvents_OnEnterBreakModeEventHandler(dOnEnterBreakMode);

de.OnEnterDesignMode += new _dispDebuggerEvents_OnEnterDesignModeEventHandler(dOnEnterDesignMode);

de.OnEnterRunMode += new _dispDebuggerEvents_OnEnterRunModeEventHandler(dOnEnterRunMode);

de.OnExceptionNotHandled += new _dispDebuggerEvents_OnExceptionNotHandledEventHandler(dOnExceptionNotHandled);

de.OnExceptionThrown += new _dispDebuggerEvents_OnExceptionThrownEventHandler(dOnExceptionThrown);

}

public void dOnContextChanged(Process p, Program pr, Thread t, StackFrame f)

{

op.OutputString("OnContextChanged\n");

}

public void dOnEnterBreakMode(dbgEventReason r, ref dbgExecutionAction a)

{

op.OutputString("OnEnterBreakMode " + r.ToString() + " " + a.ToString() + "\n");

}

public void dOnEnterDesignMode(dbgEventReason r)

{

op.OutputString("OnEnterDesignMode " + r.ToString() + "\n");

}

public void dOnEnterRunMode(dbgEventReason r)

{

op.OutputString("OnEnterRunMode " + r.ToString() + "\n");

}

public void dOnExceptionNotHandled(string e, string n, int c, string d, ref dbgExceptionAction a)

{

op.OutputString("OnExceptionNotHandled-" + e + "-" + n + "-" + c.ToString() + "-" + d + "-" + a.ToString() + "-\n");

}

public void dOnExceptionThrown(string e, string n, int c, string d, ref dbgExceptionAction a)

{

op.OutputString("OnExceptionThrown\n");

}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom) { }

public void OnAddInsUpdate(ref System.Array custom) { }

public void OnStartupComplete(ref System.Array custom) { }

public void OnBeginShutdown(ref System.Array custom) { }

}

}

 

We access the DebuggerEvents using the property DebuggerEvents. This class has six debugger events in total. In the Windows Application, first activate the Add-in and then select the Windows project.

 

Thereafter click on menu Debug followed by the menu item Start. This act triggers off an event OnEnterRunMode as the debugger has now entered run mode.  This event is used to update the user interface. It is passed one parameter that is an enum dbgEventReason that can take as many as 13 different values, and thus giving a reason as to why the event got called.

 

The value seen is dbgEventReasonGo because the Start option is clicked on. Also, when the form is closed, another event called OnEnterDesignMode gets fired. This happens since the mode is changed from the run or debug mode to design mode. The reason enum clearly indicates that there is no reason for the event being fired as the user closed the form, to switch to the Design mode.

Next click on the Debug option and then the menu option Step Into, that allows single stepping the program. This activates the event OnEnterBreakMode with a reason dbgExecutionActionDefault as it is the default way of entering the break mode.

 

Screen 8.21

 

Now add some code to the simple windows application. The Main function has a newly inserted line which is as follows.

 

static void Main()  {

throw new System.Exception("hi");

Application.Run(new Form1());

}

 

Thereafter click on the menu option Debug and then on Start. This leads to screen 8.22 where the first function to be called is Main.

 

Screen 8.22

 

The first line of code in Main throws an exception. At this juncture, click on break and then on menu Debug, Continue. The following lines are seen added to the Output window.

 

OnEnterRunMode dbgEventReasonGo

OnExceptionNotHandled-Common Language Runtime Exceptions-System.Exception-0-An unhandled exception of type 'System.Exception' occurred in vij30.exe

Additional information: hi

-dbgExceptionActionDefault-

OnEnterBreakMode dbgEventReasonExceptionNotHandled dbgExecutionActionDefault

OnEnterRunMode dbgEventReasonGo

OnEnterDesignMode dbgEventReasonNone

 

The first event is obvious and has been seen before. Then the debugger encounters a throw statement. It displays a dialog box with the options of Break or Continue. This also signifies that the exception was not handled, thus triggering the event OnExceptionNotHandled .

 

The first parameter is the name of the exception i.e. Common Language Runtime Exceptions. The other three possible values are   C++, a native runtime check or a Win32 exception. The next parameter is the name of the exception System.Exception that is thrown.

 

Screen 8.23

 

The line of code where the exception occurs is at int 0.  This is followed by a really large description of the exception including the string passed to the exception.

 

The last parameter is an enum dbgExceptionAction that notifies the system how the exception should be handled. It takes one of the four values. The value passed asks for the default action dbgExceptionActionDefault to be carried. The others are dbgExceptionActionIgnore, dbgExceptionActionBreak and dbgExceptionActionContinue.

 

The event OnExceptionThrown does not get called ever even when the throw clause is placed in the try and catch statements.

 

Now introduce one small addition to the function dOnExceptionNotHandled.

 

public void dOnExceptionNotHandled(string e, string n, int c, string d, ref dbgExceptionAction a)

{

op.OutputString("OnExceptionNotHandled-" + e + "-" + n + "-" + c.ToString() + "-" + d + "-" + a.ToString() + "-\n");

a = dbgExceptionAction.dbgExceptionActionBreak;

}

 

The dialog box is not displayed as the last ref parameter a of type dbgExceptionAction is set to a value dbgExceptionActionBreak. The default value is dbgExceptionActionDefault which displays the dialog box.  The value of dbgExceptionActionBreak sets the option to Break, thus avoiding any clicks required on the button Break in the Dialog box. Thus, the entire workings of Visual Studio.Net can be predecided and programmed.

 

Add-ins can possess extender properties, whereby properties could be added to the solution and to the other windows. However, to facilitate this, the registry needs to be modified, and certain keys containing specific data and values need to be inserted.

 

We have decided to wrap up this book at this juncture. Thereby, we have effectively postponed the explanation of this imperative task to our forthcoming book. We have been compelled to do so, because only by providing a comprehensive explanation, would we be able to do full justice to this topic.