Personalizando ferramentas e a caixa de ferramentas

É necessário definir os itens da caixa de ferramentas para os elementos que deseja disponibilizar para os usuários adicionarem aos seus modelos. Há dois tipos de ferramentas: ferramentas de elemento e ferramentas de conexão. No designer gerado, um usuário pode selecionar uma ferramenta do elemento para arrastar formas para o diagrama e pode selecionar uma ferramenta de conexão para desenhar links entre as formas. Em geral, as ferramentas do elemento permitem aos usuários adicionarem instâncias de classe de domínio aos seus modelos e as ferramentas de conexão permitem que eles adicionem instâncias de relações de domínio.

Como a caixa de ferramentas é definida

No DSL Explorer, expanda o nó do Editor e o nó abaixo dele. Geralmente aparecerá uma hierarquia que lembra isto:

Editor
     Toolbox Tabs
        MyDsl          //a tab
           Tools
               ExampleElement      // an element tool
               ExampleRelationship // a connection tool

Nessa parte do DSL Explorer, é possível:

  • Criar novas guias. As guias definem o cabeçalho da seção na caixa de ferramentas.

  • Criar novas ferramentas.

  • Copie e cole ferramentas.

  • Mova as ferramentas para cima ou para baixo na lista.

  • Exclua guias e ferramentas.

Importante

Para adicionar ou colar itens em um DSL Explorer, clique com o botão direito no avô do novo nó. Por exemplo, para adicionar uma ferramenta, clique com o botão direito na guia e não no nó Ferramentas. Para adicionar uma guia, clique com o botão direito no nó Editor.

A propriedade do Ícone da Caixa de Ferramentas de cada ferramenta faz referência a um arquivo de bitmap de 16x16. Esses arquivos geralmente são mantidos na pasta Dsl\Resources.

A propriedade Classe de uma ferramenta do elemento faz referência a uma classe do domínio concreta. Por padrão, a ferramenta criará instâncias dessa classe. No entanto, é possível escrever um código para a ferramenta criar grupos de elementos ou elementos de diferentes tipos.

A propriedade Construtor de conexões de uma ferramenta de conexão refere-se a um construtor conexão, que define que tipos de elementos podem conectar-se à ferramenta e que relações isso cria entre eles. Os construtores de conexão são definidos como nós no DSL Explorer. Construtores de conexão são criados automaticamente ao definir relações de domínio, mas é possível escrever códigos para personalizá-los.

Para adicionar uma ferramenta à caixa de ferramentas

  1. Normalmente, uma ferramenta de elemento é criada depois de ter criado uma classe de forma e a ter mapeado em uma classe de domínio.

    Normalmente, uma ferramenta de conector é criada depois de ter criado uma classe de conector e a ter mapeado em uma relação de referência.

  2. No DSL Explorer, expanda o nó do Editor e o nó das Guias da Caixa de Ferramentas.

    Clique com o botão direito em um nó da guia da caixa de ferramentas e, em seguida, em Adicionar Nova Ferramenta de Elemento ou Adicionar Nova Ferramenta de Conexão.

  3. Ajuste a propriedade Ícone da Caixa de Ferramentas para fazer referência a um bitmap 16x16.

    Se desejar definir um novo ícone, crie um arquivo bitmap no Gerenciador de Soluções na pasta Dsl\Resources. O arquivo deve conter os seguintes valores de propriedade: Build Action = Content; Copy to Output Directory = Do not copy.

  4. Para uma ferramenta de elemento: Ajuste a propriedade Classe da ferramenta para fazer referência a uma classe de domínio concreta que seja mapeada para uma forma.

    Para uma ferramenta de conector: Ajuste a propriedade Construtor de Conexão da ferramenta para um dos itens que são oferecidos na lista suspensa. Os construtores de conexão são criados automaticamente ao mapear um conector para uma relação de domínio. Se tiver criado recentemente um conector, normalmente selecionaria o construtor de conexão associado.

  5. Para testar a DSL, pressione F5 ou CTRL+F5 e, na instância experimental do Visual Studio, abra um arquivo de modelo de amostra. A nova ferramenta deve aparecer na caixa de ferramentas. Arraste-a para o diagrama para verificar se ela criou um novo elemento.

    Se a ferramenta não aparecer, pare o Visual Studio experimental. No menu Iniciar do Windows, digite redefinir o Visual Studio e, em seguida, execute o comando Redefinir a Instância Experimental do Microsoft Visual Studio correspondente à sua versão do Visual Studio. No menu Compilar, clique em Recompilar Solução. Em seguida, teste o DSL novamente.

Ferramentas de elemento personalizadas

Por padrão, a ferramenta criará uma única instância da classe especificada, mas é possível variar isso de duas maneiras:

  • Defina as Diretivas de Mescla de Elemento em outras classes, permitindo-lhes aceitar novas instâncias dessa classe e permitindo-lhes criar links adicionais quando o novo elemento é criado. Por exemplo, é possível permitir ao usuário soltar um Comentário em outro elemento, criando dessa maneira um link de referência entre os dois.

    Essas personalizações também afetam o que ocorre quando o usuário cola ou arrasta e solta um elemento.

    Para obter mais informações, confira Personalizar a criação e a movimentação de elementos.

  • Escreva um código para personalizar a ferramenta para que ela possa criar grupos de elementos. A ferramenta é inicializada por métodos no ToolboxHelper.cs que podem ser substituídos. Para obter mais informações, consulte Criação de Grupos de Elementos a partir de uma Ferramenta.

Criação de grupos de elementos a partir de uma ferramenta

Cada ferramenta do elemento contém um protótipo de elementos que ela deve criar. Por padrão, cada ferramenta do elemento cria um único elemento, mas também é possível criar um grupo de objetos relacionados a uma ferramenta. Para isso, é necessário inicializar a ferramenta com um ElementGroupPrototype que contém os itens relacionados.

O exemplo a seguir é tomado de um DSL no qual há um Transistor de tipo. Cada Transistor possui três Terminais nomeados. A ferramenta do elemento dos Transistores armazena um protótipo contendo quatro elementos de modelo e três links de relacionamento. Quando o usuário arrasta a ferramenta no diagrama, o protótipo é instalado e conectado à raiz do modelo.

Esse código substitui um método definido em Dsl\GeneratedCode\ToolboxHelper.cs.

Para obter mais informações sobre como personalizar o modelo usando o código do programa, consulte Navegando e atualizando um modelo no código do programa.

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;

  public partial class CircuitsToolboxHelper
  {
    /// <summary>
    /// Toolbox initialization, called for each element tool on the toolbox.
    /// This version deals with each Component subtype separately.
    /// </summary>
    /// <param name="store"></param>
    /// <param name="domainClassId">Identifies the domain class this tool should instantiate.</param>
    /// <returns>prototype of the object or group of objects to be created by tool</returns>
    protected override ElementGroupPrototype CreateElementToolPrototype(Store store, Guid domainClassId)
    {
        if (domainClassId == Transistor.DomainClassId)
        {
            Transistor transistor = new Transistor(store);

            transistor.Base = new ComponentTerminal(store);
            transistor.Collector = new ComponentTerminal(store);
            transistor.Emitter = new ComponentTerminal(store);

            transistor.Base.Name = "base";
            transistor.Collector.Name = "collector";
            transistor.Emitter.Name = "emitter";

            // Create an ElementGroup for the Toolbox.
            ElementGroup elementGroup = new ElementGroup(store.DefaultPartition);
            elementGroup.AddGraph(transistor, true);
            // AddGraph includes the embedded parts

            return elementGroup.CreatePrototype();
        }
        else
        {
            return base.CreateElementToolPrototype(store, domainClassId);
}  }    }

Personalização de ferramentas de conexão

Normalmente, é criada uma ferramenta de elemento ao criar uma nova classe do conector. Como alternativa, é possível sobrecarregar uma ferramenta permitindo que os tipos das duas extremidades determinem o tipo de relação. Por exemplo, é possível definir uma ferramenta de conexão que poderia criar relações pessoa-pessoa e relações pessoa-cidade.

As ferramentas de conexão invocam os construtores de conexão. Use construtores de conexão para especificar como os usuários podem conectar elementos no designer gerado. Os construtores de conexão especificam os elementos que podem ser conectados e o tipo de link que é criada entre eles.

Ao criar uma relação de referência entre as classes de domínio, um construtor de conexão é criado automaticamente. É possível usar esse construtor de conexão ao mapear uma ferramenta de conexão. Para obter mais informações sobre como criar ferramentas de conexão, consulte Configurando a Caixa de Ferramentas.

É possível modificar o construtor de conexão padrão para que ele possa lidar com uma variedade de diferentes tipos de fonte e destino e criar diferentes tipos de relações.

Também é possível escrever códigos personalizados para os construtores de conexão especificarem as classes de fonte e de destino para a conexão, definir o tipo de conexão a ser feita e tomar outras ações associadas com a criação de uma conexão.

A estrutura dos construtores de conexão

Os construtores de conexão contêm um ou mais diretivas de conexão de link, que especificam a relação de domínio e os elementos de fonte e de destino. Por exemplo, no modelo de solução de Fluxo de Tarefa, é possível visualizar o CommentReferencesSubjectsBuilder no DSL Explorer. Esse construtor de conexão contém uma diretiva de conexão de link chamada CommentReferencesSubjects, que é mapeada para a relação de domínio CommentReferencesSubjects. Essa diretiva de conexão de link contém uma diretiva da função de fonte que indica a classe de domínio Comment e uma diretiva de função de destino que indica a classe de domínio FlowElement.

Uso dos construtores de conexão para restringir as funções da fonte e do destino

É possível usar os construtores de conexão para restringir a ocorrência de determinadas classes na função de fonte ou de destino de uma determinada relação de domínio. Por exemplo, você pode ter uma classe de domínio de base que tenha uma relação de domínio com outra classe de domínio, mas pode não desejar que todas as classes derivadas da classe base tenham as mesmas funções nessa relação. Na solução do Fluxo de Tarefa, existem quatro classes de domínio concreto StartPoint, EndPoint, MergeBranch e Synchronization) que herdam diretamente da classe de domínio abstrata FlowElement e duas classes de domínio concreto Task e ObjectInState) que herdam indiretamente dela. Existe também uma relação de referência de Fluxo que leva a classe de domínio FlowElement em sua função de fonte e na função de destino. No entanto, uma instância de uma classe de domínio de EndPoint não deve ser a fonte de uma instância de uma relação de Fluxo, nem deve uma instância de uma classe de StartPoint ser destino de uma instância de um relacionamento de Fluxo. O construtor de conexão FlowBuilder possui uma diretiva de conexão de link chamada Fluxo que especifica quais classes de domínio podem desempenhar a função de fonte (Tarefa, MergeBranch, StartPoint e Synchronization) e que pode desempenhar a função de destino (MergeBranch, Endpointe Synchronization).

É possível adicionar mais de uma diretiva de conexão de link a um construtor de conexão. Isto pode ajudá-lo a esconder algumas das complexidades do modelo de domínio dos usuários e impedir que a Caixa de Ferramentas fique muito confusa. É possível adicionar diretivas de conexão de link de várias relações de domínio diferentes a um único construtor de conexão. No entanto, é necessário combinar as relações de domínio quando elas realizam aproximadamente a mesma função.

Na solução do Fluxo de Tarefa, a ferramenta de conexão de Fluxo é usada para desenhar instâncias das relações de domínio de Fluxo e a ObjectFlow. O construtor de conexão FlowBuilder possui, além da diretiva de conexão de link de Fluxo descrita anteriormente, duas diretivas de chamadas ObjectFlow. Essas diretivas especificam que uma instância de uma relação ObjectFlow pode ser estabelecida entre instâncias da classe de domínio ObjectInState ou a partir de uma instância de um ObjectInState para uma instância de uma Tarefa, mas não entre duas instâncias de uma Tarefa ou uma instância de uma Tarefa para uma instância de um ObjectInState. No entanto, uma instância de uma relação de Fluxo pode ser estabelecida entre duas instâncias de uma Tarefa. Caso compilar e executar a solução de Fluxo de Tarefas, verá que estabelecer um Fluxo de uma instância de um ObjectInState para uma instância de uma Tarefa cria uma instância de um ObjectFlow, mas estabelecer um Fluxo entre duas instâncias de uma Tarefa cria uma instância de um Fluxo.

Código personalizado para construtores de conexão

Existem quatro caixas de seleção na interface do usuário que definem diferentes tipos de personalização dos construtores de conexão:

  • a caixa de seleção Aceitação personalizada em uma diretiva de função de fonte ou destino

  • a caixa de seleção Conexão personalizada em uma diretiva de função de fonte ou destino

  • a caixa de seleção Conexão personalizada de usos em uma diretiva de conexão

  • a propriedade É Personalizado do construtor de conexão

    É necessário fornecer algum código de programa para fazer essas personalizações. Para descobrir qual código é necessário fornecer, verifique uma dessas caixas, clique em Transformar Todos os Modelos e, em seguida, construa sua solução. O resultado será um relatório de erro. Clique duas vezes no relatório de erro para visualizar um comentário que explique qual código deve ser adicionado.

Observação

Para adicionar o código personalizado, crie uma definição de classe parcial em um arquivo de código separado dos arquivos de códigos nas pastas GeneratedCode. Para evitar a perda de seu trabalho, é necessário que os arquivos de códigos gerados não sejam editados. Para obter mais informações, consulte Substituir e estender as classes geradas.

Criação de uma conexão personalizada

Em cada diretiva de conexão de link a guia Diretivas de função da fonte define a partir de quais tipos é possível arrastar. De maneira semelhante, as Diretivas de função do destino definem para quais tipos é possível arrastar. Para cada tipo, é possível especificar se deseja permitir a conexão (para essa diretiva de conexão de link) definindo o sinalizador Aceitação personalizada e, em seguida, fornecendo o código extra.

Também é possível personalizar o que ocorre quando a conexão é feita. Por exemplo, é possível personalizar apenas o caso onde o arrasto ocorre para ou de uma determinada classe, todos os casos que um diretiva de conexão de link regula ou o construtor de conexão FlowBuilder todo. Para cada uma dessas opções, é possível ajustar sinalizadores personalizados no nível apropriado. Quando transformar todos os modelos e tentar compilar a solução, as mensagens de erro irão direcioná-lo para os comentários que estão no código gerado. Esses comentários identificam o que deve ser fornecido.

No exemplo de Diagrama de Componentes, o construtor de conexão da relação do domínio de Conexão é personalizado para restringir as conexões que podem ser feitas entre as portas. As ilustrações a seguir mostram que é possível fazer conexões somente a partir dos elementos da OutPort para os elementos InPort, mas é possível aninhar os componentes dentro uns aos outros.

Conexão vindo para uma OutPort de um Componente Aninhado

Connection Builder

Portanto, pode ser desejável especificar que uma conexão pode vir de um componente aninhado para uma OutPort. Para especificar uma conexão como essa, ajuste Aceitação Personalizada de Usos no tipo InPort como uma função de fonte e o tipo OutPort como uma função de destino na janela DSL Details, como mostrado nas seguintes ilustrações:

Diretiva de conexão de link no DSL Explorer

Connection builder image

Diretiva de conexão de link na janela DSL Details

Link connect directive in DSL Details window

É necessário fornecer métodos na classe ConnectionBuilder:

  public partial class ConnectionBuilder
  {
    /// <summary>
    /// OK if this component has children
    /// </summary>
    private static bool CanAcceptInPortAsSource(InPort candidate)
    {
       return candidate.Component.Children.Count > 0;
    }

    /// <summary>
    /// Only if source is on parent of target.
    /// </summary>
    private static bool CanAcceptInPortAndInPortAsSourceAndTarget                (InPort sourceInPort, InPort targetInPort)
    {
      return sourceInPort.Component == targetInPort.Component.Parent;
    }
// And similar for OutPorts...

Para obter mais informações sobre como personalizar o modelo usando o código do programa, consulte Navegando e atualizando um modelo no código do programa.

É possível usar códigos semelhantes, por exemplo, para impedir que os usuários criem loops com links pai-filho. Essas restrições são consideradas restrições 'duras', porque os usuários não podem violá-las a qualquer momento. Também é possível criar verificações de validação 'suaves' que os usuários podem ignorar temporariamente criando configurações inválidas que eles não podem salvar.

Boas práticas na definição dos construtores de conexão

É necessário definir um construtor de conexão para criar diferentes tipos de relações, apenas se elas estão relacionadas conceitualmente. No exemplo de fluxo de tarefas, é possível usar o mesmo construtor para criar fluxos entre tarefas e também entre tarefas e objetos. No entanto, seria confuso usar o mesmo construtor para criar relações entre tarefas e comentários.

Se um construtor de conexão for definido para vários tipos de relações, é necessário garantir que ele não possa corresponder a mais de um tipo do mesmo par de objetos de fonte e de destino. Caso contrário, os resultados seriam imprevisíveis.

É possível o código personalizado para aplicar restrições 'duras', mas deve ser considerado se os usuários devem ser capazes de fazer temporariamente conexões inválidas. Se o devem, é possível modificar as restrições para que as conexões não são validadas até que os usuários tentem salvar as alterações.