Este artigo foi traduzido por máquina.

Team System

Criando uma extensão do Visual Studio Team Explorer

Brian A. Randell eMarcel de de

Baixar o código de exemplo

A interface de usuário principal que usar para interagir com o Team Foundation Server (TFS) é o cliente do Team Explorer. Equipe Explorer fornece uma maneira de acessar e exibir informações em um ou mais servidores do TFS. Por padrão, o Team Explorer fornece acesso a recursos do TFS como funcionam os itens, bibliotecas de documentos do SharePoint, relatórios, compilações e controle de origem.

As equipes de desenvolvimento de Marcel usam um número de soluções do Visual Studio separadas, cada consiste em cinco a dez projetos. Em cada uma dessas soluções, há um único ponto de implantação, manutenção e design. Às vezes há dependências de solução de cruz. A equipe gerencia essas dependências por meio de uma pasta especial dentro do repositório do controle de versão do TFS. Cada solução grava sua saída para um local conhecido e a equipe verifica na saída após cada compilação. Uma solução depende a saída de outra solução pode referenciar o assembly quando ele é recuperado do TFS.

Infelizmente, equipe de Marcel enfrentou um problema: O Visual Studio armazena referências de arquivo usando um caminho relativo à solução atual, mas desejam que os desenvolvedores a flexibilidade de usar suas próprias estruturas de diretório e mapeamentos de espaço de trabalho. Essa flexibilidade desejável resulta em frustração ao Visual Studio não pode criar soluções porque ele não pode encontrar arquivos referenciados.

Uma maneira para corrigir esse problema é mapear o local que contém referências binárias como uma unidade já substituída usando o comando subst.exe. Referenciando assemblies em unidade já substituída, Visual Studio é capaz de localizar os arquivos em um local consistente. Os desenvolvedores podem colocar os arquivos em locais que preferirem e usar subst.exe para mapear seus locais para o mapeamento padrão. Porque os arquivos residem em uma unidade diferente, o Visual Studio armazena um caminho completo em vez de um caminho relativo. Um benefício adicional é que um desenvolvedor pode testar uma versão diferente, simplesmente alterando o mapeamento.

Enquanto essa técnica funciona, melhor ainda seria uma extensão do Team Explorer permite que um desenvolvedor definir um mapeamento entre o local do controle de versão e uma unidade mapeada. Equipe de Marcel implementado essa funcionalidade em uma extensão do Team Explorer chamada SUBST Explorer. Você pode ver o menu para a extensão de SUBST Explorer consulte do Figura 1.

The Subst Explorer Loaded in the Team Explorer Window

Figura 1 do Explorer O SUBST carregado na janela Team Explorer

Introdução

Para criar seu próprio plug-in do Team Explorer, você vai precisar Visual Studio 2008 Standard Edition ou superior, Team Explorer e o SDK do Visual Studio 2008, que você pode obter a extensibilidade do Visual Studio Developer Center ( msdn.microsoft.com/vsx ).

Antes de criar um Team Explorer plug-in, você precisa criar um VSPackage. O pacote fornece uma classe que implementa a interface Microsoft.TeamFoundation.Common.ITeamExplorerPlugin definida em Microsoft.VisualStudio.TeamFoundation.Client.dll.

Se na linguagem do Visual Studio, seu plug-in torna uma parte da hierarquia. É um recurso que diferencia a hierarquia do Team Explorer de outras implementações de hierarquia no Visual Studio Team Explorer suporta o carregamento assíncrono da hierarquia, essencialmente porque Carregando hierarquia de coisas como documentos e itens de trabalho geralmente requer consultas remotas ao TFS. Essas chamadas bloquearia caso contrário, o Visual Studio durante essas consultas, resultando em uma experiência de usuário ruim. A interface ITeamExplorerPlugin implementada pelo seu VSPackage fornece o mecanismo Team Explorer usa para carregar o conteúdo de cada nó de forma assíncrona.

Criar um VSPackage selecionando arquivo | novo | projeto. Na caixa de diálogo New Project, expanda o nó Other Project Types e selecione o nó de extensibilidade. Sobre no painel modelos, selecione o modelo de pacote do Visual Studio Integration.

Após preencher a caixa de diálogo New Project e clique em OK, o Visual Studio inicia o Assistente de pacote do Visual Studio Integration. Primeiro, escolher seu idioma preferido — C++, translation from VPE for Csharp ou VisualBasic (translation from VPE for Csharp usado neste artigo). Ao escolher translation from VPE for Csharp ou VisualBasic, você precisa selecionar a opção para gerar um novo arquivo de chave de nome forte ou especificar um arquivo existente. Na próxima página, preencha informações para sua empresa e alguns detalhes sobre o pacote, incluindo Package Name, ícone e assim por diante. Muitas dessas informações é mostrada na Ajuda | sobre diálogo no Visual Studio.

Na próxima página, selecione como deseja que o Visual Studio para expor seu pacote. Para esse exemplo específico, você deseja ter somente um MenuCommand. O plug-in usará para manipular os menus de contexto. Na próxima página, você fornecer um nome de comando e a identificação do comando. Apenas aceite os padrões desde que você vai alterar posteriormente. Na próxima página, você pode adicionar suporte para projetos de teste. Nós não ser abrangendo neste artigo, tão à vontade para desmarcá-las e concluir o assistente. O assistente irá gerar as classes básicas e recursos que necessários para implementar o VSPackage.

Em seguida, você precisa adicionar as seguintes referências, fornecem acesso para as classes base do Team Explorer usado para criar plug-in do Team Explorer:

Microsoft.VisualStudio.TeamFoundation.Client.(9.0.0)
Microsoft.VisualStudio.TeamFoundation (9.0.0)
Microsoft.VisualStudio.Shell (2.0.0)

Você também precisará remover a referência padrão para Microsoft.VisualStudio.Shell.9.0, desde que a Microsoft construiu assemblies Team Foundation contra a versão 2.0 dos assemblies em vez da versão 9.0. Além disso, como gerado, o projeto pressupõe que ele pode usar a ferramenta regpkg.exe para registrar o pacote no registro após a compilação. No entanto, regpkg.exe depende o assembly Shell.9.0. Para tornar o projeto de compilação no Visual Studio 2008, você deve alterar o arquivo de .proj do projeto. Você precisa descarregar o arquivo de projeto e adicione as seguintes propriedades para o arquivo de propriedade RegisterOutputPackage:

<!-- We are 2005 compatible, and don't rely on RegPkg.exe
of VS2008 which uses Microsoft.VisualStudio.Shell.9.0 --> 
<UseVS2005MPF>true</UseVS2005MPF> 
<!-- Don't try to run as a normal user (RANA), 
create experimental hive in HKEY_LOCAL_MACHINE --> 
<RegisterWithRanu>false</RegisterWithRanu>.

O assembly Microsoft.VisualStudio.TeamFoundation.Client fornece um espaço para nome Microsoft.TeamFoundation.Common contém uma classe base chamada PluginHostPackage. Use como classe base para o seu pacote. Ele também contém uma classe base chamada BasicAsyncPlugin que implementa a interface ITeamExplorerPlugin necessária. Você precisará excluir a implementação padrão da classe gerada do pacote e, em seguida, herdam PluginHostPackage em vez da classe de pacote padrão.

Porque a classe herda a partir PluginHostPackage agora, você precisa apenas substituir o método OnCreateService. Esse método retorna uma nova instância de uma classe derivada de BasicAsyncPlugin que gerencia a implementação de plug-in real. Você pode ver a implementação do HostPackage para Explorer SUBST no do Figura 2. Você precisará registrar manualmente seu plug-in do Team Explorer, uma tarefa vai retornamos para posteriormente no artigo.

Figura 2 do implementação SubstExplorerPackage

...

[ProvideService(typeof(SubstExplorer))]
[PluginRegistration(Catalogs.TeamProject, "Subst explorer", typeof(SubstExplorer))]
public sealed class SubstExplorerPackage: PluginHostPackage, IVsInstalledProduct {
  private static SubstExplorerPackage _instance;
  public static SubstExplorerPackage Instance {
    get { return _instance; }
  }

  public SubstExplorerPackage () : base() {
    _instance = this;
  }
        
  protected override object OnCreateService(
    IServiceContainer container, Type serviceType) {

    if (serviceType == typeof(SubstExplorer)) {
      return new SubstExplorer();
    }
    throw new ArgumentException(serviceType.ToString());
  }
}

Na Figura 2, há dois atributos são de interesse especial para o plug-in do Team Explorer. ProvideService indica esse pacote fornece um serviço e o Tipo_de_serviço é SubstExplorer. PluginRegistration indica que o pacote fornece um plug-in do Team Explorer e que registro adicional é necessário. Este atributo deriva RegistrationAttribute e regpkg.exe normalmente a processa.

Nós e hierarquias

Como você pode ver na Figura 2, a implementação de OnCreateService é simples. Ele retorna uma nova instância da classe SubstExplorer fornece a implementação da classe BasicAsyncPlugin. A classe SubstExplorer é responsável por gerenciar uma parte da hierarquia do Team Explorer. Uma hierarquia no Visual Studio é uma árvore de nós onde cada nó tem um conjunto de propriedades associadas. São exemplos de outras hierarquias no Visual Studio Solution Explorer, o Server Explorer e Performance Explorer.

O SubstExplorer gerencia a hierarquia de plug-in substituindo dois métodos chamados CreateNewTree e GetNewUIHierarchy**.** Do Figura 3, você pode ver a implementação da classe SubstExplorer deriva BasicAsyncPlugin.

Figura 3 do implementação SubstExplorer

[Guid("97CE787C-DE2D-4b5c-AF6D-79E254D83111")]
public class SubstExplorer : BasicAsyncPlugin {
  public SubstExplorer() : 
    base(MSDNMagazine.TFSPlugins.SubstExplorerHostPackage.Instance) {}

  public override String Name
  { get { return "Subst drive mappings"; } }

  public override int DisplayPriority {
    get { 
      // After team explorer build, but before any installed power tools
      // power tools start at 450
      return 400; 
    }
  }

  public override IntPtr OpenFolderIconHandle
  { get { return IconHandle; }}

  public override IntPtr IconHandle
  { get { return new Bitmap(
    SubstConfigurationFile.GetCommandImages().Images[2]).GetHicon(); } }

