Wednesday, February 24, 2010

Gotcha Of The Day: ActionScript ComboBoxes Swallow Keyboard Events

I've have a gnawing sensation that I've already blogged about this -- but for the life of me, I can't find the original post. So here goes what may be attempt #2.

I've been enhancing a flash app for a client of mine that responds to keyboard shortcuts. Recently I added a ComboBox to the UI, and all of a sudden they keyboard shortcuts stopped working.

Of course, they didn't really stop working. What was happening, after carefully examining the problem, was that once the ComboBox was used it held onto the keyboard focus. The ComboBox was now getting the keyboard shortcuts, and the Stage wasn't hearing any events to which it could respond.

A Partial Solution

I needed a way to have the ComboBox give up its focus. I looked around for the equivalent of the HTML blur method, but couldn't find one. After some research I leanred that instead of a method call to control focus, you set the value of the focus field on the Stage object. I found that setting the value of focus to null did the trick nicely.

Here's the code in action:

 cb:ComboBox = new ComboBox();
 // call addItem(...) and other methods as appropriate
 cb.addEventListener(Event.CHANGE,
                     function(e:Event):void {
                        // Logic to perform when the combo box is changed
                        // goes here
                        
                        // The fix: give up focus
                        cb.stage.focus = null;
                     });

Simple enough, right? Turns out there's a bug in the above code.

What happens if the user interacts with the ComboBox but doesn't change it? The ComboBox gets focus, and the Event.CHANGE handler is never run.

When this happened, I reviewed the events on the ComboBox and realized that I should put the cb.stage.focus = null, not in Event.CHANGE but in Event.CLOSE. That way, every time the ComboBox is closed (done being interacted with), focus would be given up.

Alas, this turned out to be too easy a solution. When I put the code in the Event.CLOSE handler, I got the error:

ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller.
 at flash.display::DisplayObjectContainer/removeChild()
 at fl.controls::ComboBox/close()
 at fl.controls::ComboBox/onToggleListVisibility()

I believe I could fix this by setting focus to a valid DisplayObject rather than null. But, I didn't have a particular DisplayObject I wanted to get focus.

A Better Partial Solution

As most-of-the-way-there solution to the above issue, I added cb.stage.focus = null to the MouseEvent.CLICK handler. Between the CHANGE handler, and this one, focus manages to get unset in all the cases I needed it to be. Though, I'd really love to find a cleaner solution -- most likely one that uses the CLOSE event.

Still, a partial solution in this case did the job, so I'm a Happy Flasher.

2 comments:

  1. Hey, Ben. Be sure to file a bug. :)

    http://adobe.com/go/wish

    Thanks,
    Christian

    ReplyDelete
  2. Oooh, that's a good idea.

    Thanks!

    -Ben

    ReplyDelete