martes, 8 de abril de 2008

[DSL] Agregar un menú personalizado en VS2008

La forma en la que se añade un menú personalizado ha variado en la nueva versión de Visual Studio 2008. El SDK de extensibilidad de Visual Studio 2008 ha variado la forma en la que se generan menús y comandos de forma que ya no se utilizan los “antiguos” ficheros de configuración .ctc.

Como pequeña introducción, os digo que en la nueva versión se ha optado por un nuevo sistema de configuración más acorde a los tiempos que corren basado en XML. Dicho sistema de configuración está completa y exquisitamente explicado en el siguiente enlace (no apto para lectura al final del dia;).

Dicho esto, os aviso de antemano que la documentación que viene con la propia descarga del ejecutable SDK Extensibility 1.0 para Visual Studio 2008 es errónea y hace referencia al antiguo sistema de configuración (por lo menos en lo que nos atañe que son los menús en DSL). El antiguo sistema de agregación de menús personalizados en nuestros lenguajes de dominio está explicado bastante bien en el blog de “El Bruno” o en la propia documentación que viene con el descargable de extensibilidad que he mencionado.

Vamos a ponernos manos a la obra. Para ello parto de que tienes ya tu proyecto de lenguaje de especificación de dominio creado…

image

Tal cual está, podemos crear nuestro menú de una forma bastante más sencilla que antes siguiendo los siguientes pasos:

1. En el “Solution Explorer”, desplegar el proyecto DslPackage y editar el archivo Commands.vsct

image

En dicho archivo es donde vamos a tener que añadir los elementos del menú que queremos. En nuestro caso vamos a crearnos un botoncito que nos diga la figura que hemos clickeado.

La forma de crear nodos dentro del archivo está “explicada” muy por encima en los documentos de ayuda de mdsn (por lo menos ahora cuando escribo este post) por lo que seguramente acabarás antes viéndolo desde aquí ;)

Básicamente lo que vamos a hacer es definirnos un botón y una identificación del mismo. Importante a tener en cuenta son los nodos <Commands/> y <Symbols/> (no quiero liaros con el nodo <Extern/> que no vamos a tocar ahora)

2. Dentro del nodo <Commands/> introducir un nodo con la descripción del botón que quereis crear

<Buttons>
<Button guid="cmdDimeNombreFiguraGUID" id="cmdDimeNombreFiguraID type="Button">
<Parent guid="guidCmdSet" id="grpidContextMain"/>
<Strings>
<CanonicalName>cmdDimeNombreFigura</CanonicalName>
<ButtonText>Nombre figura</ButtonText>
<ToolTipText>Dice el nombre de la figura seleccionadaToolTipText>
</Strings>
</Button>
</Buttons>

3. Fuera del nodo <Commands/> y al mismo nivel que él, declaramos el nodo <Symbols/> de la siguiente forma

<Symbols>
<GuidSymbol name="cmdDimeNombreFiguraGUID" value="{D5A40ECA-BA87-4a92-B6A7-A36C27C858AE}">
<IDSymbol name="cmdDimeNombreFiguraID" value="1"/>
</GuidSymbol>
</Symbols>

El valor del GuidSymbol ha sido generado mediante la aplicación guidgen.exe
El valor del IDSymbol puede ser decimal o hexadecimal (por ejemplo, valores 0x104 pueden darse) y podemos darle el que queramos.

4. Estado del fichero Commands.vsct

Una vez realizado esto, el fichero queda de la siguiente manera:

<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable xmlns:xs="http://www.w3.org/2001/XMLSchema>
<!-- -->
<!-- This file contains custom command definitions. -->
<!-- -->
<!-- NOTE: Each time commands are added or changed, the "version" parameter to the -->
<!-- ProvideMenuResource attribute in Shell\Package.tt should be incremented. -->
<!-- This causes Visual Studio to re-merge the menu definitions for the package. -->
<!-- Otherwise, changes won't take effect until the next time devenv /setup is run. -->
<!-- -->
<Extern href="stdidcmd.h"/>
<Extern href="vsshlids.h"/>
<Extern href="msobtnid.h"/>
<Extern href="virtkeys.h"/>
<Extern href="DSLToolsCmdID.h"/>
<Include href="GeneratedCode\GeneratedVsct.vsct"/>
<Commands package="guidPkg">
<Buttons>
<Button guid="cmdDimeNombreFiguraGUID" id="cmdDimeNombreFiguraID" type="Button">
<Parent guid="guidCmdSet" id="grpidContextMain"/>
<Strings>
<CanonicalName>cmdDimeNombreFigura</CanonicalName>
<ButtonText>Nombre figura</ButtonText>
<ToolTipText>Dice el nombre de la figura seleccionada</ToolTipText>
</Strings>
</Button>
</Buttons>
</Commands>
<Symbols>
<GuidSymbol name="cmdDimeNombreFiguraGUID" value="{D5A40ECA-BA87-4a92-B6A7-A36C27C858AE}">
<IDSymbol name="cmdDimeNombreFiguraID" value="1"/>
</GuidSymbol>
</Symbols>
</CommandTable>

5. Ahora, siguiendo lo que dice en los comentarios del propio fichero a la parte de arriba, le incrementamos el nº de versión al atributo ProvideMenuResource del fichero “GeneratedCode/Package.tt” (notese que en el comentario referencia a Shell/Package.tt ;)

image

Para ello, lo editamos y reemplazamos lo que viene por defecto ( [VSShell::ProvideMenuResource("1000.ctmenu", 1)] ) , por [VSShell::ProvideMenuResource("1000.ctmenu", 2)]

6. Ahora, vamos a asignarle el comportamiento al botón. Para ello hemos de abrir el fichero GeneratedCode/CommandSet.cs

image

Y le añadimos los métodos OnPopUpMenuClick(), OnPopUpMenuDisplayAction() y GetMenuComands() a la clase.

Evidentemente lo que no vamos a hacer es escribir el código directamente sobre el fichero CommandSet.cs puesto que dicho fichero se machaca cuando le damos a “Transform All Templates” por lo que haciendo uso de las posibilidades de definición de clases parciales, le definiremos el código en un nuevo fichero.

7. Creamos la carpeta Customization y luego añadimos una nueva clase, que será la que contenga el código, de forma que nos quedará como la figura siguiente:

image

NOTA: En mi caso, mi lenguaje se llama LenguajeOOMM y por eso la clase generada en CommandSet.cs se llama así. Por seguir una nomenclatura estándar he creado dicho nombre al fichero. En cualquier caso, el contenido siempre ha de ser el de la definición de la clase doblemente derivada.

Antes hacíamos referencia al fichero CommandSet.cs. Por poco que lo abramos y le demos un vistazo veremos que sigue la pauta de clase doblemente derivada (que se sale del tema del post pero que en futuros post trataré) que crea una clase heredada de DslShell::CommandSet (en mi caso llamada LenguajeOOMMCommandSetBase), y luego otra clase que hereda de la anterior y es a la que vamos a añadirle la funcionalidad citada en el paso 6.

8. Introducimos el siguiente código dentro de LenguajeOOMMCommandSet.cs (en tu caso, el fichero .cs que has creado para tal fin)

using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using Microsoft.VisualStudio.Modeling.Shell;
using System.Collections;
using System.Text;

namespace LenguajeOOMM
{
/// <summary>
/// Double-derived class to allow easier code customization.
/// </summary>
internal partial class LenguajeOOMMCommandSet : LenguajeOOMMCommandSetBase
{
protected override IList<System.ComponentModel.Design.MenuCommand> GetMenuCommands()
{
IList<System.ComponentModel.Design.MenuCommand> commands = base.GetMenuCommands();

DynamicStatusMenuCommand cmdDimeNombreFigura =
new DynamicStatusMenuCommand(
new EventHandler(OnPopUpMenuDisplayAction),
new EventHandler(OnPopUpMenuClick),
new CommandID(new Guid(“D5A40ECA-BA87-4a92-B6A7-A36C27C858AE”), 1));

commands.Add(cmdDimeNombreFigura);

return commands;
}

/// <summary>
/// Lo que se desencadena al pinchar sobre el boton.
/// En principio no queremos nada mas que se muestren los objetos seleccioonados
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
internal void OnPopUpMenuClick(object sender, EventArgs e)
{
MenuCommand command = sender as MenuCommand;

StringBuilder sb = new StringBuilder();
foreach (object selectedObject in this.CurrentSelection)
{
sb.AppendLine("Objetos Seleccionados: " + selectedObject.ToString());
}

System.Windows.Forms.MessageBox.Show(sb.ToString());
}

/// <summary>
/// Se desencadena cuando vamos a mostrar el menú.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
internal void OnPopUpMenuDisplayAction(object sender, EventArgs e)
{
MenuCommand command = sender as MenuCommand;

foreach (object selectedObject in this.CurrentSelection)
{
// Solo se desencadena si entre todos los objetos seleccionados, hay algun ExampleShape
//para conectores por tanto no sale. Comportamiento a posta
if (selectedObject is ExampleShape)
{
command.Visible = true;
command.Enabled = true;
return;
}
}
//por defecto deshabilitado
command.Visible = false;
command.Enabled = false;
}
}
}

9. Una vez llegados a este punto solo nos queda pinchar sobre “Transforma ll templates” y compilar.
Ahora nos saldrá el elemento del menú en cuestión y al clickear nos saldrá un MessageBox con la información que queríamos.

image

image

Ni que decir tiene las posibilidades de esto. Podemos crearnos un menú que interactúe con nuestro propio modelo de forma que podamos cambiar incluso el aspecto o propiedades de nuestros objetos para que sin tener que borrar figuras, las podamos modificar

No hay comentarios: