Target-Action en Cocoa

julio 21, 2009
La aproximación a la programación con componentes dentro de Cocoa es algo diferente a la que los programadores en Java estamos acostumbrados. Por ejemplo, en Java si queremos crear una nueva ventana, hacemos una clase que herede de JFrame y metemos en su constructor las llamadas necesarias para añadir los componentes, para atender a los eventos, debemos crear subclases que implementen las interfaces que imponen en el sistema de eventos de Swing. Todo este proceso es bastante laborioso, incluso para crear una ventana que tenga un botón que cambie el valor de un “label” lleva una carga en código bastante fuerte.
En Cocoa, este enfoque es algo diferente, es una de las cosas que tienes que “aprender de nuevo” cuando te enfrentas a cocoa con una mente “Javera” o “.NETera”. Para crear una ventana usaremos el Interface Builder, cosa que en el fondo tampoco difiere mucho de la parte Java, sin embargo las grandes diferencias se encuentran en la forma de tratar los eventos, para ello se usa el modelo conocido como target-action. En dicho modelo tendremos una clase que no tiene por que heredar de ninguna otra, la cual sera el “target” de la clase que lanza los eventos, y se invocara a un “action” de dicha clase “target”.
¿En que consiste el target-action?
Creo que podemos verlo mejor con un ejemplo, supongamos que tenemos el típico ejemplo que consta de una ventana con un botón, y queremos que al pulsar el botón, se cambie el texto del label.
Según el esquema de funcionamiento de cocoa, vamos a tener una clase llamada Controller la cual va a ser target del botón, y le diremos que el action de pulsar el botón se le asigna al método botonPulsado de dicha clase Controller. Ademas dentro de dicha clase Controller tenemos que tener una referencia al Label para poder acceder a sus métodos y cambiar el testo que muestra.
La clase controller quedaria asi:
Controller.h
#import <Cocoa/Cocoa.h>
@interface
class Controller : NSObject
{
IBOutlet NSTextField *textField;
}
– (IBAction) botonPulsado: (id)sender;
@end
Controller.m
#import <Controller.h>
@implementation
– (IBAction) botonPulsado: (id)sender
{
NSLog(@”Boton pulsado”);
Operar con el label.
}
@end
IBOutlet indica que la variable es realmente una referencia como comentabamos anteriormente por otro lado IBAction define que la llamada se trata de una respuesta a un evento. Por el resto, la clase es bastante sencilla.
La forma mas sencilla de realizar los enlaces entre los componentes es desde el Interface builder, para ello, arrastraremos una instancia de Object a la Ventana “Doc”, la asociaremos a la clase Controller, hacemos Ctrl-Click sobre nuestro objeto Controller en la ventana “Doc” y arrastraremos el label al propio label de la ventana. Para asociar el action del botón pincharemos arrastrando desde el botón al objeto en la ventana “Doc” y seleccionaremos el método botonPulsado.
Si Compilamos y ejecutamos veremos como al pinchar sobre el botón, sale el mensaje de log y se cambia el contenido del label.
Mirando debajo del capó. Selectors
Realmente esto es muy “bonito”, la parte de codificación es bastante pequeña y todo esta bastante aislado, fácilmente podríamos cambiar los targets de la ventana y sustituir la clase Controller por otra que haga otra cosa, sin tocar nada de la vista, pero ¿en que mecanismos se basa para funcionar?
En Objective C, un “selector” guarda la referencia de un método de una clase, de tal manera que podemos hacer referencia a dicho metodo de forma dinamica. En este concepto se apoya el modelo target-action, de tal manera que en tiempo de ejecución podemos indicarle cual sera el método que una acción deberá ejecutar.
En código hay varias formas de obtener un selector, una de ellas es mediante el uso de “@selector()” a la cual se le pasa directamente el método del cual queremos obtener el selector y la otra es mediante la función NSSelectorFromString() que recibe por parámetros una cadena con el nombre del método, por ejemplo
SEL selectorBotonPulsado;
selectorBotonPulsado = @selector(botonPulsado:);
selectorBotonPulsado = NSSelectorFromString(@”botonPulsado:”);
Ademas de esto, la clase NSControl tiene un metodo llamado “setAction” y otro “setTarget” que como puede suponerse sirven para indicar ambos elementos de forma manual.
Con todo esto, si queremos hacer los enlaces que hace el interface builder, pero haciendolo nosotros a mano, el código seria algo como:
[boton setTarget: self];
[boton setAction: selectorBotonPulsado];
Habiendo definido antes un IBOutlet NSButton *boton, en Controller.h

Xcode Icon

