Welcome to MSDN Blogs Sign in | Join | Help

Visual Studio 2005 Automation Samples now available

The Visual Studio 2005 Automation Samples are now available for download at
http://msdn.microsoft.com/vstudio/downloads/code/automation/default.aspx

It includes new samples for VS 2005 and samples updated to use the new VS 2005 .AddIn mechanism. Please feel free to download and try them out!

Posted by hlong | 2 Comments

Loading VS Add-ins from URL

In Visual Studio 2005, managed add-ins are allowed to get loaded from a website as long as you enable it in Tools Options Add-in/Macros Security page and also fully trust the add-in assembly from the URL. To load an add-in from a URL, you need to:

1. Make sure in Tools Options Environment Add-in/Macros Security page,
"Allow Add-in compoments to load from a URL" is checked.

2. Use CasPol.exe to fully trust the add-in assembly from the URL.
The command is like "CasPol.exe -m -ag 1.2 -url http://myWebSite/myAddin.dll FullTrust"

(Check out the following blog entry to see more details with CasPol.exe:
http://blogs.msdn.com/shawnfa/archive/2004/12/30/344554.aspx)

3. Update the <Assembly> tag in the .Addin XML file.

The .Addin XML file looks like
<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
 <HostApplication>
  <Name>Microsoft Visual Studio</Name>
  <Version>8.0</Version>
 </HostApplication>
 <Addin>
  <FriendlyName>WebAddin</FriendlyName>
  <Description>WebAddin</Description>
  <Assembly>http://SomeWebsite/WebAddin.dll</Assembly>
  <FullClassName>WebAddin.Connect</FullClassName>
  <LoadBehavior>0</LoadBehavior>
  <CommandPreload>0</CommandPreload>
  <CommandLineSafe>0</CommandLineSafe>
 </Addin>
</Extensibility>

Also, if you are working on a Win2K3 server machine, make sure you have proper IE Security setting (go to IE Tools/ Internet Options/Security and add the website that hosting the add-in)

Posted by hlong | 18 Comments

VS Code Model events

Users of the Code Model may be alerted of significant changes of code by listening for events with a CodeModelEvents object. The CodeModelEvents object is in EnvDTE80, you get it by casting DTE.Events to EnvDTE80.Events2, e.g. in VB, it is like CType(DTE.Events, EnvDTE80.Events2).CodeModelEvents. These events will be fired as the languages rebuild the code model after edits have been made.  The timing and ordering of these events is not guaranteed to be consistent across different languages.  This object provides three events:  ElementAdded, ElementChanged, and ElementDeleted.

ElementAdded event
This event is raised when a CodeElement object has been created.  The new object is passed into the event handler. Although the object containing the new element is changed by the addition, no events are raised by the parent object.  For example, if a parameter is added to a function the ElementAdded event will be raised for the new CodeParameter object.  No events will be raised for the CodeFunction object that contains it.

ElementChanged event
This event is raised when a CodeElement object has been changed. Only one ElementChanged event is made for any given change in the code.  The "most local" object would raise the event.  For example, if a function's name is changed, the ElementChanged event would be fired for that CodeFunction object only.  (There would not be an event raised for the containing CodeClass object.)

ElementDeleted event
This event is raised when a CodeElement object is deleted. It is only raised for the outermost element that was removed.  For example, if the user deletes an entire class, the only ElementRemoved event will be only for the CodeClass object.

You can use following macros to play around with Code Model events by watching the output from OutputWindowPane "CodeModel Events Output" in the Output Window.

    Dim owpane As OutputWindowPane
    Public WithEvents myCMEvents As EnvDTE80.CodeModelEvents

    Private Sub myCMEvents_ElementAdded(ByVal Element As EnvDTE.CodeElement) Handles myCMEvents.ElementAdded
        owpane.OutputString("myCMEvents_ElementAdded - Element Name = " & Element.Name & "; Kind = " & Element.Kind.ToString() & vbCrLf)
    End Sub

    Private Sub myCMEvents_ElementChanged(ByVal Element As EnvDTE.CodeElement, ByVal Change As EnvDTE80.vsCMChangeKind) Handles myCMEvents.ElementChanged
        owpane.OutputString("myCMEvents_ElementChanged - Element Name = " & Element.Name & "; Kind = " & Element.Kind.ToString() & "  ChangeKind = " & Change.ToString() & vbCrLf)
    End Sub

    Private Sub myCMEvents_ElementDeleted(ByVal Parent As Object, ByVal Element As EnvDTE.CodeElement) Handles myCMEvents.ElementDeleted
        Dim cc As CodeElement = CType(Parent, CodeElement)
        owpane.OutputString("myCMEvents_ElementDeleted - Element Name = " & Element.Name & "; Kind = " & Element.Kind.ToString() & " Parent = " & Parent.ToString() & vbCrLf)
    End Sub

    Sub tryCodeModelEvents()
        owpane = GetOutputWindowPane("CodeModel Events Output")
        owpane.OutputString("CodeModel Events Testing starts:" & vbCrLf)
        Try
            myCMEvents = CType(DTE.Events, EnvDTE80.Events2).CodeModelEvents
        Catch ex As System.Exception
            owpane.OutputString(ex.ToString())
        End Try
    End Sub
    Function GetOutputWindowPane(ByVal Name As String, Optional ByVal show As Boolean = True) As OutputWindowPane
        Dim win As Window = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput)
        If show Then win.Visible = True
        Dim ow As OutputWindow = win.Object
        Dim owpane As OutputWindowPane
        Try
            owpane = ow.OutputWindowPanes.Item(Name)
        Catch e As System.Exception
            owpane = ow.OutputWindowPanes.Add(Name)
        End Try
        owpane.Activate()
        Return owpane
    End Function

Posted by hlong | 2 Comments

Code Model & its Interop with Text Editor Object Model

The Visual Studio Code Model can be used to read and edit the structure of the code in a file. The model gives you the ability to interact with files from different langauges in a common way without parsing the code. With this model, an automation client (e.g. add-in/macro) would start from a solution or project to get to a project item, and from the project item to get to the code model. Specifically, from a ProjectItem, you get to a FileCodeModel object that gets you to a CodeElements collection. At the FileCodeModel level, it contains a CodeElement object for each top-level code element in the file (e.g.  Implements statement, WithEvents declaration, etc).  At the class level, the CodeElements collection contains a CodeElement for each member of the class (e.g. a code function). You can QI (or cast in C# and VB) to get to more powerful language-specific interfaces.
A key functionality of code model is the ability to interop with the Text Editor OM. In other words, an automation client can go from the editor's automation model to a code model object and from a code model object to the start and end point of the code as text in the editor (for example, you have a Method object in hand, get its StartPoint, and inject a few lines of code, or you handle the LineChanged event, get the EditPoint in the event, and map it to a Method code object).
Following are some macro samples that demonstrate how to walk through the code model hierarchy of a solution and how to inteop with Text Editor Object Model.

    Sub walkThroughFileCodeModel()
        Dim projectItems As ProjectItems
        Dim projectItem As ProjectItem
        Dim fileCodeModel As FileCodeModel
        Dim codeElements As CodeElements
        Dim sRes As String = ""
        Dim level As Integer

        For Each proj As Project In DTE.Solution
            projectItems = proj.ProjectItems
            For Each projectItem In projectItems
                'Debug.WriteLine(projectItem.Name)
                fileCodeModel = projectItem.FileCodeModel
                level = -1
                If Not fileCodeModel Is Nothing Then
                    codeElements = fileCodeModel.CodeElements
                    codeElementChildren(codeElements, sRes, level)
                End If
                sRes += vbNewLine
            Next
        Next
        MsgBox(sRes)
    End Sub

    Sub codeElementChildren(ByVal codeElements As CodeElements, ByRef sRes As String, ByVal level As Integer)
        Dim i As Integer
        level += 1
        For Each clt As CodeElement In codeElements
            For i = 1 To level
                sRes += vbTab
            Next
            Try
                sRes += String.Format("{0}, Kind = {1}  Line:{2} ~ {3} ", clt.Name, clt.Kind, clt.StartPoint.Line, clt.EndPoint.Line)
            Catch ex As System.Exception
                If TypeOf clt Is EnvDTE80.CodeImport Then
                    Dim ci As EnvDTE80.CodeImport = CType(clt, EnvDTE80.CodeImport)
                    sRes += String.Format("{0}, Kind = {1}  Line:{2} ~ {3} ", ci.Namespace, clt.Kind, clt.StartPoint.Line, clt.EndPoint.Line)
                Else
                    sRes += String.Format("     , Kind = {0}  Line:{1} ~ {2} ", clt.Kind, clt.StartPoint.Line, clt.EndPoint.Line)
                End If
            End Try
            sRes += vbNewLine
            Try
                If Not clt.Children Is Nothing Then
                    codeElementChildren(clt.Children, sRes, level)
                End If
            Catch ex As System.Exception
                Debug.WriteLine(ex.Message)
            End Try
        Next
    End Sub

    Sub fromCodelModel2TextEditor()
        Dim fcm As FileCodeModel = DTE.ActiveDocument.ProjectItem.FileCodeModel
        Dim cns As CodeNamespace = fcm.CodeElements.Item("ClassLibraryCS")
        Dim cc As CodeClass = cns.Children.Item("Class1")
        Dim tp As TextPoint = cc.GetEndPoint 'cc.GetStartPoint
        Dim ep As EditPoint = tp.CreateEditPoint
        ep.Insert("blah")
    End Sub

    Sub fromTextEditor2CodeModel()
        Dim td As TextDocument = DTE.ActiveDocument.Object
        Dim cc As CodeClass = td.Selection.ActivePoint.CodeElement(vsCMElement.vsCMElementClass)
        Debug.WriteLine(cc.Name)
    End Sub

Posted by hlong | 3 Comments

Creating a ToolWindow hosting a .NET user control

In previous version of VS, if you want to create a ToolWindow hosting a .NET user control by using Windows.CreateToolWindow, you'll have to use a shim control to support it. Now with VS 2005, you don't need the shim any more. It is so handy by using Windows2.CreateToolWindow2, you pass the assembly location and the class name, you are able to host your .NET user control in the ToolWindow you created.
Following is a C# code snippet showing how to implement this in an add-in:

            Windows2 win = (Windows2)_applicationObject.Windows;
            object ctl = null;
            Guid g = Guid.NewGuid();
            System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
            Window myToolWin = win.CreateToolWindow2(_addInInstance, asm.Location, "MyToolWin.UserControl1", "My Tool Window", "{" + g.ToString() +"}", ref ctl);
            myToolWin.Visible = true;

One thing I want to point it out is when the User Control is not in the same assembly as the add-in, CreateToolWindow2 does not return a reference to the user control (ctl is null). You either need to move the control into the same assembly as the Add-in, or set all the attributes on the control to make it fully COM visible. Also, to make the ToolWindow show up, you need to set myToolWin.Visible = true.

 

Posted by hlong | 7 Comments

Displaying custom bitmap for VS add-in command button from satellite DLL

Add-ins can get resources such as strings, icons, and bitmaps from satellite DLLs which separates resources from your add-in. The first time I tried to get add-in resources from its satellite DLL, I easily find out how to do it for resources like strings and icons. But for bitmaps, it took me really a long time to figure out (for both native & managed add-ins). There indeed are some tricky steps that are hard to find out, so I thought it would be really good to blog it.

In previous verions of VS (2002 & 2003), add-ins used reg key values "SatelliteDllName" and "SatelliteDllPath" to store info for satellite DLLs. Now with Visual Studio 2005, you don't need to bother with registry for managed add-ins (although you still need it for native add-ins) any more. For resources like strings and icons, it is quite straightforward. To briefly state here, you simply prepend the resource name with the @ character in the .Addin XML file, in this way the add-in will look for the resource from its satellite DLL, e.g. <FriendlyName>@String1</FriendlyName>. "String1" is the resource name in the satellite DLL. One thing I want to point out is, for add-in Help/About box icon, you need to use tag <AboutIconLocation> instead of <AboutIconData>, like <AboutIconLocation>@Icon1</AboutIconLocation>, which may be confusing.

For resources like bitmaps, the way you get it from satellite DLLs is quite different and tricky. From my experience, It seems like if you miss one little step, you won't get it work and it is hard to figure out what's going wrong. What's more, the way you do it for native add-ins and managed add-ins is different. So I am going to list the steps in detail here for both native & managed add-ins respectively:

Creating a native satellite DLL with a custom bitmap and getting it from a native add-in:

    1. Create a C++ Win32 DLL
    2. Add a Resource File (.rc)
    3. In Resource View, add a bitmap (16 x 16), give it a numeric ID
    4. Build the DLL
    5. Create a subfolder "1033" (for English locale) in the native add-in DLL directory
    6. Copy the satellite DLL to "1033" directory

To get the bitmap show up with the command, make sure
    1. The bitmap must be 16 x 16 (True Color)
    2. Update AddNamedCommand2 in Connect.cpp with 'MSOButton' set to VARIANT_FALSE, and 'Bitmap' set to the bitmap ID
    3. Open AddIn.rgs and add two reg key values "SatelliteDllName" and "SatelliteDllPath" (see the example below)
    4. Rebuild the add-in to get the updated info registered

(NOTE: One thing you may miss here in 3. is: for "SatelliteDllPath" do not put the locale ID although it is part of the full path, it will be automatically appended at runtime)

Here is an example:

HKCU
{
 NoRemove 'SOFTWARE'
 {
  NoRemove 'Microsoft'
  {
   NoRemove 'VisualStudio'
   {
    NoRemove '8.0'
    {
     NoRemove 'AddIns'
     {
      ForceRemove 'NativeAddinCustBitmap.Connect'
      {
       val LoadBehavior = d 0
       val CommandLineSafe = d 0
       val CommandPreload = d 1
       val FriendlyName = s 'NativeAddinCustBitmap'
       val Description = s 'NativeAddinCustBitmap Description'
       val SatelliteDllName = s 'NativeAddinCustBitmapUI.dll'
       val SatelliteDllPath = s 'E:\CustomBitmap\NativeAddinCustBitmap\NativeAddinCustBitmap\Debug'
      }
     }
    }
   }
  }
 }
}

Creating a managed satellite DLL with a custom bitmap and getting it from a managed add-in:

    1. Add a Resources file to the managed add-in project, say "Resource1.resx" (set the Build Action to None)
    2. Add your bitmap (16 x 16) to the resource, give the bitmap a numeric ID
    3. Run "resgen Resource1.resx" to create "Resource1.resources"
    4. Run "al.exe /embed:Resource1.resources /culture:en-US /out:YourAddinName.resources.dll
    (use System.Globalization.CultureInfo.InstalledUICulture to get the correct culture, here I use 'en-US' as an example.)
    5. Create a subfolder named 'en-US' or the correct culture name in the add-in DLL directory
    6. Copy YourAddinName.resources.dll to the subfolder created in step 5.
       (One handy thing is that you don't even need to put satelliteDll info in the .AddIn XML file.)

To get the bitmap show up with the command, make sure:
    1. The bitmap must be 16 x 16 (True Color or 16 Color)
    2. The bitmap must have a name that is actually decimal number. The ResX designer in VS doesn't allow numeric IDs for resources. You have to edit the resx directly and ignore the warnings.
    3. Update AddNamedCommand2 with 'MSOButton' param set to false, and the 'bitmap' param set to the bitmap ID.

Hope I have made it clear.

Posted by hlong | 25 Comments
 
Page view tracker