2009-07-12

Monos y Manzanas: Binding entre la UI y el Modelo

En el último post expliqué como hacer una aplicación sencilla para Mac cuya UI estaba hecha con Interface Builder, y el resto de la plomería de fondo utilizaba C#, gracias a Mono y el bridge Monobjc.

Dada la extrema simplicidad de nuestro CocoaBrowser, hubieron algunos conceptos que escaparon al ejemplo inicial.

En esta nueva entrega de Monos y Manzanas veremos como hacer uso del binding provisto por Cocoa y el Interface Builder para conectar nuestra interfaz gráfica al Modelo de nuestra aplicación.

Por ahora, definiremos una nueva clase BrowserModel, la cual representará el “modelo” de nuestra apliación de juguete.

public class BrowserModel
{
private string _url;
public string GetUrl()
{
return _url;
}

public void SetUrl(string newUrl)
{
_url = newUrl;
}
}


Posiblemente se pregunten: Por qué la propiedad esta definida con metodos Get y Set, en vez se ser una propiedad “normal” de C#, al estilo “public string Url { get; set'; }”?

Es por un requerimiento del bridge con Objective-C y el binding con la UI, pero vamos a verlo bien mas adelante.


La idea es que lo que usuario tipee dentro del TextField utilizado para ingresar la URL de nuestro browser actualice automáticamente nuestro modelo y, en caso de actualizar el modelo desde otro lugar, se actualice automáticamente el TextField.


Para poder hacer esto, agregaremos un nuevo campo a la clase BrowserController que nos de acceso al modelo, y lo haremos visible para el bridge, con los siguientes getter y setter:


[ObjectiveCClass]
public class BrowserController : NSObject
{
//
// Mucho codigo antes...
//

private BrowserModel _modelo = new BrowserModel();

[ObjectiveCMessage("modelo")]
public BrowserModel getModelo()
{
return _modelo;
}

[ObjectiveCMessage("setModelo:")]
public void setModelo(BrowserModel nuevoModelo)
{
WillChangeValueForKey("modelo");
_modelo = nuevoModelo;
DidChangeValueForKey("modelo");
}

//
// Mucho codigo despues...
//

}

Esta forma de crear los campos permite a ObjectiveC utilizar la clase con algo conocido como Key Value Coding, accediendo a ciertas propiedades de nuestra clase como si fuesen pares Clave - Valor, tanto para leer como para escribir.

Los métodos WillChangeValueForKey y DidChangeValueForKey permiten avisarle a la UI que hubo un cambio en el valor la propiedad.


Para que las propiedades de la clase BrowserModel sean accesibles de la misma manera, las decoramos de forma similar, quedando como resultado:


[ObjectiveCClass]
public class BrowserModel : NSObject
{
// Constructores requeridos por NSObject
public BrowserModel()
{
}

public BrowserModel (IntPtr nativePointer) : base(nativePointer)
{
}

private string _url;

[ObjectiveCMessage("url")]
public string GetUrl()
{
return _url;
}

[ObjectiveCMessage("setUrl:")]
public void SetUrl(string newUrl)
{
WillChangeValueForKey("url");
_url = newUrl;
DidChangeValueForKey("url");
}
}

Como pueden ver, es requisito agregar el atributo [ObjectiveCClass] y hacer heredar la clase de NSObject (o algún hijo) para que la misma sea visible desde ObjectiveC. Al hacer esto, deberán agregar los dos constructores indicados en el código.


Dado que ahora el URL a navegar será indicado por las propiedades del modelo, podemos sacar la referencia al TextField del BrowserController y modificar el metodo Browse de la siguiente manera:


[ObjectiveCClass]
public class BrowserController : NSObject
{
//
// Mucho codigo antes...
//

[ObjectiveCMessage("browse:")]
public void Browse(Id sender)
{
string url = this.fModelo.getUrl();
browser.MainFrameURL = url;
}

//
// Mucho codigo despues...
//

}

Con esto listo, solo resta hacer los bindings propiamente dichos en el Interface Builder.


El primer paso será agregar un ObjectController desde la Library. Este tipo de controller nos permite hacer binding facilmente a un objeto concreto del modelo. En caso de querer hacer binding contra Arrays, Diccionarios y otros tipos de datos, existen controllers mas apropiados.


Picture 1


Dentro de los atributos del ObjectController, agregar una nueva clave llamada “url”, la cual usaremos como nexo entre el modelo y la UI.


Picture 2


Dentro de los bindings del ObjectController, indicamos que el Controller Content debe hacer binding con el BrowserController, utilizando como Model Key Path “modelo”. Esto hará referencia a la propiedad modelo que creamos – e hicimos visible con setter y getter para ObjectiveC – dentro de la clase creada en C#.


Picture 3


Finalmente, seleccionamos el TextField de la URL y dentro de la solapa de bindings indicamos que se deberá hacer el binding con el ObjectController, utilizando “selection” como Controller Key y “url” como Model Key Path. Para que el modelo se actualice cada vez que se modifica el TextField y no únicamente al terminar de editarlo cambiando el foco, deberemos marcar el check de “Continuosly Update Value”.


Picture 4


Como yapa, en esta versión de los fuentes, podrán ver como hacer binding contra una clase especial llamada NSUserDefaultsController, la cual nos permite mantener guardadas las preferencias del usuario haciendo binding contra las mismas. En este ejemplo se utiliza para guardar el titulo de la ventana de la aplicación, aunque su utilidad mas común es dentro de los paneles de preferencias.


El código lo pueden bajar de aca.


Hasta la próxima!
Zaiden

No hay comentarios.: