2009-07-14

Monos y Manzanas: Key Value Coding y Configuración

Hacia el final de la entrega anterior de Monos y Manzanas estuve contando sobre como utilizar los bindings de Cocoa con Monobjc. En el capítulo final de esta serie de posts veremos como es posible agilizar la implementación de clases que utilicen Key Value Coding (KVC de ahora en mas) desde .NET y como agregar un panel de preferencias para modificar la configuración de la aplicación y que la misma sea persistida de forma automática.

Ante todo, fe de erratas: una persona del equipo de MonobjC con la cual me contacté, me acalró que no es necesario crear una instancia que nadie usa de NSAutoReleasePool en el bootstrapping de la aplicación, ya que eso se hace automáticamente.

En el último ejemplo mostrado, cada vez que se quería implementar una propiedad que pudiese ser utilizada para los bindings con KVC era necesario crear los setters y getters, indicarles que iban a ser llamados como mensajes de ObjectiveC y llamar a las notificaciones de cambios de propiedad.
Para no tener que hacer esto por cada propiedad que queramos exponer al runtime de ObjectiveC, crearemos un nuevo atributo el cual indique que esa propiedad podrá ser utilizada con KVC:

using System;

namespace zPod.Objc
{
public class KeyValueCodingAttribute : Attribute
{
public KeyValueCodingAttribute (string propertyName)
{
PropertyName = propertyName;
}

public string PropertyName
{
get;
set;
}
}
}

Teniendo esto, las propiedades que querramos exponer se verán así:


[KeyValueCoding("modelo")]
public BrowserModel Modelo
{
get;
set;
}

Muy lindo hasta ahora pero... ¿Como le indicamos al runtime de ObjectiveC que deberá tomar la propiedad "modelo" al intentar llamar al mensaje "modelo" o "setModelo:"? Simple, sobreescribiendo el llamado a los mensajes "setValue:forUndefinedKey:" y "valueForUndefinedKey"


[ObjectiveCMessage("setValue:forUndefinedKey:")]
public override void SetValueForUndefinedKey (Id val, NSString key)
{
this.ResolveSetValueForUndefinedKey(val, key);
}

[ObjectiveCMessage("valueForUndefinedKey:")]
public override Id ValueForUndefinedKey (NSString key)
{
return this.ResolveValueForUndefinedKey(key);
}

Al no encontrar una clave definida, Cocoa avisa a la clase que se intento leer o dar valor a la clave utilizando estos mensajes.


En estas implementaciones se está llamando a los métodos ResolveSetValueForUndefinedKey y ResolveValueForUndefinedKey. Los mismos son extension methods creados para la clase NSObject que resuelven cual es la propiedad que deberá ser devuelta o modificada utilizando reflection para saber si tienen el atributo definido anteriormente. El código de estos métodos se incluye dentro de los fuentes de este ejemplo.




Hacia el final de la entrega anterior de Monos y Manzanas comenté que Cocoa proveía un controller especial llamado NSUserDefaultsController que permite guardar automáticamente las preferencias del usuario.
Ahora vamos a ver como crear un nuevo panel de preferencias donde configurar la URL Home de nuestro browser.


Volviendo al Interface Builder, agregamos un nuevo NSPanel y le agregamos un TextField para ingresar la URL Home


Picture 1


Para poder abrir el panel desde el menu de preferencias, conectamos la opción Preferences... del menú con el panel creado, asociándolo a la acción makeKeyAndOrderFront:.


Picture 3


Dado que se necesita únicamente una instancia de este panel, lo configuramos como se muestra en la siguiente imagen. Lo mas importante es desmascar las opciones Released When Closed y Visible at launch para que no se muestre al incio, pero que al cerrar la ventana solamente se oculte y no se destruya el objeto.


Picture 2


Creamos el binding para el value del TextField, conectándolo con la instancia de SharedUserDefaultsController, utilizando la clave values y el model key path homePage.
Al utilizar este controller, los valores conectados serán persistidos automáticamente.


Picture 4


Como último paso dentro del Interface Builder, agregamos un nuevo boton para ir a la home, agregamos una nueva acción llamada goHome: al BrowserController y los asociamos de la misma manera que el boton Go.


Picture 5


Volviendo a C#, Dentro del código del BrowserController, modificamos el método AwakeFromNib y agregamos el método que responda al mensaje goHome:, para que al iniciar la aplicación vaya a la URL definida como home y, en caso de no haber nada, definirle un valor default (recuenden que en _userDefaults tenemos guardada una instancia de SharedUserDefaultsController):


[ObjectiveCMessage("goHome:")]
public void GoHome(Id sender)
{
NSString home = _userDefaults.ValueForKeyPath("values.homePage");
this.Modelo.WillChangeValueForKey("url");
this.Modelo.Url = home;
this.Modelo.DidChangeValueForKey("url");
Browse(sender);
}

[ObjectiveCMessage("browse:")]
public void Browse(Id sender)
{
NSString url = this.Modelo.Url;
browser.MainFrameURL = url;
_userDefaults.SetValueForKeyPath("CocoaBrowser - " + url, "values.title");
}

[ObjectiveCMessage("awakeFromNib")]
public void AwakeFromNib ()
{
NSString home = _userDefaults.ValueForKeyPath("values.homePage");
if (home == null)
{
_userDefaults.SetValueForKeyPath(new NSString("http://zPod.com.ar"), "values.homePage");
}

GoHome(null);
}

Listo! Ahora a compilar y a disfrutar de su nuevo browser con botón de Home para Mac hecho en .NET!


Picture 6


Con esto queda finalizada la serie de posts: Monos y Manzanas. El código completo lo pueden bajar de aca.



Hasta la próxima!
Zaiden

No hay comentarios.: