Flex 4.5 TextArea auto-scroll e desabilitar scrolls

O TextArea é um componente incrível, além de suportar textos simples (String) ele também suporta o Text Layout Framework, e com essa nova arquitetura Spark a coisa ficou tão fácil que até parece brincadeira de criança.

Recemente alguns e-mails chegaram em minha caixa de entrada com várias perguntas a respeito do auto-scroll do TextArea, uma coisa bastante comum no Flex 3 TextArea é que você tem que programaticamente dar o scroll para baixo via actionscript, já no Flex 4.5 você vai perceber que isso é automático e então você não precisa debulhar o componente e criar um novo apenas para isso.

Por um lado ele traz essa praticidade, por outro ele traz um desavento devido a nova API e métodos distintos, por exemplo, Se você quiser manter o scroll mesmo texto crescendo no topo, você tem que usar o método scrollToRange, que faz o metodo inverso do comportamento natural do Flex 4.5 TextArea.

Por exemplo scroll inverso:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
	<fx:Script>
		<![CDATA[
			import spark.events.TextOperationEvent;
 
			protected function meuTexto_changeHandler(event:TextOperationEvent):void
			{
				meuTexto.textDisplay.scrollToRange(0,0);
			}
 
		]]>
	</fx:Script>
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>
	<s:TextArea id="meuTexto" x="72" y="81" minWidth="361" minHeight="276" prompt="Cole algum texto aqui" change="meuTexto_changeHandler(event)"/>
</s:Application>

O scrollToRange é acessível apenas pela propriedade textDisplay que é detentora dos textos(simples, não confunda com textFlow) atribuídos ao TextArea. Com essa propriedade você consegue expor mais opções para seu componente como é o caso da propriedade pública maxChars diretamente acessível pelo TextArea.

Abra o código fonte e você vai ver que ele primeiro seta via textDisplay.

 public function set maxChars(value:int):void
    {
        if (textDisplay)
        {
            textDisplay.maxChars = value; // aqui você seta.
            textDisplayProperties = BitFlagUtil.update(
                uint(textDisplayProperties), MAX_CHARS_PROPERTY_FLAG, true);
        }
        else
        {
            textDisplayProperties.maxChars = value;
        }
 
        // Generate an UPDATE_COMPLETE event.
        invalidateProperties();
    }

E com a brincadeira de Skins, você pode fazer muito mais, por exemplo veja esse Skin que criei para meu TextArea.

<?xml version="1.0" encoding="utf-8"?>
 
<!--
 
    ADOBE SYSTEMS INCORPORATED
    Copyright 2008 Adobe Systems Incorporated
    All Rights Reserved.
 
    NOTICE: Adobe permits you to use, modify, and distribute this file
    in accordance with the terms of the license agreement accompanying it.
 
-->
 
<!--- The default skin class for Spark TextArea component.
 
     @see spark.components.TextArea
 
      @langversion 3.0
      @playerversion Flash 10
      @playerversion AIR 1.5
      @productversion Flex 4
-->
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
    alpha.disabledStates="0.5" blendMode="normal">
 
    <fx:Metadata>
    <![CDATA[
        /**
         * @copy spark.skins.spark.ApplicationSkin#hostComponent
         */
        [HostComponent("spark.components.TextArea")]
    ]]>
    </fx:Metadata>
 
    <fx:Script fb:purpose="styling">
        <![CDATA[
        import mx.core.FlexVersion;
 
        private var lineBreakChanged:Boolean;
        private var paddingChanged:Boolean;
        private var verticalAlignChanged:Boolean;
 
        /* Define the skin elements that should not be colorized.
           For text area, the skin itself is colorized but the individual parts are not as well
           as the border since it uses the borderColor style. */
        static private const exclusions:Array = ["background", "scroller", "promptDisplay", "border"];
 
        /* exclusions before Flex 4.5 for backwards-compatibility purposes */
        static private const exclusions_4_0:Array = ["background", "scroller", "promptDisplay"];
 
        /**
         * @private
         */
        override public function get colorizeExclusions():Array
        {
            // Since border is styleable via borderColor, no need to allow chromeColor to affect
            // the border.  This is wrapped in a compatibility flag since this change was added
            // in Flex 4.5
            if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_5)
            {
                return exclusions_4_0;
            }
 
            return exclusions;
        }
 
        /* Define the content fill items that should be colored by the "contentBackgroundColor" style. */
        static private const contentFill:Array = ["bgFill"];
 
        /**
         *  @private
         */
        override public function get contentItems():Array {return contentFill};
 
        /**
         *  @private
         */
        override protected function commitProperties():void
        {
            super.commitProperties();
 
            if (lineBreakChanged)
            {
                updateStringStyle("lineBreak");
                lineBreakChanged = false;
            }
            if (paddingChanged)
            {
                updatePadding();
                paddingChanged = false;
            }
            if (verticalAlignChanged)
            {
                updateStringStyle("verticalAlign");
                verticalAlignChanged = false;
            }
        }
 
        /**
         * @private
         */
        override protected function initializationComplete():void
        {
            useChromeColor = true;
            super.initializationComplete();
        }
 
        /**
         *  @private
         */
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            if (getStyle("borderVisible") == true)
            {
                border.visible = true;
                shadow.visible = true;
                background.left = background.top = background.right = background.bottom = 1;
                textDisplay.left = textDisplay.top = textDisplay.right = textDisplay.bottom = 1;
                if (promptDisplay)
                {
                    promptDisplay.setLayoutBoundsSize(unscaledWidth - 2, unscaledHeight - 2);
                    promptDisplay.setLayoutBoundsPosition(1, 1);
                }
            }
            else
            {
                border.visible = false;
                shadow.visible = false;
                background.left = background.top = background.right = background.bottom = 0;
                if (promptDisplay)
                {
                    promptDisplay.setLayoutBoundsSize(unscaledWidth - 1, unscaledHeight - 1);
                    promptDisplay.setLayoutBoundsPosition(1, 1);
                }
            }
 
            borderStroke.color = getStyle("borderColor");
            borderStroke.alpha = getStyle("borderAlpha");
 
            super.updateDisplayList(unscaledWidth, unscaledHeight);
        }
 
        /**
         *  @private
         */
        private function updatePadding():void
        {
            if (!textDisplay)
                return;
 
            // Push padding styles into the textDisplay
            var padding:Number;
 
            padding = getStyle("paddingLeft");
            if (textDisplay.getStyle("paddingLeft") != padding)
                textDisplay.setStyle("paddingLeft", padding);
 
            padding = getStyle("paddingTop");
            if (textDisplay.getStyle("paddingTop") != padding)
                textDisplay.setStyle("paddingTop", padding);
 
            padding = getStyle("paddingRight");
            if (textDisplay.getStyle("paddingRight") != padding)
                textDisplay.setStyle("paddingRight", padding);
 
            padding = getStyle("paddingBottom");
            if (textDisplay.getStyle("paddingBottom") != padding)
                textDisplay.setStyle("paddingBottom", padding);
 
            if (!promptDisplay)
                return;
 
            padding = getStyle("paddingLeft");
            if (promptDisplay.getStyle("paddingLeft") != padding)
                promptDisplay.setStyle("paddingLeft", padding);
 
            padding = getStyle("paddingTop");
            if (promptDisplay.getStyle("paddingTop") != padding)
                promptDisplay.setStyle("paddingTop", padding);
 
            padding = getStyle("paddingRight");
            if (promptDisplay.getStyle("paddingRight") != padding)
                promptDisplay.setStyle("paddingRight", padding);
 
            padding = getStyle("paddingBottom");
            if (promptDisplay.getStyle("paddingBottom") != padding)
                promptDisplay.setStyle("paddingBottom", padding);
        }
 
        /**
         *  @private
         */
        private function updateStringStyle(styleName:String):void
        {
            if (!textDisplay)
                return;
 
            // Push style into the textDisplay
            var style:String;
 
            style = getStyle(styleName);
            if (textDisplay.getStyle(styleName) != style)
                textDisplay.setStyle(styleName, style);
 
            if (!promptDisplay)
                return;
 
            // Push style into the promptDisplay
            style = getStyle(styleName);
            if (promptDisplay.getStyle(styleName) != style)
                promptDisplay.setStyle(styleName, style);
        }
 
        /**
         *  @private
         */
        override public function styleChanged(styleProp:String):void
        {
            var allStyles:Boolean = !styleProp || styleProp == "styleName";
 
            super.styleChanged(styleProp);
 
            if (allStyles || styleProp.indexOf("lineBreak") == 0)
            {
                lineBreakChanged = true;
                invalidateProperties();
            }
            if (allStyles || styleProp.indexOf("padding") == 0)
            {
                paddingChanged = true;
                invalidateProperties();
            }
            if (allStyles || styleProp.indexOf("verticalAlign") == 0)
            {
                verticalAlignChanged = true;
                invalidateProperties();
            }
        }
 
			protected function changeSelectHandler(event:Event):void
			{
				// TODO Auto-generated method stub
				if(allText.selected)
					textDisplay.selectAll();
				else
					textDisplay.selectRange(textDisplay.text.length,textDisplay.text.length);
 
			}
 
		]]>
    </fx:Script>
 
    <fx:Script>
        <![CDATA[
        /**
         * @private
         */
        private static const focusExclusions:Array = ["textDisplay"];
 
        /**
         *  @private
         */
        override public function get focusSkinExclusions():Array { return focusExclusions;};
        ]]>
    </fx:Script>
 
    <s:states>
        <s:State name="normal"/>
        <s:State name="disabled" stateGroups="disabledStates"/>
        <s:State name="normalWithPrompt"/>
        <s:State name="disabledWithPrompt" stateGroups="disabledStates"/>
    </s:states>
 
 
    <!-- border -->
    <!--- @private -->
    <s:Rect id="border" left="0" right="0" top="0" bottom="0">
        <s:stroke>
            <!--- @private -->
            <s:SolidColorStroke id="borderStroke" weight="1"/>
        </s:stroke>
    </s:Rect>
 
    <!-- fill -->
    <!--- Defines the appearance of the TextArea component's background. -->
    <s:Rect id="background" left="1" right="1" top="1" bottom="1">
        <s:fill>
            <!--- @private Defines the background fill color. -->
            <s:SolidColor id="bgFill" color="0xFFFFFF" />
        </s:fill>
    </s:Rect>
 
    <!-- shadow -->
    <!--- @private -->
    <s:Rect id="shadow" left="1" top="1" right="1" height="1">
        <s:fill>
            <s:SolidColor color="0x000000" alpha="0.12" />
        </s:fill>
    </s:Rect>
 
    <!--- Defines the scroller that is used to scroll the TextArea control. -->
    <s:Scroller id="scroller" left="0" top="20" right="0" bottom="0" minViewportInset="1" measuredSizeIncludesScrollBars="false" hasFocusableChildren="false">
        <!--- @copy spark.components.supportClasses.SkinnableTextBase#textDisplay -->
        <s:RichEditableText id="textDisplay" heightInLines="10" widthInChars="15" />
    </s:Scroller>
 
    <!--- Defines the Label that is used for prompt text. The includeInLayout property is false so the prompt text does not affect measurement. -->
    <s:Label id="promptDisplay"
                mouseEnabled="false" mouseChildren="false"
                includeIn="normalWithPrompt,disabledWithPrompt"
                includeInLayout="false"
                />
	<!-- grupo header -->
	<s:Rect left="0" right="0" top="0" height="20">
		<s:fill>
			<s:SolidColor color="#f4f4f4"/>
		</s:fill>
	</s:Rect>
	<s:Group left="0" right="0" top="0">
		<s:layout>
			<s:HorizontalLayout verticalAlign="middle"/>
		</s:layout>
		<s:Label text="Posição atual da seleção: {textDisplay.selectionActivePosition}"/>
		<s:CheckBox includeIn="normal" id="allText" label="Selecionar tudo" change="changeSelectHandler(event)"/>
	</s:Group>
</s:SparkSkin>

Basta aplicar ele em qualquer textArea que ele vai mostrar a posição atual do cursor na palavra e terá um checkBox perguntando se você quer selecionar todo o texto ou não.

Para aplicar é simples:

	<s:TextArea skinClass="MyTextAreaSkin" id="meuTexto" x="72" y="81" minWidth="361" minHeight="276"
				prompt="Cole algum texto aqui" change="meuTexto_changeHandler(event)"/>

Chamei meu Skin de MyTextAreaSkin, fácil não? Começe agora mesmo a dedilhar cada nova classe do SDK, você vai ficar maravilhado com as simplicidades que ele traz e a facilidade de expandir.

Deixe uma resposta

Conecte-se com:

O seu endereço de email não será publicado Campos obrigatórios são marcados *

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>