Why would I ever need to use threading, or asynchronous processing, in an add-in? Better yet, how do I get the Code Window for a Class or Form, that was just added to the current project, to open and become the active window in the Visual Studio IDE? I want to automatically place code in the new code window. The answer is, since there are timing issues involved, you must use threading. The timing problem is caused by the fact that at the time the ProjectItemsEvent fires, the new item has not yet been placed in the Solution Explorer and it must be there before we can open its code window.
The first thought might be to do a DoEvents call to give the system time to list the new object in the explorer. Although a simple solution, it will not work because DoEvents allows Windows to handle waiting events, but it does not return control from your add-in to the IDE. That only happens when you exit from the event handler, and you must return to the IDE, from the add-in, for the desired results to happen. By then, the add-in has given up control and will not be called again by the IDE. Starting another thread in the add-in allows you to get control after a time period, so that when the add-in get control again, the new project item will be in the Solution Explorer. I am not actually using the System.Threading object, rather I am simply creating an asynchronous object that will wait for 100ms and then its Timer will expire causing it to run. In the mean time I will return to the IDE so that the IDE can insert the new ProjectItem into the Solution Explorer.
When an object, such as a Class or Form is added to a project, you can be notified of the event by setting up event handlers for ProjectItemsEvents in the Connect class of your add-in. First, you need to declare the following objects in the declarations section of your Connect class.
| Public WithEvents eventsPIVB As EnvDTE.ProjectItemsEvents Public WithEvents eventsPICSharp As EnvDTE.ProjectItemsEvents Public Shared StopAutoEvents As Boolean |
| ' sink the event handlers for events events = oVB.Events eventsPIVB = oVB.Events.GetObject("VBProjectItemsEvents") eventsPICSharp = oVB.Events.GetObject("CSharpProjectItemsEvents") |
| Private Sub eventsPIVB_ItemAdded(ByVal ProjectItem As EnvDTE.ProjectItem) _ Handles eventsPIVB.ItemAdded Try If StopAutoEvents Then Exit Sub If ProjectItem.Name.ToLower.IndexOf(".resx") > -1 Then Exit Sub StopAutoEvents = True PI = ProjectItem Dim o As New CWindowTimer(oVB) Catch ex As System.Exception StructuredErrorHandler(ex) End Try End Sub Private Sub eventsPICSharp_ItemAdded(ByVal ProjectItem As EnvDTE.ProjectItem) _ Handles eventsPICSharp.ItemAdded Try If StopAutoEvents Then Exit Sub If ProjectItem.Name.ToLower.IndexOf(".resx") > -1 Then Exit Sub StopAutoEvents = True PI = ProjectItem Dim o As New CWindowTimer(oVB) Catch ex As System.Exception End Try End Sub |
| Imports System.Timers Public Class CWindowTimer Private oVB As EnvDTE.DTE WithEvents WindowTimer As New System.Timers.Timer() Public Sub New(ByRef roVB As EnvDTE.DTE) oVB = roVB Me.WindowTimer.Interval = 100 WindowTimer.Enabled = True End Sub Private Sub WindowTimer_Elapsed(ByVal sender As Object, _ ByVal e As System.Timers.ElapsedEventArgs) _ Handles WindowTimer.Elapsed Static busy As Boolean Try If busy Then Exit Sub busy = True OpenRequestedCodeWindow(Connect.PI) Dim i As Integer ' next call returns string("VF"|"VC"|"CF"|"CC") ' depending on language and form or class being added to project Dim s As String = GetCodeWindowTypeAndLanguage If s.StartsWith("V") Then i = 8 ElseIf s.StartsWith("C") Then i = 9 Else Me.WindowTimer.Enabled = False Exit Sub End If s = oVB.ActiveWindow.Caption If IsProjectItemAForm(s) Then s = "F" Else s = "C" End If ' Process of adding code to the window can go here, ' the code window is open and active. ' shut the timer down and since this object was created ' with a local variable in the event handler, it should ' now go away... Me.WindowTimer.Enabled = False Me.Finalize() StopAutoRegions = False busy = False Catch ex As System.Exception StructuredErrorHandler(ex) busy = False CRegions.StopAutoRegions = False End Try End Sub Protected Overrides Sub Finalize() MyBase.Finalize() On Error Resume Next WindowTimer.Enabled = False WindowTimer = Nothing End Sub End Class |
| Public Shared Function OpenRequestedCodeWindow(ByRef pi As ProjectItem) _ As Boolean ' Open the rsWin window if not already open. ' rsWin ="sln.name\prj.name\winname.vb" Dim prj As Project Dim sln As String = oVB.Solution.Item(1).Name Dim FN As String Dim bOpen As Boolean = False DoEvents() On Error Resume Next prj = GetActiveSolutionProject() If IsCodeWindow(pi.Name) Then Dim pn As String = prj.Name Dim s As String = pi.Name.ToString Dim s2 As String = sln & "\" & pn & "\" & s bOpen = pi.IsOpen oVB.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate() oVB.ActiveWindow.Object.GetItem(s2).Select( _ vsUISelectionType.vsUISelectionTypeSelect) oVB.ActiveWindow.Object.DoDefaultAction() DoEvents() oVB.ExecuteCommand("View.ViewCode") DoEvents() End If Return bOpen End Function |
| Public Function IsProjectItemACodeWindow(ByRef pi As ProjectItem) _ As Boolean Try If pi.FileCodeModel Is Nothing Then Return False Else Return True End If Catch Return False End Try End Function |
| Public Function IsProjectItemAForm() As Boolean Dim oWin As Window = oVB.ActiveWindow Return TypeOf oWin.Object Is System.ComponentModel.Design.IDesignerHost If TypeOf oWin.Object Is System.ComponentModel.Design.IDesignerHost Then Return True Else Return False End If End Function |
| Public Function GetActiveSolutionProject() As Project ' Gets currently selected project and ' return the project to the caller. Dim projs As System.Array Dim proj As Project Dim projects As Projects Try projs = Connect.oVB.ActiveSolutionProjects() If projs.Length > 0 Then proj = CType(projs.GetValue(0), EnvDTE.Project) Return proj End If Catch ex As System.Exception StructuredErrorHandler(ex.ToString) End Try End Function |