lunes, 27 de abril de 2009

Expandir, Contraer, Cerrar, cambiar tamaño y mover un Panel


Una de las funcionalidades mas buscadas en un panel son CERRAR, EXPANDIR, CONTRAER Y RESIZE. En este tutorial vamos a explicar su implementacion en un Panel clásico del framework de flex. Utilizando un una clase llamada SuperPanel hecha por Wietse Veenstra y publicada en su blog.

Para empezar debemos tener en nuestro proyecto la clase SuperPanel.as y cada vez que queramos utilizarla en vez de crear un panel de tipo mx.Containers.Panel debemos crear un panel tipo SuperPanel.

Es importante resaltar que la clase SuperPanel hereda directamente de la clase panel, por lo tanto es un panel en si mismo y Flex lo reconoce como un panel, con todos sus comportamientos naturales, mas lo que vamos a ver a continuacion.
Antes de empezar les coloco un demo de la aplicación y el link para que puedan descargar el codigo fuente que vamos a estar utilizando en este tutorial.


Lo pimero que debemos hacer es ir al metodo createChildren(), por ser este, el primer método que se invoca en el ciclo de vida del SuperPanel.

override protected function createChildren():void {
super.createChildren();
this.pTitleBar = super.titleBar;
this.setStyle("headerColors", [0xC3D1D9, 0xD2DCE2]);
this.setStyle("borderColor", 0xD2DCE2);
this.doubleClickEnabled = true;

if (enableResize) {
this.resizeHandler.width = 12;
this.resizeHandler.height = 12;
this.resizeHandler.styleName = "resizeHndlr";
this.rawChildren.addChild(resizeHandler);
this.initPos();
}

if (showControls) {
this.normalMaxButton.width = 10;
this.normalMaxButton.height = 10;
this.normalMaxButton.styleName = "increaseBtn";
this.closeButton.width = 10;
this.closeButton.height = 10;
this.closeButton.styleName = "closeBtn";
this.pTitleBar.addChild(this.normalMaxButton);
this.pTitleBar.addChild(this.closeButton);
}

this.positionChildren();
this.addListeners();
}

Lo primero que hacemos en este metodo es definir la apariencia del panel le colocamos un color al header y un color al borde, luego habilitamos que se pueda hacer dobleclick (hay que recordar que el evento doble click solo se dispara si la propiedad doubleClickEnabled es igual a true, por eso la estamos haciendo true) al hacer doble click sobre el titulo del panel, este ultimo se contraerá.

Luego pueden notar los dos condicionales, uno para saber si esta habilido el comportamiento resize y otro para verificar si esta habilitado el comportamiento de cerrar, espandir y contraer. En el caso de que enable resize este "verdadero" entonces se colocara el botón en la esquina inferior derecha del componente (en el metodo initPos se verifica cuales son las cordenadas de esa esquina). Si la propiedad showControls es verdadera entonces se colocan los botones cerrar y expandir sobre dentro de la barra de titulo del panel.

public function positionChildren():void {
if (showControls) {
this.normalMaxButton.buttonMode = true;
this.normalMaxButton.useHandCursor = true;
this.normalMaxButton.x = this.unscaledWidth - this.normalMaxButton.width - 24;
this.normalMaxButton.y = 8;
this.closeButton.buttonMode = true;
this.closeButton.useHandCursor = true;
this.closeButton.x = this.unscaledWidth - this.closeButton.width - 8;
this.closeButton.y = 8;
}

if (enableResize) {
this.resizeHandler.y = this.unscaledHeight - resizeHandler.height - 1;
this.resizeHandler.x = this.unscaledWidth - resizeHandler.width - 1;
}
}

public function addListeners():void {
this.addEventListener(MouseEvent.CLICK, panelClickHandler);
this.pTitleBar.addEventListener(MouseEvent.MOUSE_DOWN, titleBarDownHandler);
this.pTitleBar.addEventListener(MouseEvent.DOUBLE_CLICK, titleBarDoubleClickHandler);

if (showControls) {
this.closeButton.addEventListener(MouseEvent.CLICK, closeClickHandler);
this.normalMaxButton.addEventListener(MouseEvent.CLICK, normalMaxClickHandler);
}

if (enableResize) {
this.resizeHandler.addEventListener(MouseEvent.MOUSE_OVER, resizeOverHandler);
this.resizeHandler.addEventListener(MouseEvent.MOUSE_OUT, resizeOutHandler);
this.resizeHandler.addEventListener(MouseEvent.MOUSE_DOWN, resizeDownHandler);
}
}

El metodo "positionChildren" se encarga de ubicar a los botones de cerrar, expandir, contraer y cambiar tamaño dentro del panel.

El el segundo metodo addListeners() se encarga de iniciar los "Listeners" o "Escuchadores" que monitorearán cuando algun click, dobleclick o drag ocurra sobre los botones que recien acabamos de agregar en el metodo createChildren().

public function panelClickHandler(event:MouseEvent):void {
this.pTitleBar.removeEventListener(MouseEvent.MOUSE_MOVE, titleBarMoveHandler);
this.parent.setChildIndex(this, this.parent.numChildren - 1);
this.panelFocusCheckHandler();
}

public function titleBarDownHandler(event:MouseEvent):void {
this.pTitleBar.addEventListener(MouseEvent.MOUSE_MOVE, titleBarMoveHandler);
}

public function titleBarMoveHandler(event:MouseEvent):void {
if (this.width < screen.width) {
Application.application.parent.addEventListener(MouseEvent.MOUSE_UP, titleBarDragDropHandler);
this.pTitleBar.addEventListener(DragEvent.DRAG_DROP,titleBarDragDropHandler);
this.parent.setChildIndex(this, this.parent.numChildren - 1);
this.panelFocusCheckHandler();
this.alpha = 0.5;
this.startDrag(false, new Rectangle(0, 0, screen.width - this.width, screen.height - this.height));
}
}

public function titleBarDragDropHandler(event:MouseEvent):void {
this.pTitleBar.removeEventListener(MouseEvent.MOUSE_MOVE, titleBarMoveHandler);
this.alpha = 1.0;
this.stopDrag();
}

public function panelFocusCheckHandler():void {
for (var i:int = 0; i < this.parent.numChildren; i++) {
var child:UIComponent = UIComponent(this.parent.getChildAt(i));
if (this.parent.getChildIndex(child) < this.parent.numChildren - 1) {
child.setStyle("headerColors", [0xC3D1D9, 0xD2DCE2]);
child.setStyle("borderColor", 0xD2DCE2);
} else if (this.parent.getChildIndex(child) == this.parent.numChildren - 1) {
child.setStyle("headerColors", [0xC3D1D9, 0x5A788A]);
child.setStyle("borderColor", 0x5A788A);
}
}
}

Luego de iniciar todos los listeners y variables del panel tenemos los handlers (métodos) que se ejecutarán cuando ocurran los eventos que empezamos a escuchar. El primero de estos "panelClickHandler" se encarga de darle el foco al panel que el usuario le acaba de dar "click", lo trae al frente de los otros panels.

El segundo método "titleBarDownHandler" se ejecuta cuando el usuario hace un MOUSE_DOWN sobre el titlebar y se encarga de empezar a escuchar el MOUSE_MOVE para mover el panel junto con el mouse mientras se mantenga presionado el boton de este ultimo (una implementacion de drag and drop).

El tercer metodo "TitleBarMoveHandler" es el encargado de iniciar el drag para mover el panel, se ejecuta cuando el listener recien agregado en el "titleBarDownHandler" detecta un movimiento del mouse, este método tambien escucha un MOUSE_UP para terminar con el drag and drop y si usuario deja de presionar el botón del mouse.

El cuarto metódo "titleBarDragDropHandler" se encarga de terminer el drag and drop del panel, y se ejecuta cuando ocurre un MOUSE_UP. Por último el metodo "panelFocusCheckHandler" le camiba la apariencia al panel segun si tiene o no tiene el foco del mouse.

public function titleBarDoubleClickHandler(event:MouseEvent):void {
this.pTitleBar.removeEventListener(MouseEvent.MOUSE_MOVE, titleBarMoveHandler);
Application.application.parent.removeEventListener(MouseEvent.MOUSE_UP, resizeUpHandler);

this.upMotion.target = this;
this.upMotion.duration = 300;
this.upMotion.heightFrom = oH;
this.upMotion.heightTo = 28;
this.upMotion.end();

this.downMotion.target = this;
this.downMotion.duration = 300;
this.downMotion.heightFrom = 28;
this.downMotion.heightTo = oH;
this.downMotion.end();

if (this.width < height ="=" visible =" false;" visible =" true;">

En el código de arriba encontramos la implementación del efecto de minimizar cuando hacemos dobleclick sobre el título del panel. El método titleBarDoubleClickHandler se encarga de definir los valores iniciales para los efectos Resize que se definieron al inicio de la clase, estos efectos cambian la altura del panel hasta contraerlo completamente, quedando solo visible la barra del titulo, o visceversa.

public function normalMaxClickHandler(event:MouseEvent):void {
if (this.normalMaxButton.styleName == "increaseBtn") {
if (this.height > 28) {
this.initPos();
this.x = 0;
this.y = 0;
this.width = screen.width;
this.height = screen.height;
this.normalMaxButton.styleName = "decreaseBtn";
this.positionChildren();
}
} else {
this.x = this.oX;
this.y = this.oY;
this.width = this.oW;
this.height = this.oH;
this.normalMaxButton.styleName = "increaseBtn";
this.positionChildren();
}
}

public function closeClickHandler(event:MouseEvent):void {
this.removeEventListener(MouseEvent.CLICK, panelClickHandler);
this.parent.removeChild(this);
}

Estos dos metodos son los encarcados de las funciones de expandir, contraer y cerrar el panel, el primero de ellos se ejecuta cuando se hace click sobre el botón "normalMaxButton" y lo primero que hace es detectar si el panel se encuentra expandido o contraido y luego ejecuta el codigo para expandir o contraer el codigo segun el caso. El segundo metodo "closeClickHandler" se llama cuando el usuario hace click sobre el botón cerrar, este metodo elimina el listener sobre el evento click que habiamos definido durante la funcion create children y hace que el panel se auto elimine.

public function resizeOverHandler(event:MouseEvent):void {
this.resizeCur = CursorManager.setCursor(resizeCursor);
}

public function resizeOutHandler(event:MouseEvent):void {
CursorManager.removeCursor(CursorManager.currentCursorID);
}

public function resizeDownHandler(event:MouseEvent):void {
Application.application.parent.addEventListener(MouseEvent.MOUSE_MOVE, resizeMoveHandler);
Application.application.parent.addEventListener(MouseEvent.MOUSE_UP, resizeUpHandler);
this.resizeHandler.addEventListener(MouseEvent.MOUSE_OVER, resizeOverHandler);
this.panelClickHandler(event);
this.resizeCur = CursorManager.setCursor(resizeCursor);
this.oPoint.x = mouseX;
this.oPoint.y = mouseY;
this.oPoint = this.localToGlobal(oPoint);
}

public function resizeMoveHandler(event:MouseEvent):void {
this.stopDragging();

var xPlus:Number = Application.application.parent.mouseX - this.oPoint.x;
var yPlus:Number = Application.application.parent.mouseY - this.oPoint.y;

if (this.oW + xPlus > 140) {
this.width = this.oW + xPlus;
}

if (this.oH + yPlus > 80) {
this.height = this.oH + yPlus;
}
this.positionChildren();
}

public function resizeUpHandler(event:MouseEvent):void {
Application.application.parent.removeEventListener(MouseEvent.MOUSE_MOVE, resizeMoveHandler);
Application.application.parent.removeEventListener(MouseEvent.MOUSE_UP, resizeUpHandler);
CursorManager.removeCursor(CursorManager.currentCursorID);
this.resizeHandler.addEventListener(MouseEvent.MOUSE_OVER, resizeOverHandler);
this.initPos();
}

Estos últimos cuatro metodos se dedican a implementar la funcionalidad de resize para el panel: El primero de ellos se encarga de cambiar el icono del mouse cuando tengamos el cursor sobre la esquina inferior derecha (para indicarnos que podemos hacer un resize del panel). El segundo metodo es muy similiar pero lo que hace es devolver el icono del mouse a su estado natural (la flechita) cuando quitemos el curso de la esquina inferior derecha del panel.

El tercer metodo "resizeDownHandler" se ejecuta cuando presionamos el botón izquierdo del mouse sobre el boton "resizeHandler" que habiamos insertado en el createChildren(), este metodo se encarga de inicializar los valores X y Y de la esquina y de iniciar los listeners para el evento MOUSE_MOVE.

El metodo "resizeMoveHandler" se encarga de cambiar el tamaño del panel en funcion del movimiento del mouse. Solo se ejecuta mientras tengamos el boton derecho del mouse preisonado, ya que una de las primeras cosas que hace es agregar un listener para el evento MOUSE_UP para detectar cuando dejamos de apretar el botón.

El método "resizeUpHandler" el ultimo metodo de todos se encarga de eliminar el listener de MOUSE_MOVE para no continuar con el resize (cambio de tamaño) del panel.

¡Y eso es todo! Como pueden apreciar es una clase bastante sencilla y facil de usar, por eso me gusta tanto, aunque tiene algunos bugs que iran descubriendo y puliendo sobre la marcha. En los próximos tutoriales publicare modificaciones bastante interesantes que he realizado sobre este u otros panels. Espero que haya sido lo suficientemente claro, cualquier pregunta no duden en preguntarme.

3 comentarios:

  1. Todo bien
    pero como se agrega la libreria al proyecto
    sopy novato
    ademas son varios archivos k hay k hacer compilarla?

    ResponderEliminar
  2. Gracias, muy buen codigo :D, se agradece la publicación

    ResponderEliminar
  3. hola, k tal una consulta as probado hacer esto en flex 4?..prok trate de hacerlo pero no llega a correr bien

    ResponderEliminar