• Introducción a Composite UI Application Block (CAB) V – UI Extension Sites

    Published by mconverti on September 27th, 2007 6:04 am under CAB

    No Comments

    Descripción

    La mayoría de las aplicaciones basadas en Windows tienen elementos comunes en la interfaz de usuario, como son los menús, las barras de estado, las barras de herramientas, etc. que poseen la funcionalidad necesaria para realizar gran variedad de acciones que pueden afectar a distintos sectores de la aplicación. Esto generalmente provoca acoplamiento entre los módulos que necesiten usar o modificar estos elementos. Composite UI Application Block permite que los UIElements puedan ser accedidos por todos los módulos de una manera desacoplada.

    Antes de continuar voy a introducir el concepto de UIElement. Los UIElements son controles que están hosteados en el Shell los cuales presentan propiedades y funcionalidades requeridas por la mayoría de los módulos de la aplicación.

    Durante el proceso de inicialización de la aplicación, cada módulo que se carga tiene la posibilidad de agregar elementos propios a los UIElements registrados. La Figura 1 muestra el proceso de la carga de un ítem a la barra de menú del shell durante la carga de un módulo.

    UIExtensionSites

    Figura 1

    Proceso de carga de un item a la barra de menú durante la carga de un módulo.

    El siguiente diagrama de clases muestra las relaciones entre las clases involucradas con el proceso mostrado en la Figura 1 y sus principales métodos.

    Diag clases - UIElement

    Figura 2

    Clases involucradas con los UIElement y sus adapters.

    Para poder entender mejor el diagrama anterior, la siguiente tabla muestra una breve descripción de las clases que lo componen.

    Clase

    Características

    IUIElementAdapter Interfaz implementada por los objetos que van a proveer soporte para un determinado UIElement.
    UIElementAdapter Es una clase genérica que implementa la interfaz IUIElementAdapter. Se usa como soporte para facilitar la creación de adapters, ya que trae implementación genérica para los métodos de la interfaz .
    UIExtensionSite Esta clase permite que los UIElements sean extendidos por los todos los módulos. Es una especie de “gancho” donde los módulos pueden colgar elementos a un determinado elemento de la interfaz de usuario.
    UIExtensionSiteCollection Representa una colección compartida por todos los módulos donde se guardan los IUIElementAdapter y UIExtensionSite para cada UIElement. La colección los identifica con un string como ID.
    IUIElementAdapterFactory Provee la lógica necesaria para crear una instancia apropiada de un IUIElementAdapter para un determinado UIElement.
    UIElementAdapterFactoryCatalog Clase que implementa la Interfaz IUIElementAdapterFactoryCatalog. La clase contiene un catálogo donde se guardan todas las factories de los adapters para cada UIElement soportado.

    Tabla 1

    Interfaces y Clases responsables de registrar y utilizar los UIElement.

    La clase WorkItem posee la propiedad UIExtensionSites que expone una colección de tipo UIExtensionSitesCollection. El usuario puede registrar todos sus UIElements dentro de esa colección mediante el método RegisterSite. Este método posee dos sobrecargas. La primera versión del método es la siguiente:

    [C#]

    public void RegisterSite(string siteName, IUIElementAdapter adapter)

    [Visual Basic]

    Public Sub RegisterSite(ByVal siteName As String, ByVal adapter As IUIElementAdapter)

    Este método crea una instancia de la clase UIExtensionSite para el adapter pasado y lo agrega a la colección asociándolo con el ID recibido en el string siteName.

    La otra versión del método RegisterSite es:

    [C#]

    public void RegisterSite(string siteName, object uiElement)

    [Visual Basic]

    Public Sub RegisterSite(ByVal siteName As String, ByVal uiElement As Object)

    Este método se encarga de crear un nuevo IUIElementAdapter para el UIElement pasado utilizando una IUIElementAdapterFactory la cual tuvo que ser registrada anteriormente para ese tipo de UIElement en particular. Una vez echo esto invoca a la version anterior del método con el adapter recién creado.

    Luego invocando al siguiente método de la colección UIExtensionSites del WorkItem:

    [C#]

    public UIExtensionSite this[string siteName]

    [Visual Basic]

    Default Public ReadOnly Property Item(ByVal siteName As String) As UIExtensionSite

    Se accede, mediante el siteName, al UIExtensionSite encargado de manejar al IUIElementAdapter creado para el UIElement que se agregó.

    Es muy importante notar que la colección UIExtensionSites no permite acceder directamente a los UIElements que se registraron sino que sólo se obtiene una referencia a un objeto de tipo UIExtensionSite el cual es el responsable de utilizar al adapter creado para manejar el objeto.

    Registrar un UIElement

    Generalmente los UIElements se registran en el método AfterShellCreated de la clase principal de la aplicación (la que hereda de FormShellApplication) como se muestra en el siguiente código.

    [C#]

    // MainMenuStrip expone una instancia de un MenuStrip.

    // MainStatusStrip expone una instancia de un StatusStrip.

    // MainToolbarStrip expone una instancia de un ToolStrip.

    protected override void AfterShellCreated()

    {

    base.AfterShellCreated();

    RootWorkItem.UIExtensionSites.RegisterSite(“MainMenu”, this.Shell.MainMenuStrip)

    RootWorkItem.UIExtensionSites.RegisterSite(“MainStatus”, this.Shell.MainStatusStrip)

    RootWorkItem.UIExtensionSites.RegisterSite(“MainToolbar”, this.Shell.MainToolbarStrip)

    }

    [Visual Basic]

    ‘ MainMenuStrip expone una instancia de un MenuStrip.

    ‘ MainStatusStrip expone una instancia de un StatusStrip.

    ‘ MainToolbarStrip expone una instancia de un ToolStrip.

    Protected Overrides Sub AfterShellCreated()

    MyBase.AfterShellCreated()

    RootWorkItem.UIExtensionSites.RegisterSite(“MainMenu”, Me.Shell.MainMenuStrip)

    RootWorkItem.UIExtensionSites.RegisterSite(“MainStatus”, Me.Shell.MainStatusStrip)

    RootWorkItem.UIExtensionSites.RegisterSite(“MainToolbar”, Me.Shell.MainToolbarStrip)

    End Sub

    Es buena práctica utilizar constantes para guardar los ID de los UIElements. Estas constantes se deberían ubicar en clases accesibles por todos los módulos. Para ello se puede crear un proyecto donde se albergue todo lo que se considere de uso común entre los módulos. Esto permite un mayor desacople.

    Nota: Smart Client Software Factory provee una estructura generada por la Guiance Package la cual establece un proyecto (Infrastructure.Interface) donde se definen todas las constantes y clases comunes que son usadas por todos los módulos. Además registra por defecto al menú principal, la barra de estado y la barra de herramientas dentro de la colección UIExtensionSites.

    Utilizar los UIExtensionSite para agregar ítems al Shell

    En general los ítems particulares de cada módulo se cargan en el método OnRunStarted del WorkItem del módulo como se muestra en el siguiente código de ejemplo.

    [C#]

    // En este ejemplo se agregará un menú con dos botones a la barra de menu

    // del shell.

    protected override void OnRunStarted()

    {

    ExtendMenu();

    AddViews();

    base.OnRunStarted();

    }

    private void ExtendMenu()

    {

    ToolStripMenuItem menu = new ToolStripMenuItem(“Menu del Modulo”);

    ToolStripMenuItem button1 = new ToolStripMenuItem(“Button1″);

    ToolStripMenuItem button2 = new ToolStripMenuItem(“Button2″);

    menu.DropDownItems.Add(button1);

    menu.DropDownItems.Add(button2);

    UIExtensionSites["MainMenu"].Add(menu);

    }

    [Visual Basic]

    ‘ En este ejemplo se agregará un menú con dos botones a la barra de menu

    ‘ del shell.

    Public Overrides Sub OnRunStarted()

    ExtendMenu()

    AddViews()

    MyBase.OnRunStarted()

    End Sub

    Private Sub ExtendMenu()

    Dim menu As ToolStripMenuItem

    Dim button1 As ToolStripMenuItem

    Dim button2 As ToolStripMenuItem

    menu = New ToolStripMenuItem(“Menu del Modulo”)

    button1 = New ToolStripMenuItem(“Button1″)

    button2 = New ToolStripMenuItem(“Button2″)

    menu.DropDownItems.Add(button1)

    menu.DropDownItems.Add(button2)

    UIExtensionSites(“MainMenu”).Add(menu)

    End Sub

    Si se remueve el nombre del módulo en el archivo ProfileCatalog.xml o el ModuleLoaderService verifica que el usuario actual no puede tener acceso a ese módulo, entonces no se cargará el módulo anterior y por lo tanto no aparecerán los ítems en la barra de menús. La aplicación podrá continuar normalmente.

    Crear Adapters para UIElements

    Cada tipo de UIElement requiere su propio adapter para que sea correctamente manejado. CAB provee la interfaz IUElementAdapter que define los miembros que debe soportar un adapter y además la template class UIElementAdapter la cual provee implementación genérica de los métodos de la interfaz.

    CAB trae soporte para MenuStrips y ToolBarStrips (las clases ToolStrip, ToolStripItem, ToolStripItemCollection, MenuStrip y todas las clases derivadas de estas). Utiliza las clases ToolStripItemCollectionUIAdapter, ToolStripItemOwnerCollectionUIAdapter como adapters y la clase ToolStripUIAdapterFactory como factory para crear los adapters. Sin embargo CAB permite incluir soporte para clases customizadas si se construyen los adapters correspondientes. Para que tenga sentido crear un adapter para un determinado UIElement, este deberá poseer una colección de objetos que permita agregar y quitar elementos.

    El siguiente ejemplo muestra como se implementa un adapter para la clase ListBox del assembly System.Windows.Forms.

    [C#]

    using Microsoft.Practices.CompositeUI.UIElements;

    using System.Windows.Forms;

    using Microsoft.Practices.CompositeUI.Utility;

    public class ListBoxAdapter : IUIElementAdapter

    {

    private ListBox _listBox;

    public ListBoxAdapter(ListBox listBox)

    {

    Guard.ArgumentNotNull(listBox, “listBox”);

    _listBox = listBox;

    }

    #region IUIElementAdapter Members

    public object Add(object uiElement)

    {

    if (_listBox != null)

    {

    if (!_listBox.Items.Contains(uiElement))

    {

    _listBox.Items.Add(uiElement);

    }

    }

    return uiElement;

    }

    public void Remove(object uiElement)

    {

    if (_listBox != null)

    {

    if (_listBox.Items.Contains(uiElement))

    {

    _listBox.Items.Remove(uiElement);

    }

    }

    }

    #endregion

    }

    [Visual Basic]

    Imports Microsoft.Practices.CompositeUI.UIElements

    Imports System.Windows.Forms

    Imports Microsoft.Practices.CompositeUI.Utility

    Public Class ListBoxAdapter

    Implements IUIElementAdapter

    Private _listBox As ListBox = Nothing

    Public Sub New(ByVal listBox As ListBox)

    Guard.ArgumentNotNull(listBox, “listBox”)

    _listBox = listBox

    End Sub

    Public Function Add(ByVal uiElement As Object) As Object Implements IUIElementAdapter.Add

    If Not _listBox Is Nothing Then

    If Not _listBox.Items.Contains(uiElement) Then

    _listBox.Items.Add(uiElement)

    End If

    End If

    Return uiElement

    End Function

    Public Sub Remove(ByVal uiElement As Object) Implements IUIElementAdapter.Remove

    If Not _listBox Is Nothing Then

    If _listBox.Items.Contains(uiElement) Then

    _listBox.Items.Remove(uiElement)

    End If

    End If

    End Sub

    End Class

    Como se puede ver la clase ListBoxAdapter implementa la interfaz IUIElementAdapter.

    Con la clase implementada anteriormente ya se pueden registrar objetos de tipo ListBox en la colección UIExtensionSites del WorkItem de la siguiente forma:

    [C#]

    // La propiedad ListBox expone una instancia de la clase ListBox.

    RootWorkItem.UIExtensionSites.RegisterSite(“ListBox”, new ListBoxAdapter (this.Shell.ListBox));

    [Visual Basic]

    ‘ La propiedad ListBox expone una instancia de la clase ListBox.

    RootWorkItem.UIExtensionSites.RegisterSite(“ListBox”, New ListBoxAdapter (Me.Shell.ListBox))

    Si se prefiere utilizar la otra sobrecarga del método RegisterSite donde se pasa directamente el UIElement en el segundo parámetro, se deberá registrar una factory que se encargue de crear los adapters. El siguiente código muestra como implementarla continuando el ejemplo anterior.

    [C#]

    using Microsoft.Practices.CompositeUI.UIElements;

    using System.Windows.Forms;

    public class ListBoxAdapterFactory : IUIElementAdapterFactory

    {

    #region IUIElementAdapterFactory Members

    public IUIElementAdapter GetAdapter(object uiElement)

    {

    if (Supports(uiElement))

    {

    return new ListBoxAdapter((ListBox)uiElement);

    }

    return null;

    }

    public bool Supports(object uiElement)

    {

    if (uiElement is ListBox)

    return true;

    return false;

    }

    #endregion

    }

    [Visual Basic]

    Imports Microsoft.Practices.CompositeUI.UIElements

    Imports System.Windows.Forms

    Public Class ListBoxAdapterFactory

    Implements IUIElementAdapterFactory

    Public Function GetAdapter(ByVal uiElement As Object) As IUIElementAdapter Implements IUIElementAdapterFactory.GetAdapter

    If Supports(uiElement) Then

    Return New ListBoxAdapter(CType(uiElement, ListBox))

    End If

    Return Nothing

    End Function

    Public Function Supports(ByVal uiElement As Object) As Boolean Implements IUIElementAdapterFactory.Supports

    If TypeOf uiElement Is ListBox Then

    Return True

    End If

    Return False

    End Function

    End Class

    Como se observa en el código anterior, la factory implementa la interfaz IUIElementAdapterFactory.

    El método Support de la clase ListBoxAdapterFactory devuelve true si el objeto pasado como parámetro es soportado por la factory. En caso contrario devuelve false. Este método es utilizado por la clase UIElementAdapterFactoryCatalog en el método GetFactory. Dentro de ese método se recorre todo el catálogo de factories hasta encontrar una que soporte el objeto requerido.

    En cuanto al método GetAdapter devuelve una instancia del adapter correcto para el tipo de objeto pasado como parámetro. Si el objeto no es soportado por la factory devuelve null/Nothing.

    Por ultimo se deberá agregar la factory creada anteriormente al catálogo de fábricas del WorkItem (el catálogo está registrado como un servicio que implementa la interfaz IUIElementAdpterFactoryCatalog). Esto generalmente se realiza en el método AfterShellCreated de la clase principal de la aplicación (la que hereda de FormShellApplication) como se muestra en el siguiente código.

    [C#]

    protected override void AfterShellCreated()

    {

    base.AfterShellCreated();

    IUIElementAdapterFactoryCatalog catalogFactory = RootWorkItem.Services.Get();

    catalogFactory.RegisterFactory(new ListBoxAdapterFactory());

    // TODO: Agregar los UIElements a la coleccion UIExtensionSites.

    }

    [Visual Basic]

    Protected Overrides Sub AfterShellCreated()

    MyBase.AfterShellCreated()

    Dim catalogFactory As IUIElementAdapterFactoryCatalog = RootWorkItem.Services.Get(Of IUIElementAdapterFactoryCatalog)()

    catalogFactory.RegisterFactory(New ListBoxAdapterFactory())

    ‘ TODO: Agregar los UIElements a la coleccion UIExtensionSites.

    End Sub

    En el siguiente post se explicarán los principales rasgos de los Commands.

    Tags:

  • Leave a comment

    Your email address will not be published.