Visual Studio 2010 的 CfgPropertyPagesGuidsAddCSharp 替代品

Alternative to CfgPropertyPagesGuidsAddCSharp for Visual Studio 2010

我正在尝试开发一个 VSIX 程序包,它为项目设计器添加了一个选项卡,用于几个自定义项目属性。我使用 CfgPropertyPagesGuidsAddCSharp 在 VS 2013 中运行包,如 this post 中所述。但是,将 VSIX 项目移植到 VS 2010 后,自定义 属性 页面未加载。

This question from 2011 doesn't have an answer. The answer to another question 建议创建一个自定义项目子类型,但这似乎是一个可怕的工作量,只是为了能够从 GUI 编辑一些额外的项目属性。这是我第一次使用 VSIX 程序包,所以我尽量让事情变得简单。

我已经尝试浏览 .NET project system 的源代码,但我不确定我要寻找什么才能正确注册该页面;任何指导将不胜感激。

谢谢。

前段时间我写了一篇 blog post 描述如何使用 CfgPropertyPagesGuidsAddCSharp 属性 添加自定义 属性 页面。也许你会发现它有用。

我最终为包创建了一个项目子类型,这比我预期的要容易。更大的挑战是找出一种在 VS2013 和 VS2010 包之间共享应用程序代码的方法,因为它们引用不同的 SDK 版本。我最终创建了两个单独的项目文件,并将共享代码作为 link 参考包含在每个项目中。

我根据 PropPageBase and PropPageUserControlBase 创建了自己的 IPropertyPage 实现。由于微软提供的代码比较复杂,我已经包含了该代码的一部分以供参考。

Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Diagnostics.CodeAnalysis
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports Microsoft.VisualStudio
Imports Microsoft.VisualStudio.OLE.Interop
Imports Microsoft.VisualStudio.Shell.Interop

Imports ControlPosition = System.Drawing.Point
Imports ControlSize = System.Drawing.Size

