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: