Archive for February, 2010

Applying selection dynamically to Spark RichText component

Flex 4 documentation article named "Selecting and modifying text" lists an finite final list of components that supports the selection:

  • RichEditableText
  • Label (Spark only)
  • TextInput (both MX and Spark)
  • TextArea (both MX and Spark)
  • RichTextEditor and all controls that have a TextArea as a subcomponent

   This list does not include RichText component, but luckily with a new Text Layout Framework (TLF) available starting from Flash player 10, it is quite easy to "simulate" dynamic selection of the text with some ActionScript 3 code.

   In the following example, try to drag the slider to the left and to the right to control the dynamic selection of the text of RichText component:

Get Adobe Flash player

Code listing:

<?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/halo"
               width="200"
               height="75">
    <s:layout>
        <s:HorizontalLayout paddingLeft="25"
                            paddingTop="25"
                            paddingRight="25"/>
    </s:layout>
    <fx:Script>
        <![CDATA[            
            import flashx.textLayout.edit.EditManager;
            import flashx.textLayout.edit.SelectionState;
            import flashx.textLayout.formats.TextLayoutFormat;            
 
            protected function highlightItem(endSelectionCharIndex : int):void
            {
                var containerFormat:TextLayoutFormat = new TextLayoutFormat();                
                var paragraphFormat:TextLayoutFormat = new TextLayoutFormat();                
                var characterFormat:TextLayoutFormat = new TextLayoutFormat();
 
                characterFormat = _selectedTextFormat;
 
                var selectionState : SelectionState = new SelectionState(rt.textFlow, 0, endSelectionCharIndex, _selectedTextFormat);                
 
                // apply format to the selection
                _textEditManager.applyFormat(
                    characterFormat, 
                    paragraphFormat, 
                    containerFormat, 
                    selectionState);                                
 
                characterFormat = _notSelectedTextFormat;                
 
                // apply format to the rest of the text
                var notSelectionState : SelectionState = new SelectionState(rt.textFlow, endSelectionCharIndex, rt.text.length, _notSelectedTextFormat);                
                _textEditManager.applyFormat(
                    characterFormat, paragraphFormat, containerFormat, notSelectionState);
            }
 
 
            protected function onRichTextCreationComplete():void
            {
                _textEditManager = new EditManager();
                rt.textFlow.interactionManager = _textEditManager;
 
                _selectedTextFormat = new TextLayoutFormat();
                _selectedTextFormat.backgroundColor = 0xFF99CC;
                _selectedTextFormat.color = 0x000000;
 
                _notSelectedTextFormat = new TextLayoutFormat();
                _notSelectedTextFormat.backgroundColor = 0xFFFFFF;
                _notSelectedTextFormat.color = 0x000000;
            }
 
            private var _textEditManager : EditManager;
 
            private var _selectedTextFormat : TextLayoutFormat;
 
            private var _notSelectedTextFormat : TextLayoutFormat;            
 
        ]]>
    </fx:Script>                   
    <s:RichText id="rt" text="Sample text" 
                creationComplete="onRichTextCreationComplete()"/>
    <s:HSlider id="slider" 
              minimum="0" 
              maximum="10" 
              change="highlightItem(slider.value)">        
    </s:HSlider>        
</s:Application>

When overriding “commitProperties” put “super.commitProperties” call to the end of the method

My use-case
   Recently I've tried to introduce skin states to the pretty complex UI component in Adobe Flex 4. I have got puzzled by the behaviour that my invalidateSkinState() calls never had triggered execution of getCurrentSkinState method that controlls the current UI component skin state value as expected.

Invalidation of the same phase while processing that phase is ignored - by Alex Harui

   Under closer examination I found out that I fall into the same invalidation trap that James Polanco did in November 2010.

   I must stress out that it was easy to fall into this trap in my case because inside "commitProperties" call I was doing pretty complex calculations that involved, for example, the creation of display object sub-children, listening for "preinitialize" events from those sub-children, reacting to it, etc.

    To cut the talk short, to prevent this property invalidation trap from happening I suggest to always follow the simple rule of the thumb:

When overriding "commitProperties" put "super.commitProperties" call to the end of the method

override protected function commitProperties():void
{
    if (myPropertyChanged)
    {        
        disableMyUIComponent = true;
        invalidateSkinState();
        myPropertyChanged = false;
    }
    super.commitProperties();
}
 
override protected function getCurrentSkinState():String { 
    var returnState:String = "normal"; 
 
    // Use information in the class to determine the new view state of the skin class. 
    if (disableMyUIComponent)     { 
        returnState = "disabled"; 
    } 
    return returnState; 
}

Futher reading:
Discussion at 2008 at Flexcoders mailing list (yeah, at that time, this mailing list was more interesting to read) "Must call super.commitProperties at END of overrided commitProperties when extending XXXX?"