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
我没有使用强积金ProjectFactory
class,因为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 编辑器中打开您的项目文件并手动调整一些构建属性。您至少需要将 GeneratePkgDefFile
和 IncludeAssemblyInVSIXContainer
设置为 true
。
我正在尝试开发一个 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
我没有使用强积金ProjectFactory
class,因为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 编辑器中打开您的项目文件并手动调整一些构建属性。您至少需要将 GeneratePkgDefFile
和 IncludeAssemblyInVSIXContainer
设置为 true
。