MX ComboBox Open Direction Bug - Alternative Solution

Written by

Here’s another ComboBox bug (plus potential feature) that irritated me no end with a fix being made difficult due to the relevent methods I needed access to in ComboBox.as declared as private (as so many are in mx components [bangs head]!!!).

Bug: ComboBox opens in a specific direction according to its current position on the stage, so the logic is if its too close to the bottom boundary and its content is of a particular height then it will open upwards. This does function but it is inconsistent and the result is a list being cropped with options therefore rendered inaccessible [bad]. Ideally we want to override this logic written in ComboBox.as but it’s private so you have absolutely no control over the direction the list opens.

Solution: Ditch the ComboBox and extend PopUpButton (as pointed out by the above link) applying some small modifications as I have done here http://gist.github.com/505255. What I have done completely removes the stage detection and enables you to declare whether the list opens upwards or downwards. It’s a simple solution so don’t expect bells and whistles and essentially fixes the problem I was having leaving you free to modify too your hearts content adding in any other functionality i.e. stage boundary detection etc.

package
{
import flash.display.DisplayObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
import mx.controls.PopUpButton;
import mx.core.UIComponent;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.effects.Tween;
import mx.events.InterManagerRequest;
import mx.managers.ISystemManager;
import mx.managers.PopUpManager;
use namespace mx_internal;
public class PopUpCombo extends PopUpButton
{
protected var _isOpen:Boolean = false;
protected var _inTween:Boolean = false;
protected var _tween:Tween;
private var _specificOpenDirection:String;
public static const OPEN_UP:String = "OPEN_UP";
public static const OPEN_DOWN:String = "OPEN_DOWN";
public function PopUpCombo()
{
super();
}
public function get specificOpenDirection():String
{
if(!_specificOpenDirection) _specificOpenDirection = PopUpCombo.OPEN_DOWN;
return _specificOpenDirection;
}
public function set specificOpenDirection(value:String):void
{
_specificOpenDirection = value;
}
override protected function clickHandler(event:MouseEvent):void
{
event.stopImmediatePropagation();
displayPopUp();
}
override protected function keyDownHandler(event:KeyboardEvent):void
{
super.keyDownHandler(event);
if (event.ctrlKey && event.keyCode == Keyboard.DOWN)
{
event.stopImmediatePropagation();
displayPopUp();
}
}
public function displayPopUp():void
{
var show:Boolean = !_isOpen;
var popUpGap:Number = getStyle("popUpGap");
var point:Point = new Point(0, unscaledHeight + popUpGap);
point = localToGlobal(point);
var initY:Number;
var endY:Number;
var easingFunction:Function;
var duration:Number;
if (popUp.parent == null)
{
PopUpManager.addPopUp(popUp, this, false);
popUp.owner = this;
}
else
PopUpManager.bringToFront(popUp);
if(show)
{
if(specificOpenDirection == PopUpCombo.OPEN_UP)
{
point.y -= (unscaledHeight + popUp.height + 2*popUpGap);
initY = -popUp.height;
}
else
{
initY = popUp.height;
}
point.x = Math.min( point.x, visibleScreen.right - popUp.getExplicitOrMeasuredWidth());
point.x = Math.max( point.x, 0);
point = popUp.parent.globalToLocal(point);
if (popUp.x != point.x || popUp.y != point.y)
popUp.move(point.x, point.y);
popUp.scrollRect = new Rectangle(0, initY,
popUp.width, popUp.height);
if (!popUp.visible)
popUp.visible = true;
endY = 0;
_isOpen = show;
duration = getStyle("openDuration");
easingFunction = getStyle("openEasingFunction") as Function;
}
else
{
_isOpen = show;
if (popUp.parent == null)
return;
point = popUp.parent.globalToLocal(point);
endY = (specificOpenDirection == PopUpCombo.OPEN_UP) ? -popUp.height - 2 : popUp.height + 2;
initY = 0;
duration = getStyle("closeDuration")
easingFunction = getStyle("closeEasingFunction") as Function;
}
_inTween = true;
UIComponentGlobals.layoutManager.validateNow();
// Block all layout, responses from web service, and other background
// processing until the tween finishes executing.
UIComponent.suspendBackgroundProcessing();
_tween = new Tween(this, initY, endY, duration);
if (easingFunction != null)
_tween.easingFunction = easingFunction;
}
protected function get visibleScreen():Rectangle
{
var sm:ISystemManager = systemManager.topLevelSystemManager;
var sbRoot:DisplayObject = sm.getSandboxRoot();
var _screen:Rectangle;
if (sm != sbRoot)
{
var request:InterManagerRequest = new InterManagerRequest(InterManagerRequest.SYSTEM_MANAGER_REQUEST,
false, false,
"getVisibleApplicationRect");
sbRoot.dispatchEvent(request);
_screen = Rectangle(request.value);
}
else
_screen = sm.getVisibleApplicationRect();
return _screen;
}
override mx_internal function onTweenUpdate(value:Number):void
{
popUp.scrollRect =
new Rectangle(0, value, popUp.width, popUp.height);
}
override mx_internal function onTweenEnd(value:Number):void
{
popUp.scrollRect =
new Rectangle(0, value, popUp.width, popUp.height);
_inTween = false;
UIComponent.resumeBackgroundProcessing();
if (!_isOpen)
{
popUp.visible = false;
popUp.scrollRect = null;
}
}
}
}
view raw PopUpCombo.as hosted with ❤ by GitHub
<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2008/02/11/determining-if-a-check-box-menu-item-is-toggled-in-a-flex-popupbutton-controls-pop-up-menu/ -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
verticalAlign="middle"
backgroundColor="white"
xmlns:local="*">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.controls.Menu;
import mx.events.DropdownEvent;
import mx.events.MenuEvent;
private var menu:Menu;
private function init():void {
menu = new Menu();
menu.variableRowHeight = true;
menu.dataProvider = arr;
menu.addEventListener(MenuEvent.CHANGE, menu_change);
popUpButton.popUp = menu;
}
private function menu_change(evt:MenuEvent):void {
popUpButton.displayPopUp();
}
]]>
</mx:Script>
<mx:Array id="arr">
<mx:Object label="Option 1"
type="label"/>
<mx:Object label="Option 2"
type="label"/>
</mx:Array>
<local:PopUpCombo id="popUpButton"
openAlways="true"
label="Click to open..."
specificOpenDirection="{PopUpCombo.OPEN_UP}"
initialize="init();" />
</mx:Application>

Comments