7.
Add-ins
This section of the book
appertains to extending Visual Studio.Net using Add-ins. The issue of what is
an Add-in and what we could do with it, is grist for another day. It should
suffice to say that the Add-in is the launch pad for any programmer who wishes
to extend Visual Studio.Net in myriad ways.
Everything about Visual
Studio.Net can be transformed or modified by the programmer. Visual Studio.Net
is bound to reign as the most extensible product in the world for a
considerable span of time. This product can be extended using the principles of
Automation.
The first step in this direction
leads to Add-ins, a point from where we shall initiate our exploration. Not all
of us are blessed with exceptional brilliance and grasp. Keeping this
deficiency in mind, we shall press forward one step at a time.
When the Visual Studio.Net
program starts normally, it displays the Start Page and the menu option of
Tools. Click on this menu option to arrive at screen 7.1.
|
|
Screen 7.1 |
Screen 7.2 |
Contingent upon how you
configure your copy of Visual Studio.Net, your output is bound to vary. In all
likelihood, you would not encounter a screen similar to ours. However, you shall
surely come across the menu option of Add-In Manager, which is to be clicked
on. This result of doing so is evidenced in screen 7.2.
An active project is required to
work with Add-ins. Therefore, a new project has been created ahead of time. To
reiterate, the basic format of the dialog box remains the same, although the
contents of your screen may appear disparate from ours. Our screen projects an
Add-in that has been installed previously.
In order to install another
Add-in, certain fundamental criteria need to be conformed to, so as to enable
the Visual Studio.Net framework to recognize it, and to thereafter keep track
of it. Any program running under
Windows, including Windows itself, maintains its state by storing data in a
hidden file called the registry. This file subsists on every hard disk of every
computer that has the Windows operating system installed on it.
This registry file has its own
unique format. Neither is it an XML file nor an ASCII file. The registry simply
stores name-value pairs in a specific format to facilitate quick and speedy
retrieval. There exists a set of .Net classes that permit us to read and write
to the registry. Furthermore, there is a program called Regedit, which allows
access to the data in the registry.
However, a file with a .reg
extension has to be created in order to enter data manually. The contents of
this file must necessarily be in name-value pairs. The program when executed
Regedit displays the registry data, as seen in screen 7.3.
|
Screen 7.3 |
The program window displays
queer names such as HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER, etc. These names represent categories by means of
which, the name-value pairs can be systematically organized. This concept is akin
to having namespaces to organize C# classes. What remains a mystery is the
motive that impels names to begin with the word HKEY!
It is the registry that has
provided the foundation for Windows since its inception. HKEY_CLASSES_ROOT
simply serves as a category name or a key under which name-value pairs can be
stored. The same applies to the key HKEY_CURRENT_USER, which in turn, houses-in
some more keys within itself.
|
Screen 7.4 |
One of the keys is named as
Software. It has a plus sign, which when clicked on, unravels a large number of
keys from the Software category. The key named Microsoft contains a host of
other keys. Clicking on the plus sign brings forth the keys within it.
Visual Studio evinces two keys,
viz. 6.0 and 7.0. This amply fortifies the fact that internally, Visual
Studio.Net is recognized as Visual Studio 7.0. Clicking on the plus sign
exhibits a sub-key named Add-ins, which has no keys at all, as seen in screen
7.5. Now, add a name-value pair to this level.
|
Screen 7.5 |
a.reg
REGEDIT4
[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\7.0\AddIns\
aa.Connect]
"FriendlyName"="vijay"
"Description"="C#"
Enter the above contents in the
ASCII file named a.reg and store it in the folder c:\a2. Since we are seasoned
warhorses in the world of computers, we use the DOS box to execute the file
a.reg. This can be simply achieved by giving the command
C:\a2>a.reg {Enter}
If you have an aversion to the
DOS box, you could use the File Manager and simply double-click on the file
a.reg. A message box bobs up, as shown in screen 7.6. This eventuates since
Windows inherently believes that the reg file was clicked upon inadvertently.
|
|
Screen 7.6 |
Screen 7.7 |
Clicking on the Yes button
results in the execution of some activity internally, and finally, the message
box confirms successful registration, as depicted in screen 7.7.
Refresh the Regedit window. The
Add-ins key now shows a plus sign. Click on it to view the updated window, as
has been brought out in screen img193. The key that is added is aa.Connect,
which now holds two name-value pairs, as is apparent in the right window pane.
The names are Description and FriendlyName, having values vijay and C#,
respectively.
|
|
Screen 7.8 |
Screen 7.9 |
We now hark back to Visual
Studio.Net and click on the menu Tools, Add-in Manager. This projects the
Screen 7.9.
The window now displays the
freshly added Add-in called vijay, with the Description of C#. Thus, from the
perspective of the Add-in Manager, an Add-in is merely a registry entry.
Let us now fathom the inner
workings of the file a.reg. (If the Add-in is not visible, run the command regedit
a.reg). The Regedit program deems a reg file to be valid only if it starts with
the word REGEDIT4 in the first line. To substantiate this logic, delete this
line from the a.reg file. An error gets reported, as is evident in screen 7.10.
|
Screen 7.10 |
In the case of ASCII files, a
specific file format needs to be adhered to, in order to maintain the validity
of the file format. The Regedit program employs the word REGEDIT4 as an
identification tag for registry files. Blank lines are disregarded. Therefore,
no error is generated. What ensues is the name of the key enclosed within
square brackets. The full path name of the key under which the new entry is to
be added, is stipulated in the form of name-value pairs.
Thus, the reg file has the
starting key HKEY_CURRENT_USER, which culminates in aa.Connect. This results in
the Add-ins key acquiring a sub-key called aa.Connect. The name-value pairs are
stipulated below the name of the key. Here, the name appears first, i.e.
FriendlyName, followed by an 'equal to' sign, and finally, followed by the
actual value, i.e. vijay.
One of the major detriments here
is that there are a number of feckless rules. For instance, the name-value
pairs have to be specified within double inverted commas. However, even if this
rule is not complied with, no errors are reported. All the same, the name-value
pairs get overlooked by the Regedit program.
The name does not have to end
with Connect. But, since all the Add-ins have resorted to this word, we too
decided to follow suit, probably as a good luck charm. We now return to the
Add-in Manager in Visual Studio.Net. Here, the name of the Add-in is vijay.
This is because, the name FriendlyName in the registry, has a value of vijay,
while the value of the name Description is used as a help.
Thus, as far as the Add-in
Manager is concerned, an Add-in is merely a registry entry. The word aa.Connect
is called a ProgID. The ProgID bears considerable significance, which we shall
touch upon in a short while. Conclusively, all that the Add-in Manager does is,
to hunt for sub-keys, by looking under the key HKEY_CURRENT_USER \ Software \
Microsoft \ VisualStudio \ 7.0 \ AddIns. Currently, it ignores the name or the
ProgID of the keys, but it displays the FriendlyName of each one of them.
Select the Add-in vijay by
clicking on the checkbox, as is shown in screen 7.11, and then, click on the OK
button. An error gets reported as shown in screen 7.12, since the Add-in has
not been entered yet.
|
|
Screen 7.11 |
Screen 7.12 |
Here, we select the option of
No, because had we chosen the option of Yes, we would have had to run the reg
file a.reg yet again. Thus, here, we have merely been able to convince the
Add-in Manager that vijay is a valid Add-in. Let us now actually start writing
the Add-in.
|
Screen 7.13 |
An Add-in is nothing but a dll.
Therefore, a simple project is created, which results in a dll. This is
achieved by clicking on the menu-options
File, New, Project. Then, you need to choose Visual C# as the project
type and select Class Library Template as the Template. The project is named as
vij20.
using System;
namespace vij21
{
using System.Runtime.InteropServices;
[GuidAttribute("66ABBE67-F973-45E6-BD09-A18877323888"), ProgId("aa.Connect")]
public class zzz
{
}
}
The class library template
builds a dll, which could have also been created manually with effortless ease.
Replace the existing code with the code displayed above.
As usual, the program commences
with the namespace, since Microsoft insists on having the entire code enclosed
within a namespace. The namespace is called vij21. There is no co-relation
between the name of the dll and that of the namespace. Therefore, we have opted
for different names for the dll and the namespace.
A simple class called zzz is
placed in the namespace. It is prefaced with an attribute called GuidAttribute,
which belongs to the namespace System.Runtime.InteropServices. The attribute is
assigned two parameters; a 16 byte number and a name or a ProgID, viz.
aa.Connect. This introduces a link with the a.reg file, where the ProgID is
again named as aa.Connect. Thus, to register an Add-in, we merely have to
specify the ProgID as aa.Connect.
Eons ago, Microsoft introduced a
concept called Activex/OLE/COM. Although this is not the most opportune time to
delve into the details of COM, it suffices to say that a COM object is
identified by a 16 byte or 128 bit number, which distinguishes it from all the
other existing COM objects. A 16 byte number is unique across time and space,
since it is truly colossal in size.
The class zzz is assigned a
unique number by placing the GuidAttribute in front of it. This number is also
known as a GUID or a UUID. The acronyms GUID and UUID stand for 'Globally
Unique Identifier' and 'Universally
Unique Identifier', respectively.
There are programs available
with Windows, which can generate a GUID of this ilk. However, we have chosen a
number at random. There is a project template that creates an Add-in, which in
turn generates these GUIDs mechanically. The format for writing a GUID is also
predetermined.
Thus, so far, the class zzz has
been assigned a unique 16 byte number, in order that it may be able to
interoperate with COM. The ensuing step is to add some registry entries, so
that class zzz gets registered as a COM class.
The class zzz has been tagged
with the ProgID of aa.Connect and assigned a GUID. The Add-in Manager scans the
registry for a ProgID called aa.Connect. Then, it scouts for the assembly/dll
vij20.dll, which contains the class zzz. We are truly clueless about the
precise registry entries that are required to be added to the registry for COM
interoperability.
Click on the project name vij20
in the Solution Explorer with the right mouse button, as demonstrated in screen
7.14. (If the Solution Explorer is not active, choose the menu option View,
followed by the Solution Explorer.)
|
|
Screen 7.14 |
Screen 7.15 |
In the menu, select the last
option of properties to arrive at the screen 7.15.
Here, the name of the dll is
denoted as vij20 along with several other details. The options that are of
interest are located within the item Configuration Properties. When you double
click on it, the options that show up are displayed in screen 7.16.
|
Screen 7.16 |
The option that we are in quest
of, viz. 'Register for COM interop', exposes itself. The default value is
False. Click on the drop-down list and set the value to True.
Now, build the project by
pressing the key combination of Ctrl-Shift-B. A few extra lines get added in
the Build window, which postulate that a program is being executed in order to
add some registry entries.
|
Screen 7.17 |
After having registered the
class zzz as a COM object, close Visual Studio.Net and restart it. The
application must be closed and reloaded since the Add-in and the active project
happen to be one and the same entity. Also, if the dll is created without
reloading the application, multiple errors materialise.
Now, click on Tools, Add-in
Manager and select the Add-in called vijay.
Click on the OK button. The screen 7.18 clearly brings to view a
different error that has been generated by the Add-in Manager.
|
Screen 7.18 |
The class zzz has been derived
from an object, whereas it should have actually been derived from an interface.
Hence, the error is generated.
namespace vij21
{
using System.Runtime.InteropServices;
using Extensibility;
[GuidAttribute("66ABBE67-F973-45E6-BD09-A18877323888"), ProgId("aa.Connect")]
public class zzz : Extensibility.IDTExtensibility2
{
}
}
Add two more lines to the above
program. Firstly, include a namespace called Extensibility, and secondly,
derive the class zzz from the interface named IDTExtensibility2. When we attempt
to build the project, two errors emerge. The C# complier feigns ignorance about
the assembly containing the namespace Extensibility.
While using the C# complier, the
/R option has to be specified with the assembly name. However, in the Visual
Studio.Net framework, the reference has to be added by clicking the right mouse
button on the References item, as seen in screen 7.19. Thereafter, you need to
choose the Add Reference option. This brings us to screen 7.20.
|
|
Screen 7.19 |
Screen 7.20 |
The window that pops up
encapsulates a large number of namespaces. Scroll down the list and select the
one named extensibility. Double click on it to select it, as shown in screen
7.21. The namespace that is selected gets placed at the bottom. Finally, click
on the OK button and close the dialog box. The window undergoes a
transformation, as seen in screen 7.22.
|
|
Screen 7.21 |
Screen 7.22 |
The references item displays the
Extensibility namespace, which has been added recently. It also contains the
three namespaces of System, System.Xml and System.Data, which are already present
by default. Now, build the project.
An error was bound to occur. It
is because the interface IDTExtensibility2 encompasses five methods, whereas
the project has not implemented even a single one of these.
namespace vij21
{
using System.Runtime.InteropServices;
using Extensibility;
[GuidAttribute("66ABBE67-F973-45E6-BD09-A18877323888"), ProgId("aa.Connect")]
public class zzz : Extensibility.IDTExtensibility2
{
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom){}
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){}
}
}
Insert the above code in the
Code Painter to eliminate the errors. Then, select the Add-in named vijay from
the Add-in Manager dialog box. Nothing at all happens! Well, this indeed is a
good omen and calls for a celebration, since it means that everything has gone
off smoothly. It also goes to prove that the dll named vij20 has been
recognized as a full-fledged Add-in.
The next task is to identify the
particular functions that get called in the Add-in by the Add-in Manager, and
also, when exactly do they get called. For this purpose, a static function
named Show from the class MessageBox is utilized. This class is present in the
namespace of System.Windows.Forms. Hence, the assembly has to be added to the
Reference section.
As was done earlier, click the
right mouse button on the References item. Then, choose Add Reference, and
thereafter, scroll down to the end of the dialog box to select the option of
System.Windows.Forms. Then, double click on the namespace, as depicted in
screen 7.23; and finally, click on OK.
|
Screen 7.23 |
namespace vij21
{
using System.Runtime.InteropServices;
using Extensibility;
using System.Windows.Forms;
[GuidAttribute("66ABBE67-F973-45E6-BD09-A18877323888"), ProgId("aa.Connect")]
public class zzz : Extensibility.IDTExtensibility2
{
public zzz() {
MessageBox.Show("Constructor");
}
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{
MessageBox.Show("OnConnection");
}
public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
{
MessageBox.Show("OnDisconnection");
}
public void OnAddInsUpdate(ref System.Array custom)
{
MessageBox.Show("OnAddInsUpdate");
}
public void OnStartupComplete(ref System.Array custom)
{
MessageBox.Show("OnStartupComplete");
}
public void OnBeginShutdown(ref System.Array custom)
{
MessageBox.Show("OnBeginShutdown");
}
}
}
What should follow next is the
introduction of the constructor and the Show function in the six functions.
Now, whenever any of the functions gets called, the Message Box projects
itself.
Even though we are convinced
that you require no further reminders, we still reiterate that you should close
Visual Studio.Net, restart it and then, build the project. Now, select the
Add-in named vijay from the Add-in Manager, and click on the OK button. The
screen shows a message box, as is obvious from screen 7.24.
|
|
|
Screen 7.24 |
Screen 7.25 |
Screen 7.26 |
The Add-in Manager loads the
Add-in, and in the process, it beckons the constructor. Clicking on OK bespeaks
of the function OnConnection being called. This is followed by a call to the
function OnAddinsUpdate. These functions have been featured in screens 7.25 and
7.26.
When an Add-in is loaded, the
first function to be called is OnConnection. There are three plausible means by
which an Add-in can get loaded in Visual Studio.Net. The first method is by the
application of the Add-in Manager, as illustrated above. The second method
pertains to checking of the Startup checkbox in the Add-in Manager. The third method is by setting the value of the
Connect property of the Add-in to True.
Most Add-ins place the code for
initialization in the function OnConnection. The second function to be called is
OnAddInsUpdate. This function is summoned whenever a modification is effected
in the list of Add-ins in the Add-in Manager. This could occur whenever an
Add-in is either loaded or unloaded.
There are alternate means of
loading and unloading an Add-in besides resorting to the use of the Add-in
Manager. One such method includes the usage of a button on a toolbar named
Command.
There is a distinctive feature
of Add-ins that needs to be comprehended. If one more Add-in is loaded, the
function OnAddInsUpdate gets called for the newly loaded Add-in, as well as for
all other Add-ins that are already loaded.
When any Add-in is loaded or
unloaded, the function OnAddInsUpdate is called for all the Add-ins that are
already loaded. This is primarily done to apprise the Add-ins that a change has
occurred. Thus, the OnConnection method gets called only once, whereas the
OnAddInsUpdate function gets called on multiple occasions.
On closing Visual Studio.Net,
the function OnBeginShutdown gets called. This event gets called whenever
Visual Studio.Net begins to shutdown. Further, this function gets called only
if the Add-in is loaded in memory. Therefore, if some code needs to be called
when a running application is shut down, it can be placed in this function. One
such instance is when all activities that take place in a particular session
need to be logged to a file on disk.
This function may be summoned
more than once. To site an instance, it gets called when some program cancels
the event of calling the Add-in, and also when the application shuts down.
Thus, there is no guaranteeing the fact that the application would actually
close after this function gets executed.
The last function to be called
is OnDisconnection. This function is analogous to the OnConnection function.
This function can be called in three different events. It can be attributed to
the fact that an Add-in can be unloaded in three different ways.
The first is by un-checking the
checkbox in the Add-in Manager; the second is by closing Visual Studio.Net; and
the third is by resetting the Connect property of the Add-in object to False.
The OnConnection function has a companion in OnDisconnection.
When Visual Studio.Net is
restarted, it does not show any message box. You need to click on the menu Tools,
Add-in Manager, in order to get a better drift of the Event Model. Then, select
the checkbox of Startup for the Add-in vijay. Now, close and restart Visual
Studio.Net.
|
|
|
|
Screen 7.27 |
Screen 7.28 |
Screen 7.29 |
Screen 7.30 |
The Constructor gets called
first, as seen in screen 7.27; and then, the function OnConnection gets called,
followed by the function OnAddinsUpdate. Subsequently, Visual Studio.Net gets
loaded, and finally, OnStartupComplete gets called, as is evident from screen
7.30. This event is the last in the Add-in series and is an indication that the
application has completed executing its startup code.
The function OnStartupComplete,
which we discoursed about earlier, does not get called when the Add-in Manager
calls the Add-ins. It is because the function gets called only once, when the
Startup checkbox is checked. This function embodies code that gets executed
after the application has been loaded. You may uncheck the startup option to
validate its implications.
By now, we are sufficiently
informed about the precise occasions when, and the frequency with which these
functions get called from the class. Firmly grounded on this knowledge, we
shall now venture forth to explore the vitals of Visual Studio.Net.
To begin with, let us build the
same Add-in, but by employing a Wizard this time. Click on the menu File, New,
Project. In this instance, choose the Other Project option, and within this
option, select the Extensibility Project option. Then, select the Visual
Studio.Net Add-in option in the second pane and name it as vij25, as
demonstrated in screen 7.31.
|
Screen 7.31 |
Once the OK button has been
clicked upon, the Wizard screen emerges. The initial screen of the Wizard
should always be disregarded. So, click on the Next button.
Screen 7.32 represents the first
of six screens that constitute the Add-in Wizard. This screen determines the
programming language in which the code of the Add-in is to be written. Three
choices have been spread before us, viz. Visual C#, Visual Basic and Visual
C++. We are content with the default option of Visual C#. So, click on the Next
button to arrive at screen 7.33.
|
|
Screen 7.32 |
Screen 7.33 |
The Add-in can be loaded in two
applications, viz. the Visual Studio.Net IDE, which is the environment that we
are currently using; and in the IDE that is employed while writing macros. Both
applications are displayed as selected. Since this is acceptable to us, click
the Next button to arrive at screen 7.34.
|
|
Screen 7.34 |
Screen 7.35 |
This screen poses the same two
questions that apply to the a.reg file. The first question is with regard to
the name of the Add-in, while the second question relates to the description.
Change the name of the Add-in to vij25 and click on the Next button to arrive
at screen 7.35.
A few more Add-in options can be
selected, as depicted in screen 7.35. We have taken a conscious decision not to
opt for any of them, since we are opposed to augmenting the code generated by
the Wizard.
When we click on the Next
button, we get transported to screen 7.36, where the requirement for an About
box is ascertained. Yet again, we reply in the negative for the same reasons
specified earlier and we click on the Next button.
|
|
Screen 7.36 |
Screen 7.37 |
The screen that now floats
before the eyes is the last screen of the Wizard, as seen in screen 7.37. This
is merely a summary of what has been selected so far. We have the option to
revert back and modify some of the options. Clicking on the Finish button darts
us back to the Visual Studio.Net IDE.
Now, build the solution, and
then, activate the Add-in Manager, as depicted in screen 7.38.
|
Screen 7.38 |
Here, it is amply evident that
the Add-in called vij25 has been added to the registry. Moreover, in the
project options, the COM interop option has been set to True.
Finally, let us size up the code
generated by the wizard, sans the comments.
Connect.cs
namespace vij25
{
using System;
using Microsoft.Office.Core;
using Extensibility;
using System.Runtime.InteropServices;
using EnvDTE;
[GuidAttribute("0B3AB622-23B2-490C-9023-779CB76C0786"), ProgId("vij25.Connect")]
public class Connect : Object, Extensibility.IDTExtensibility2
{
public Connect()
{
}
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{
applicationObject = (_DTE)application;
addInInstance = (AddIn)addInInst;
}
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)
{
}
private _DTE applicationObject;
private AddIn addInInstance;
}
}
Since the project was assigned
the name of vij25, the entire code is encompassed in a namespace with the same
name of vij25. There are five namespaces that get created. Further, six
references are added by the template that creates the project.
The GuidAttribute has a GUID,
which is generated by a well-defined algorithm located in the public domain. A
different GUID is generated each time a new Add-in Project is created. The
Wizard creates the ProgID by concatenating the name of the namespace and the
word Connect. The user never interacts directly with a ProgID.
As mentioned earlier, the word
ProgID is seemingly a good luck charm. Since the name of the class has been specified
as vij25, the Wizard adds the word Connect to it. Then, it derives the class
from the interface IDTExtensibility2, along with the Object. In any case, all
the classes are derived from Object.
The five functions that
constitute the interface are itemized, and two instance objects
applicationObject and addInInsatnce, of type _DTE and Add-in, respectively, are
defined. The final outcome is that the Add-in generated by the Wizard and the
previous Add-in bear a striking resemblance to each other. Now, it is for you
to make a choice between using the Wizard and doing the job manually.
If you heed our advice, we
recommend that you should first figure out exactly how the Wizard works. Once the
procedure is discerned, the process can be executed manually, since the Wizards
are time-consuming. You should bear in mind that the COM interop option first
un-registers the Add-in from the registry, and subsequently, registers it
again.
Finally, before we conclude this
chapter, let us pore over a simple Add-in, which creates a menu on being
loaded. Then, when the menu items are clicked, the Add-in displays a plain and
simple message box. To achieve this, load the project vij25 and modify the code
to the following:-
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
{
DTE applicationObject;
public Connect()
{
MessageBox.Show("Connect");
}
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{
applicationObject = (DTE)application;
CommandBars cb;
cb = applicationObject.CommandBars;
CommandBar ct;
ct = cb["Tools"];
CommandBarControls cs;
cs = ct.Controls;
try
{
CommandBarControl c;
c = cs.Add(MsoControlType.msoControlButton, 1, null,1, false);
c.Visible = true;
c.Caption = "Vijay Mukhi1";
Events ev;
ev = applicationObject.Events;
CommandBarEvents e;
e = ev.get_CommandBarEvents(c);
e.Click += new _dispCommandBarControlEvents_ClickEventHandler(abc);
}
catch (System.Exception x)
{
MessageBox.Show(x.ToString());
}
}
public void abc(object c, ref bool h, ref bool ca)
{
MessageBox.Show("Hell");
}
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 get underway by defining an instance
variable called applicationObject, which is of type or class DTE. This class
could also be written as _DTE. The object name is specified as
applicationObject, because the wizard has taken a fancy to using such
long-drawn names. Hence, we have made an exception here.
The class DTE is derived only
from object, which is the 'mother of all classes' in the Visual Studio
Automation Object Model. All classes used in the Automation world are sourced
from the DTE class. It is the counter-part of the class object. DTE stands for
Development Tools Extensibility. It is an integral part of the namespace EnvDTE
and the code of this class resides in the file dte.olb.
All code that is required to be
called just once, is posited in the function OnConnection, even though the
function OnAddInsUpdate gets called after OnConnection. Placing such code in
the function OnAddInsUpdate could mean a spot of bother, since it gets called
on more than one occasion, when another Add-in is loaded.
The first parameter of OnConnection
is the most crucial one, since it is of type DTE and not of object. This
parameter application is stored in an instance variable called
applicationObject, although in this case, it is not mandatory to do so. The
rationale behind this is that the other four functions in the interface
IDTExtensibility2 are not passed this DTE object. Hence, by saving it in an
instance variable, the object is made accessible to the other functions too.
The use of the object
applicationObject could have easily been circumvented, but since the Microsoft
samples resort to this technique, we decided to follow in their footsteps.
This DTE parameter is of utmost
significance, since it allows us to access and modify the Visual Studio
environment. The property CommandBars is used to add a menu. This provides a
CommandBars object, which is stored in the variable cb. Then, using this
CommandBars object, the menus of the Visual Studio.Net can be accessed.
This object is part of the
Microsoft Office type library code, which is stored in the file mso.dll.
Therefore, Microsoft's heart is set upon using the code of Office 2000 in
Visual Studio.Net, thereby making the programmer adept at writing Office code
as well.
The CommandBars represents all
the commands or the menus in Visual Studio. However, we wish to gain access to
only the menu Tools. It is because all Add-ins by convention are required to
place their menu items under the Tools menu.
The indexer approach is used for
this purpose. The indexer is one of the most potent concepts introduced in C#,
which facilitates retrieval of the items in a collection. Thus, using the
indexer, the object ct of type CommandBar is extracted from the CommandBars
object cb. You may have noticed the extra 's'. The object ct becomes a handle to
the entire Tools menu. Based on the similar principle, any other menu may also
be accessed.
The CommandBar object ct that
represents the Tools menu has a property Controls. This property provides
access to the CommandBarControls object cs. Any object, whose name ends with
the letter 's', represents a collection.
Fresh menu items can be added to
the Tools menu by using the Add function. This function returns a handle to the
recently added menu item in the Tools menu, which is then saved in a
CommandBarControl object. Thus, a CommandBarControls object is fabricated out
of CommandBarControl objects. The CommandBarControl object can be one of the 23
available types.
The Windows XP Developer's Guide
provides more details on the same. All Office applications under Windows employ
the same underlying technology for creating menus and toolbars.
The Command Bars Object model
has been created to utilize the knowledge acquired from one Office application
in all other applications. This signifies astuteness or good judgment, since
menus and toolbars are splitting images of each other in all the Office
applications.
There are a total of three types
of CommandBar objects that we need to focus upon, viz. toolbars, menus and
pop-up menus.
The first parameter to the Add
function is an enum named MsoControlType. It may possess the following values
for the different types of entities to be placed within a menu item: button
msoControlButton, textbox msoControlEdit, drop-down listbox msoControlDropdown,
combo box msoControlComboBox or pop-up menu msoControlPopup.
For the current application, the
various parameters are as follows:-
• The first parameter is an option of type Button.
• The second parameter is the object, which is set to the number 1.
• The third parameter may be passed on to the menu.
• The fourth parameter is the object before which this menu item is to show up. Changing this value will push the menu item down in the list of menu items.
• The value of the fifth and the last parameter determines whether the object is temporary or otherwise.
Next, the menu item object c,
returned by the Add function, is manipulated by setting certain properties. The
value of the Visible property is set to True, even though it already has a
default value of True. The Caption property determines as to what should get
displayed in the menu item.
At this stage, you may pause and
build the above solution. Then, activate the solution in the Add-in Manager.
The Tools menu will bring forth a new menu item right at the top, containing
the words Vijay Mukhi1, as shown in screen 7.39. If no value has been specified
for the Caption property, then an empty menu item gets projected.
|
Screen 7.39 |
All this may appear to be
majestic, but the primary mission is yet to be accomplished. We endeavour to
execute some of our indigenous code when this menu item is clicked on.
The DTE object has a read-only
property called Events of type Events, which offers access to all the Events in
the Automation model. This Events object has been safely stashed away in the
variable ev.
Events is an interface derived
from Idispatch. Seasoned programmers would surely recall that it had formed a
part of the OLE/COM world. The Automation Model employs the Events interface as
the starting point for the Event Handling.
The Events class has a property
called CommandBarEvents. It accepts a property or a menu item object, or even
the CommandBarControl, which has been appended to the Tools menu. On occasions
aplenty, the properties refuse to accept parameters. However, if they do, the
get_ prefix is then used to call the property.
Thus, ev.get_CommandBarEvents( c
) is employed to call the Get accessor of the property CommandBarEvents from
the Events class, and to pass it a parameter named 'c'. This property provides
access to a CommandBarEvents object, which represents the event-handling for
the freshly added menu option.
Thus, by resorting to this
property, a CommandBarEvents object is obtained, which contains a Click event
for an event called Click. This event is set to the function abc using the
delegate called dispCommandBarControlEvents_ClickEventHandler.
Thus, whenever the menu option
Vijay Mukhi1 is clicked on, the function abc gets called, which simply displays
a Message Box, as brought forth in screen 7.40.
|
Screen 7.40 |
Three parameters are passed to
this function abc:-
• The first parameter represents the CommandBar object.
• The second parameter signifies whether the click event has been taken care of or not.
• Finally, the third parameter determines whether the built-in command should be invoked or not.
In this case, there is no
built-in command, since we have inserted this menu item. This is how we can add
a menu item to the existing menus and activate the code present in the Add-in.
The 'try and catch' clause could well have been omitted. However, we have
included it with the sole purpose of identifying the cause and the location of
an exception that may occur while writing code.
In the Add function, which is used
to add a new menu item, replace the enum value to msoControlComboBox for the
first parameter, as follows:-
c = cs.Add(MsoControlType.msoControlComboBox , 1, null,1, false);
This modification eventuates in
the display of a combo box along with the words Vijay Mukhi1, as seen in screen
7.41.
|
Screen 7.41 |
The Add-in can be loaded in
myriad ways. One possibility is to load it through the Add-in Manager, while
the other is to load it automatically at startup. To implement this, remove the
abc function from the above program and replace the OnConnection function as
follows:-
public void OnConnection(object application, ext_ConnectMode c, object addInInst, ref System.Array custom) {
MessageBox.Show(c.ToString());
}
As elucidated earlier, the first
parameter is the most crucial one, since it represents the DTE object. The
second parameter c represents an enum ext_ConnectMode, which dwells in the
namespace EnvDTE. The message box merely displays its value.
Now, build the solution. The
subsequent step is to select it in the Add-in Manager, and also to select the
checkbox that reads 'startup'. The message box that emerges has a constant
value of ext_cm_AfterStartup or 0, as seen in screen 7.42.
|
Screen 7.42 |
This value corroborates the fact
that the Add-in has been loaded only after Visual Studio.Net was started. This
could have been accomplished either by using the Add-in Manager or by setting
its Connect property to True.
Now, shutdown and restart Visual
Studio.Net. This time, the message box display that is returned by the Add
function, illustrates the constant ext_cm_Startup with the value of 1, as seen
in screen 7.43. This validates the fact that the Add-in was loaded at startup.
|
Screen 7.43 |
There are four more approaches
by means of which an Add-in can be loaded. The value ext_cm_External or 2
signifies that the Add-in was loaded by an external entity, which may have been
a program or a component. The value 3 or ext_cm_CommandLine indicates that the
Add-in was loaded by running Visual Studio, using the command line devenv. The
Fourth approach would be to utilize the services of an Add-in. At this point,
the enum name is ext_cm_Solution and the value is 4. The final possibility is
ext_cm_UISetup or 5, which is employed when the Add-in starts for the very
first time after installation.