Patrones y Prácticas

Directamente al grano

Al crear nuestro proyecto WPF, a simple vista nos encontramos con nuestro control XAML y asociado a este, nuestra conocida clase Code–Behind , con el que “handlearemos” los eventos de cada componente de nuestra UI. El ejemplo siguiente expone una aplicación sencilla WPF :

XAML
<Window x:Class="MiProyecto.MiVentana"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MiProyecto" Height="344" Width="355" >
<Grid>
<Button Name="but" BorderBrush="Azure">CLIC AQUI</Button>
</Grid>
</Window>

 

ARCHIVO CODE BEHIND
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace MiProyecto
{
public partial class MiVentana : System.Windows.Window
{
public MiVentana()
{
InitializeComponent();
/// cool code, definimos al boton que al hacer clic delegue a
///un metodo (handler) la accion, pero para que definir un metodo
/// si lo podemos especificar ahi mismo
but.Click += delegate
{
MessageBox.Show("HOLA");
};
}
}
}

En el momento de ejecutar nuestro build de proyecto WPF, implícitamente el compilador está convirtiendo nuestro código XAML en BAML (Binary Application Markup Language), un archivo binario que representa nuestro código de marcas, manipulable directamente por nuestro Framework, y a la vez un archivo autogenerado ( G.CS) que contiene la implementación de nuestro Initialize Component y la definición de cada objeto.

Básicamente este método invoca directamente al LoadComponent de la clase System.Windows.Application. que se encarga de extraer el BAML del assembly, lo parsea y crea los controles, y eventos asociados.

//MiVentana.g.cs
using System;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MiProyecto {

public partial class MiVentana : System.Windows.Window, System.Windows.Markup.IComponentConnector {

internal System.Windows.Controls.Button but; //el boton

private bool _contentLoaded;
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;

//Instanciamos un objeto de recursos como uri hacia nuestro archivo xaml.
System.Uri resourceLocater = new System.Uri(”/MiProyecto;component/MiVentana.xaml”, System.UriKind.Relative);
System.Windows.Application.LoadComponent(this, resourceLocater);
}

[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(”Microsoft.Design”, “CA1033:InterfaceMethodsShouldBeCallableByChildTypes”)]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
this.but = ((System.Windows.Controls.Button)(target));
return;
}
this._contentLoaded = true;
}
}
}

¿Qué sucede cuando no podemos vincular nuestro Code-Behind con nuestro archivo auto generado G.CS?

Al momento de buildear nuestro proyecto WPF , obtenemos por ejemplo el error “The name 'InitializeComponent' does not exist in the current context”.

Esto se debe a que automáticamente el compilador reasigna al archivo G.CS el NameSpace del archivo code-behind con el que fue creado . Por ende estamos obligados a que el NameSpace de nuestro XAML, del code-behind y del G.CS coincidan.

Como solución inmediata podríamos renombrar los NameSpaces de las clases, al que tiene el ultimo archivo G.CS compilado. También podríamos buscar por file system nuestro archivo G.CS y renombrar su NameSpace, al que corresponda.

Está claro que si el XAML y su archivo asociado respetan esta igualdad, el G.CS se va a construir sin inconvenientes.

Debemos tener en cuenta que para el Visual Studio 2005, cada build que hagamos reconstruye el G.CS. En cierta forma lo podríamos considerar como un bug, ya que si modificamos el XAML y lo guardamos, nuestro archivo autogenerado no se entera y generara problemas para referenciarnos si no reconstruimos el proyecto.

Este detalle fue considerado en la nueva versión Orcas Beta 1, donde el archivo autogenerado se regenera cada vez que guardamos nuestro XAML.

METIENDO MANO EN EL G.CS

Cada objeto dentro del G.CS tiene un nivel de accesibilidad Internal, y esto no lo podemos modificar y aunque lo hagamos, cuando volvamos a buildear el proyecto, se volverán a asignar de la misma manera. Pero .Net nos brinda una ventaja… la clase Partial. Si en nuestro control WPF, queremos utilizar un objeto que no sea Internal, solo tendríamos que incluirlo en otra Partial Class con el mismo nombre y el nivel de accesibilidad que quisiéramos.

Podremos modificar directamente el G.CS, pero no es lo recomendable ya que el compilador se basa inicialmente en este archivo para reconstruir la UI y no en el XAML.

Bueno, esto fue todo por ahora.

Hasta pronto!,

Gato

Developer @ ID Corporate Solutions

 

Command Bindings

Hace unos días un co-equiper de ID Coporate se acercó y me dijo, refiriéndose a un proyecto nuevo en wpf: “voy a implementar, para darle un valor agregado a esta aplicación, un menú contextual para el botón derecho en determinados lugares”. De esta frase se desprende la siguiente entrada, que tiene que con la facilidad de hacer esto dados los command bindings del nuevo framework.

Por qué esto?

Para explicarlo, antes que nada, me voy a referir a cómo era la implementación de esto, por ejemplo, en el framework anterior, para no “duplicar código”.

Supongamos que teníamos un Windows form, con un Window class, una clase de Tareas de aplicación que llamaré para el ejemplo “TareasDeAplicación” y otro componente que se haga cargo de la impresión propiamente dicha (PrinterClass). Para implementar varios “entry points” a una misma tarea (voy a tomar una tarea de impresión, por ejemplo), debíamos crear varios event handler que hagan referencia a la clase TareasDeAplicación y al método correspondiente. Este último delegaría la impresión a un componente a este efecto (PrinterClass). Un diagrama tentativo sería algo así:

(haga click en la imagen para verla completa)

Esto que vemos, aunque sabemos que es totalmente aplicable, nos obliga a escribir tantos event handlers como puntos de entrada tengamos que interfacear con el usuario. En este ejemplo en particular estamos armando tres eventhandlers que están haciendo exactamente lo mismo (tareasDeAplicacion.imprimirDocumento()).

Más allá de esto, nosotros estábamos obligados a generar la lógica para habilitar o deshabilitar dichas operaciones en dónde y cuándo correspondiese. Supongamos que vamos a dejarle imprimir una grilla, pero solo cuándo esta tiene datos; de estar vacía dicha grilla, deberíamos deshabilitar el botón, el menú y deberíamos preguntar en el Grid_KeyDown si es que esa grilla está vacía o no antes de ejecutar la operación.

Los command bindings del framework 3.0 nos permiten hacer eso sin necesidad de crear dichos event handlers (y sin el dolor de cabeza de armar la lógica de habilitar y deshabilitar en cada caso). El modelo de commands del framework 3.0 nos deja “adjuntar” cualquier objeto de nuestra aplicación a un command determinado mediante el binding.

(haga click en la imagen para verla completa)

Además de poder controlar todo desde un solo lugar (un command) nos permite mantener el estado de habilitación/deshabilitación sincronizado desde el mismo objeto command.

Cómo?

La interfaz de commands del framwork 3.0 (public interface ICommand) nos permite implementar 3 partes para hacer todo esto:

void Execute (object parameter);

bool CanExecute(object parameter);

event EventHandler CanExecuteChanged;

En una implemetación simple como la que tratamos en el ejemplo, el método Execute debería contente la lógica de aplicación (imprimirDocumento()) y será ejecutado por el objeto que haga el binding , en este caso la grilla con el shortcut (ctrl-p), el botón de imprimir y el menú contextual.

El método CanExecute devuelve el estado del comando y deberá contener la lógica de si este puede o no ser ejecutado (por ejemplo, verificar si la grilla – o mejor dicho, si el objeto que contiene la grilla – está o no vacía).

Por último, el evento CanExecuteChanged es levantado cuándo el estado del comando cambió. Esto avisa a los “sources” (el botón o el menú) que tiene que habilitarse o deshabilitarse según corresponda.

Como consecuencia directa de esto, implementar un menú contextual (sobre todo si este estará “imitando” la funcionalidad de un botón que ya existe, y lo que queremos es darle más puntos de entrada al usuario para que nuestra aplicación sea más sencilla de utilizar) es una tarea que nos llevará muy poco tiempo.

Nos vemos,

Sonny

Developer @ ID Corporate Solutions
 
ID Corporate Solutions - Ciudad Autónoma de Buenos Aires - Argentina