La aproximación a la programación con componentes dentro de Cocoa es algo diferente a la que los programadores en Java estamos acostumbrados. Por ejemplo, en Java si queremos crear una nueva ventana, hacemos una clase que herede de JFrame y metemos en su constructor las llamadas necesarias para añadir los componentes, para atender a los eventos, debemos crear subclases que implementen las interfaces que imponen en el sistema de eventos de Swing. Todo este proceso es bastante laborioso, incluso para crear una ventana que tenga un botón que cambie el valor de un “label” lleva una carga en código bastante fuerte.

En Cocoa, este enfoque es algo diferente, es una de las cosas que tienes que “aprender de nuevo” cuando te enfrentas a cocoa con una mente “Javera” o “.NETera”. Para crear una ventana usaremos el Interface Builder, cosa que en el fondo tampoco difiere mucho de la parte Java, sin embargo las grandes diferencias se encuentran en la forma de tratar los eventos, para ello se usa el modelo conocido como target-action. En dicho modelo tendremos una clase que no tiene por que heredar de ninguna otra, la cual sera el “target” de la clase que lanza los eventos, y se invocara a un “action” de dicha clase “target”.

¿En que consiste el target-action?

Creo que podemos verlo mejor con un ejemplo, supongamos que tenemos el típico ejemplo que consta de una ventana con un botón, y queremos que al pulsar el botón, se cambie el texto del label.

Según el esquema de funcionamiento de cocoa, vamos a tener una clase llamada Controller la cual va a ser target del botón, y le diremos que el action de pulsar el botón se le asigna al método botonPulsado de dicha clase Controller. Ademas dentro de dicha clase Controller tenemos que tener una referencia al Label para poder acceder a sus métodos y cambiar el testo que muestra.

La clase controller quedaria asi:

Controller.h

#import <Cocoa/Cocoa.h>
@interface
class Controller : NSObject
{
IBOutlet NSTextField *textField;
}
- (IBAction) botonPulsado: (id)sender;
@end

Controller.m

#import <Controller.h>
@implementation
- (IBAction) botonPulsado: (id)sender
{
NSLog(@"Boton pulsado");
Operar con el label.
}
@end

IBOutlet indica que la variable es realmente una referencia como comentabamos anteriormente por otro lado IBAction define que la llamada se trata de una respuesta a un evento. Por el resto, la clase es bastante sencilla.

La forma mas sencilla de realizar los enlaces entre los componentes es desde el Interface builder, para ello, arrastraremos una instancia de Object a la Ventana “Doc”, la asociaremos a la clase Controller, hacemos Ctrl-Click sobre nuestro objeto Controller en la ventana “Doc” y arrastraremos el label al propio label de la ventana. Para asociar el action del botón pincharemos arrastrando desde el botón al objeto en la ventana “Doc” y seleccionaremos el método botonPulsado.

Si Compilamos y ejecutamos veremos como al pinchar sobre el botón, sale el mensaje de log y se cambia el contenido del label.

Mirando debajo del capó. Selectors

Realmente esto es muy “bonito”, la parte de codificación es bastante pequeña y todo esta bastante aislado, fácilmente podríamos cambiar los targets de la ventana y sustituir la clase Controller por otra que haga otra cosa, sin tocar nada de la vista, pero ¿en que mecanismos se basa para funcionar?

En Objective C, un “selector” guarda la referencia de un método de una clase, de tal manera que podemos hacer referencia a dicho metodo de forma dinamica. En este concepto se apoya el modelo target-action, de tal manera que en tiempo de ejecución podemos indicarle cual sera el método que una acción deberá ejecutar.

En código hay varias formas de obtener un selector, una de ellas es mediante el uso de “@selector()” a la cual se le pasa directamente el método del cual queremos obtener el selector y la otra es mediante la función NSSelectorFromString() que recibe por parámetros una cadena con el nombre del método, por ejemplo

SEL selectorBotonPulsado;

selectorBotonPulsado = @selector(botonPulsado:);

selectorBotonPulsado = NSSelectorFromString(@”botonPulsado:”);

Ademas de esto, la clase NSControl tiene un metodo llamado “setAction” y otro “setTarget” que como puede suponerse sirven para indicar ambos elementos de forma manual.

Con todo esto, si queremos hacer los enlaces que hace el interface builder, pero haciendolo nosotros a mano, el código seria algo como:

[boton setTarget: self];

[boton setAction: selectorBotonPulsado];

Habiendo definido antes un IBOutlet NSButton *boton, en Controller.h

Anuncios

Drag and Drop en Mac usando Cocoa

julio 8, 2009
Desarrollar una aplicación que soporte Drag and Drop usando cocoa es algo bastante sencillo.
Antes de ver el drag and drop, hay que conocer lo que en Cocoa se conoce como Pasteboard.
En Mac, el Pasteboard es un servidor que se ejecuta como demonio en nuestro sistema. Concretamente el ejecutable esta en /usr/sbin/pboard. Dicho servidor nos proporciona un almacenamiento común a todas las aplicaciones, de tal manera que sea posible compartir datos entre ellas. Operaciones como el “Copy&Paste” o el “Drag&Drop” utilizan este servidor.
Desde el Foundation framework, para manipular este espacio, podemos utilizar la clase NSPasteboard. Dentro del servidor tenemos varios espacios identificados por un nombre, y dentro de cada espacio, podremos almacenar un dato asociados a su tipo.
A grandes rasgos, con el mensaje generalPasteboard obtendremos el pasteboard principal, y usando el metodo declareTypes:owner: informaremos de los tipos de dato que el receptor podrá encontrar en el pasteboard.
Finalmente utilizaremos los mensajes setData:forType: y dataForType: para leer y escribir en el pasteboard.
Hablando ya del drag and drop, recordemos que es una operación que involucra a dos partes, por un lado el origen que proporciona los datos, y por otro lado el receptor que recibe los datos y los interpreta.
La parte “emisora” de los datos quizás sea la mas compleja, ya que, para respetar el aspecto visual, debemos crear una imagen que informe al usuario de que estamos enviando a la aplicación receptora.
El primer paso, es indicar que operaciones nuestra vista va a soportar, cocoa sabe cuales son esas operaciones ya que lo devuelve el método que debemos implementar draggingSourceOperationMaskForLocal:
Algunos posibles valores para devolver son:
NSDragOperationCopy
NSDragOperationDelete
NSDragOperationMove
En la parte emisora, este método sólo dice lo que podemos ofrecer, es en la parte receptora donde se especifica el tipo de operación a realizar. Cuando se realice el drop, la parte emisora podrá saber que operación se realizó y así actualizar sus contenidos en conveniencia.
Por ejemplo, si arrastramos nuestro contenido a la papelera, la papelera solicitará una operación de tipo NSOperationDelete, por lo que la vista emisora deberia hacer desaparecer dicho contenido.
Para implementar la parte visual, debemos poner nuestro código en el método mouseDragged, dentro de este método debemos incluir una llamada al método de NSView dragImage:at:offset:event:pasteboard:source:sildeBack:
Cuando el drop se ha completado, a nuestra vista nos llega el mensaje draggedImage:endedAt:operation donde principalmente, dependiendo de la operación realizada debemos actualizar el contenido de nuestra vista.
En la parte receptora, la vista que va a recibir los datos, primeramente debe registrar los tipos que va a poder recibir. Para ello existe dentro de NSView la llamada registerForDraggedTypes: la cual recibe como parámetro un array de dichos tipos. Los tipos son muy diversos pero podemos destacar:
NSStringPboardType
NSURLPboardType
NSPDFPboardType
NSRTFPboardType
Como vemos, la parte en negrita nos especifica el tipo de datos.
Habitualmente lo mas apropiado es llamar a este metodo en el initWithFrame: de nuestra vista. Así por ejemplo, en nuestra aplicación de prueba el código sería:
[self registerForDraggedTypes:[NSArray arrayWithObject:NSStringPboardType]];
Una vez que hemos registrado los tipos de datos que aceptamos, el siguiente paso es implementar los métodos que controlan la operación:
draggingEntered:
draggingUpdated:
draggingExited:
prepareForDragOperation:
performDragOperation:
concludeDragOperation:
Como vemos, cada mensaje cubre una parte del ciclo del “drop”, los tres primeros controlan el hecho de que el contenido que viene desde la parte emisora entra en nuestra vista, y finalmente los últimos segmentan en tres partes la parte en la que realmente se completa el “drop”.
En los últimos métodos en ningún momento se nos pasa el dato que forma parte de la transacción, sino que nosotros mismos debemos ir al PBoard y leerlo de allí, normalmente esto se hace en el método performDragOperation:, por ejemplo el código para leer una cadena sería:
– (BOOL) performDragOperation:(id <NSDraggingInfo>) sender
{
NSPasteboard *pb = [sender draggingPasteboard];
if ( [[pb types] containsObject:NSStringPboardType] ) {
NSString *value = [pb stringForType:NSStringPboardType];
NSLog(@”Dropped value: %@”, value);
return YES;
}
return NO;
}

capturaDnD

Desarrollar una aplicación que soporte Drag and Drop usando cocoa es algo bastante sencillo.

Antes de ver el drag and drop, hay que conocer lo que en Cocoa se conoce como Pasteboard.

En Mac, el Pasteboard es un servidor que se ejecuta como demonio en nuestro sistema. Concretamente el ejecutable esta en /usr/sbin/pboard. Dicho servidor nos proporciona un almacenamiento común a todas las aplicaciones, de tal manera que sea posible compartir datos entre ellas. Operaciones como el “Copy&Paste” o el “Drag&Drop” utilizan este servidor.

Desde el Foundation framework, para manipular este espacio, podemos utilizar la clase NSPasteboard. Dentro del servidor tenemos varios espacios identificados por un nombre, y dentro de cada espacio, podremos almacenar un dato asociados a su tipo.

A grandes rasgos, con el mensaje generalPasteboard obtendremos el pasteboard principal, y usando el metodo declareTypes:owner: informaremos de los tipos de dato que el receptor podrá encontrar en el pasteboard.

Finalmente utilizaremos los mensajes setData:forType: y dataForType: para leer y escribir en el pasteboard.

Hablando ya del drag and drop, recordemos que es una operación que involucra a dos partes, por un lado el origen que proporciona los datos, y por otro lado el receptor que recibe los datos y los interpreta.

La parte “emisora” de los datos quizás sea la mas compleja, ya que, para respetar el aspecto visual, debemos crear una imagen que informe al usuario de que estamos enviando a la aplicación receptora.

El primer paso, es indicar que operaciones nuestra vista va a soportar, cocoa sabe cuales son esas operaciones ya que lo devuelve el método que debemos implementar draggingSourceOperationMaskForLocal:

Algunos posibles valores para devolver son:

  • NSDragOperationCopy
  • NSDragOperationDelete
  • NSDragOperationMove

En la parte emisora, este método sólo dice lo que podemos ofrecer, es en la parte receptora donde se especifica el tipo de operación a realizar. Cuando se realice el drop, la parte emisora podrá saber que operación se realizó y así actualizar sus contenidos en conveniencia.

Por ejemplo, si arrastramos nuestro contenido a la papelera, la papelera solicitará una operación de tipo NSOperationDelete, por lo que la vista emisora deberia hacer desaparecer dicho contenido.

Para implementar la parte visual, debemos poner nuestro código en el método mouseDragged, dentro de este método debemos incluir una llamada al método de NSView dragImage:at:offset:event:pasteboard:source:sildeBack:

Cuando el drop se ha completado, a nuestra vista nos llega el mensaje draggedImage:endedAt:operation donde principalmente, dependiendo de la operación realizada debemos actualizar el contenido de nuestra vista.

En la parte receptora, la vista que va a recibir los datos, primeramente debe registrar los tipos que va a poder recibir. Para ello existe dentro de NSView la llamada registerForDraggedTypes: la cual recibe como parámetro un array de dichos tipos. Los tipos son muy diversos pero podemos destacar:

  • NSStringPboardType
  • NSURLPboardType
  • NSPDFPboardType
  • NSRTFPboardType

Como vemos, la parte en negrita nos especifica el tipo de datos.

Habitualmente lo mas apropiado es llamar a este metodo en el initWithFrame: de nuestra vista. Así por ejemplo, en nuestra aplicación de prueba el código sería:

[self registerForDraggedTypes:[NSArray arrayWithObject:NSStringPboardType]];

Una vez que hemos registrado los tipos de datos que aceptamos, el siguiente paso es implementar los métodos que controlan la operación:

  • draggingEntered:
  • draggingUpdated:
  • draggingExited:
  • prepareForDragOperation:
  • performDragOperation:
  • concludeDragOperation:

Como vemos, cada mensaje cubre una parte del ciclo del “drop”, los tres primeros controlan el hecho de que el contenido que viene desde la parte emisora entra en nuestra vista, y finalmente los últimos segmentan en tres partes la parte en la que realmente se completa el “drop”.

En los últimos métodos en ningún momento se nos pasa el dato que forma parte de la transacción, sino que nosotros mismos debemos ir al PBoard y leerlo de allí, normalmente esto se hace en el método performDragOperation:, por ejemplo el código para leer una cadena sería:

- (BOOL) performDragOperation:(id <NSDraggingInfo>) sender
{
    NSPasteboard *pb = [sender draggingPasteboard];
    if ( [[pb types] containsObject:NSStringPboardType] ) {
        NSString *value = [pb stringForType:NSStringPboardType];
        NSLog(@"Dropped value: %@", value);
        return YES;
    }
    return NO;
}

Para ilustrar la entrada y completar el código que falta por especificar, desde aquí podeis bajaros un ejemplo de uso de drag and drop.

Si se arrastra el cuadrado negro de una vista a otra, vemos en el log de ejecución como la palabra “Prueba” llega desde la vista origen a la vista destino.