Restoring a Lost Command and Its User Interface
You might notice from time to time when you're developing an add-in (especially just after you've created the add-in) that the user interface for your command and the command itself have seemed to disappear. This happens because of the way the information for your command is stored. When Visual Studio .NET closes, all the information about the menu and toolbar placement, including the commands built into Visual Studio .NET and the ones you create, are saved to a file on disk. But if something happens that keeps Visual Studio .NET from saving this file, your commands and their user interface might be lost.For example, suppose you create an add-in using the Add-in Wizard and you start debugging the resulting project. If you close the debugged instance of Visual Studio .NET by choosing File | Exit (or by using any other way of closing an application gracefully), its toolbar information is saved to a file on disk. When you close the instance of Visual Studio .NET that you used to develop the add-in, its toolbar information is saved as well, but the information defining your toolbar placements that was generated by the instance being debugged is overwritten. Thus, you lose any of the command information your add-in created.You can lose this information in another way. Suppose you run the Add-in Wizard and generate an add-in, and then you start debugging that add-in by pressing the F5 key. Rather than closing down the debugged instance Visual Studio .NET (by choosing File | Exit or some other way), however, you choose to stop debugging in the debugger. That instance of Visual Studio .NET never has a chance to save its command information to disk, so that information is lost.
devenv /setup
You can re-create your command in a number of ways. First, you can close down all instances of Visual Studio .NET and from a command prompt window (the MS-DOS prompt), you can type devenv /setup. This will cause Visual Studio .NET to start and rebuild all the command bar information, removing any commands or CommandBar objects that were created by all add-ins. Running devenv /setup will also reset the appropriate flags to cause the add-in to rebuild its command information when Visual Studio .NET is started the next time.CautionUsing devenv /setup can also produce undesired side effects: when command information is regenerated, all commands created by add-ins as well as customizations of commands (such as the moving of a button from one command bar to another) will be lost.You can also re-create your command from the ResetCmdBarInfo add-in included with the book's sample files. ResetCmdBarInfo creates a command on the Tools menu. Choosing this menu item creates an instance of Visual Studio .NET that specifies the /setup command-line option. When Visual Studio .NET is done re-creating the toolbar information, it closes. This has the same effect as typing devenv /setup from the command prompt.
Custom Registration
Another way to re-create a command is to reset the CommandPreload flag (first discussed in Chapter 6) that's registered for your add-in during build time. If the add-in you're building is written using Visual C++, you can easily modify the registration code for your add-in to reset this flag within the .rgs file, but a .NET assembly is not registered like Visual C++ COM objects are. To do custom system registry manipulation when an assembly is registered as a COM object, you can use .NET attributes. The .NET Framework libraries contain two attributes located in the System.Runtime.InteropServices namespace, named ComRegisterFunctionAttribute and ComUnregisterFunctionAttribute. These attributes, when placed on a static (for C#) or shared (for Visual Basic .NET) public method that takes an argument of type System.Type, are called during the RegAsm phase of registering a .NET component as a COM object (which happens every time the add-in project is built). Therefore, you can insert code that looks like this into your add-in's class declaration:
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type t)
{
string progID = String.Empty;
foreach (System.Attribute attrib in t.GetCustomAttributes(false))
{
if (attrib.GetType().FullName ==
"System.Runtime.InteropServices.ProgIdAttribute")
{
ProgIdAttribute progIdAttrib = (ProgIdAttribute)attrib;
progID = progIdAttrib.Value;
}
}
if (progID != String.Empty)
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\VisualStudio\7.1\AddIns\" + progID,
true);
if (key != null)
{
if (((int)key.GetValue("CommandPreload", -1)) == 2)
{
key.SetValue("CommandPreload", 1);
}
}
key = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\VSA\7.1\AddIns\" + progID, true);
if (key != null)
{
if (((int)key.GetValue("CommandPreload", -1)) == 2)
key.SetValue("CommandPreload", 1);
}
key = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\VisualStudio\7.1\PreloadAddinState",
true);
if (key != null)
{
key.DeleteValue(progID, false);
}
key = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\VSA\7.1\PreloadAddinState", true);
if (key != null)
{
key.DeleteValue(progID, false);
}
}
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type t)
{
string progID = String.Empty;
foreach (System.Attribute attrib in t.GetCustomAttributes(false))
{
if (attrib.GetType().FullName ==
"System.Runtime.InteropServices.ProgIdAttribute")
{
ProgIdAttribute progIdAttrib = (ProgIdAttribute)attrib;
progID = progIdAttrib.Value;
}
}
if (progID != String.Empty)
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\VisualStudio\7.1\PreloadAddinState",
true);
if (key != null)
{
key.DeleteValue(progID, false);
}
key = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\VSA\7.1\PreloadAddinState", true);
if (key != null)
{
key.DeleteValue(progID, false);
}
}
}
So, what does this code do? The first few lines of code in each function find the class that the function is implemented within, using reflection. The code then walks the list of attributes of that class, searching for the attribute that sets the ProgID of the add-in when it is registered as a COM objectthe ProgIdAttribute attribute. Once this attribute is found, the ProgID for the add-in can be retrieved. The remainder of the code simply checks for certain registry values, which if present and set to the value 2 are reset back to 1. Currently, the code resets values if they are present for the Visual Studio .NET Macros editor and Visual Studio .NET. If one or the other application isn't suitable for your application, you should remove that registry check. Also, if more applications based on Visual Studio .NET technology become available and you set your add-in to load for that application, you should add an appropriate check for that application's registry settings. You can find code that demonstrates resetting these flags in the ReRegisterCS add-in included with the book's sample files.NoteThis registration method will repair only a broken command creation and won't fully create all the necessary add-in registration settings. You can use this method to add your own registration code to register the add-in, but our example here doesn't do that.These methods are useful for another situation besides repairing a lost command: moving the command bar user interface. Suppose you used the Add-in Wizard to create the default add-in code that places a button on the Tools menu. During development, however, you decide that the user interface for your command belongs on a different command barfor example, the View menu. After rewriting the code to find the correct menu, you must reset the CommandPreload flag so the code will run to place the user interface elements.
Add-in PerformanceTo make sure your commands are created correctly and to work around the problems that we've discussed, you might be tempted to set your add-in to load on startup, call AddNamedCommand to create your commands after the add-in loads, and then call Delete to remove the commands when the add-in is unloaded. We advise you not to do this for one reason: performance. The Visual Studio .NET automation team designed commands so the user of an add-in is not penalized for having add-ins installed.Suppose a user has 10 add-ins installed. If each of these add-ins were set to load on startup, you'd have to create, initialize, and execute the code of 10 separate components just to have the user interface for these add-ins available to that user, even if she never uses the add-ins. Also, it might not seem that bad to have your add-in load on startup, but the user will notice the additional time it takes to start Visual Studio .NET. The problems with commands not being saved from one instance of Visual Studio .NET to the next is a problem only for the developer and will rarely, if ever, be seen by the user.A user once told Microsoft of a bug where Visual Studio .NET seemed to get slower over time, to where it was taking well over a minute to start. Microsoft looked for the standard performance-related problems, such as a badly fragmented disk drive, background processes that consume too much CPU bandwidth, and low memory conditions. The problem turned out to originate with an add-in that was set to load on startup and re-created its commands each time it was loaded. The third-party supplier of this add-in (which shall remain nameless) changed the code, and the user has been happily using the add-in ever since. |