  protected override BaseUIHierarchy GetNewUIHierarchy(
    IVsUIHierarchy parentHierarchy, uint itemId) { 

    SubstExplorerUIHierarchy uiHierarchy = 
      new SubstExplorerUIHierarchy(parentHierarchy, itemId, this);
    return uiHierarchy;
  }

  protected override BaseHierarchyNode CreateNewTree(
    BaseUIHierarchy hierarchy) {

    SubstExplorerRoot root = 
      new SubstExplorerRoot(hierarchy.ProjectName + 
      '/' + "SubstExplorerRoot");
    PopulateTree(root);
    // add the tree to the UIHierarchy so it can handle the commands
    if (hierarchy.HierarchyNode == null)
      { hierarchy.AddTreeToHierarchy(root, true); }
    return root;
  }

  public static void PopulateTree(BaseHierarchyNode teNode) {
    string projectName = 
      teNode.CanonicalName.Substring(0, 
      teNode.CanonicalName.IndexOf("/"));
    var substNodes = 
      SubstConfigurationFile.GetMappingsForProject(projectName);
    if (substNodes != null) {
      foreach (var substNode in substNodes) {
        SubstExplorerLeaf leafNode = 
          new SubstExplorerLeaf(substNode.name, substNode.drive, 
          substNode.versionControlPath);
        teNode.AddChild(leafNode);
      }
      // (bug workaround) force refresh of icon that changed 
      // during add, to force icon refresh
      if (teNode.IsExpanded) {
        teNode.Expand(false);
        teNode.Expand(true);
      }
    }
  }
}

A classe SubstExplorer gerencia a criação de um conjunto de nós de hierarquia. Para o pacote SubstExplorer esses nós representam locais de pasta virtual que o plug-in pode mapear como uma unidade. Cada nó contém as propriedades necessárias para mapear uma unidade usando o comando subst.exe. O pacote irá controlar nome, Drive Letter e local (no repositório de controle de versão).

O pacote cria a árvore em duas etapas. Primeiro, cria a classe de manipulador de comando de todos os nós de hierarquia, melhor conhecido como um UIHierarchy. Método GetNewUIHierarchy inicia esta etapa. Segundo, o método CreateNewTree manipula a criação da árvore de nós que representam os mapeamentos de unidade virtual.

GetNewUIHierarchy chamado do thread da interface do usuário e retorna uma instância de uma classe que deriva da classe base BaseUIHierarchy. Você encontrará a implementação do pacote na classe SubstExplorerUIHierarchy. SubstExplorerUIHierarchy precisa manipular todos os adicionar, excluir e editar comandos executados a partir de qualquer um de nós que o pacote adiciona ao Team Explorer. A classe do método ExecCommand manipula esses comandos. Mas primeiro você precisa criar menus e comandos no Visual Studio.

Na classe SubstExplorer, substituir o método de CreateNewTree que é chamado de um segmento sem interface do usuário e retorna a árvore de nós que representam todas as substituições unidade configuradas para um projeto de equipe. A árvore sempre começa com um nó raiz, derivado da classe RootNode. Para cada definição, você adicionará um nó filho à raiz. Nó folha contém as propriedades que você precisa mapear uma unidade.

Propriedades e comandos

Agora que viu os requisitos básicos para configurar um Team Explorer plug-in, você precisa adicionar alguma funcionalidade a ele. A classe SubstExplorerRoot deriva da classe RootNode encontrada no assembly Microsoft.TeamFoundation.Common. Aqui substituem os ícones, PropertiesClassName e ContexMenu propriedades.

A propriedade ícones retorna um ImageList que contém os ícones que você deseja usar para exibir os nós. No construtor de RootNode, você precisa definir a ImageIndex para que ele aponta para a direita imagem em ImageList.

O PropertiesClassName retorna uma seqüência de caracteres que representa o nome que o Visual Studio exibe na janela Propriedades de grade quando você selecionar um nó. Qualquer seqüência que você acha que é apropriada será suficiente aqui.

A propriedade ContextMenu retorna uma ID de comando que representa o menu de contexto que você deseja mostrar. Para o nó raiz, você precisa de menu de contexto com uma opção, chamada Add. Figura 4 mostra a implementação de SubstExplorerRoot.

Figura 4 do SubstExplorerRoot

public class SubstExplorerRoot : RootNode {
  static private readonly CommandID command = 
    new CommandID(GuidList.guidPackageCmdSet, 
    CommandList.mnuAdd);

  public SubstExplorerRoot(string path) : base(path) {
    this.ImageIndex = 2;
    NodePriority  = (int)TeamExplorerNodePriority.Folder;
  }

  public override System.Windows.Forms.ImageList Icons
  { get { return SubstConfigurationFile.GetCommandImages();  } }

  public override string PropertiesClassName {
    //Name of the node to show in the properties window
    get { return "Subst Explorer Root"; }
  }

  public override 
    System.ComponentModel.Design.CommandID ContextMenu
  { get { return command; } }
}

A classe de nó folha que SubstExplorerLeaf (consulte a Figura 5 de ) deriva BaseHierarchyNode e aqui você precisará substituir as propriedades ContextMenu, PropertiesClassName e PropertiesObject. Youl também precisará fornecer uma implementação personalizada de DoDefaultAction. O Visual Studio chama esse método quando você clica duas vezes em um nó folha. DoDefaultAction executa o código que executa o comando Subst. Se você anteriormente tiver executado o comando SUBST, remove o mapeamento.

Figura 5 do SubstExplorerLeaf

public class SubstExplorerLeaf : BaseHierarchyNode {
  private enum SubstIconId {
    unsubsted = 1,
    substed = 2
  }

  CommandID command = 
    new CommandID(GuidList.guidPackageCmdSet, 
    CommandList.mnuDelete);
  bool IsDriveSubsted { get; set; }

  public string VersionControlPath { get; set; }
  public string SubstDriveLetter { get; set; }

  public SubstExplorerLeaf(string path,  
    string substDriveLetter, string versionControlPath)
    : base(path, path + " (" + substDriveLetter + ":)") {

    this.ImageIndex = (int)SubstIconId.unsubsted;
    this.NodePriority = (int)TeamExplorerNodePriority.Leaf;
          
    this.VersionControlPath = versionControlPath;
    this.SubstDriveLetter = substDriveLetter;
    this.IsDriveSubsted = false;
  }

  public override void DoDefaultAction() {
    if (!IsDriveSubsted) {
      SubstDrive();
    }
    else {
      UnsubstDrive(SubstDriveLetter);
    }
  }

  public override CommandID ContextMenu
  { get { return command;  } }

  public override string PropertiesClassName
  { get { return "Subst Leaf Node"; }}

  public override ICustomTypeDescriptor PropertiesObject {
    get {
      return new SubstExplorerProperties(this);
    }
  }

  private void SubstDrive() {
    if (IsDriveAlreadySubsted(SubstDriveLetter)) {
      UnsubstDrive(SubstDriveLetter);
    }
    string substresponse = 
      SubstHelper.Subst(SubstDriveLetter, GetLocalFolder());
 
    if (string.IsNullOrEmpty(substresponse)) {
      IsDriveSubsted = true;
      this.ImageIndex = (int)SubstIconId.substed;
    }
    else {
      MessageBox.Show(string.Format(
        "Unable to make subst mapping. Message:\n {0}", 
        substresponse));
    }
  }

  private bool IsDriveAlreadySubsted(string driveLetter) {
    bool IsdrivePhysicalyMaped = 
      SubstHelper.SubstedDrives().Where(
      d => d.Contains(driveLetter + ":\\")).Count() != 0;
    bool IsdriveKnownToBeMaped = 
      (from substedNode in _substedNodes
      where substedNode.SubstDriveLetter == driveLetter
      select substedNode).ToArray<SubstExplorerLeaf>().Length > 0;
    return IsdriveKnownToBeMaped || IsdrivePhysicalyMaped;
  }

  public void UnsubstDrive(string substDriveLetter) {
    string substResponse = SubstHelper.DeleteSubst(substDriveLetter);
    IsDriveSubsted = false;
    this.ImageIndex = (int)SubstIconId.unsubsted;
  }

  public string localPath {
    get { return VersionControlPath; }
  }
}

Propriedade ContextMenu representa o menu de contexto que você deseja mostrar no nó folha. Menu de contexto expõe dois comandos: Propriedades e excluir. Na classe, o PropertiesClassName tem a mesma finalidade como no nó raiz. Use a propriedade PropertiesObject para retornar um objeto que você pode usar para exibir as propriedades do nó selecionado na janela Propriedades. Para o nó folha Propriedades expostas será nome, DriveLetter e VersionControlPath.

Retornar uma nova instância do tipo SubstExplorerProperties (consulte do Figura 6). Use esse objeto para exibir as propriedades de nó folha. SubstExplorerProperties fornece uma implementação de interface do ICustomTypeDescriptor retorna informações sobre as propriedades que você deseja mostrar e como deseja mostrá-los. BaseHierarchyNode tem um objeto de propriedades padrão que mostra coisas como o URL, nomedoservidor e ProjectName, mas que não parecer útil para nosso nó folha.

Figura 6 do SubstExplorerProperties

public class SubstExplorerProperties 
  : ICustomTypeDescriptor, IVsProvideUserContext {

  private BaseHierarchyNode m_node = null;
  public SubstExplorerProperties(BaseHierarchyNode node)
    { m_node = node; }

  public string GetClassName()
    { return m_node.PropertiesClassName;}

  public string GetComponentName()
    { return m_node.Name; }
  public PropertyDescriptorCollection 
    GetProperties(Attribute[] attributes) { 

    // create for each of our properties the 
    // appropriate PropertyDescriptor
    List<PropertyDescriptor> list = new List<PropertyDescriptor>();
    PropertyDescriptorCollection descriptors = 
      TypeDescriptor.GetProperties(this, attributes, true);

    for (int i = 0; i < descriptors.Count; i++) {
      list.Add(new DesignPropertyDescriptor(descriptors[i]));
    }
    return new PropertyDescriptorCollection(list.ToArray());
  }

  public object GetPropertyOwner(PropertyDescriptor pd) {  
    // return the object implementing the properties
    return this;
  }

  // rest of ICustomTypeDescriptor methods are not 
  // shown since they are returning defaults 
  // actual properties start here
  [Category("Drive mapping")]
  [Description("...")]
  [DisplayName("Version Control Path")]
  public string VersionControlPath
    {  get { return ((SubstExplorerLeaf)m_node).VersionControlPath; } }

  [Category("Drive mapping")]
  [Description("...")]
  [DisplayName("Subst drive letter")]
  public SubstDriveEnum SubstDriveLetter {
    get { return 
      (SubstDriveEnum)Enum.Parse(typeof(SubstDriveEnum),
      ((SubstExplorerLeaf)m_node).SubstDriveLetter); }
  }

  [Category("Drive mapping")]
  [Description("...")]
  [DisplayName("Mapping name")]
  public string MappingName
    {  get { return ((SubstExplorerLeaf)m_node).Name; } }
}

Se você examinar a raiz e implementações de nó folha, consulte que ambos necessário para exibir um menu de contexto. O nó raiz precisa ter um item de menu Add. Um nó folha precisa propriedades e excluir itens de menu. Ambas as implementações de nó retornam uma instância de ID de comando como a implementação de suas respectivas propriedades ContextMenu. Para a classe de ID de comando funcionar corretamente, você precisa definir os menus e comandos na solução.

Para adicionar um menu e o comando para o Visual Studio, você precisa definir os comandos em uma tabela de comando. Adicionar tabelas de comando para o assembly como um recurso incorporado. Além disso, você precisa registrar o registro do sistema e a tabela de comando durante o registro do pacote. Quando você executar devenv /setup, Visual Studio reúne todos os recursos de comando de todos os pacotes registrados e cria uma representação interna de todos os comandos no ambiente de desenvolvimento.

Iniciando com o Visual Studio 2005, você poderia definir comando tabelas em um arquivo XML com a extensão .vsct. Esse arquivo, você definir os menus, os grupos de comando e botões que você deseja mostrar no menu. Um comando do Visual Studio é parte de um grupo de comando. Coloque os grupos de comando nos menus.

Para o nó raiz, você precisa de um comando Add, colocado em um grupo contido por um menu. Nó folha precisa comandos DELETE e propriedades. Você precisa definir um segundo menu que contém um grupo diferente que contém esses dois comandos. (Consulte o download que acompanha este artigo para um exemplo de arquivo .vsct.)

O arquivo .vsct precisa tratamento especial no projeto Visual Studio. Você deve compilá-lo em um recurso e incorporar o recurso no assembly. Após instalar o SDK do Visual Studio, você pode selecionar uma ação de compilação especial para seu arquivo de comando chamado VSCTCompile. Esta ação se encarrega de compilação e a incorporação do recurso no assembly.

Tabela comando XML, alguns símbolos são usados na definição de menus e comandos. Adicionar todos os menus, comandos e botões para o mesmo commandSet chamado GuidPackageCmdSet:

<Symbols>
  <!-- This is the package guid. -->
  <GuidSymbol name="GuidPackage" value=
"{9B024C14-2F6F-4e38-AA67-3791524A807E}"/>
  <GuidSymbol name="GuidPackageCmdSet" value=
"{D0C59149-AC1D-4257-A68E-789592381830}"/>
    <IDSymbol name="mnuAdd" value="0x1001" />
    <IDSymbol name="mnuDelete" value="0x1002" />

Você precisará fornecer informações de menu de contexto, todos os lugares você consultar novamente a esse símbolo como o contêiner do menu. Assim, em implementações SubstExplorerRootNode e SubstExplorerLeafNode, criar uma instância do tipo ID de comando e usar GuidPackageCommandSet como primeiro argumento e o menu real que você deseja exibir como o segundo argumento:

CommandID command = new CommandID(
  GuidList.guidPackageCmdSet, 
  CommandList.mnuDelete);

No arquivo .vsct, há três comandos que o UIHierarchy precisa responder a. Método ExecCommand é chamado quando você clica em um dos itens de menu. O método precisa selecionar a ação para executar com base em nCmdId passado para ele. A implementação básica do SubstExplorerUIHierarchy é mostrada no do Figura 7.

Figura 7 do SubstExplorerUIHierarchy

public class SubstExplorerUIHierarchy : BaseUIHierarchy, 
  IVsHierarchyDeleteHandler, IVsHierarchyDeleteHandler2 {

  public SubstExplorerUIHierarchy(IVsUIHierarchy parentHierarchy, 
    uint itemId, BasicAsyncPlugin plugin)
    : base(parentHierarchy, itemId, plugin, 
    MSDNMagazine.TFSPlugins.SubstExplorerHostPackage.Instance) { 
  }
        
  public override int ExecCommand(uint itemId, 
    ref Guid guidCmdGroup, uint nCmdId, 
    uint nCmdExecOpt, IntPtr pvain, IntPtr p) {

    if (guidCmdGroup == GuidList.guidPackageCmdSet) {
      switch (nCmdId) {
        case (uint)CommandList.cmdAdd:
             AddNewDefinition(this.ProjectName);
             return VSConstants.S_OK;
        case (uint)CommandList.cmdDelete:
             RemoveDefinition(itemId);
             return VSConstants.S_OK;
        case (uint)CommandList.cmdEdit:
             EditDefinition(itemId);
             return VSConstants.S_OK;
        default: return VSConstants.E_FAIL;
      }
    }

    return base.ExecCommand(itemId, ref guidCmdGroup, nCmdId, nCmdExecOpt, pvain, p);
  }
  ...
}

Adicionar, editar e excluir

Agora você precisa fornecer uma maneira para que o usuário adicionar, excluir ou editar mapeamentos em nós raiz ou de folha. O código é in-loco para manipular as chamadas para adicionar o nó raiz e para os comandos de edição e exclusão em nós folha. Adicionar um novo mapeamento requer entrada do usuário e precisa armazenar os dados de mapeamento em um local conhecido. Esse local é preferencialmente no perfil móvel do usuário. Portanto, vamos dar uma olhada mais detalhada em como você pode responder para o comando Adicionar.

Método AddNewDefinition na classe SubstExplorerUIHierarchy manipula o comando Adicionar. AddNewDefinition mostra uma caixa de diálogo que permite aos usuários especificar o mapeamento que desejam criar. Um mapeamento precisa ter um nome e uma letra de unidade para o comando Subst. Além disso, o mapeamento precisa apontar para um caminho no repositório de controle de versão. Você deseja permitir que o usuário escolha o local do controle de versão em vez de precisar inserir um caminho complexo manualmente. Você pode ativar usando o modelo de objeto do TFS, especificamente o método GetServer da classe TeamFoundationServerFactory. GetServer aceita uma URL que representa o servidor que você deseja usar e um credentialsProvider caso o usuário não está no mesmo domínio do servidor e a conexão com o servidor exige nova autenticação. Depois de ter acesso a uma instância TeamFoundationServer válida, você tem acesso a vários serviços fornecidos pelo TFS.

Você precisa serviço VersionControlServer para obter informações sobre a estrutura de pasta dentro do projeto de equipe atual. Em janeiro de Brian coluna Team System de 2007 (msdn.microsoft.com/magazine/cc163498), ele mostrou como você poderia usar esse serviço para criar seu próprio controle de versão pasta navegador diálogo. Podemos reutilizou diálogo descrito nesse artigo (consulte do Figura 8). A caixa de diálogo retorna a pasta selecionada pelo usuário no repositório de controle de versão, como mostrado na Figura 9 de . Armazenar o caminho retornado em um arquivo de configuração.

Quando o usuário clica em OK, você pode adicionar um novo nó para o arquivo de configuração e um novo nó filho da hierarquia. Adicionar um novo nó chamando o método AddChild na instância HierarchyNode.

Adding a New Mapping Definition

Figura 8 adicionando uma nova definição de mapeamento

Choosing a Location in Version Control

Figura 9 Escolhendo um local no controle de versão

Executar o comando padrão

A classe SubstExplorerUIHierarchy é responsável pelo tratamento todos os comandos acionados pelas opções de menu oferecidas pelo plug-in. Um dos outros comandos que você precisa manipular é quando um usuário clica duas vezes em um nó. Método DoDefaultAction processa este evento. Nó raiz, a ação padrão de recolhendo ou expandindo os nós na hierarquia é aceitável. No entanto, para nós de folha, você fornecerá uma implementação personalizada.

Você deseja substituir a unidade com base nas propriedades definidas para esse nó. Para uma unidade subst, você pode emitir uma ação de linha de comando e fornecer os parâmetros necessários. Para essa finalidade, criamos um SubstHelperClass que chama o namespace System.Diagnostics, para criar um novo processo chamado subst.exe e fornecer com os parâmetros necessários. Os parâmetros necessários são a letra da unidade e a pasta local que você deseja mapear como a unidade. Você tem a letra de unidade disponível. No entanto, você precisa mapear o caminho de controle de versão para a pasta local. Novamente, você vai usar o modelo de objeto do TFS e obter uma referência para o objeto VersionControlServer. Você pode consultar este objeto para todos os espaços de trabalho disponíveis e tente obter um mapeamento para uma pasta local baseado no caminho de controle de versão que você possui. Figura 10 fornece uma implementação.

Figura 10 mapeamento um caminho de controle de versão para um local no disco

private string GetLocalFolder() {
  VersionControlServer vcs = 
    (VersionControlServer)((
    SubstExplorerUIHierarchy)ParentHierarchy).
    tfs.GetService(typeof(VersionControlServer));
  Workspace[] workspaces = 
    vcs.QueryWorkspaces(null, vcs.AuthenticatedUser, 
    Environment.MachineName);
  foreach (Workspace ws in workspaces) {
    WorkingFolder wf = 
      ws.TryGetWorkingFolderForServerItem(VersionControlPath);
    if (wf != null) {
      // We found a workspace that contains this versioncontrolled item
      // get the local location to map the drive to this location....
      return wf.LocalItem;
    }
  }
  return null;
}

Toques finais

Agora você tem a lógica para mostrar uma árvore de nós e manipular o mapeamento de unidade. No entanto, você quer seu Team Explorer plug-in destacar. Convém adicionar que alguns recursos adicionais em termos de excluir nó tratamento e outros toques profissionais, como adicionar um ícone na tela inicial do Visual Studio.

Adicionando a funcionalidade Excluir requer que você implemente uma interface adicional na classe SubstExplorerUIHierarchy. O Visual Studio tem uma interface específica chamada IVsHierarchyDeleteHandler implementar para mostrar uma caixa de diálogo padrão quando você pressionar a tecla delete. Para este plug-in, você desejará fornecer uma caixa de diálogo personalizada perguntando ao usuário para confirmar a exclusão do nó que está selecionada. Para fazer esse trabalho, você também precisa implementar a interface IVsHierarchyDeleteHandler2 para tratamento de exclusão trabalhar do teclado. Desde que você já implementou a funcionalidade Excluir real, você só precisa implementar esta interface e chamar funções existentes. Do Figura 11 você pode ver a implementação das interfaces.

Figura 11 do manipulador de IVsHierarchyDelete implementação

#region IVsHierarchyDeleteHandler2 Members

public int ShowMultiSelDeleteOrRemoveMessage(
  uint dwDelItemOp, uint cDelItems, 
  uint[] rgDelItems, out int pfCancelOperation) {

  pfCancelOperation = Convert.ToInt32(true);
  return VSConstants.S_OK;
}

public int ShowSpecificDeleteRemoveMessage(
  uint dwDelItemOps, uint cDelItems, uint[] rgDelItems, 
  out int pfShowStandardMessage, out uint pdwDelItemOp) {

  SubstExplorerLeaf nodeToDelete = 
    NodeFromItemId(rgDelItems[0]) as SubstExplorerLeaf;
  if (AreYouSureToDelete(nodeToDelete.Name)) { 
    pdwDelItemOp = 1; // == DELITEMOP_DeleteFromStorage; 
                      // DELITEMOP_RemoveFromProject==2; 
  }
  else { 
    pdwDelItemOp = 0; // NO delete, user selected NO option }

  pfShowStandardMessage = Convert.ToInt32(false);
  return VSConstants.S_OK;
}

#endregion
#region IVsHierarchyDeleteHandler Members

public int DeleteItem(uint dwDelItemOp, uint itemid) {
  SubstExplorerLeaf nodeToDelete = 
    NodeFromItemId(itemid) as SubstExplorerLeaf;
  if (nodeToDelete != null) {
    // remove from storage
    RemoveDefinitionFromFile(nodeToDelete);
    // remove from UI
    nodeToDelete.Remove();
  }
  return VSConstants.S_OK;
}

public int QueryDeleteItem(uint dwDelItemOp, uint itemid, 
  out int pfCanDelete) {

  pfCanDelete = Convert.ToInt32(NodeFromItemId(itemid) is SubstExplorerLeaf);
  return VSConstants.S_OK;
}
#endregion

É importante observar que o plug-in não oferece suporte vários nós selecionado sendo excluídos de uma vez, portanto pfCancelOperation é definido como true no método ShowMultiSelDeleteOrRemoveMessage. Na implementação de método ShowSpecificDeleteRemoveMessage, você precisa retornar o valor correto de que deseja excluir. Retornar um valor de 1 para indicar o que removeu do armazenamento. Esses sinalizadores são normalmente usados no sistema de projeto do Visual Studio e somente um valor de 1 produz os resultados corretos.

Também convém adicionar suporte para integração de tela inicial. Por padrão, cada vez que iniciar o Visual Studio, você verá uma tela inicial listando produtos registrados. Para fazer isso implementando a interface IVsInstalledProduct na classe de implementação SubstExplorerHostPackage. Os métodos existe necessitam de que registrar o recurso identificações para o ícone usar na tela inicial e o ícone para usar na caixa sobre.

A implementação é nada mais definindo o parâmetro de saída para o valor inteiro correto e incorporação de um ícone de 32 x 32 pixels como um recurso no assembly. Para incorporar o recurso corretamente no seu assembly, você precisa abrir o arquivo resources.resx no editor de XML e adicionar as seguintes linhas para o arquivo de recurso:

<data name="500" 
  type="System.Resources.ResXFileRef, System.Windows.Forms">
  <value>..\Resources\SplashIcon.bmp;System.Drawing.Bitmap, 
    System.Drawing, Version=2.0.0.0, Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a</value> 
</data>

Isso adiciona o bitmap de recurso localizado na pasta recursos no projeto para o recurso e incorpora em referência 500. No método IdBmpSplash, agora você pode definir pIdBmp para 500 e retornar constante S_OK como um valor de retorno. Para obter o ícone na tela inicial, você precisa criar o assembly e executar devenv /setup da linha de comando. Isso irá obter as informações do pacote que você criou e ele irá armazenar em cache os dados. Isso garante que o pacote não precisa ser carregado quando o Visual Studio mostra a tela inicial. Para fazer isso pelas mesmas razões necessários para fazer isso para as opções de menu que você adicionou: Para acelerar o tempo de carga do Visual Studio.

Registro de pacote

Agora que você terminar a extensão do Team Explorer, é hora de pacote do produto e obtê-lo em execução em sistemas ’s desenvolvedor outros. Portanto, como você pode distribuir os resultados?

Primeiro, Visual Studio comportamento diferente quando você instalou o SDK. Por padrão, ele irá aceitar ou carregar qualquer VSPackage. Isso não será o caso em máquinas onde você não instalou o SDK.

Para um pacote carregar corretamente, você precisará incorporar uma chave de carga do pacote, que obter da Microsoft (consulte de msdn.microsoft.com/vsx/cc655795 ). A parte mais importante deste processo é garantir que você fornecer exatamente as mesmas informações quando registrando para sua chave como as informações fornecidas nos atributos para a classe hostPackage (no caso da classe SubstExplorerHostPackage). Além disso, quando o site pede que você insira o nome do pacote, você deve fornecer o nome do produto usado no atributo ProvideLoadKey.

Depois de obter a chave de carga, pode colá-lo no arquivo de recurso com o identificador de recurso fornecido como último argumento do atributo ProvideLoadKey. Certifique-se de que remover os feeds de retorno/linha do carro da seqüência de caracteres quando copiar do site é uma seqüência de caracteres consecutiva antes de colar no arquivo de recurso.

Agora você pode testar se seu plug-in funciona, especificando um parâmetro de depuração adicionais: / NoVsip. Este parâmetro garante que o Visual Studio usa o comportamento de carregamento normal. Se a chave não for aceita, o Visual Studio exibirá uma caixa de diálogo de falha de carga. Com o SDK instalado, você encontrará no menu Visual Studio Tools o analisador de carga do pacote. Você pode apontar no seu assembly para ajudar a depurar o que há de errado. Se for apenas a chave de carga do pacote, garantir que digitou exatamente os mesmos parâmetros no site como no seu atributo.

A última etapa que permanece é o registro de pacote para uma máquina de produção. Infelizmente, porque os assemblies do Team System usam uma versão diferente dos assemblies shell, não é possível usar regpkg.exe para registrar seu pacote. Em vez disso, você precisa fazer manualmente usando um arquivo do Registro. No arquivo, você precisa publicar o pacote no local correto do Registro. Do Figura 12 mostra o script de registro necessário.

Figura 12 do script de registro do pacote

REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\TeamSystemPlugins\Team Explorer Project Plugins\SubstExplorer]
@="97CE787C-DE2D-4b5c-AF6D-79E254D83111"
"Enabled"=dword:00000001
 
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\9.0\Services\{97ce787c-de2d-4b5c-af6d-79e254d83111}]
@="{9b024c14-2f6f-4e38-aa67-3791524a807e}"
"Name"="SubstExplorer"
 

[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\9.0\Packages\{9b024c14-2f6f-4e38-aa67-3791524a807e}]
@="MSDNMagazine.TFSPlugins.SubstExplorerHostPackage, TFSSubstExplorer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=324c86b3b5813447"
"InprocServer32"="C:\\Windows\\system32\\mscoree.dll"
"Class"="MSDNMagazine.TFSPlugins.SubstExplorerHostPackage"
"CodeBase"="c:\\program files\\msdnsamples\\TFSSubstExplorer.dll"
"ID"=dword:00000065
"MinEdition"="Professional"
"ProductVersion"="1.0"
"ProductName"="SubstExplorer"
"CompanyName"="vriesmarcel@hotmail.com"

[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\9.0\InstalledProducts\SubstExplorerHostPackage]
"Package"="{9b024c14-2f6f-4e38-aa67-3791524a807e}"
"UseInterface"=dword:00000001
 
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\9.0\Menus]
"{9b024c14-2f6f-4e38-aa67-3791524a807e}"=", 1000, 1"

O script de registro, você verá um número de entradas.A primeira entrada registra uma nova extensão do Team Explorer Team Explorer deve carregar assim que ele carrega.Aqui você fornecer um valor de registro que se refere à identificação de serviço que fornece uma implementação ITeamExplorerPlugin.Próxima entrada fornece o registro de serviço onde você ver a identificação de serviço para mencionado anteriormente, bem como um valor de registro que aponta para o pacote que fornece o plug-in.

Próxima entrada é o registro do pacote.Lá você usar a identificação do pacote como uma nova chave e fornecer informações onde o assembly pode ser encontrado, como pode ser carregadas usando a infra-estrutura COM, e o pacote de qual versão do Visual Studio oferece suporte.As duas últimas entradas são o registro dos produtos instalados, usado para a tela inicial.Aqui chave UseInterface indica o que Visual Studio deve chamar a interface IVsInstalledProduct em vez de depender de atributo InstalledProductRegistration para fornecer uma descrição do produto e ícone que precisa ser mostrado na inicialização.

A última entrada é o registro dos menus de contexto.Aqui você consultar seu pacote, mas também fornecer informações sobre onde você tiver incorporado os recursos no assembly.Esses são recursos incorporados criado antes de usar arquivos .vsct e ação nesse arquivo de compilação personalizada.Com este script e o assembly criado, você pode implantá-lo em outras máquinas.Simplesmente coloque o assembly no sistema de arquivo, ajustar o script de registro para refletir o local correto do assembly e mesclar no registro.Em seguida, a etapa final é executar devenv /setup nessa máquina.Quando você inicia o Visual Studio, você verá o ícone na tela inicial e quando você carregar Team Explorer, você verá o nó raiz do plug-in que você criou.

Brian A.Randellé consultor sênior da MCW Technologies LLC.Brian ocupa seu tempo falando, ensinando e escrevendo sobre tecnologias da Microsoft.Ele é o autor do curso Applied Team System da Pluralsight e é um MVP da Microsoft.Entre em contato com Brian via seu blog em mcwtech.com/blogs/brianr de.

Marcel de Vriesé arquiteto IT Info Support na Holanda, onde ele cria soluções para bancária grande e seguro empresas todo o país e ministra cursos Team System e o Windows Workflow.Marcel é um palestrante freqüente em conferências de desenvolvedor na Europa, um MVP do System Team desde 2006 e um diretor regional da Microsoft desde janeiro de 2009.

Graças aos seguintes especialistas técnicos para revisão deste artigo: Dennis Habib e Buck Hodges