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.

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.

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.