DevSecOps

27 mai, 2009

Criando um Custom Panel – Minimizar – Maximizar – Fechar

Publicidade

Salve, pessoal.

Na maioria dos projetos em que participo ou participei, são comuns os clientes ou usuários finais quererem janelas que tenham os botões de maximizar, minimizar, fechar e até mesmo outros tipos de componentes no topo do Panel.

O que venho observando nestes projetos é que a maior partes dos clientes já possuem algum tipo de sistema computacional, assim os mesmos já possuem uma forma de usabilidade de trabalho em seu sistema. Devido a isso as empresas buscam sistemas RIAS com uma aparência de sistemas operacionais, criando a assim o termo: Desktop Virtual.

A grande vantagem de trabalhar desta maneira é a possibilidade de fazer várias tarefas sem ter de abrir e fechar telas, refazer pesquisas que foram perdidas ao fechar janela e outros tipos de situação.

Um outra característica do Desktop Virtuais é a facilidade e flexibilidade da utilização de módulos (Module), mas este é um assunto para outro artigo.

Aplicação real do Custom Panel

Recentemente participei de um projeto para o Estado do Rio de Janeiro. O sistema realiza a monitoração das frotas de ônibus, carros e caminhões. Para isso, eles queriam que houvesse uma janela em que o conteúdo fosse um mapa indicando a posição de sua frota em tempo real e uma outra janela em que eles pudessem ver os detalhes de cada veículo. Nesta circunstância, o Custom Panel caiu muito bem, pois eles podiam ficar com o mapa aberto ou minimizá-lo e ver os detalhes ou simplesmente colocar as janelas lado a lado.

Assim, hoje irei demonstrar como criar este componente de uma maneira simples e eficaz. Então vamos ao primeiro passo.

Este componente será similar ao FlexMdi, que hoje este faz parte do pacote flexlib.

Informações e requisitos para a realização do artigo

Nivel de dificuldade: 6
Tempo disponível: 1h
Sdk instalado
FlexBuilder ou outra IDE

Passo 1

Certo. Primeiramente iremos criar um projeto Flex com a seguinte estrutura de pastas.

Com a estrutura acima criada, vamos criar uma classe ‘as’ chamada BaseCustomPanel que irá estender um Panel. Esta classe será a base de todas as futuras telas e, por sua vez, não irá conter os botões de maximizar, minimizar e fechar. Porém, a mesma lhe dará a total flexibilidade de adicionar qualquer componente no header (Topo – Cabeçalho) do Panel. Abaixo segue o código AS.

