技术开发 频道

VS2010 Extension特性实践:Feature开发

  【IT168 技术文档】首先,要想建立VS Extension工程,你需要安装VS2010 SDK,目前是Beta2版本,你可以到这里可以下载:http://go.microsoft.com/fwlink/?LinkID=165597),这里我是通过Editor Text Adornment模板创建的工程,嗯,我就不详细写如何通过模板创建自己Extension工程了。

  建好工程以后,会自动生成TextViewCreationListener,这里实现了IWpfTextViewCreationListener接口,并通过MEF导出IWpfTextViewCreationListener对象:

  [TextViewRole("DOCUMENT")]

  [Export(
typeof(IWpfTextViewCreationListener))]

  [ContentType(
"text")]

  
internal sealed class PETextViewCreationListener : IWpfTextViewCreationListener

  {

  
void IWpfTextViewCreationListener.TextViewCreated(IWpfTextView textView)

  {

  
//...

  }

  }

  这样VS就会在合适的时候调用IWpfTextViewCreationListener.TextViewCreated方法来通知文字编辑的状态改变。

  为了实现浮动一个自己的工具栏,这里还需要导出一个AdornmentLayerDefinition,并通过Order Attribute来定制这个Adornment层的显示位置和次序:

  [Name("QuickToolbarAdornmentLayer")]

  [Order(After
= "Text")]

  [Export(
typeof(AdornmentLayerDefinition))]

  
public AdornmentLayerDefinition QuickToolbarLayerDefinition

  {

  
get;

  
set;

  }

  这里的Name Attribute很重要,以后的代码中要获取我们的AdornmentLayer就得靠它了:

  this._adornmentLayer = this._textView.GetAdornmentLayer("QuickToolbarAdornmentLayer");

  扯得远了,回到IWpfTextViewCreationListener.TextViewCreated,通过这里,可以得到一个IWpfTextView,

  这是所有操作的目标和展现,另外,还需要挂他的Closed、LayoutChanged、MouseHovered、SelectionChanged等事件,以响应用户行为。

  由于我们要通过工具栏操作代码,所以需要通过MEF导入IEditorOperationsFactoryService:

  [Import]

  
internal IEditorOperationsFactoryService EditorOperationsFactoryService

  {

  
get;

  
set;

  }

  这样就可以在IWpfTextViewCreationListener.TextViewCreated中通过IEditorOperationsFactoryService.GetEditorOperations(ITextView)来获得IEditorOperations,有了它,就可以方便快捷的编辑代码了。

  接下来要实现工具栏的界面,这个就不多说了,建一个UserControl,里面放上ToolBar就搞定了。那么何时何地显示这个ToolBar呢?这就要依赖IWpfTextView的SelectionChanged事件了,上面提到会挂这个事件就是为这里用的。

