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 {<br /><br /> import flash.display.DisplayObject;<br /><br /> import mx.containers.Panel;<br /> import mx.core.Application;<br /> import mx.core.UIComponent;<br /> import mx.effects.Blur;<br /> import mx.events.FlexEvent;<br /> import mx.managers.PopUpManager;<br /><br /> [Style(name="headerHorizontalGap", type="Number", inherit="no")]<br /> /**<br /> * @author Fabiel Prestes<br /> */<br /> public class BaseCustomPanel extends Panel {<br /><br /> /**<br /> * Aqui será armazenado todos os componente FILHOS que serão adicionados<br /> * ao HEADER<br /> */<br /> [ArrayElementType("mx.core.UIComponent")]<br /> public var arrayFilhos:Array = [];<br /><br /> private var blurIn:Blur;<br /><br /> /**<br /> * Construtor Padrao<br /> */<br /> public function BaseCustomPanel() {<br /> super();<br /> }<br /><br /> /**<br /> * @inheritDoc<br /> */<br /> override protected function childrenCreated():void {<br /> super.childrenCreated();<br /><br /> configStyle();<br /><br /> /* Para cada Filho adicionado no objeto 'arrayFilhos'<br /> * será realizado algumas configurações como:<br /> * Adiciona-lo no HEADER do PANEL<br /> * Propriedade buttonMode = true ....*/<br /> for each (var child:UIComponent in arrayFilhos)<br /> addTitleBarComponent(child);<br /> }<br /><br /> /**<br /> * @private<br /> */<br /> private function abreComEfeito():void{<br /> blurIn = new Blur();<br /> blurIn.blurXFrom = 10;<br /> blurIn.blurXTo = 0;<br /> blurIn.blurYFrom = 10;<br /> blurIn.blurYTo = 0;<br /> blurIn.duration = 600;<br /> blurIn.play([this]);<br /> }<br /><br /> /**<br /> * Realizando a configuração padrão dos Estilos<br /> */<br /> private function configStyle():void {<br /> this.setStyle("headerHorizontalAlign", "rigth");<br /> this.setStyle("headerVerticalAlign", "middle");<br /> this.setStyle("headerHorizontalGap", 5);<br /> }<br /><br /> /**<br /> * @inheritDoc<br /> */<br /> override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {<br /> super.updateDisplayList(unscaledWidth, unscaledHeight);<br /><br /> reposicioneElementosNoCabecalho();<br /> }<br /><br /> /**<br /> * Reposiciona todos os componentes FILHOS ao PAI 'HEADER'<br /> */<br /> protected function reposicioneElementosNoCabecalho():void {<br /> var componenteFilho:UIComponent;<br /> var posicaoX:Number = 0;<br /> var tamanhoPai:Number = 0;<br /> var posicaoY:Number = 0;<br /> var headerHorizontalGap:Number = getStyle('headerHorizontalGap');<br /><br /> /* Caso não tenha sido adicionado Nenhum filho, deve apenas retornar e continuar<br /> * o processamento natural. */<br /> if (arrayFilhos.length == 0)<br /> return;<br /><br /> tamanhoPai = this.width - 10;<br /><br /> /* Para cada filho Definido para ser adicionado no cabeçalho deve ser<br /> * verificado seu tamanho para assim ser posicionado no componente HEADER do PANEL. */<br /> for (var i:int; i < arrayFilhos.length; i++) {<br /> componenteFilho = UIComponent(arrayFilhos[i]);<br /> componenteFilho.setActualSize(componenteFilho.getExplicitOrMeasuredWidth(), componenteFilho.getExplicitOrMeasuredHeight());<br /><br /> /* Abaixo é pego a altura do HEADER do PANEL subtraido com a altura do COMPONENTE filho<br /> * e DIVIDIDO por 2, afim de pegar o valor que será definido no Y. Desta maneira<br /> * o filho ficará no meio do HEADER. */<br /> posicaoY = (getHeaderHeight() - componenteFilho.getExplicitOrMeasuredHeight()) / 2;<br /><br /> /* Abixo é pego o tomanho total do PANEL subtraido com o tamanho total do COMPONENTE<br /> * filho SUBTRAIDO com o HORIZONTAL GAP, afim de pegar o valor que será definido no X.<br /> * Desta maneira os componente FILHOS ficarão sempre a DIREITA do PANEL.<br /> * A variavel TAMANHOPAI e tambem sempre recebe o resultado desta subtração para que assim<br /> * os FILHOS não irão ficar sobre postos um ao outro.*/<br /> posicaoX = tamanhoPai - componenteFilho.getExplicitOrMeasuredWidth() - headerHorizontalGap;<br /> tamanhoPai = posicaoX;<br /><br /> /* Move o componente FILHO para o X e Y calculado anteriormente. */<br /> componenteFilho.move(posicaoX, posicaoY);<br /> }<br /> }<br /><br /> /**<br /> * Responsavel por Adicionar o Filho ao HEADER e realizar algumas pré configurações<br /> */<br /> public function addTitleBarComponent(child:UIComponent):void {<br /> child.buttonMode = true;<br /><br /> /* Este metodo é responsavel por adicionar o FILHO ao HEADER do PANEL. */<br /> titleBar.addChild(child);<br /><br /> invalidateDisplayList();<br /> }<br /><br /> public function openPanel():void{<br /> PopUpManager.addPopUp(this, DisplayObject(Application.application));<br /> PopUpManager.centerPopUp(this);<br /> abreComEfeito();<br /> }<br /> }<br />}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 {<br /> import com.imaster.event.WindowPanelEvent;<br /><br /> import flash.events.MouseEvent;<br /> import flash.filters.GlowFilter;<br /><br /> import mx.containers.Box;<br /> import mx.controls.Image;<br /> import mx.core.UIComponent;<br /> import mx.effects.Blur;<br /> import mx.effects.Move;<br /> import mx.effects.Parallel;<br /> import mx.effects.Resize;<br /> import mx.effects.Sequence;<br /> import mx.events.EffectEvent;<br /> import mx.managers.PopUpManager;<br /> import mx.utils.ObjectUtil;<br /><br /><br /> [Event(name="minimixar", type="com.imaster.event.WindowPanelEvent")]<br /> [Event(name="maximizar", type="com.imaster.event.WindowPanelEvent")]<br /> [Event(name="fechar", type="com.imaster.event.WindowPanelEvent")]<br /> /**<br /> * @author Fabiel Prestes<br /> */<br /> public class WindowPanel extends BaseCustomPanel {<br /><br /> private var imgFechar:Image;<br /> private var imgMaximizar:Image;<br /> private var imgMinimizar:Image;<br /><br /> private var glow:GlowFilter;<br /><br /> private var sequenceMinimizar:Sequence;<br /><br /> private var parallelMaximizar:Parallel;<br /> private var moveMaximizar:Move;<br /> private var resizeMaximizar:Resize;<br /><br /> private var parallelMinimizar:Parallel;<br /> private var resizeMinimizar:Resize;<br /> private var moveMinimizar:Move;<br /><br /> private var widthOriginal:Number;<br /> private var heightOriginal:Number;<br /><br /> [Bindable]<br /> private var xOriginal:Number;<br /><br /> [Bindable]<br /> private var yOriginal:Number;<br /><br /> public var panelPai:UIComponent;<br /> public var barra:Box;<br /><br /><br /> /**<br /> * Contrutor Padrão<br /> */<br /> public function WindowPanel() {<br /> super();<br /> }<br /><br /> /**<br /> * @inheritDoc<br /> */<br /> override protected function childrenCreated():void{<br /> this.setStyle("styleName", "windowpanel");<br /><br /> configBotoes();<br /><br /> super.childrenCreated();<br /><br /> widthOriginal = ObjectUtil.copy(this.width) as Number;<br /> heightOriginal = ObjectUtil.copy(this.height) as Number;<br /><br /> configEfeitoBotoes();<br /> }<br /><br /> /**<br /> * @private<br /> * Metodo responsavel por guarda a posição X e Y atual da tela para que<br /> * assim ao maximizar a tela a mesma volte para a sua posição original.<br /> */<br /> private function trateMouseUpMinimizar(evt:MouseEvent):void{<br /> xOriginal = this.x;<br /> yOriginal = this.y;<br /> }<br /><br /> /**<br /> * @private<br /> * Responsavel por criar e configurar os botoes de Maximizar, Minimizar e Fechar.<br /> */<br /> private function configBotoes():void{<br /><br /> imgMaximizar = new Image();<br /> imgMaximizar.source = "assets/img/maximizar.png";<br /> imgMaximizar.width = 20;<br /> imgMaximizar.height = 20;<br /><br /> imgMinimizar = new Image();<br /> imgMinimizar.source = "assets/img/minimizar.png";<br /> imgMinimizar.width = 20;<br /> imgMinimizar.height = 20;<br /><br /> imgFechar = new Image();<br /> imgFechar.source = "assets/img/fechar.png";<br /> imgFechar.width = 20;<br /> imgFechar.height = 20;<br /><br /> imgMaximizar.addEventListener(MouseEvent.MOUSE_OVER, trateMouseOver);<br /> imgMaximizar.addEventListener(MouseEvent.MOUSE_OUT, trateMouseOut);<br /> imgMaximizar.addEventListener(MouseEvent.CLICK, trateMaximizarClick);<br /><br /> imgMinimizar.addEventListener(MouseEvent.MOUSE_OVER, trateMouseOver);<br /> imgMinimizar.addEventListener(MouseEvent.MOUSE_OUT, trateMouseOut);<br /> imgMinimizar.addEventListener(MouseEvent.CLICK, trateMinimizarClick);<br /> imgMinimizar.addEventListener(MouseEvent.MOUSE_UP, trateMouseUpMinimizar);<br /><br /> imgFechar.addEventListener(MouseEvent.MOUSE_OVER, trateMouseOver);<br /> imgFechar.addEventListener(MouseEvent.MOUSE_OUT, trateMouseOut);<br /> imgFechar.addEventListener(MouseEvent.CLICK, trateFecharClick);<br /><br /> this.arrayFilhos.push(imgFechar);<br /> this.arrayFilhos.push(imgMaximizar);<br /> this.arrayFilhos.push(imgMinimizar);<br /> }<br /><br /> /**<br /> * @private<br /> * Responsavel por instanciar e configurar os efeitos que serão realizados<br /> * pelo click do mouse nas imagens no HEADER.<br /> */<br /> private function configEfeitoBotoes():void{<br /> /* Este efeito será utilizado quando o mouse estiver em cima do botoes/imagens<br /> * no HEADER */<br /> glow = new GlowFilter();<br /> glow.color = 0xffffff;<br /> glow.blurX = glow.blurY = 20;<br /> }<br /><br /> /**<br /> * @private<br /> * Responsavel por instanciar e configurar os efeitos que serão realizados<br /> * pelo click do mouse na imagem de Maximizar.<br /> */<br /> private function configEfeitoMaximizar():void{<br /><br /> if(moveMaximizar == null){<br /> moveMaximizar = new Move(this);<br /> moveMaximizar.duration = 600;<br /> }<br /><br /> moveMaximizar.xFrom = barra.x;<br /> moveMaximizar.yFrom = barra.y;<br /><br /> moveMaximizar.xTo = xOriginal;<br /> moveMaximizar.yTo = yOriginal;<br /><br /> if(resizeMaximizar == null){<br /> resizeMaximizar = new Resize(this);<br /><br /> resizeMaximizar.heightTo = heightOriginal;<br /> resizeMaximizar.widthTo = widthOriginal;<br /> resizeMaximizar.duration = 600;<br /> }<br /><br /> if(parallelMaximizar == null){<br /> parallelMaximizar = new Parallel(this);<br /><br /> parallelMaximizar.addChild(moveMaximizar);<br /> parallelMaximizar.addChild(resizeMaximizar);<br /> }<br /> }<br /><br /> /**<br /> * @private<br /> * Responsavel por instanciar e configurar os efeitos que serão realizados<br /> * pelo click do mouse na imagem de Minimizar.<br /> */<br /> private function configEfeitoMinimizar():void{<br /> if(resizeMinimizar == null){<br /> resizeMinimizar = new Resize(this);<br /> resizeMinimizar.widthTo = 200;<br /> resizeMinimizar.heightTo = 35;<br /> resizeMinimizar.duration = 600;<br /> }<br /><br /> if(moveMinimizar == null){<br /> moveMinimizar = new Move(this);<br /> moveMinimizar.duration = 600;<br /> }<br /><br /> var filhosBarra:int = barra.getChildren().length;<br /><br /> if(filhosBarra >= 1){<br /> moveMinimizar.xTo = (barra.getChildAt(0).width + 10) * filhosBarra;<br /> }else{<br /> moveMinimizar.xTo = barra.x;<br /> }<br /><br /> moveMinimizar.yTo = barra.y;<br /><br /> if(parallelMinimizar == null){<br /> parallelMinimizar = new Parallel();<br /> parallelMinimizar.addChild(resizeMinimizar);<br /> parallelMinimizar.addChild(new Blur());<br /> parallelMinimizar.addChild(moveMinimizar);<br /> }<br /><br /> if(sequenceMinimizar == null){<br /> sequenceMinimizar = new Sequence(this);<br /> sequenceMinimizar.addChild(parallelMinimizar);<br /> sequenceMinimizar.addEventListener(EffectEvent.EFFECT_END, trateFimEfeitoMinimizar);<br /> }<br /> }<br /><br /> /**<br /> * @private<br /> * Trata o OVER do mouse nas imagens do HEADER<br /> */<br /> private function trateMouseOver(evt:MouseEvent):void{<br /> evt.currentTarget.filters =[glow];<br /> }<br /><br /> /**<br /> * @private<br /> * Trata o OUT do mouse nas imagens do HEADER<br /> */<br /> private function trateMouseOut(evt:MouseEvent):void{<br /> evt.currentTarget.filters = [];<br /> }<br /><br /> /**<br /> * @private<br /> * Trata o CLICK da imagem maximizar do HEADER<br /> */<br /> private function trateMaximizarClick(evt:MouseEvent):void{<br /> barra.removeChild(this);<br /> PopUpManager.addPopUp(this, panelPai);<br /><br /> configEfeitoMaximizar();<br /> parallelMaximizar.play();<br /> this.dispatchEvent(new WindowPanelEvent(WindowPanelEvent.MAXIMIZAR));<br /> }<br /><br /> /**<br /> * @private<br /> * Trata o CLICK da imagem mniimizar do HEADER<br /> */<br /> private function trateMinimizarClick(evt:MouseEvent):void{<br /> configEfeitoMinimizar();<br /> sequenceMinimizar.play(); <br /> }<br /><br /> /**<br /> * @private<br /> * Trata o CLICK da imagem fechar do HEADER<br /> */<br /> private function trateFecharClick(evt:MouseEvent):void{<br /> var blur:Blur = new Blur(this);<br /> blur.blurXFrom = 0;<br /> blur.blurYFrom = 0;<br /> blur.blurXTo = 10;<br /> blur.blurYTo = 10;<br /> blur.duration = 600;<br /> blur.addEventListener(EffectEvent.EFFECT_END, trateFimEfeitoFechar);<br /> blur.play();<br /> }<br /><br /> /**<br /> * @private<br /> * Apos terminar o efeito de minimizar, é dispachado o evento do tipo MINIMIZAR<br /> */<br /> private function trateFimEfeitoMinimizar(evt:EffectEvent):void{<br /> barra.addChild(this);<br /> this.dispatchEvent(new WindowPanelEvent(WindowPanelEvent.MINIMIZAR));<br /> }<br /><br /> /**<br /> * @private<br /> * Apos terminar o efeito de mafechar, é dispachado o evento do tipo FEHCAR<br /> * e logo em seguida a tela é removida.<br /> */<br /> private function trateFimEfeitoFechar(evt:EffectEvent):void{<br /> try{<br /> PopUpManager.removePopUp(this);<br /> barra.removeChild(this);<br /> panelPai.removeChild(this);<br /> }catch(er:Error){}<br /> this.dispatchEvent(new WindowPanelEvent(WindowPanelEvent.FECHAR));<br /> }<br /> }<br />}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"?><br /><WindowPanel xmlns="com.imaster.customPanel.*" xmlns:mx="http://www.adobe.com/2006/mxml"<br /> width="400" height="300" title="Window Teste"><br /><br /> <mx:VBox borderStyle="none" width="100%" height="100%" backgroundColor="gray"/><br /><br /></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"?><br /><mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"<br /> creationComplete="init()"><br /> <mx:Style source="assets/css.css"/><br /><br /> <mx:Script><br /> <![CDATA[<br /> import com.imaster.WindowPanelTeste;<br /> import mx.managers.PopUpManager;<br /> import mx.controls.PopUpButton;<br /> import com.imaster.customPanel.WindowPanel;<br /><br /> public var filhos:Number = 1;<br /><br /> private function init():void{<br /> var tela:WindowPanelTeste = new WindowPanelTeste();<br /> tela.panelPai = this;<br /> tela.barra = hbBarra;<br /> tela.title = "Tela 1";<br /><br /> PopUpManager.addPopUp(tela, this);<br /> PopUpManager.centerPopUp(tela);<br /><br /> var tela2:WindowPanelTeste = new WindowPanelTeste();<br /> tela2.panelPai = this;<br /> tela2.barra = hbBarra;<br /> tela2.title = "Tela 2";<br /><br /> PopUpManager.addPopUp(tela2, this);<br /> PopUpManager.centerPopUp(tela2);<br /> }<br /><br /> private function novaJanela():void{<br /> var tela:WindowPanelTeste = new WindowPanelTeste();<br /> tela.panelPai = this;<br /> tela.barra = hbBarra;<br /> tela.title = "Nova Janela";<br /> tela.openPanel();<br /> }<br /> ]]><br /> </mx:Script><br /> <mx:Button id="btNovo" label="Nova Janela" top="10" left="10" click="novaJanela();"/><br /><br /> <mx:HBox id="hbBarra" left="10" bottom="10" height="50" right="10"/><br /></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:







