Mostrando entradas con la etiqueta prism. Mostrar todas las entradas
Mostrando entradas con la etiqueta prism. Mostrar todas las entradas

viernes, 12 de febrero de 2010

Caliburn… ¿sientes el poder de MVVM en tus manos?

Los más frikis de por aquí, sabréis que Caliburn (Caliburnus para ser exactos) era el nombre de una poderosa espada que luego alguien decidió rebautizar como Excalibur… Como frikis hay en todas partes y en eso de la informática pues quizás más, Caliburn también resulta ser el nombre de un framework para aplicaciones Silverlight y WPF. Dicho así parece ser lo mismo que PRISM y en cierta manera ambos frameworks tienen el mismo objetivo y comparten muchas características. Por ejemplo ambos frameworks se abstraen del contendor IoC a utilizar (es decir requieren uno, pero no se atan a ninguno), ambos dan soporte a vistas compuestas y ambos tienen el concepto de módulo… entonces ¿en que se diferencian? Pues en como se enfocan para llegar al objetivo. El objetivo de este post es iniciar una serie de posts (no se de cuantos :P) para hablar sobre Caliburn y compararlo con PRISM. Hoy, pero vamos a empezar por lo más básico… :)

1. Preparando el entorno

No es muy complicado preparar el entorno para trabajar con Caliburn: basta con descargarse el framework (actualmente está en v1 RTW). Caliburn (de nuevo al igual que PRISM) existe en dos sabores: Silverlight y WPF. Ambas versiones son esencialmente la misma salvando las diferencias técnológicas que existen entre Silverlight y WPF. Vamos a optar en este caso por una aplicación WPF.

Abrimos VS2008 y creamos una nueva aplicación WPF. El siguiente paso es añadir referencias a los ensablados de Caliburn que estarán en el directorio Bin/NET-3.5/debug (o release, hay ambas versiones). Nota: Yo os recomiendo que os descargueis el codigo fuente y compileis Caliburn… Así será más fácil depurar!

Si en lugar de WPF nuestra aplicación fuese Silverlight entonces debemos ir al directorio Bin/Silverlight-2.0 o Silverlight-3.0 según sea necesario. Para empezar vamos a usar Caliburn.Core.dll, Caliburn.PresentationFramework.dll y Microsoft.Practices.ServiceLocation.dll.

Ahora sí! Ya estamos listos para desenvainar la espada …

2. Empezando con Caliburn

Caliburn tiene una idea muy clara sobre como se debe organizar la IU de tu aplicación: usando MVVM. Eso significa que vamos a tener un grupo de clases llamadas ViewModels que van a ser los que tengan toda la información sobre los datos a mostrar. A cada ViewModel le corresponderá una vista (View). El enlace entre las vistas y los ViewModels será a través de Bindings… por supuesto que todo esto se puede hacer sin Caliburn, pero Caliburn nos da herramientas para que sea un poco más fácil.

Para empezar vamos a crear un ViewModel y una vista y vamos a dejar que la magia de Caliburn nos lo una. Para ello, creamos una carpeta llamada ViewModel en nuestro proyecto. Es importante el nombre de esta carpeta, puesto que Caliburn asume que lo que en ella esté son ViewModels o vistas. Dentro de dicha carpeta creamos una clase tal y como sigue:

public class UserViewModel
{
public string Nombre { get; set; }
public string Foto { get; set; }
}





Ya tenemos el ViewModel de un usuario: su nombre y su foto. Ahora el siguiente paso es crear una vista. Para ello añadid un User Control que se llame UserView. El nombre de nuevo es importante: Caliburn asumirá que UserView es la vista para los ViewModels de tipo UserViewModel. Poned el user control fuera de la carpeta ViewModel. El código xaml puede ser algo parecido a:




<UserControl x:Class="CaliburnDemo.UserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<Label Height="28" Margin="25,47,28,0" VerticalAlignment="Top" Content="{Binding Nombre}"></Label>
<Image Margin="109,81,127,0" Stretch="Fill" Width="64" Height="64" VerticalAlignment="Top"
Source="{Binding Foto}"/>
<Label Height="28" Margin="85,13,101,0" VerticalAlignment="Top">Datos del Usuario:</Label>
</Grid>
</UserControl>





Finalmente sólo nos queda un paso: modificar nuestra aplicación para que derive de CaliburnApplication. Para ello, en App.cs modificad la clase para que derive de CaliburnApplication:




public partial class App : CaliburnApplication
{
protected override object CreateRootModel()
{
return new UserViewModel();
}
}





Fijaos que redefinimos el método CreateRootModel: Este método (definido en CaliburnApplication) es el punto de entrada de nuestra aplicación. El tipo de ViewModel que creemos determinará el tipo de vista a utilizar y los datos iniciales a mostrar. Un detalle: Fijaos que no vamos a crear una ventana nunca (nuestra vista UserView es un UserControl). No hay problema, porque si el ViewModel inicial no es una ventana, Caliburn la va a crear para nosotros.



Hemos modificado la clase base de la aplicación en el fichero .cs y debemos hacer lo mismo en App.xaml:




<caliburn:CaliburnApplication x:Class="CaliburnDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:caliburn="clr-namespace:Caliburn.PresentationFramework.ApplicationModel;assembly=Caliburn.PresentationFramework">
<Application.Resources>
</Application.Resources>
</caliburn:CaliburnApplication>





No hay secreto: Declaro el namespace caliburn y modifico el tag raiz para que en lugar de Application sea CaliburnApplication que es nuestra nueva clase base. También elimino el StartupUri ya que no es necesario.



Y listos… ya podemos ejecutar!



3. Plaf! La primera en la frente!



Si has seguido mis indicaciones te vas a encontrar algo parecido a esto (si no has compilado Caliburn quizá simplemente te salga una excepción en lugar de esta “preciosa” ventana).



image



Hombre… no es muy bonito que digamos… cual es el problema? Fácil: la vista no está situada en el lugar que toca… Recordáis que os dije que la pusierais fuera de la carpeta ViewModel? Pues debe ir dentro… Así, que moved UserView dentro de la carpeta ViewModel y modificad el namespace para que incluya ViewModel:




namespace CaliburnDemo.ViewModel
{
}





De hecho lo importante es el namespace, no la ubicación física del archivo xaml, así que si no quereis moverlo no lo hagáis per el namespace de la clase UserView debe ser el mismo que el de UserViewModel.



Ahora sí que sí! Si ejecutamos vemos una triste ventana… pero es nuestra ventana:



image



Está vacía porque el ViewModel que hemos creado lo está, pero eso tiene fácil arreglo modificando el método CreateRootModel para que el UserViewModel creado tenga datos:




protected override object CreateRootModel()
{
return new UserViewModel()
{
Nombre = "Edu",
Foto = "/CaliburnDemo;component/avatar.png"
};
}





(La foto está añadida como Resource en el proyecto, de ahí esta ruta).



Ahora si que vemos ya nuestros datos:



image



¡Mola! Que es lo que ha hecho Caliburn por nosotros? Pues a partir de un ViewModel ha creado la vista correspondiente y ha asignado el ViewModel como DataContext de la vista…



Ok… no es nada que no podamos hacer nosotros mismos con pocas líneas de código… pero esto es sólo el principio! En sucesivos posts iremos viendo otras cosillas de Caliburn. Obviamente si alguien ha trabajado con Caliburn y/o con PRISM y quiere contar sus opiniones… adelante!



Un saludo a todos!



PD: Dejo el código del proyecto en este ficherillo zip! (en mi skydrive)



PD2: Obviamente… otro crosspost desde mi blog de geeks.ms… Porque no te pasas por allí, mejor?

miércoles, 11 de marzo de 2009

PRISM y Winforms: Mostrar vistas en nuevos formularios

En un post anterior (PRISM y Winforms), comentaba como usar PRISM para realizar aplicaciones Winforms.

Un comentario de Jose en esta entrada, me ha motivado a escribir el siguiente post, para mostrar como podríamos mostrar vistas en regiones que estén incrustadas no en un UserControl (típicamente un Panel) de la ventana principal, sino incrustadas en un nuevo formulario.

Para poder usar regiones en Winforms era necesario definirnos un RegionAdapter para la clase “Control” que era básicamente el objetivo del post anterior. En el método Adapt() teníamos el código que “incrustaba” la vista dentro del control. Dicho método recibía (de PRISM) la región y el target, o control donde colocar dicha región.

