替代Visual Studio 2010的CfgPropertyPagesGuidsAddCSharp

时间:2017-05-09 16:47:07

标签: visual-studio visual-studio-2010 envdte

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

来自2011年的

This question没有答案。 another question的答案建议创建一个自定义项目子类型,但这似乎是一项非常大的工作,只是为了能够从GUI编辑一些其他项目属性。这是我第一次使用VSIX软件包,所以我尽量保持简单。

我已经尝试浏览.NET project system的来源,但我不确定我正在寻找什么来正确注册页面;任何指导都会非常感激。

感谢。

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

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

我在PropPageBasePropPageUserControlBase上建立了自己的IPropertyPage实施方案。我已经包含了一部分代码供参考,因为Microsoft提供的代码更复杂。

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

我没有使用MPF ProjectFactory类,因为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

然后,项目类需要将自定义属性页的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