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.