Archive for September, 2008

WindowRegionAdapter for CompositeWPF (Prism)

With the help of my dear workmate Julian Dominguez, I’ve been developing a new region adapter: the WindowRegionAdapter. It provides a way to show views in separate windows and has the following features to take into account:

  • It uses a SingleActiveRegion, so that only one (or no) view (one window) is active at a time.
  • It provides a WindowStyle property (of type Style) which allows the developer to specify a custom style to use as a template for all the windows shown in the region.

How to use the WindowRegionAdapter

Step 1 – Register the adapter mapping

The first thing you will need to do is to register the mapping for the Region Adapter for the type Window. To accomplish this, you must override the ConfigureRegionAdapterMappings() method of the Bootstrapper, retrieving the RegionAdapterMappings from the container, and calling its RegisterMapping() method, sending the Window type and a new instance of the WindowRegionAdapter, as follows:

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    RegionAdapterMappings regionAdapterMappings = Container.TryResolve<RegionAdapterMappings>();

    if (regionAdapterMappings != null)
    {
        regionAdapterMappings.RegisterMapping(typeof(Window), new WindowRegionAdapter());
    }

    return base.ConfigureRegionAdapterMappings();
}

[Recommended]: Alternatively, you may provide a custom style to use as template for all the windows shown in the region by setting its WindowStyle property on initialization (as shown in the sample application):

regionAdapterMappings.RegisterMapping(typeof(Window), new WindowRegionAdapter() { WindowStyle = (Style)Application.Current.FindResource(“WindowTemplate”) });

 

Step 2 – Set the owner for the child windows

To use the WindowRegionAdapter, you must add the cal:RegionManager.RegionName attribute to a Window (for example, the Shell window), as follows:

<Window x:Class=”Example.Shell”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:cal=”http://www.codeplex.com/CompositeWPF”
Title=”MainWindow” Height=”400″ Width=”500″ WindowStartupLocation=”CenterScreen” cal:RegionManager.RegionName=”MyWindowRegion”>

The WindowRegionAdapter will get this window and will use it to set the value of the Owner property of each child window to this window.

 

Screenshots

WRAViewDesign

Figure 1. View at design time.

 

WRAViewRun

Figure 2. View displayed in a new window (by the Window Region Adapter) at run time.

 

WRAExampleRunning

Figure 3. The sample application running with several views displayed.

 

Disclaimer

This code is provided “AS IS” with no warranties. You can use it freely. It includes the binaries of Composite Application Library and Unity Application Block.

 

Download the WRAExample sample application here!

 

EDITED 10/26/2008: Now, it’s available at CompositeWPF Contrib! Get the latest change set here!

 

Enjoy! :D

As always, any feedback is welcome! :)

 

Related posts:

Cheers!

Nacho

Una mirada cercana al SmartPartPlaceholder

Ya hace un tiempo que venía esbozando la idea de postear sobre el SmartPartPlaceholder. Es un control útil, pero es otro concepto acerca del cual tenemos poca información. Así que decidí tomarme mi tiempo para ver que se podía hacer con este control, y como eso sucedía.

Basicamente, la forma en que podemos usar el placeholder es bastante simple:

Agregamos el placeholder en una vista o formulario y le ponemos algun ID a la propiedad SmartPartName. Luego, nos aseguramos de haber agregado una vista con dicho ID en un WorkItem antes de agregar la vista del placeholder, de modo tal que el placeholder la encuentre y la muestre.

Tan simple como parece, no? :P

Pero, como sucede eso? Eso es todo lo que podemos hacer con el placeholeder? Por qué la vista con el placeholder está encapsulada en un WorkItem en el SmartPart Quickstart?

Todas estas preguntas tienen una respuesta, y de eso tratará este artículo.

Los Actores

Primero, comenzaré describiendo las diferentes clases que trabajan juntas para lograr "la mágia detrás de escenas".

La interfaz ISmartPartPlaceholder

SmartPartPlaceholder-SPPHInterface

La interfaz ISmartPartPlaceholder contiene solo dos propiedades que son implementadas por la clase del control SmartPartPlaceholder. Estas propiedades son:

  • SmartPartName. Esta propiedad obtiene o establece el nombre por defecto de la SmartPart contenida en el WorkItem actual.
  • SmartPart. Esta propiedad obtiene o establec la SmartPart contenida por el placeholder.

La clase SmartPartPlaceholder

SmartPartPlaceholder-SPPHClass

Aquí está el personaje principal! La clase que tiene el papel principal en la obra de este artículo! :P

Afortunadamente, no es una clase compleja. Consiste de un constructor, un método que dibuja el borde en tiempo de diseño, las dos propiedades implementadas de la interfaz  ISmartPartPlaceholder, y un método que dispara un evento cuando la SmartPart se muestra.

Entonces, veámoslos en detalle:

  • El constructor del SmartPartPlaceholder establece el estilo del placeholder para soportar color de fondo transparente. Luego, establece el fondo del placeholder en transparente.
  • El método protegido OnPaint(PaintEventArgs e) dibuja el rectángulo intermintente que forma el borde del SmartPartPlaceholder en tiempo de diseño.
  • La propiedad SmartPartName es usada por la estrategia ControlSmartPartStrategy para obtener la SmartPart (cuyo ID concuerda con la propiedad SmartPartName) del WorkItem actual para mostrarla en el placeholder. NOTA: El valor de esta propiedad no puede ser nulo, de lo contrario se lanzará una excepción.
  • La propiedad SmartPart muestra la SmartPart, que obtiene como valor, en el placeholder. Verifica que el valor establecido no sea nulo y que sea del tipo Control(de lo contrario se lanzará una excepción). Una vez hecho ésto, castea el valor a Control, establece su propiedad Dock en DockStyle.Fill y llama a su método Show(). Luego, la colección Controls del placeholder es vaciada y la SmartPart casteada es agregada a esta colección. Finalmente, se llama al método OnSmartPartShown, enviando la SmartPart como argumento.
  • El método privado OnSmartPartShown recibe un objetco como argumento (en efecto, la SmartPart que recién ha sido mostrada en el placeholder). Verifica si el evento SmartPartShown tiene algun manejador (handler) adherido, y, si es así, dispara el evento con el placeholder como sender y una nueva instancia de la clase SmartPartPlaceHolderEventArgs como argumento del evento (esta instancia contiene la SmartPart, en su propiedad SmartPart de solo lectura, que fue enviada como argumento en su constructor).

La clase SmartPartPlaceHolderEventArgs

La clase SmartPartPlaceHolderEventArgs tiene un constructor que recibe un objeto como argumento (la SmartPart recientemente mostrada en el placeholder). Este constructor verifica que el argumento que recibe no sea nulo (de lo contrario se lanzará una excepción) y establece el valor de un miembro privado (llamado “smartPart”) con este argumento. Esta clase tambien contiene una propiedad de solo lectura llamada SmartPart (del tipo object) que puede ser utilizada para obtener la SmartPart que recién fue mostrada en el placeholder.

La clase ControlSmartPartStrategy

La clase ControlSmartPartStrategy es una estrategia de constructor (builder strategy) que se encarga de detectar el SmartPartPlaceholder cuando un control pasa a través del pipeline de ObjectBuilder (NOTA: la estrategia también busca Workspaces y SmartParts). Una vez que se detecta el placeholder, la estrategia verifica su propiedad SmartPartName, de modo tal que obtenga la SmartPart, cuyo ID coincide con la propiedad SmartPartName, de la colección Items del WorkItem actual. Habiendo obtenido la SmartPart, establece el valor de la propiedad SmartPart del placeholder con el valor obtenido. Si el valor obtenido es nulo, no se establece el valor de la propiedad SmartPart; es por ésto que ninguna excepción se dispará cuando no se encuentra ninguna SmartPart. La estrategia también agrega el placeholder a la coleción Items del WorkItem actual, con el nombre del placeholder como su ID (si el placeholder no tiene nombre, se establece una GUID en su lugar).

Aquí está el código del método ReplaceIfPlaceHolder(WorkItem workItem, Control control) que realiza lo que se descrivió anteriormente:

private void ReplaceIfPlaceHolder(WorkItem workItem, Control control)

{

    ISmartPartPlaceholder placeholder = control as ISmartPartPlaceholder;

    if (placeholder != null)

    {

        Control replacement = workItem.Items.Get<Control>(placeholder.SmartPartName);

        if (replacement != null)

            placeholder.SmartPart = replacement;

    }

}

La Obra

El siguiente diagrama de secuencia muestra las interacciones descriptas en la sección “Los Actores”. Por razones de simplicidad, algunas intreacciones están comentadas como notas.

smartpartplaceholder-interactions-esp

Encapsulando placeholders en WorkItems?

Como se mostró en la sección “Los Actores”, la clase ControlSmartPartStrategy agregaba el placeholder al WorkItem actual con su nombre como ID. Por ende, si uno agrega dos instancias de la vista que contiene el placeholder al mismo WorkItem, se obtendrá una excepción indicando que ya hay un objeto con dicho ID. Ésta es una de las razones por las que los placeholders son encapsulados en diferentes WorkItems hijo.

La otra razón se relaciona con la vista que será mostrada en el placeholder. Como dije antes, la propiedad SmartPartName del placeholder no puede ser nula (o se obtendrá una excepción). Para lograr que el placeholder trabaje con esta propiedad, se debe agregar una vista con algún ID al WorkItem antes de agregar la vista del placeholder. Pero, si se agregan dos vistas con el mismo ID, se obtendrá una excepción debido a que ya hay un objeto con el mismo ID. Es por ésto que, en el SmartPart Quickstart, la vista mostrada en el placeholder y la vista con placeholder son encapsuladas en un WorkItem hijo.

Sin embargo, este tipo de encapsulamiento es necesario si alguna de estas razones descriptas arriba pueden suceder.

Tip

Una forma útil de utilizar el SmartPartPlaceholder es como un “DeckWorkspace reducido” o un “DeckWorkspace ligero”. Antes de comenzar a describir ésto, permitanme aclarar que un placeholder puede ser agregado en formularios también, no sólo en vistas! Ahora, continuemos. Como dije, el placeholder es agregado al WorkItem con algún ID, por lo tanto, se puede agregar en el ShellForm con un ID y accederlo en los modules. Por ejemplo:

SmartPartPlaceholder placeholder = WorkItem.RootWorkItem.Items.Get<SmartPartPlaceholder>(“Placeholder”);
MyView view = WorkItem.SmartParts.AddNew<MyView>();
placeholder.SmartPart = view;

De esta forma, se puede mostrar una vista en el placeholder, y cuando se agregue otra, la primera se removerá automáticamente del placeholder.

Para este propósito, debe tener en mente que:

  • Llamar al método Presenter.OnCloseView() no realiza nada, ya que busca un workspace contenedor y llama a su método IWorkspace.Close(object view) y, en estes caso, la vista se encuentra en un SmartPartPlaceholder.
  • Si se desea remover la vista del placeholder, se debe remover la vista explicitamente de la colección Controls.
  • El placeholder no contiene un evento SmartPartClosing.
  • El placeholder no contiene una colección de SmartParts.

Espero que este artículo les haya dejado un concepto más claro de la clase SmartPartPlaceholder.

Como siempre: cualquier feedback es bienvenido! :D

Saludos!

- Nacho

See this post in English.

A close view on the SmartPartPlaceholder

It’s been a time since I was wondering about posting about the SmartPartPlaceholder. It’s a useful control, yet it’s another concept that we have little information about. So, I decided to take my time and see what could be done with this control, and how it was done.

Basically, the way in which we can use the placeholder is quite simple:

We add the placeholder in a view or form and set its SmartPartName property with some ID. Then, we make sure that we have added a view with that ID in a WorkItem before adding the view of the placeholder, so that the placeholder gets it and displays it.

As simple as it gets, right? :P

But, how does that happen? Is that everything we can do with the placeholder? Why is the view with the placeholder scoped into a WorkItem in the SmartPart Quickstart?

All these questions have an answer, and that’s what this article will deal with.

The Actors

First of all, I’ll start describing the different classes that work together to perform “the magic behind the scenes”.

The ISmartPartPlaceholder interface

SmartPartPlaceholder-SPPHInterface

The ISmartPartPlaceholder interface contains only two properties which are implemented in the SmartPartPlaceholder control class. These properties are:

  • SmartPartName. This property gets or sets the default name for the SmartPart contained in the current WorkItem.
  • SmartPart. This property gets or sets the SmartPart contained by the placeholder.

The SmartPartPlaceholder control class

SmartPartPlaceholder-SPPHClass

Here’s the main character! The class that has the main role in this article’s play! :P

Luckily, it’s not a complex class at all. It consists of one constructor, a method that draws its border at design-time, the two properties implemented from the ISmartPartPlaceholder interface, and a method that fires an event when a SmartPart is shown.

So, let’s see them in detail:

  • The SmartPartPlaceholder constructor sets the style of the placeholder to support transparent color as its background color. Then, it sets the background color of the placeholder to transparent.
  • The protected OnPaint(PaintEventArgs e) method draws the dashed rectangle that forms the border of the SmartPartPlaceholder at design-time.
  • The SmartPartName property is used by the ControlSmartPartStrategy builder strategy to get the SmartPart (whose ID matches the SmartPartName property) from the current WorkItem in order to display it in the placeholder. NOTE: The value of this property cannot be null, otherwise an exception will be thrown.
  • The SmartPart property displays the SmartPart it gets as value in the placeholder. It checks that the set value is not null and that it is type of Control (otherwise, an exception is thrown). Once this is done, it casts the value to Control, sets its Dock property to DockStyle.Fill and calls its Show() method. Then, the Controls collection of the placeholder is cleared and the casted SmartPart is added to this collection. Finally, the OnSmartPartShown method is called, sending the SmartPart as argument.
  • The OnSmartPartShown private method receives an object as argument (in fact, the SmartPart that has just been displayed in the placeholder). It checks whether the SmartPartShown event has any handler attached, and, if so, fires the event with the placeholder as sender and with a new instance of the SmartPartPlaceHolderEventArgs class as event arguments (this instance contains the SmartPart in its SmartPart read-only property which was sent as argument in its constructor).

The SmartPartPlaceHolderEventArgs class

The SmartPartPlaceHolderEventArgs class has a constructor that receives an object as argument (the SmartPart recently shown in the placeholder). This constructor checks that the argument it’s receiving is not null (otherwise, it throws an exception) and sets the value of a private member (named "smartPart") to this argument. This class also has a read-only property named SmartPart (of type object) that can be used to retrieve the SmartPart that has just been displayed in the placeholder.

The ControlSmartPartStrategy class

The ControlSmartPartStrategy class is a builder strategy that is in charge of detecting the SmartPartPlaceholder whenever a control is run through the pipeline of ObjectBuilder (NOTE: the strategy also looks for Workspaces and SmartParts). Once the placeholder is detected, the strategy checks its SmartPartName property, so it can retrieve the SmartPart whose ID matches SmartPartName property from the Items collection of the current WorkItem. Having retrieved the SmartPart, it sets the value of the SmartPart property of the placeholder to the retrieved value. If the retrieved value is null, the SmartPart property of the placeholder is not set at all; this is why no exception is thrown when no matching SmartPart is found. The strategy also adds the placeholder to the Items collection of the current WorkItem, with the name of the placeholder as its ID (if the placeholder has no name, a GUID is set instead).

Here’s the code of the ReplaceIfPlaceHolder(WorkItem workItem, Control control) method that performs what was described previously:

private void ReplaceIfPlaceHolder(WorkItem workItem, Control control)

{

ISmartPartPlaceholder placeholder = control as ISmartPartPlaceholder;

if (placeholder != null)

{

Control replacement = workItem.Items.Get<Control>(placeholder.SmartPartName);

if (replacement != null)

placeholder.SmartPart = replacement;

}

}

The Play

The following sequence diagram depicts the interactions described in the "The Actors" section. For the sake of simplicity, some interactions are commented as notes.

smartpartplaceholder-interactions

WorkItem-scope for placeholders?

As it was shown in the "The Actors" section, the ControlSmartPartStrategy class added the placeholder to the current WorkItem with its name as ID. So, if you add two instances of the view that contains that placeholder to the same WorkItem, you will get an exception stating that there’s already an object with that ID. This is one of the reasons why placeholders are scoped in different child WorkItems.

The other reason deals with the view that will be displayed in the placeholder. As I said before, the SmartPartName property of the placeholder cannot be null (or you will get an exception). To get the placeholder work with this property, you must add a view with some ID to the WorkItem before you add the view with the placeholder. But, if you add two views with the same ID, you will get an exception because there’s already an object with the same ID. This is why, in the SmartPart Quickstart, the view displayed in the placeholder and the view with the placeholder are scoped into a child WorkItem.

Nevertheless, this kind of scope is necessary if any of the reasons described above may happen.

Tip

One useful way to use the SmartPartPlaceholder is like a "reduced DeckWorkspace" or a "lightweight DeckWorkspace". Before I start describing this, let me state that a placeholder can be put in forms too, not only in views! Now, let’s move on. As I said, the placeholder is added to the WorkItem with some ID, so you may put it in the ShellForm with some ID and access it in your modules. For example:

SmartPartPlaceholder placeholder = WorkItem.RootWorkItem.Items.Get<SmartPartPlaceholder>("Placeholder");
MyView view = WorkItem.SmartParts.AddNew<MyView>();
placeholder.SmartPart = view;

In that way, you may display a view in the placeholder, and when you add another, the first view will automatically be removed from the placeholder.

For this purpose, you should keep in mind that:

  • Calling the Presenter.OnCloseView() does nothing as it searches for a container workspace and calls its IWorkspace.Close(object view) method and, in this case, the view is contained by a SmartPartPlaceholder.
  • If you want to remove the view from the placeholder, you should explicitly remove the view from the Controls collection.
  • The placeholder does not contain a SmartPartClosing event.
  • The placeholder does not contain a collection of SmartParts.

I hope this article gives you a clearer concept of the SmartPartPlaceholder class.

As always: any feedback is welcome! :)

Cheers!

- Nacho

Ver este post en español.