Como responder a alterações em um modelo UML
Você pode escrever código que é executado sempre que ocorre uma alteração em um modelo UML no Visual Studio. Responder igualmente alterações são feitas diretamente pelo usuário e por outros Visual Studio as extensões.
Aviso
Esse recurso não é diretamente suportado pela API de extensão UML. Portanto, você precisa usar técnicas ligeiramente não convencionais. O código necessário é fornecido neste tópico. No entanto, é possível que os efeitos inesperados podem ocorrer em alguns casos. Também é possível que as alterações futuras na Visual Studio a implementação do UML invalidará as técnicas descritas neste tópico.
Para usar as técnicas descritas neste tópico, é útil estar familiarizado com a visualização e a modelagem SDK (VMSDK), com o qual as ferramentas UML são implementadas. Para obter mais informações, consulte Domain-Specific Languages.
Neste tópico:
Criando um projeto de extensão UML
Eventos e regras
Definindo eventos
Exemplo: Classes de coloração de estereótipos usando eventos
Definição de regras
Exemplo: Classes de coloração de estereótipos usando regras
Exemplo: Associações são bidirecionais por padrão
Núcleo e modelos de exibição
Criando um projeto de extensão UML
Em muitos casos, você irá adicionar um manipulador de eventos para uma extensão que já implementa um manipulador de comando ou o gesto. Nesse caso, você pode adicionar o código aqui descrito para o mesmo Visual Studio project. Para obter mais informações, consulte Como: Definir um comando de Menu em um diagrama de modelagem e Como: Definir uma queda e clique duas vezes o manipulador de um diagrama de modelagem.
Se você deseja criar o manipulador de eventos em um separado Visual Studio a extensão, inicie, criando um projeto da validação novo do UML. No Novo projeto caixa de diálogo, clique em Projetos de modelageme selecione Extensão de validação do modelo de. Como alternativa, você pode seguir o procedimento a definição de uma extensão de validação na Como: Definir restrições de validação dos modelos UML.
Você deve adicionar as seguintes referências ao projeto:
\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.Uml.dll
Microsoft.VisualStudio.ArchitectureTools.Extensibility.dll
Microsoft.VisualStudio.Modeling.Sdk.10.0dll
Microsoft.VisualStudio.Modeling.Sdk.Diagrams.10.0.dll
Microsoft.VisualStudio.Uml.Interfaces.dll
Eventos e regras
VMSDK fornece dois métodos para detectar alterações no armazenamento principais:
Um manipulador de eventos responde a uma alteração após o término da transação na qual a alteração ocorreu. Manipuladores de eventos são normalmente usados para propagar alterações fora do modelo, interfaces de usuário, arquivos ou bancos de dados. Você também pode escrever um manipulador de eventos que faz uma alteração adicional para o modelo em uma nova transação.
A regra responde a uma alteração dentro da transação na qual a alteração ocorreu. Normalmente, as regras são usadas para propagar alterações dentro do modelo, para manter a consistência entre duas partes do modelo. Você também pode escrever uma regra que impeça uma alteração inválida, cancelando a transação.
Para obter mais informações sobre transações, consulte Como: Atualizações do modelo de link usando transações.
Definindo manipuladores de eventos
Para que seu manipulador de eventos chamado quando uma alteração ocorre, você deve registrá-lo. Você deve registrar o manipulador para cada classe de elemento que você deseja monitorar, como, por exemplo, caso de uso ou atividade. Não é necessário registrá-lo para cada instância.
O exemplo a segue define a cor de uma classe de UML de acordo com o estereótipo em que o usuário se aplica. Os manipuladores de eventos são registrados para ser disparado sempre que uma instância de estereótipo é criada ou excluída. Como este exemplo usa manipuladores de eventos e não regras, os manipuladores são chamados após a conclusão da transação na qual o estereótipo é alterado. Como a cor é também uma alteração no armazenamento de VMSDK, ele deve ser realizado em uma transação de segunda.
Você também pode usar os manipuladores de eventos para realizar alterações fora do armazenamento.
Você pode adaptar o código do exemplo a seguir para responder a eventos de sua própria escolha. Os pontos importantes a ser notada sobre esse código são:
A API de validação é usada para registrar os manipuladores de eventos. A estrutura de validação fornece uma maneira conveniente de executar código quando o modelo é aberto. Mas o código realmente não executar a validação e o usuário não precisa chamar a validação para executar atualizações.
Manipuladores de eventos são métodos que são adicionados ao Store.EventManagerDirectory. Este é o Store da implementação subjacente do VMSDK (DSL), não o UML ModelStore. EventManagerDirectorytem um conjunto fixo de dicionários para diferentes tipos de eventos, como ElementAdded e ElementDeleted.
Para registrar um evento, você deve saber o nome da classe de implementação ou relacionamento que você deseja monitorar. Essas classes são definidas em Microsoft.VisualStudio.Modeling.Uml.dll, e você pode ver os nomes de classe ao assistir a propriedades no depurador. Você pode converter esses membros de classe para os tipos de interface apropriada, como IClass, IStereotype. Para obter uma lista dos tipos de interface, consulte Tipos de elemento de modelo.
Os nomes de classe de implementação podem ser diferentes em versões futuras.
Manipuladores de eventos são chamados quando o usuário chama o Desfazer e Refazer comandos. Por exemplo, após um evento de adicionar desfazer irá disparar um evento Delete. O manipulador de eventos deve responder a esses eventos se ele está propagando alterações fora do armazenamento. Mas ele não deve fazer alterações dentro do armazenamento em resposta para desfazer ou refazer e não deve fazer alterações quando o modelo que está sendo lido do arquivo. Você pode usar if (!store.InUndoRedoOrRollback && !store.InSerializationTransaction)....
O exemplo mostra os manipuladores de eventos para adicionar e excluir objetos do modelo. Você também pode criar manipuladores de eventos para alterações em valores de propriedade. Para obter mais informações, consulte How to: Register to be Notified on an Event.
Para obter mais informações sobre eventos, consulte How to: Register to be Notified on an Event.
Exemplo: Classes de um estereótipo de cores usando eventos
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Linq;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Uml.AuxiliaryConstructs;
using Microsoft.VisualStudio.Uml.Classes;
using Microsoft.VisualStudio.Uml.Profiles;
using Microsoft.VisualStudio.Uml.UseCases;
using Microsoft.VisualStudio.Uml.ModelStore; // in private assembly. Used for Get|IsElementDefinition()
namespace UmlEvents
/// <summary>
/// Wraps a UML model to add stereotype coloring.
/// </summary>
public partial class ColoringModelAdapter
{
// This is the underlying DSL store, not the wrapping UML ModelStore:
private Store store;
/// <summary>
/// This isn't actually validation. It's to couple this adapter to the model before we start.
/// The validation package creates an instance of this class and then calls this method.
/// See "Validation": https://msdn.microsoft.com/library/bb126413.aspx
/// </summary>
/// <param name="vcontext"></param>
/// <param name="model"></param>
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Open)]
public void ConnectAdapterToModel(ValidationContext vcontext, IModel model)
{
// This is the underlying DSL store, not the wrapping UML ModelStore:
store = (model as ModelElement).Store;
// Add an event that triggers on creating a stereotype instance.
// See "Event handlers": https://msdn.microsoft.com/library/bb126250.aspx
DomainClassInfo siClass = store.DomainDataDirectory.FindDomainClass
("Microsoft.VisualStudio.Uml.Classes.StereotypeInstance");
store.EventManagerDirectory.ElementAdded.Add(siClass,
new EventHandler<ElementAddedEventArgs>(StereotypeInstanceAdded));
// For the deletion, we need to trigger from the deleted link
// between the stereotype instance and the model element -
// because after deletion we can't find the element from the stereotype instance.
DomainRelationshipInfo linkToStereotypeClass = store.DomainDataDirectory.FindDomainRelationship
("Microsoft.VisualStudio.Uml.Classes.ElementHasAppliedStereotypeInstances");
store.EventManagerDirectory.ElementDeleted.Add(linkToStereotypeClass,
new EventHandler<ElementDeletedEventArgs>(StereotypeInstanceDeleted));
// Add here handlers for other events.
}
/// <summary>
/// Event handler called whenever a stereotype instance is linked to a uml model element.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void StereotypeInstanceAdded(object sender, ElementAddedEventArgs e)
{
// Don't handle changes in undo or load from file:
if (store.InUndoRedoOrRollback || store.InSerializationTransaction) return;
IStereotypeInstance si = e.ModelElement as IStereotypeInstance;
IElement umlElement = si.Element;
// Work only with the core model, not the views:
if (!umlElement.IsElementDefinition()) return;
// I'm only interested in coloring classes and interfaces:
if (!(umlElement is IType)) return;
Color? color = ColorForStereotype(si.Name);
if (color.HasValue)
{
SetColorOfShapes(si.Element, color.Value);
}
}
/// <summary>
/// Called whenever a stereotype instance is deleted - well, actually,
/// when the link between the stereotype instance and the uml model element is deleted.
/// Triggering on the link deletion allows us to get both ends.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void StereotypeInstanceDeleted(object sender, ElementDeletedEventArgs e)
{
// Don't handle changes in undo or load from file:
if (store.InUndoRedoOrRollback || store.InSerializationTransaction) return;
// Use the generic link type to avoid unearthing the UML implementation DLL:
ElementLink elementToStereotypeLink = e.ModelElement as ElementLink;
IElement umlElement = elementToStereotypeLink.LinkedElements[0] as IElement;
IStereotypeInstance si = elementToStereotypeLink.LinkedElements[1] as IStereotypeInstance;
// Work only with the core model, not the views:
if (!umlElement.IsElementDefinition()) return;
// We're here either because a stereotype is being un-applied,
// or because the uml element is being deleted.
// Don't bother if the element is being deleted:
if ((umlElement as ModelElement).IsDeleting) return;
// We're only interested in classes and interfaces:
if (!(umlElement is IType)) return;
// Because more than one stereotype can be applied to an element,
// we should check to see if there are any remaining:
Color newColor = Color.WhiteSmoke; // Default if there aren't.
foreach (IStereotypeInstance remainingSi in umlElement.AppliedStereotypes)
{
Color? color = ColorForStereotype(remainingSi.Name);
if (color.HasValue)
{
newColor = color.Value;
break;
}
}
SetColorOfShapes(umlElement, newColor);
}
private void SetColorOfShapes(IElement element, Color color)
{
foreach (IShape shape in element.Shapes())
{
shape.Color = color;
}
}
/// <summary>
/// This example deals only with a subset of the standard stereotypes.
/// </summary>
private Color? ColorForStereotype(string name)
{
switch (name)
{
case "focus": return Color.AliceBlue;
case "auxiliary": return Color.Bisque;
case "specification": return Color.OliveDrab;
case "realization": return Color.LightSeaGreen;
case "implementationClass": return Color.PaleGoldenrod;
}
return null;
}
}}
Definição de regras
Você pode definir uma regra para propagar uma alteração no armazenamento do VMSDK. A alteração de acionamento e a regra são executadas na mesma transação. Quando o usuário invoca desfazer, ambas as alterações serão desfeitas juntos.
Uma desvantagem do exemplo anterior é que ele usa manipuladores de eventos para alterar a cor das formas. A cor é anexada a um campo no armazenamento de VMSDK e, portanto, deve ser executada em uma transação. Conseqüentemente, se o usuário chama o Desfazer comando após a aplicação de um estereótipo, a alteração de cor é desfeita, mas o estereótipo permanece aplicado. Outro Desfazer é necessária para reverter a aplicação de um estereótipo. Em alguns casos, isso pode ser o efeito pretendido, mas se não, você pode propagar as alterações dentro de uma transação com a definição de regras.
As regras são menos útil para propagar alterações fora da loja, porque eles não são chamados quando o usuário executa um Desfazer ou Refazer comando.
Uma regra é uma classe que está registrada com o Gerenciador de regra. Normalmente, quando você escreve o código VMSDK, registre uma regra, anexando um atributo à classe e incluindo a classe em uma lista que é lido quando o código de extensão é carregado. Mas como a implementação de UML já é compilada, você deve adicionar a regra para o Gerenciador de regra dinamicamente. O código fornecido no exemplo é altamente dependente com o gerenciamento de regra de implementação atual, que pode mudar em futuras versões.
Para adicionar uma regra, você precisa saber os nomes das classes de implementação. Eles podem mudar em versões futuras. Sempre que possível, você deve converter elementos para os tipos de API de UML, como IClass, IProfile.
O exemplo mostra as regras que lidam com a adição e exclusão de objetos no modelo UML. Você também pode criar regras que respondem a alterações nas propriedades dos objetos. Para obter mais informações, consulte How to: Create Custom Rules.
Exemplo: Classes de um estereótipo de cores usando regras
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Uml.AuxiliaryConstructs;
using Microsoft.VisualStudio.Uml.Classes;
using Microsoft.VisualStudio.Uml.UseCases;
using Microsoft.VisualStudio.Uml.ModelStore; // in private assembly. Used for Get|IsElementDefinition()
namespace UmlRules
{
class ColorByStereotype
{
/// <summary>
/// Singleton wrappers: one per model.
/// </summary>
private static Dictionary<IPackage, ColorByStereotype > modelAdapters =
new Dictionary<IPackage, ColorByStereotype >();
private class Wrapper
{
/// <summary>
/// This isn't actually validation.
/// It sets up some store rules.
/// The validation package creates an instance of this class and then calls this method.
/// See "Validation": https://msdn.microsoft.com/library/bb126413.aspx
/// </summary>
/// <param name="vcontext"></param>
/// <param name="model"></param>
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Open)]
private void ConnectAdapterToModel(ValidationContext vcontext, IModel model)
{
modelAdapters.Add(model, new ColorByStereotype (model));
}
}
private IModel model;
private Store store;
private ColorByStereotype (IModel model)
{
this.model = model;
// This is the underlying DSL store, not the wrapping UML ModelStore:
store = (model as ModelElement).Store;
SetRule<StereotypeInstanceAddedRule>(
store.DomainDataDirectory.FindDomainClass(
"Microsoft.VisualStudio.Uml.Classes.StereotypeInstance"));
// For the deletion, we need to trigger from the deleted link
// between the stereotype instance and the model element -
// because after deletion we can't find the element from the stereotype instance.
SetRule<StereotypeInstanceDeletedRule>(
store.DomainDataDirectory.FindDomainRelationship(
"Microsoft.VisualStudio.Uml.Classes.ElementHasAppliedStereotypeInstances"));
}
/// <summary>
/// Register a rule.
/// Normally, you set a rule by prefixing the rule class with
/// [RuleOn(typeof(TargetClass))]
/// but we are setting up the rule at runtime, so must add
/// the rules to the relevant dictionaries.
/// </summary>
/// <typeparam name="T">Rule class</typeparam>
/// <param name="classInfo">Class or relationship to which to attach the rule.</param>
private void SetRule<T>(DomainClassInfo classInfo) where T : Rule, new()
{
T rule = new T();
rule.FireTime = TimeToFire.TopLevelCommit;
System.Type tt = typeof(T);
string ruleSet = (typeof(AddRule).IsAssignableFrom(tt)) ? "AddRules" :
(typeof(ChangeRule).IsAssignableFrom(tt)) ? "ChangeRules" :
(typeof(DeleteRule).IsAssignableFrom(tt)) ? "DeleteRules" :
(typeof(DeletingRule).IsAssignableFrom(tt)) ? "DeletingRules" : "";
// The rest of this method uses reflection to achieve the following:
// store.RuleManager.RegisterRule(rule);
// classInfo.AddRules.Add(rule);
System.Reflection.BindingFlags privateBinding =
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic;
System.Reflection.MethodInfo mi =
typeof(RuleManager).GetMethod("RegisterRule", privateBinding);
mi.Invoke(store.RuleManager, new object[] { rule });
store.RuleManager.EnableRule(typeof(T));
System.Reflection.PropertyInfo pi =
typeof(DomainClassInfo).GetProperty(ruleSet, privateBinding);
dynamic rules = pi.GetValue(classInfo, null);
System.Type ruleListType = rules.GetType();
System.Reflection.FieldInfo listpi =
ruleListType.GetField("list", privateBinding);
dynamic list = listpi.GetValue(rules);
System.Type listType = list.GetType();
System.Reflection.MethodInfo addmi = listType.GetMethod("Add");
addmi.Invoke(list, new object[] { rule });
System.Reflection.MethodInfo resetRulesCache =
typeof(DomainClassInfo).GetMethod("ResetRulesCache", privateBinding);
resetRulesCache.Invoke(classInfo, null);
}
#region Rules.
private class StereotypeInstanceAddedRule : AddRule
{
public override void ElementAdded(ElementAddedEventArgs e)
{
base.ElementAdded(e);
Store store = e.ModelElement.Store;
// Don't handle load from file:
if (store.InSerializationTransaction)
return;
IStereotypeInstance si = e.ModelElement as IStereotypeInstance;
IElement umlElement = si.Element;
// Work only with the core model, not the views:
if (!umlElement.IsElementDefinition()) return;
// I'm only interested in coloring classes and interfaces:
if (!(umlElement is IType)) return;
Color? color = ColorForStereotype(si.Name);
if (color.HasValue)
{
SetColorOfShapes(si.Element, color.Value);
}
}
}
private class StereotypeInstanceDeletedRule : DeleteRule
{
public override void ElementDeleted(ElementDeletedEventArgs e)
{
base.ElementDeleted(e);
Store store = e.ModelElement.Store;
// Use the generic link type to avoid using the UML implementation DLL:
ElementLink elementToStereotypeLink = e.ModelElement as ElementLink;
IElement umlElement = elementToStereotypeLink.LinkedElements[0] as IElement;
// Work only with the core model, not the views:
if (!umlElement.IsElementDefinition()) return;
// We're here either because a stereotype is being un-applied,
// or because the uml element is being deleted.
// Don't bother if the element is being deleted:
if ((umlElement as ModelElement).IsDeleting) return;
// We're only interested in classes and interfaces:
if (!(umlElement is IType)) return;
// Because more than one stereotype can be applied to an element,
// we should check to see if there are any remaining:
Color newColor = Color.WhiteSmoke; // Default if there aren't.
foreach (IStereotypeInstance remainingSi in umlElement.AppliedStereotypes)
{
Color? color = ColorForStereotype(remainingSi.Name);
if (color.HasValue)
{
newColor = color.Value;
break;
}
}
SetColorOfShapes(umlElement, newColor);
}
}
/// <summary>
/// Set the color of the shapes that display an element.
/// </summary>
/// <param name="element"></param>
/// <param name="color"></param>
private static void SetColorOfShapes(IElement element, Color color)
{
foreach (IShape shape in element.Shapes())
{
shape.Color = color;
}
}
/// <summary>
/// For this sample, we just deal with some of the standard stereotypes.
/// </summary>
/// <param name="name">Stereotype name</param>
/// <returns></returns>
private static Color? ColorForStereotype(string name)
{
switch (name)
{
case "focus": return Color.AliceBlue;
case "auxiliary": return Color.Bisque;
case "specification": return Color.OliveDrab;
case "realization": return Color.LightSeaGreen;
case "implementationClass": return Color.PaleGoldenrod;
}
return null;
}
#endregion
}
}
Exemplo: Associações são bidirecionais por padrão
Por padrão, quando você desenha uma associação em um diagrama de classe, a nova associação é navegável somente em uma direção. Ele tem uma ponta de seta em uma extremidade. Para algumas finalidades, é mais conveniente desenhar as associações de bidirecional, com sem pontas de seta. Você pode fazer associações bidirecionais padrão adicionando a seguinte regra.
/// <summary>
/// Rule invoked when an Association is created.
/// This rule sets both ends navigable, which is convenient for representing requirements.
/// </summary>
private class AssociationAddRule : AddRule
{
public override void ElementAdded(ElementAddedEventArgs e)
{
Store store = e.ModelElement.Store;
IAssociation association = e.ModelElement as IAssociation;
// Do not apply the rule if we are reading from file or undoing a deletion:
if (association.MemberEnds.Count() == 0
|| store.InSerializationTransaction || store.InUndoRedoOrRollback) return;
// Do not apply the rule unless a containing package or model has imported
// a profile that defines the stereotype "Default Binary Associations" for associations:
// if (!association.ApplicableStereotypes.Any
// (s => s.DisplayName == "Default Binary Associations")) return;
// Don’t apply the rule to use cases:
if (!(association.SourceElement is IUseCase && association.TargetElement is IUseCase))
{
association.OwnedEnds.First().SetNavigable(true);
association.OwnedEnds.Last().SetNavigable(true);
}
}
}
Para registrar a regra, você deve usar o SetRule método descrito em Definindo regras:
SetRule<AssociationAddRule>(store.DomainDataDirectory.
FindDomainRelationship("Microsoft.VisualStudio.Uml.Classes.Association"));
Se você deseja ser capaz de habilitar ou desabilitar esta regra, um método de fazer então é definir um perfil no qual um determinado estereótipo é definido. Você pode adicionar código à sua regra para verificar que o perfil foi ativado em um pacote contendo ou o modelo. Para obter mais informações, consulte Como: Definir um perfil de estender o UML.
Núcleo e modelos de exibição
O modelo UML consiste em mais de um modelo de VMSDK (DSL):
O o modelo de núcleo contém representações de todos os elementos no modelo UML. O usuário pode ver esses elementos na janela Gerenciador de modelos UML e você pode acessá-los por meio de UML ModelStore API. O modelo de núcleo ocupa uma partição de armazenamento do VMSDK.
Há um o modelo de exibição para todos os diagramas UML no projeto UML. Os objetos em cada modelo de exibição são proxies para objetos do modelo de núcleo. Há um objeto de modelo de exibição para cada elemento que é exibido no diagrama UML. Cada modelo de exibição ocupa uma partição de armazenamento do VMSDK.
Há um objeto de forma VMSDK para cada elemento exibido em um diagrama. Há uma relação 1: 1 entre os elementos de modelo de exibição e formas.
Quando você define as regras ou manipuladores de eventos, eles serão chamados ambos quando os objetos principal e modo de exibição de alterar. Você deve tratar somente as alterações para os principais objetos. Os manipuladores do uso de exemplos element.IsElementDefinition() para determinar se eles estão lidando com o objeto de núcleo.
using Microsoft.VisualStudio.Uml.ModelStore; // in private assembly. Used for GetElementDefinition()
...
// Determine whether an element is view or core:
if (anElement.IsElementDefinition())
{ /* core * / }
else
{ /* view */ }
...
// If shapeElement is a shape on a diagram -
// The VMSDK element connected to the shape is in the view:
IElement viewModelElement = shapeElement.ModelElement as IElement;
// Get the core element for which the view is a proxy:
IElement coreModelElement = viewModelElement.GetElementDefinition();
...
Consulte também
Tarefas
How to: Register to be Notified on an Event
Conceitos
Histórico de alterações
Date |
History |
Motivo |
---|---|---|
Março de 2011 |
Tópico criado. |
Comentários do cliente. |