Antes que nada recordad tres conceptos que a veces se confunden:

  1. Region: En PRISM una región es un conjunto de vistas (algunas activas, otras no activas) que se muestran en algún sitio determinado.
  2. Target: El target de una región es el lugar donde se muestran. P.ej. un TableLayout podría ser el target de una región y cada vista de la región podría mostrarse en distintas celdas.
  3. Vista: Una vista muestra una determinada información. P.ej. si tenemos una aplicación que nos muestra la cotización de varias acciones, podríamos tener varias instancias de una vista, donde cada instancia nos mostraría la cotización de una acción. En winforms generalmente es un UserControl.

En PRISM las vistas se colocan en regiones y las regiones se incrustan en los targets… recordad que una región puede tener varias vistas.

En el RegionAdapter que vimos en el post anterior, siempre recibíamos la región y el target. El target ya estaba creado porque era un control ya existente en la ventana principal.

Esto no nos sirve si queremos mostrar una vista en un formulario nuevo ya que ahora debemos crear el formulario cada vez que queramos mostrar la región… ¿como podemos hacerlo?

Además del método Adapt() que usamos en el post anterior, los RegionAdapter pueden redefinir otro método llamado AttachBehaviors. En este método podemos añadir la lógica que queramos para personalizar el comportamiento de la región… en este caso podremos aprovechar para crear el target.

Un vistazo al código…

El código que sigue a continuación es una adaptación de la clase WindowRegionAdapter de Composite WPF Contrib, que he adaptado para que funcione con Windows Forms.

El método AttachBehaviors lo redefinimos de la siguiente manera:

protected override void AttachBehaviors(IRegion region, 
Form regionTarget)
{
base.AttachBehaviors(region, regionTarget);
FormRegionBehavior behavior =
new FormRegionBehavior
(regionTarget, region, FormBorderStyle.FixedSingle);
behavior.Attach();
}

Los parámetros que recibe son los mismos que Adapt: la región y el target (en este caso un Form) ya que derivamos de RegionAdapterBase<Form>. En este método creamos un objeto FormRegionBehavior que será el que tendrá todo el código para gestionar regiones que estén dentro de un formulario. En concreto dicha clase será la responsable de:



  1. Crear un formulario por cada nueva vista añadida a la región y incrustar dicha vista en el formulario
  2. Cerrar el formulario que contiene una vista si esta se elimina de la región.
  3. Eliminar una vista de la región si se cierra el formulario que la contiene.
  4. Activar o desactivar la vista cuando su formulario es activado o desactivado.

A continuación pongo el código más relevante de dicha clase. Al final del post adjunto una aplicación de demo.


1 y 2. Crear un formulario por cada nueva vista y eliminar el formulario de una vista eliminada


El método Attach() de la clase FormRegionBehavior de suscribe a los dos eventos CollectionChanged de las colecciones Views y ActiveViews de la región:

internal void Attach()
{
IRegion region = _regionWeakReference.Target as IRegion;
if (region != null)
{
region.Views.CollectionChanged +=
new NotifyCollectionChangedEventHandler
(Views_CollectionChanged);
region.ActiveViews.CollectionChanged +=
new NotifyCollectionChangedEventHandler
(ActiveViews_CollectionChanged);
}
}

En el método Views_CollectionChanged es donde sabemos si se ha añadido una vista a la región o se ha eliminado, y así podemos crear o destruir el formulario asociado:

private void Views_CollectionChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
Form owner = _ownerWeakReference.Target as Form;
if (owner == null)
{
Detach();
return;
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (object view in e.NewItems)
{
// Creamos un formulario por cada vista y lo mostramos...
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object view in e.OldItems)
{
// Buscamos el formulario que contiene cada vista
// para cerrarlo y "disposarlo" :)
}
}
}

3. Eliminar la vista de la región si se cierra el formulario


Para ello, cuando creamos un formulario, nos suscribimos al evento Closed para poder eliminar la vista asociada a la región:

private void Form_Closed(object sender, EventArgs e)
{
Form frm = sender as Form;
IRegion region = _regionWeakReference.Target
as IRegion;
if (frm != null && frm.Controls.Count > 0
&& region != null)
if (region.Views.Contains(frm.Controls[0]))
region.Remove(frm.Controls[0]);
}

4. Activar o desactivar la vista cuando su formulario es activado o desactivado


Para ello, cuando creamos un formulario nos suscribimos a los eventos Activated y Deactivate, para desactivar o activar la vista correspondiente:

private void Form_Activated(object sender, EventArgs e)
{
IRegion region = _regionWeakReference.Target
as IRegion;
Form frm = sender as Form;
if (frm != null && frm.Controls.Count > 0 &&
!region.ActiveViews.Contains(frm.Controls[0]))
region.Activate(frm.Controls[0]);
}

5. Algunas consideraciones finales


Para PRISM toda región tiene un solo contenedor (o target) que es aquel que se usa cuando se llama a AttachNewRegion. Aunque este RegionAdapter va creando distintos formularios, para PRISM la región debe estar vinculada a un único target… que debe existir cuando llamemos a  AttachNewRegion y cuyo tipo debe ser Form puesto que nuestro RegionAdapter trabaja con targets de tipo Form.


Esto nos deja en una situación curiosa, puesto que necesitamos tener un Form creado para poder crear la región. Aunque luego este formulario no contendrà ninguna de las vistas de la región. Consideraremos a este formulario el formulario padre, y cuando el formulario padre muera el RegionAdapter dejarà de tratar a la región (digamos que la región habrá muerto).


Otra cosa que también debemos solucionar es dado una vista, encontrar que formulario la contiene. El RegionAdapter no se guarda una lista de los formularios creados, en su lugar solo tiene una WeakReference al formulario padre (el usado para llamar a AttachNewRegion y que nunca contendrá vistas de esta región) y usa el formulario padre para encontrar a los formularios que contiene la vista. Esto es posible porque al crear un formulario que contiene una vista se indica que su padre es el formulario padre:

Form owner = _ownerWeakReference.Target as Form;
// Creamos un formulario por cada vista y lo mostramos...

frm.Owner = owner;

De este modo se puede usar la propiedad OwnedForms del formulario padre para iterar sobre todos los formularios creados y ver cual contiene la vista en cuestión.


Finalmente, el hecho de tener que pasar un formulario ya existente a AttachNewRegion, no es excesivamente problemático: lo más fácil es usar el formulario principal del programa!


Adjunto el programa de demostración. El RegionAdapter DialogRegionAdapter es el que muestra vistas en nuevos formularios, mientras que el RegionAdapter ControlRegionAdapter las muestra en paneles (de hecho en cualquier control).


Descargar el código de demostración.


Espero que os sea útil! :)


pd: Crossposting desde mi blog en geeks.ms :)

jueves, 22 de enero de 2009

Gestionando las dependencias entre módulos cargados on-demand en PRISM

Una aplicación PRISM se compone de varios módulos que colaboran entre ellos. Un módulo PRISM simplemente es un objeto que implementa la interfaz IModule. En un mismo assembly pueden haber tantos módulos PRISM como se desee.

PRISM ofrece dos métodos para la carga de los módulos: O bien se cargan todos al principio de la aplicación, o bien se cargan on-demand (es decir, cuando se necesitan). La primera opción es la más simple, pero en algunos casos nos interesa ir cargando los módulos cuando se necesiten (bien porque hay muchos módulos posibles o bien porque la inicialización de estos módulos es un poco pesada).

Igualmente los módulos pueden tener dependencias entre ellos: si el módulo A depende del módulo B, indica que el módulo B debe estar cargado cuando se cargue el módulo A. En caso contrario PRISM lanzará una excepción con el mensaje “A module declared a dependency on another module which is not declared to be loaded”.

Los módulos se cargan mediante dos clases: el IModuleEnumerator, que enumera los módulos existentes y el IModuleLoader que los carga e inicializa. Existen varias implementaciones de esta clases y nosotros nos podemos crear las nuestras. En función del IModuleEnumerator que usemos debemos usar un mecanismo u otro para indicar que el módulo no se carga por defecto, sino que se cargará on-demand. P.ej. si usamos el DirectoryLookupModuleEnumerator (que enumera todos los módulos de todos los assemblies de un directorio en particular), debemos decorar el módulo con el atributo Module con la propiedad StartupLoaded a false:

[Module(ModuleName = ModuleNames.MARKETPLACE_MODULE, 
StartupLoaded = false)]
public class MarketModule : IModule
{
}

Para cargar un módulo on-demand debemos obtenerlo mediante el IModuleEnumerator y cargarlo mediante el IModuleLoader (usando el método Initialize):

ModuleInfo mi = Container.Resolve<IModuleEnumerator>().
GetModule(ModuleNames.MARKETPLACE_MODULE);
Container.Resolve<IModuleLoader>().Initialize(new ModuleInfo[] { mi });

(Container es la propiedad que me da acceso al contenedor IoC usado, que me permite obtener el IModuleEnumerator y el IModuleLoader).


De forma similar a como indicamos que un módulo se cargará on-demand podemos especificar que un módulo depende de otro. La forma exacta de hacerlo depende de nuevo del IModuleEnumerator usado. Si usamos el DirectoryLookupModuleEnumerator debemos decorar la clase módulo con el atributo ModuleDependency indicando de que módulo depende dicho módulo:

[Module(ModuleName = ModuleNames.MARKETPLACE_MODULE, 
StartupLoaded = false)]
[ModuleDependency(ModuleNames.CARDS_MODULE)]
public class MarketModule : IModule
{
}

El módulo MARKETPLACE_MODULE depende del módulo CARDS_MODULE: el segundo debe estar cargado antes de cargar el primero. Así pues es de esperar que el IModuleLoader cuando cargue el módulo MARKETPLACE_MODULE cargue también el módulo CARDS_MODULE…


… pues no. El IModuleLoader no cargará automáticamente el módulo CARDS_MODULE, en su lugar si no está cargado lanzará la excepción previamente comentada.


Vosotros debeis saber que dependencias tiene cada módulo y aseguraros que cada módulo está cargado. Es decir, en mi caso yo debo cargar CARDS_MODULE antes que MARKETPLACE_MODULE, o como muy tarde a la vez:

ModuleInfo mc = Container.Resolve<IModuleEnumerator>().
GetModule(ModuleNames.CARDS_MODULE);
ModuleInfo mm = Container.Resolve<IModuleEnumerator>().
GetModule(ModuleNames.MARKETPLACE_MODULE);
Container.Resolve<IModuleLoader>().
Initialize(new ModuleInfo[] { mc, mm });

Por suerte es posible un workaround para no tener que ir buscando todas las dependencias: hacerse un método de extensión sobre IModuleEnumerator, y que devuelva un array de ModuleInfo: el módulo junto con todas sus dependencias:

namespace Microsoft.Practices.Composite.Modularity
{
public static class ModuleEnumeratorExtensions
{
public static ModuleInfo[] GetModuleWithDependencies(
this IModuleEnumerator moduleEnumerator, string moduleName)
{
List<ModuleInfo> moduleInfoList = new List<ModuleInfo>();
ModuleInfo module = moduleEnumerator.GetModule(moduleName);
moduleInfoList.Add(module);
if (module.DependsOn != null)
{
foreach (string dependencyName in module.DependsOn)
{
if (!moduleInfoList.Exists(
existingModule => existingModule.ModuleName ==
dependencyName))
{
moduleInfoList.AddRange(
GetModuleWithDependencies(
moduleEnumerator, dependencyName));
}
}
}
return moduleInfoList.ToArray();
}
}
}

Y para cargar un módulo junto con todas sus dependencias:

ModuleInfo[] mis = Container.Resolve<IModuleEnumerator>().
GetModuleWithDependencies(ModuleNames.MARKETPLACE_MODULE);
Container.Resolve<IModuleLoader>().Initialize(mis);

Es una de esas cosas que uno se pregunta porque no lo habrán añadido de serie… :)


Nota: La información de este post está sacada de este post de Mariano Converti. Como es habitual, todo el mérito para él… Su blog sobre PRISM es de imprescindible consulta!


Crossposting desde mi blog de geeks.ms

martes, 20 de enero de 2009

PRISM y AvalonDock

Hola a todos!

Conocéis PRISM? Viene a ser, salvando las distancias, la CAB de WPF: es decir un conjunto de buenas prácticas para la creación de aplicaciones compuestas en WPF y una librería que implementa dichas buenas prácticas. Si desarrollais aplicaciones en WPF es obligatorio echarle un vistazo. Pasaos por la página de PRISM en codeplex.

Por otro lado, AvalonDock es una muy buena librería que proporciona soporte para interfaces dockables usando WPF que simula al estilo de docking de Visual Studio.

Estoy desarrollando una aplicación usando ambas librerías y me he encontrado con un problemilla: al añadir una vista usando PRISM dentro de un contenedor de AvalonDock aparece un error. A ver, que me explico un poco mejor… :)

PRISM usa el concepto de “regiones” para dividir el espacio de la ventana de la aplicación. En cada “región” se pueden incrustar una o más vistas (objetos que se representan visualmente). Por ejemplo podemos mapear una región de PRISM a un ItemsControl y cada vista que añadamos aparecerá dentro de este ItemsControl. Para mapear una región PRISM a un control se usa XAML:

<ItemsControl Grid.Row="0" cal:RegionManager.RegionName="HelpZone" />

Por su lado AvalonDock se basa en proporcionar un contenedor especial (el DockingManager) dentro del cual se insertan otros contenedores especiales que contienen el contenido a mostrar… el cual tiene que ser un objeto de unas clases especiales, llamadas DockableContent o DocumentContent. Estas clases son las que realmente contienen el contenido real. P.ej. para mostrar una cadena dentro de una ventana dockable usando AvalonDock necesito el siguiente código XAML:

<ad:DockingManager Name="mainDockingManager" Grid.Row="1">
<
ad:DocumentPane>
<
ad:DockableContent>Hola AvalonDock</ad:DockableContent>
</
ad:DocumentPane>
</
ad:DockingManager>

La clase DocumentPane contiene tantos DockableContent como ventanas dockables se quieran tener.


Al mezclar PRISM y AvalonDock es cuando surgen los primeros problemas. Si definimos una región dentro del DocumentPane:

<ad:DockingManager Name="mainDockingManager" Grid.Row="1">
<
ad:DocumentPane cal:RegionManager.RegionName="MainZone" />
</ad:DockingManager>


Cuando añadamos una vista, no dará una excepción: DocumentPane can contain only DockableContents or DocumentContents!


Esto ocurre porque PRISM intenta asociar como contenido del DocumentPane la vista que directamente le hemos indicado, que será un UserControl o algún otro objeto pero no un DockableContent o un DocumentContent.


Lo que hemos de conseguir es que PRISM, de forma transparente para nosotros, nos cree un DockableContent (o un DocumentContent) y nos lo añada al contenido del DocumentPane al cual está mapeado nuestra región de PRISM. Por suerte,  en PRISM tenemos el concepto de RegionAdapter, que como su nombre indica es una clase que “adapta” los contenidos de una región de PRISM al contenedor real (nuestro DocumentPane). Vamos a ver como podemos implementar un RegionAdapter para DocumentPane.


El código quedaría más o menos así:

public class DockableRegionAdapter : RegionAdapterBase<DocumentPane>
{
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}

protected override void Adapt(IRegion region,
DocumentPane regionTarget)
{
region.ActiveViews.CollectionChanged += delegate
{
var childs = new Dictionary<object, DockableContent> ();
foreach (var child in regionTarget.Items)
{
if (child is DockableContent)
{
childs.Add(((DockableContent)child).Content,
(DockableContent)child);
}
}
regionTarget.Items.Clear();
foreach (object ci in region.Views)
{
DockableContent dc = childs.ContainsKey(ci)
? childs[ci]
: new DockableContent() { Content = ci };
regionTarget.Items.Add(dc);
}
};
}
}

Los dos métodos que hay que redefinir cuando se crea un RegioAdapter de PRISM son CreateRegion y Adapt. En el primero simplemente hemos de devolver el tipo de región que queremos. En este caso simplemente creo un objeto AllActiveRegion, que es un región de PRISM que todas las vistas que tenga las considera activas.


El segundo método es Adapt, y es donde se hace todo el trabajo. Cada vez que cambie la colección ActiveViews, básicamente borro el contenido del DocumentPane de AvalonDock y lo añado de nuevo, creando un DockableContent nuevo para cada vista que haya en la región. El código que rellena el diccionario childs es para reutilizar aquellos DockableContent que se hubiesen creado anteriormente.


Finalmente solo nos queda informar a PRISM que tenemos un RegionAdapter nuevo. Para ello redefinimos el método ConfigureRegionAdapterMappings del Bootstrapper para añadir nuestro RegionAdapter vinculado a contenedores de tipo DocumentPane:

protected override RegionAdapterMappings 
ConfigureRegionAdapterMappings()
{
RegionAdapterMappings mappings =
base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(DocumentPane),
new DockableRegionAdapter());
return mappings;
}

Y listos! Ahora al añadir una vista a la región de PRISM, se crea automáticamente un DockableContent y se añade al DocumentPane que contiene la región, lo que añade una ventana dockable con la nueva vista en la interfaz de usuario.


No puedo asegurar que sea la mejor implementación posible pero bueno… a mi me funciona :)


¡Bien por PRISM!