<ComVisible(True)>
Public MustInherit Class PropertyPageProviderBase
    Implements IPropertyPage, IDisposable

    Private ReadOnly _dirtyProperties As New Dictionary(Of String, String)()

    Private _control As Control
    Private _defaultSize As System.Drawing.Size?
    Private _hostedInNative As Boolean
    Private _objects As Object()
    Private _pageSite As IPropertyPageSite

    <SuppressMessage( _
        "Microsoft.Reliability", _
        "CA2006:UseSafeHandleToEncapsulateNativeResources", _
        Justification:="Handle is not owned by us, we are just tracking a reference")>
    Private _previousParent As IntPtr

    Protected Sub New()
    End Sub

    ' ...

    Protected Property [Property](propertyName As String) As String
        Get
            If String.IsNullOrEmpty(propertyName) Then
                If propertyName Is Nothing Then
                    Throw New ArgumentNullException("propertyName")
                End If
                Throw New ArgumentException( _
                    "Empty property name is invalid", _
                    "propertyName")
            End If
            Dim dirtyValue As String = Nothing
            If _dirtyProperties.TryGetValue(propertyName, dirtyValue) Then
                Return dirtyValue
            End If
            Return ReadProperty(propertyName)
        End Get
        Set(value As String)
            If String.IsNullOrEmpty(propertyName) Then
                If propertyName Is Nothing Then
                    Throw New ArgumentNullException("propertyName")
                End If
                Throw New ArgumentException( _
                    "Empty property name is invalid", _
                    "propertyName")
            End If
            If _objects IsNot Nothing Then
                _dirtyProperties.Item(propertyName) = value
                If _pageSite IsNot Nothing Then
                    _pageSite.OnStatusChange(PROPPAGESTATUS.DIRTY)
                End If
            Else
                Debug.Fail("Accessing property while not bound to project")
            End If
        End Set
    End Property

    ' ...

    Protected Overridable Sub Apply()
        If _objects Is Nothing Then
            If _dirtyProperties.Count <> 0 Then
                Debug.Fail("Cannot save changes. Not bound to project")
            End If
            Exit Sub
        End If
        For Each dirtyProperty As KeyValuePair(Of String, String) In _dirtyProperties
            WriteProperty(dirtyProperty.Key, dirtyProperty.Value)
        Next
        _dirtyProperties.Clear()
        If _pageSite IsNot Nothing Then
            _pageSite.OnStatusChange(PROPPAGESTATUS.CLEAN)
        End If
    End Sub

    ' ...

    Private Shared Function ContainsMultipleProjects(vsObjects As Object()) As Boolean
        Debug.Assert(vsObjects IsNot Nothing)
        If vsObjects IsNot Nothing AndAlso vsObjects.Length > 1 Then
            Dim first As IVsHierarchy = GetProjectHierarchy(vsObjects(0))
            For i As Integer = 1 To vsObjects.Length - 1
                Dim current As IVsHierarchy = GetProjectHierarchy(vsObjects(i))
                If current IsNot first Then
                    Return True
                End If
            Next
        End If
        Return False
    End Function

    ' ...

    Private Shared Function GetProjectHierarchy(vsObject As Object) As IVsHierarchy
        Dim hierarchy As IVsHierarchy = Nothing
        Dim itemId As UInteger
        Dim vsCfgBrowsable As IVsCfgBrowseObject = TryCast(vsObject, IVsCfgBrowseObject)
        If vsCfgBrowsable IsNot Nothing Then
            ErrorHandler.ThrowOnFailure(vsCfgBrowsable.GetProjectItem(hierarchy, itemId))
            Return hierarchy
        End If
        Dim vsBrowsable As IVsBrowseObject = TryCast(vsObject, IVsBrowseObject)
        If vsBrowsable IsNot Nothing Then
            ErrorHandler.ThrowOnFailure(vsBrowsable.GetProjectItem(hierarchy, itemId))
            Return hierarchy
        End If
        Throw New NotSupportedException("Unsupported VS object type")
    End Function 

    ' ...

    Private Shared Sub WriteProperty(vsObject As Object, propertyName As String, propertyValue As String)
        Dim hierarchy As IVsHierarchy = GetProjectHierarchy(vsObject)
        Dim buildStorage As IVsBuildPropertyStorage = TryCast(hierarchy, IVsBuildPropertyStorage)
        If buildStorage Is Nothing Then
            Debug.Fail("Unsupported VS object")
            Exit Sub
        End If
        ErrorHandler.ThrowOnFailure(buildStorage.SetPropertyValue( _
                                    propertyName, _
                                    String.Empty, _
                                    STORAGETYPE.PROJECT_FILE, _
                                    propertyValue))
    End Sub

    ' ...

    Private Sub _SetObjects(cObjects As UInteger, ppunk() As Object) Implements IPropertyPage.SetObjects
        If cObjects = 0 OrElse ppunk Is Nothing OrElse ppunk.Length = 0 Then
            SetObjects(Nothing)
            Exit Sub
        End If
        If ContainsMultipleProjects(ppunk) Then
            SetObjects(Nothing)
            Exit Sub
        End If
        Debug.Assert(cObjects = CUInt(ppunk.Length), "Huh?")
        SetObjects(ppunk)
    End Sub

    ' ...

    Private Sub SetObjects(vsObjects As Object())
        _dirtyProperties.Clear()
        _objects = vsObjects
        OnObjectsChanged(EventArgs.Empty)
    End Sub

    ' ...

    Private Sub WriteProperty(propertyName As String, propertyValue As String)
        If _objects Is Nothing Then
            Debug.Fail("Accessing property while not bound to project")
            Exit Sub
        End If
        Debug.Assert(_objects.Length <> 0, "Should never have zero objects if collection is non-null")
        For i As Integer = 0 To _objects.Length - 1
            WriteProperty(_objects(i), propertyName, propertyValue)
        Next
    End Sub

End Class

创建包非常简单;只记得在初始化步骤中调用 RegisterProjectFactory

Imports System
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports Microsoft.VisualStudio.Modeling.Shell
Imports Microsoft.VisualStudio.Shell
Imports Microsoft.VisualStudio.Shell.Interop

<ComVisible(True)>
<ProvideBindingPath()>
<Guid(Guids.MyCustomPackage)>
<PackageRegistration( _
    UseManagedResourcesOnly:=True)>
<ProvideAutoLoad(UIContextGuids.SolutionExists)>
<ProvideProjectFactory( _
    GetType(MyCustomProjectFactory), _
    Nothing, _
    Nothing, _
    Nothing, _
    Nothing, _
    Nothing)>
<ProvideObject( _
    GetType(MyCustomPropertyPageProvider))>
Public Class MyCustomPackage
    Inherits Package
    Protected Overrides Sub Initialize()
        MyBase.Initialize()
        Dim factory As New MyCustomProjectFactory(Me)
        Try
            Me.RegisterProjectFactory(factory)
        Catch ex As ArgumentException
            Debug.Fail(ex.Message, ex.ToString())
        End Try
    End Sub
End Class

我没有使用强积金ProjectFactoryclass,因为MPF isn't designed for project sub-types。相反,我直接继承自 FlavoredProjectFactoryBase.

Imports System
Imports System.Diagnostics.CodeAnalysis
Imports System.Runtime.InteropServices
Imports Microsoft.VisualStudio.Shell.Flavor

<SuppressMessage( _
    "Microsoft.Interoperability", _
    "CA1405:ComVisibleTypeBaseTypesShouldBeComVisible", _
    Justification:="Blame Microsoft? No other way around this")>
<ComVisible(True)>
<Guid(Guids.MyCustomProjectFactory)>
Public Class MyCustomProjectFactory
    Inherits FlavoredProjectFactoryBase

    Private ReadOnly _package As MyCustomPackage

    Public Sub New()
        Me.New(Nothing)
    End Sub

    Public Sub New(package As MyCustomPackage)
        If package Is Nothing Then
            Throw New ArgumentNullException("package")
        End If
        _package = package
    End Sub

    Protected Overrides Function PreCreateForOuter(outerProjectIUnknown As IntPtr) As Object
        Return New MyCustomProject(_package)
    End Function

End Class

项目 class 然后需要将自定义 属性 页面的 GUID 添加到 属性 页面 GUID 列表中。

Imports System
Imports System.Collections.Generic
Imports System.Diagnostics.CodeAnalysis
Imports System.Runtime.InteropServices
Imports Microsoft.VisualStudio
Imports Microsoft.VisualStudio.Shell.Flavor
Imports Microsoft.VisualStudio.Shell.Interop

<SuppressMessage( _
    "Microsoft.Interoperability", _
    "CA1405:ComVisibleTypeBaseTypesShouldBeComVisible", _
    Justification:="Blame Microsoft? No other way around this")>
<ComVisible(True)>
<Guid(Guids.MyCustomProject)>
Public Class MyCustomProject
    Inherits FlavoredProjectBase

    Private Const GuidFormat As String = "B"

    Private Shared ReadOnly PageSeparators As String() = {";"}

    Private ReadOnly _package As MyCustomPackage

    Public Sub New()
        Me.New(Nothing)
    End Sub

    Public Sub New(package As MyCustomPackage)
        If package Is Nothing Then
            Throw New ArgumentNullException("package")
        End If
        _package = package
    End Sub

    Protected Overrides Function GetProperty(itemId As UInteger, propId As Integer, ByRef [property] As Object) As Integer
        If propId = CInt(__VSHPROPID2.VSHPROPID_PropertyPagesCLSIDList) Then
            ErrorHandler.ThrowOnFailure(MyBase.GetProperty(itemId, propId, [property]))
            Dim pages As New HashSet(Of String)()

            If [property] IsNot Nothing Then
                For Each page As String In CStr([property]).Split(PageSeparators, StringSplitOptions.RemoveEmptyEntries)
                    Dim blah As Guid = Nothing
                    If Guid.TryParseExact(page, GuidFormat, blah) Then
                        pages.Add(page)
                    End If
                Next
            End If

            pages.Add(Guids.MyCustomPropertyPageProviderGuid.ToString(GuidFormat))
            [property] = String.Join(PageSeparators(0), pages)
            Return VSConstants.S_OK
        End If
        Return MyBase.GetProperty(itemId, propId, [property])
    End Function

    Protected Overrides Sub SetInnerProject(innerIUnknown As IntPtr)
        If MyBase.serviceProvider Is Nothing Then
            MyBase.serviceProvider = _package
        End If
        MyBase.SetInnerProject(innerIUnknown)
    End Sub

End Class

给遇到问题的人的最后提示:您必须在 XML 编辑器中打开您的项目文件并手动调整一些构建属性。您至少需要将 GeneratePkgDefFileIncludeAssemblyInVSIXContainer 设置为 true