Random Etc. Notes to self. Work, play, and the rest.

Fix for buggy Flash TextField selection (on mouse leave)

Today's post is about tiny details.

I've noticed a frustrating problem with text in Flash that I've finally managed to find a workaround for. I'm testing in Mac OS X using Firefox 3 and Flash 9, but I've seen it on other systems too, and recently had help reproducing this bug from our client at MSNBC, so I'm pretty sure it's widespread.

Since this is a fairly esoteric issue that will only bother Flash programmers, I'll continue only in the full version of this post.

Here's the setup to reproduce the bug:

  1. add an editable text field (flash.display.TextField or fl.controls.TextInput) to the left side of the stage
  2. select the text in one sweep, releasing the mouse outside the browser window
  3. start typing, or move the mouse back over the stage

If you start typing in this scenario, then the letters are erased as they are typed, because the text is still being selected as if you hadn't released the mouse. If you move your mouse back over the stage, then the selecting action is still in progress and doesn't stop until the mouse is clicked. Two-for-one buggy behaviour, how frustrating!

Note that the continued selection behaviour is a common problem in Flash that affects any site that uses SIFR to enhance headlines, for example.

Here's a swf that demonstrates the problem ("offscreen" includes outside of the flash):

And the code for the above swf.

 
package {
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.text.TextField;
  import flash.text.TextFieldType;
 
  [SWF(backgroundColor="#eeeeee")]
  public class TextInputSelectionBad extends Sprite
  {
    public var textField:TextField;
 
    public function TextInputSelectionBad()
    {
      stage.align = StageAlign.TOP_LEFT;
      stage.scaleMode = StageScaleMode.NO_SCALE;
 
      textField = new TextField();
      textField.type = TextFieldType.INPUT;
      textField.text = "select me and release the mouse off screen";
      textField.x = textField.y = 10;
      textField.width = textField.textWidth + 8;
      textField.height = textField.textHeight + 4;
      addChild(textField);
 
      textField.textColor = 0x202020;
      textField.border = true;
      textField.borderColor = 0xdddddd;
      textField.background = true;
      textField.backgroundColor = 0xffffee;
    }
 
  }
}
 

Once more I'm assuming you're familiar with Flex Builder's actionscript projects, if not then similar code should work in the Flash editor, but you're on your own with that I'm afraid.

OK OK, what's the fix?

The solution I came up with is to add a listener to the stage for mouse leave, and to temporarily disable selection. Text entry still works because the field maintains focus, but the text is not deleted on input. Additionally, the continued selection behaviour disappears. Clicking anywhere on the stage immediately restores the selectable flag on the field and everything should work as normal.

Here's an example of a swf with the fix implemented:

Flash focus behaviour works differently in Mac and Windows, on Mac I think you have to click twice but this is normal once the mouse has been released outside an application – I haven't managed to get the caret to go to the clicked point in the field, caretIndex is read-only sadly.

The code for the fixed swf is:

 
 
package {
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.text.TextField;
  import flash.text.TextFieldType;
 
  [SWF(backgroundColor="#eeeeee")]
  public class TextInputSelection extends Sprite
  {
    public var textField:TextField;
 
    public function TextInputSelection()
    {
      stage.align = StageAlign.TOP_LEFT;
      stage.scaleMode = StageScaleMode.NO_SCALE;
 
      textField = new TextField();
      textField.type = TextFieldType.INPUT;
      textField.text = "select me and release the mouse off screen";
      textField.x = textField.y = 10;
      textField.width = textField.textWidth + 8;
      textField.height = textField.textHeight + 4;
      addChild(textField);
 
      textField.textColor = 0x202020;
      textField.border = true;
      textField.borderColor = 0xdddddd;
      textField.background = true;
      textField.backgroundColor = 0xffffee;
 
      stage.addEventListener(Event.MOUSE_LEAVE, onStageMouseLeave);
    }
 
    private function onStageMouseLeave(event:Event):void
    {
      textField.selectable = false;
      stage.addEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown, true);
    }
 
    private function onStageMouseDown(event:Event):void
    {
      stage.removeEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown, true);
      textField.selectable = true;
      textField.setSelection(0,0);
    }
 
  }
}
 

I promise that my next post will have prettier pictures.


3 Comments

I was wrestling with the same problem. I ended up doing the same thing you describe, but with a further refinement that actually solves your last issue — getting the selection to start from the character under the mouse. (Well… almost solves it, but there are still some nuances that don’t exactly work as expected, but they’re too much trouble to describe here.)

First, rather than listening to the stage’s mouse down, you should listen to the textField’s mouse down. This way, when the mouse is pressed down on the textfield to begin the highlighting you can check for the index of the character under the mouse, using the textField’s getCharIndexAtPoint() method. Finally, you then set the selection to be that index.
So the code looks like this:

// Listen to the textField’s mouse down events, rather than the stage’s
textField.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);

private function mouseDownHandler(event:MouseEvent):void
{
if(textField.selectable == false)
{
var index:Number;
if(textField.mouseX > textField.textWidth)
index = textField.text.length;
else
index = textField.getCharIndexAtPoint(textField.mouseX, textField.mouseY);

textField.setSelection(index, index);
textField.selectable = true;
}
}

Posted by Amit on 8 January 2009 @ 6pm

jan 2010 and the bug is still there. Thanks for this workaround!

Posted by Mattijs on 18 January 2010 @ 3am

Thanks, bug still here, well another workaround is: textField.autoSize = TextFieldAutoSize.NONE;

And if not work, you can set a fixed height, try this below the code above:
textField.height -= 20; // This value is based on the font, so is relative, you can change.

Posted by Louis on 18 July 2012 @ 3pm