DevSecOpsARTIGO

Criando um Custom Panel – Minimizar – Maximizar – Fechar

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:


trabalha a desde 2005 em desenvolvimento e integração de sistemas WEB JEE. Começou a trabalhar com desenvolvimento de aplicações RIA Flex/Air em 2007. Atualmente é analista de sistemas pela JExperts, participa de fóruns e listas de discussões como Anddev, Adroid.org, GUJ, FlexDev, FloripaFlex e PortalAndroid.

Ver perfil