package com.imaster.customPanel {

import flash.display.DisplayObject;

import mx.containers.Panel;
import mx.core.Application;
import mx.core.UIComponent;
import mx.effects.Blur;
import mx.events.FlexEvent;
import mx.managers.PopUpManager;

[Style(name="headerHorizontalGap", type="Number", inherit="no")]
/**
* @author Fabiel Prestes
*/
public class BaseCustomPanel extends Panel {

/**
* Aqui será armazenado todos os componente FILHOS que serão adicionados
* ao HEADER
*/
[ArrayElementType("mx.core.UIComponent")]
public var arrayFilhos:Array = [];

private var blurIn:Blur;

/**
* Construtor Padrao
*/
public function BaseCustomPanel() {
super();
}

/**
* @inheritDoc
*/
override protected function childrenCreated():void {
super.childrenCreated();

configStyle();

/* Para cada Filho adicionado no objeto 'arrayFilhos'
* será realizado algumas configurações como:
* Adiciona-lo no HEADER do PANEL
* Propriedade buttonMode = true ....*/
for each (var child:UIComponent in arrayFilhos)
addTitleBarComponent(child);
}

/**
* @private
*/
private function abreComEfeito():void{
blurIn = new Blur();
blurIn.blurXFrom = 10;
blurIn.blurXTo = 0;
blurIn.blurYFrom = 10;
blurIn.blurYTo = 0;
blurIn.duration = 600;
blurIn.play([this]);
}

/**
* Realizando a configuração padrão dos Estilos
*/
private function configStyle():void {
this.setStyle("headerHorizontalAlign", "rigth");
this.setStyle("headerVerticalAlign", "middle");
this.setStyle("headerHorizontalGap", 5);
}

/**
* @inheritDoc
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);

reposicioneElementosNoCabecalho();
}

/**
* Reposiciona todos os componentes FILHOS ao PAI 'HEADER'
*/
protected function reposicioneElementosNoCabecalho():void {
var componenteFilho:UIComponent;
var posicaoX:Number = 0;
var tamanhoPai:Number = 0;
var posicaoY:Number = 0;
var headerHorizontalGap:Number = getStyle('headerHorizontalGap');

/* Caso não tenha sido adicionado Nenhum filho, deve apenas retornar e continuar
* o processamento natural. */
if (arrayFilhos.length == 0)
return;

tamanhoPai = this.width - 10;

/* Para cada filho Definido para ser adicionado no cabeçalho deve ser
* verificado seu tamanho para assim ser posicionado no componente HEADER do PANEL. */
for (var i:int; i < arrayFilhos.length; i++) {
componenteFilho = UIComponent(arrayFilhos[i]);
componenteFilho.setActualSize(componenteFilho.getExplicitOrMeasuredWidth(), componenteFilho.getExplicitOrMeasuredHeight());

/* Abaixo é pego a altura do HEADER do PANEL subtraido com a altura do COMPONENTE filho
* e DIVIDIDO por 2, afim de pegar o valor que será definido no Y. Desta maneira
* o filho ficará no meio do HEADER. */
posicaoY = (getHeaderHeight() - componenteFilho.getExplicitOrMeasuredHeight()) / 2;

/* Abixo é pego o tomanho total do PANEL subtraido com o tamanho total do COMPONENTE
* filho SUBTRAIDO com o HORIZONTAL GAP, afim de pegar o valor que será definido no X.
* Desta maneira os componente FILHOS ficarão sempre a DIREITA do PANEL.
* A variavel TAMANHOPAI e tambem sempre recebe o resultado desta subtração para que assim
* os FILHOS não irão ficar sobre postos um ao outro.*/
posicaoX = tamanhoPai - componenteFilho.getExplicitOrMeasuredWidth() - headerHorizontalGap;
tamanhoPai = posicaoX;

/* Move o componente FILHO para o X e Y calculado anteriormente. */
componenteFilho.move(posicaoX, posicaoY);
}
}

/**
* Responsavel por Adicionar o Filho ao HEADER e realizar algumas pré configurações
*/
public function addTitleBarComponent(child:UIComponent):void {
child.buttonMode = true;

/* Este metodo é responsavel por adicionar o FILHO ao HEADER do PANEL. */
titleBar.addChild(child);

invalidateDisplayList();
}

public function openPanel():void{
PopUpManager.addPopUp(this, DisplayObject(Application.application));
PopUpManager.centerPopUp(this);
abreComEfeito();
}
}
}

Passo 2

OK, com a nossa classe base criada iremos agora realizar uma extensão da mesma, fazendo algumas alterações como inclusão dos botões de minimizar, maximizar e fechar.

Segue abaixo o código da classe WindowPanel.as

package com.imaster.customPanel {
import com.imaster.event.WindowPanelEvent;

import flash.events.MouseEvent;
import flash.filters.GlowFilter;

import mx.containers.Box;
import mx.controls.Image;
import mx.core.UIComponent;
import mx.effects.Blur;
import mx.effects.Move;
import mx.effects.Parallel;
import mx.effects.Resize;
import mx.effects.Sequence;
import mx.events.EffectEvent;
import mx.managers.PopUpManager;
import mx.utils.ObjectUtil;


[Event(name="minimixar", type="com.imaster.event.WindowPanelEvent")]
[Event(name="maximizar", type="com.imaster.event.WindowPanelEvent")]
[Event(name="fechar", type="com.imaster.event.WindowPanelEvent")]
/**
* @author Fabiel Prestes
*/
public class WindowPanel extends BaseCustomPanel {

private var imgFechar:Image;
private var imgMaximizar:Image;
private var imgMinimizar:Image;

private var glow:GlowFilter;

private var sequenceMinimizar:Sequence;

private var parallelMaximizar:Parallel;
private var moveMaximizar:Move;
private var resizeMaximizar:Resize;

private var parallelMinimizar:Parallel;
private var resizeMinimizar:Resize;
private var moveMinimizar:Move;

private var widthOriginal:Number;
private var heightOriginal:Number;

[Bindable]
private var xOriginal:Number;

[Bindable]
private var yOriginal:Number;

public var panelPai:UIComponent;
public var barra:Box;


/**
* Contrutor Padrão
*/
public function WindowPanel() {
super();
}

/**
* @inheritDoc
*/
override protected function childrenCreated():void{
this.setStyle("styleName", "windowpanel");

configBotoes();

super.childrenCreated();

widthOriginal = ObjectUtil.copy(this.width) as Number;
heightOriginal = ObjectUtil.copy(this.height) as Number;

configEfeitoBotoes();
}

/**
* @private
* Metodo responsavel por guarda a posição X e Y atual da tela para que
* assim ao maximizar a tela a mesma volte para a sua posição original.
*/
private function trateMouseUpMinimizar(evt:MouseEvent):void{
xOriginal = this.x;
yOriginal = this.y;
}

/**
* @private
* Responsavel por criar e configurar os botoes de Maximizar, Minimizar e Fechar.
*/
private function configBotoes():void{

imgMaximizar = new Image();
imgMaximizar.source = "assets/img/maximizar.png";
imgMaximizar.width = 20;
imgMaximizar.height = 20;

imgMinimizar = new Image();
imgMinimizar.source = "assets/img/minimizar.png";
imgMinimizar.width = 20;
imgMinimizar.height = 20;

imgFechar = new Image();
imgFechar.source = "assets/img/fechar.png";
imgFechar.width = 20;
imgFechar.height = 20;

imgMaximizar.addEventListener(MouseEvent.MOUSE_OVER, trateMouseOver);
imgMaximizar.addEventListener(MouseEvent.MOUSE_OUT, trateMouseOut);
imgMaximizar.addEventListener(MouseEvent.CLICK, trateMaximizarClick);

imgMinimizar.addEventListener(MouseEvent.MOUSE_OVER, trateMouseOver);
imgMinimizar.addEventListener(MouseEvent.MOUSE_OUT, trateMouseOut);
imgMinimizar.addEventListener(MouseEvent.CLICK, trateMinimizarClick);
imgMinimizar.addEventListener(MouseEvent.MOUSE_UP, trateMouseUpMinimizar);

imgFechar.addEventListener(MouseEvent.MOUSE_OVER, trateMouseOver);
imgFechar.addEventListener(MouseEvent.MOUSE_OUT, trateMouseOut);
imgFechar.addEventListener(MouseEvent.CLICK, trateFecharClick);

this.arrayFilhos.push(imgFechar);
this.arrayFilhos.push(imgMaximizar);
this.arrayFilhos.push(imgMinimizar);
}

/**
* @private
* Responsavel por instanciar e configurar os efeitos que serão realizados
* pelo click do mouse nas imagens no HEADER.
*/
private function configEfeitoBotoes():void{
/* Este efeito será utilizado quando o mouse estiver em cima do botoes/imagens
* no HEADER */
glow = new GlowFilter();
glow.color = 0xffffff;
glow.blurX = glow.blurY = 20;
}

/**
* @private
* Responsavel por instanciar e configurar os efeitos que serão realizados
* pelo click do mouse na imagem de Maximizar.
*/
private function configEfeitoMaximizar():void{

if(moveMaximizar == null){
moveMaximizar = new Move(this);
moveMaximizar.duration = 600;
}

moveMaximizar.xFrom = barra.x;
moveMaximizar.yFrom = barra.y;

moveMaximizar.xTo = xOriginal;
moveMaximizar.yTo = yOriginal;

if(resizeMaximizar == null){
resizeMaximizar = new Resize(this);

resizeMaximizar.heightTo = heightOriginal;
resizeMaximizar.widthTo = widthOriginal;
resizeMaximizar.duration = 600;
}

if(parallelMaximizar == null){
parallelMaximizar = new Parallel(this);

parallelMaximizar.addChild(moveMaximizar);
parallelMaximizar.addChild(resizeMaximizar);
}
}

/**
* @private
* Responsavel por instanciar e configurar os efeitos que serão realizados
* pelo click do mouse na imagem de Minimizar.
*/
private function configEfeitoMinimizar():void{
if(resizeMinimizar == null){
resizeMinimizar = new Resize(this);
resizeMinimizar.widthTo = 200;
resizeMinimizar.heightTo = 35;
resizeMinimizar.duration = 600;
}

if(moveMinimizar == null){
moveMinimizar = new Move(this);
moveMinimizar.duration = 600;
}

var filhosBarra:int = barra.getChildren().length;

if(filhosBarra >= 1){
moveMinimizar.xTo = (barra.getChildAt(0).width + 10) * filhosBarra;
}else{
moveMinimizar.xTo = barra.x;
}

moveMinimizar.yTo = barra.y;

if(parallelMinimizar == null){
parallelMinimizar = new Parallel();
parallelMinimizar.addChild(resizeMinimizar);
parallelMinimizar.addChild(new Blur());
parallelMinimizar.addChild(moveMinimizar);
}

if(sequenceMinimizar == null){
sequenceMinimizar = new Sequence(this);
sequenceMinimizar.addChild(parallelMinimizar);
sequenceMinimizar.addEventListener(EffectEvent.EFFECT_END, trateFimEfeitoMinimizar);
}
}

/**
* @private
* Trata o OVER do mouse nas imagens do HEADER
*/
private function trateMouseOver(evt:MouseEvent):void{
evt.currentTarget.filters =[glow];
}

/**
* @private
* Trata o OUT do mouse nas imagens do HEADER
*/
private function trateMouseOut(evt:MouseEvent):void{
evt.currentTarget.filters = [];
}

/**
* @private
* Trata o CLICK da imagem maximizar do HEADER
*/
private function trateMaximizarClick(evt:MouseEvent):void{
barra.removeChild(this);
PopUpManager.addPopUp(this, panelPai);

configEfeitoMaximizar();
parallelMaximizar.play();
this.dispatchEvent(new WindowPanelEvent(WindowPanelEvent.MAXIMIZAR));
}

/**
* @private
* Trata o CLICK da imagem mniimizar do HEADER
*/
private function trateMinimizarClick(evt:MouseEvent):void{
configEfeitoMinimizar();
sequenceMinimizar.play();
}

/**
* @private
* Trata o CLICK da imagem fechar do HEADER
*/
private function trateFecharClick(evt:MouseEvent):void{
var blur:Blur = new Blur(this);
blur.blurXFrom = 0;
blur.blurYFrom = 0;
blur.blurXTo = 10;
blur.blurYTo = 10;
blur.duration = 600;
blur.addEventListener(EffectEvent.EFFECT_END, trateFimEfeitoFechar);
blur.play();
}

/**
* @private
* Apos terminar o efeito de minimizar, é dispachado o evento do tipo MINIMIZAR
*/
private function trateFimEfeitoMinimizar(evt:EffectEvent):void{
barra.addChild(this);
this.dispatchEvent(new WindowPanelEvent(WindowPanelEvent.MINIMIZAR));
}

/**
* @private
* Apos terminar o efeito de mafechar, é dispachado o evento do tipo FEHCAR
* e logo em seguida a tela é removida.
*/
private function trateFimEfeitoFechar(evt:EffectEvent):void{
try{
PopUpManager.removePopUp(this);
barra.removeChild(this);
panelPai.removeChild(this);
}catch(er:Error){}
this.dispatchEvent(new WindowPanelEvent(WindowPanelEvent.FECHAR));
}
}
}

O grande detalhe desta classe é que esta armazena a referência de seu Parent (Pai) e da barra onde a mesma ficará minimizada. Nela foram incluídos efeitos diferentes em cada ação de botão, por exemplo.

Botão Minimizar: foi aplicado um efeito paralelo no qual a tela vai se movimentando (Move) e se redimensionando (Resize) até atingir a barra de minimização, com um pequeno Blur ao final.

Botão Maximizar: neste existe apenas o efeito Resize, deixando mais simples.

Botão Fechar: neste também foi aplicado apenas o efeito Blur simples mais bem atrativo aos olhos do usuário final, pois se assemelha bastante com o efeito que o Windows Vista oferece.

Também foi aplicado nos botões um efeito de Glow, no momento em que o usuário passa o mouse por cima dos botões, efeito bem similar ao Vista.
Todos os botões têm listener Mouse.Click e Mouse.Mouse_Over e cada um destes possui seus próprios comportamentos, porém a cada fim de comportamento os mesmos despacham seus próprios eventos, a fim de tornar mais flexível a sua utilização pelos outros desenvolvedores que utilizarão seu componente.

É um componente relativamente simples para a construção e agora vocês verão que também é de simples aplicação. Vamos às classes de teste.

Passo Final

WindowPanelTeste.mxml, uma simples classe que estende o WindowPanel e adiciona um VBox com um background Cinza.

<?xml version="1.0" encoding="utf-8"?>
<WindowPanel xmlns="com.imaster.customPanel.*" xmlns:mx="http://www.adobe.com/2006/mxml"
width="400" height="300" title="Window Teste">

<mx:VBox borderStyle="none" width="100%" height="100%" backgroundColor="gray"/>

</WindowPanel>

Main.mxml, uma simples classe de teste. A mesma já é instanciada com duas janela WindowTeste criada como default, também possui um botão para adicionar novas janelas testes.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
creationComplete="init()">
<mx:Style source="assets/css.css"/>

<mx:Script>
<![CDATA[
import com.imaster.WindowPanelTeste;
import mx.managers.PopUpManager;
import mx.controls.PopUpButton;
import com.imaster.customPanel.WindowPanel;

public var filhos:Number = 1;

private function init():void{
var tela:WindowPanelTeste = new WindowPanelTeste();
tela.panelPai = this;
tela.barra = hbBarra;
tela.title = "Tela 1";

PopUpManager.addPopUp(tela, this);
PopUpManager.centerPopUp(tela);

var tela2:WindowPanelTeste = new WindowPanelTeste();
tela2.panelPai = this;
tela2.barra = hbBarra;
tela2.title = "Tela 2";

PopUpManager.addPopUp(tela2, this);
PopUpManager.centerPopUp(tela2);
}

private function novaJanela():void{
var tela:WindowPanelTeste = new WindowPanelTeste();
tela.panelPai = this;
tela.barra = hbBarra;
tela.title = "Nova Janela";
tela.openPanel();
}
]]>
</mx:Script>
<mx:Button id="btNovo" label="Nova Janela" top="10" left="10" click="novaJanela();"/>

<mx:HBox id="hbBarra" left="10" bottom="10" height="50" right="10"/>
</mx:Application>

Aí está um componente simples e flexível. Muitos devem até estar se perguntando, “por que criar este componente se já existem outros?”. Certamente já existem componentes similares a este, contudo a questão aqui é demonstrar em maneira simples de como é possível criar seus próprios componentes e não ficar dependendo da comunidade ou dos criadores do mesmo para resolverem problemas com as lib. E, com certeza, a partir deste componente você poderá deixá-lo muito mais robusto e atraente, basta usar a criatividade.

Espero mais uma vez ter ajudado e até a próxima. Segue o link do source para download: http://www.fabielprestes.com.br/arquivos/customPanel/CustomPanel.rar

Exemplo Funcionando: