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?
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
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
Aquí está el personaje principal! La clase que tiene el papel principal en la obra de este artículo!
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.
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!
Saludos!
- Nacho
See this post in English.