private void MayBeAdornmentShowCondition()
{
    
if (!this._textView.Selection.IsEmpty)
     {
         SnapshotPoint startPos
= this._textView.Selection.Start.Position;
         SnapshotPoint endPos
= this._textView.Selection.End.Position;
         IWpfTextViewLine textViewLineContainingBufferPosition
= this._textView.GetTextViewLineContainingBufferPosition(startPos);
         TextBounds characterBounds
= textViewLineContainingBufferPosition.GetCharacterBounds(startPos);
         TextBounds bounds2
= this._textView.GetTextViewLineContainingBufferPosition(endPos).GetCharacterBounds(endPos);
if (this._fromMouseHover)
{
this._mustHaveAdornmentDisplayed = true;
}
else
{
PELeftButtonMouseProcessor property
= null;
try
{
property
= this._textView.Properties.GetProperty<PELeftButtonMouseProcessor>(typeof(PELeftButtonMouseProcessor));
}
catch
{
}
this._mustHaveAdornmentDisplayed = (property != null)
  
&& (property.IsLeftButtonDown
|| ((DateTime.Now - property.LastLeftButtonDownTime).TotalMilliseconds < 400.0));
}
if (this._mustHaveAdornmentDisplayed)
{
TextBounds selectionBounds
= !this._textView.Selection.IsReversed ? bounds2 : characterBounds;
int offset = 7;
double top = selectionBounds.Top + (!this._textView.Selection.IsReversed ? (offset + textViewLineContainingBufferPosition.Height) : (-offset - this._adornmentUI.ActualHeight));
if (top < 0.0)
top
= 0.0;
}
double left = characterBounds.Left + ((bounds2.Left - characterBounds.Left) / 2.0);
if ((left + this._adornmentUI.ActualWidth) > this._textView.ViewportWidth)
{
left
= this._textView.ViewportWidth - this._adornmentUI.ActualWidth;
}
Canvas.SetTop(
this._adornmentUI, top);
Canvas.SetLeft(
this._adornmentUI, left);
long chars = 0L;
try
{
chars
= this._textView.Selection.SelectedSpans[0].Span.Length;
}
catch
{
}
this._adornmentUI.SetStatus(chars);
this.RenderSelectionPopup();
}
}
else
{
this._mustHaveAdornmentDisplayed = false;
this._adornmentLayer.RemoveAdornmentsByTag(this._adornmentTag);
}
}
private void RenderSelectionPopup()
{
if (this._mustHaveAdornmentDisplayed)
{
IAdornmentLayerElement element
= null;
try
{
element
= this._adornmentLayer.Elements.First<IAdornmentLayerElement>(
(IAdornmentLayerElement ile)
=> ile.Tag.ToString() == this._adornmentTag);
}
catch (InvalidOperationException)
{
}
if (element == null)
{
this._adornmentLayer.AddAdornment(this._textView.Selection.SelectedSpans[0], this._adornmentTag, this._adornmentUI);
}
this._timer.Stop();
this._timer.Start();
}
}
private void selection_SelectionChanged(object sender, EventArgs e)
{
this._fromMouseHover = false;
this.MayBeAdornmentShowCondition();
}

  然后要注意的是IWpfTextView的Closed事件处理要记得取消所有挂这个事件等等收尾工作。

  接下来编译工程,打包VSIX就完成了,目前实现的主要Feature:

  ·当在代码编辑器中选择一段文字,并将鼠标移到文字区域时,QuickToolbar会以半透明的方式“浮”文字的旁边。

  ·当鼠标移到QuickToolbar区域,QuickToolbar会变成不透明,其上的按钮会响应鼠标动作。

  ·目前支持的操作有:

  剪切(Cut)

  复制(Copy)

  粘贴(Paste)

  删除(Delete)

  减小缩进(Decrease Indent)

  增加缩进(Increase Indent)

  注释代码(Comment)

  取消注释(Uncomment)

  等等

  如何深入挖掘VS2010 Extension,使它成为锋利的军刀,而不是绣花枕头。鉴于此,这里就从上面提到了的Feature——注释和取消注释选择的代码来剖析,希望可以为大家拓宽思路,更好的利用VS2010。

  当时是基于TextViewLine做注释代码的,这里有两个潜在问题:其一,TextViewLine,顾名思义,是“可视区域”的行,所以如果选择超出可视区域,超出的部分就没有注释掉;其二,当选择的结束位置在行的结尾时,无法实现IDE注释代码后保持Caret在选择结尾而不跳到下一行的行为,当尝试自己重新选择并移动Caret就会收到ITextSpanshot无效的异常。

  上面提到了VS2010 Extension对编辑器的编辑行为的控制能力仅仅提供了通用的,比如Cut/Copy/Paste等等,而其他的诸如注释/取消注释代码,添加、删除、导航到Bookmark等程序员常用功能没有暴露出来,具体可以参考IEditorOperations Interface(http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.text.operations.ieditoroperations_methods%28VS.100%29.aspx),这里的所有Member表达了其所支持的编辑操作。总之,这条路只有这么几个目的地。

  那么,还有其他方法吗?貌似走到了死胡同了,但是当我们使用IDE时候,却是可以很容易的通过Edit菜单找到所有的功能的,问题是,它们要怎样才能为我所用呢?

  我首先想到的是在VSSDK中找找,结果一个名字看起来很顺眼的接口撞到眼里,它就是IVsUIShell Interface(http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.interop.ivsuishell%28VS.100%29.aspx)。

  也就是说这是一个由IDE提供的全局的Service,可以创建、访问工具窗口和编辑窗口。浏览一下这个所有Member,发现了一个叫IVsUIShell.PostExecCommand(...)(http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.interop.ivsuishell.postexeccommand%28VS.100%29.aspx)的方法,MSDN描述说通过它可以异步执行Command,那么,只要找到注释代码的Command,在通过这个接口就可以实现VS IDE一样的注释代码的Feature了。酷毙了,就是它,当怎么得到它呢?现在请留心MSDN上的解释,就是上面我使用红色粗体表示出来的部分——这个由IDE提供的全局的Service,那么可以通过Package.GetGlobalService(...)(http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.package.getglobalservice%28VS.100%29.aspx)来获取:

  IVsUIShell shell = Package.GetGlobalService(typeof(IVsUIShell)) as IVsUIShell;接下来是找到自己需要Command,然后PostExecCommand就搞定了;而VS提供的Command有两部分组成:Guid和CommandID,这个大部分都在VSConstants Class(http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.vsconstants%28VS.100%29.aspx)里面,以注释代码为例,其Guid是:VsConstants.VSStd2k,而CommandID是VSConstants.VSStd2kCmdID.COMMENTBLOCK。下面是我包装的注释和取消注释的代码片段:

  public static void ProcessComments(bool comment)

  {

  IVsUIShell shell
= Package.GetGlobalService(typeof(IVsUIShell)) as IVsUIShell;

  
if (shell != null)

  {

  Guid std2k
= VSConstants.VSStd2K;

  
uint cmdId = comment ?

  (
uint)VSConstants.VSStd2KCmdID.COMMENT_BLOCK :

  (
uint)VSConstants.VSStd2KCmdID.UNCOMMENT_BLOCK;

  
object arg = null;

  shell.PostExecCommand(
ref std2k, cmdId, 0, ref arg);

  }

  }

 

  至此,我们通过VSSDK提供的能力,顺利的挖掘出VS2010 Extension的部分宝藏,你是不是也有点心动,要自己去挖掘一点呢?

0
相关文章