By way of example here is a sample class I have created. The killMe method is an attempt to make the GC want to take out the trash.
package Classes.Input { import flash.events.DataEvent; import flash.events.Event; import flash.events.FocusEvent; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; import flash.utils.Dictionary; import mx.controls.ComboBox; import mx.events.ListEvent; /** * postValidate Code to execute after validateData method completes. * **/ [Event(name="postValidate", type="flash.events.DataEvent")] /** * Create an extended version of the ComboBox that allows the definition of invalid indexes and * multiple keystroke selection. * * <pre> * The developer defines one or more invalid indexes in a comma separated list using the property badIndexes. * Most commonly the developer would specify "0" which is the first element in the list. * * New properties are; * badIndexes - A string that allows the developer to define a comma separated list of invalid indexes. * isVald - A boolean that defines whether the field is valid. * * New Methods are; * validateData() - This method checks if the data is valid and returns true if valid, false if invalid. * * Enhanced Properties; * value - When the property value is set, the combobox will attempt to find the appropriate value in the * dataprovider, and set the selectedIndex to that item. It will set the value to -1 if not found. * * </pre> */ public class ValidatedComboBox extends ComboBox { /** Bad indexes - A comma separated list of invalid indexes. */ private var _badIndexes:String = ""; /** Bad Data - A comma separated list of invalid Data. */ private var _badData:String = ""; /** Has this field passed validation */ private var _isValid:Boolean = true; /** value */ private var _value:Object; /** should we validate data */ private var _doValidateData:Boolean = true; /** promptLabel */ private var _promptLabel:String; /** dataType */ private var _dataType:String = "string"; /** dataField */ private var _dataField:String = "DATA"; /** toolTipField */ private var _toolTipField:String = ""; /** Default value A literal that represents the value that will replace the "value" * property when the method setDefault is executed */ private var _defaultValue:String = ""; private var _eventHandler:Function = this["checkData"]; private var _tabOut:Function = null; private var _focusOutTab:Boolean = false; private var _typedText:String = ""; public function ValidatedComboBox() { //TODO: implement function super(); this.addEventListener(Event.CHANGE,_eventHandler,false,0,true) } override protected function createChildren():void { super.createChildren(); if(this.textInput) { // Use ValidatedTextInput instead of TextInput and Setup various fields textInput["id"] = "vti" + this.id; // textInput.parentDrawsFocus = true; } } /** * badIndexes- A comma seperated list of indexes that will be considered invalid. * * @return the specified bad indexes */ [Inspectable( type="String" , defaultValue="" )] public function get badIndexes():String { return this._badIndexes; } /** * Sets the the specified bad indexes */ public function set badIndexes( badIndexes:String ):void { this._badIndexes = badIndexes; if (_badIndexes.length < 1) { errorString = ""; _isValid = true; } } /** * badData- A comma seperated list of Data that will be considered invalid. * * @return the specified bad Data */ [Inspectable( type="String" , defaultValue="" )] public function get badData():String { return this._badData; } /** * Sets the the specified bad Data */ public function set badData( badData:String ):void { this._badData = badData; if (_badData.length < 1) { errorString = ""; _isValid = true; } } /** * promptLabel - Specify a human readable label for the field. * * @return the specified promptLabel */ [Inspectable( type="String" , defaultValue="" )] public function get promptLabel():String { return this._promptLabel; } /** * Sets the the specified promptLabel */ public function set promptLabel( promptLabel:String ):void { this._promptLabel = promptLabel; } /** * dataType - specify whether to use numeric or string matching when setting * the value of this field. * * <p> * Use this field when the drop down's data is a number. Particularly useful if * the current field in the database is string and can contain leading zeroes. * </p> * * @return the specified validate data flag. */ [Inspectable( type="String" , defaultValue=false, enumeration="string,number" )] public function get dataType():String { return this._dataType; } /** * Sets the specified validate data flag. */ public function set dataType( dataType:String ):void { this._dataType = dataType; } /** * doValidateData - specify the whether this field should be validated. Default is true. * * @return the specified validate data flag. */ [Inspectable( type="Boolean" , defaultValue=false, enumeration="true,false" )] public function get doValidateData():Boolean { return this._doValidateData; } /** * Sets the specified validate data flag. */ public function set doValidateData( doValidateData:Boolean ):void { this._doValidateData = doValidateData; } /** * toolTipField - Specify a field in the dataProvider whose value will be used as the * to populate the toolTip. * * @return the specified toolTipField */ [Inspectable( type="String" , defaultValue="" )] public function get toolTipField():String { return this._toolTipField; } /** * Sets the the specified toolTipField */ public function set toolTipField( toolTipField:String ):void { this._toolTipField = toolTipField; } /** * defaultValue - allows the specification of a default value for a field. * To set the current text/value properties use the the setDefault() method. * * @return the specified defaultValue value */ [Inspectable( type="String" , defaultValue="" )] public function get defaultValue():String { return this._defaultValue; } /** * Sets the specified Default value */ public function set defaultValue( defaultValue:String ):void { this._defaultValue = defaultValue; var event:Event; } /** * dataField - the field to be used as the data field from the dataProvider. * * The value of the field defined here is what the value is set to based * on the selection. * * @return the specified dataFiel */ [Inspectable( type="String" , defaultValue="DATA" )] public function get dataField():String { return this._dataField; } /** * Sets the the specified dataField */ public function set dataField( dataField:String ):void { this._dataField = dataField; } /** * tabOut - A function to execute if the tabkey is pressed.. * * @return the specified tabOut Function */ [Inspectable( type="Function" , defaultValue="" )] public function get tabOut():Function { return this._tabOut; } /** * Sets the the specified tabOut */ public function set tabOut( tabOut:Function ):void { this._tabOut = tabOut; } /** * focusOutTab - A boolean defining if the last focus out was a tab. * * @return focusOutTab */ public function get focusOutTab():Boolean { return this._focusOutTab; } /** * Sets the the specified focusOutTab */ public function set focusOutTab( focusOutTab:Boolean ):void { this._focusOutTab = focusOutTab; } /** * isValid - Returns a boolean that defines whether the current value is valid. */ public function get isValid():Boolean { return _isValid; } override public function get value():Object { return _value; } override public function set dataProvider(dpValue:Object):void { var objCurrentValue:Object = _value; super.dataProvider = dpValue; if (this.hasOwnProperty("dropdown")) if (this.dropdown != null)this.dropdown.dataProvider = dpValue; _value = objCurrentValue; if (dpValue != null) { if (dpValue.length > 0) { if (_value != null) { if(_value.toString().length > 0) setSelectedItem(_value); } else { if (this.selectedItem != null) _value = this.selectedItem[dataField]; } if (this.selectedIndex == -1) { this.selectedIndex = 0; _value = this.selectedItem[dataField]; if (this.selectedItem != null) if (this.selectedItem.hasOwnProperty(dataField)) { _value = this.selectedItem[dataField]; dispatchEvent( new ListEvent( ListEvent.CHANGE ) ); } } } } dpValue = null; objCurrentValue = null; } override public function set selectedIndex(intValue:int):void { // if (this.hasOwnProperty("id")) trace("id=" + this.id + " set intValue=" + intValue); super.selectedIndex = intValue; if (intValue > -1) { if (this.selectedItem != null) { if (this.selectedItem.hasOwnProperty(dataField)) { _value = this.selectedItem[dataField]; } } } } /** * Sets the the specified value based on the item selected. */ public function set value(value:Object): void { // if (this.hasOwnProperty("id")) trace("id=" + this.id + " set value=" + value); // trace("ValidatedComboBox-" + this.id + "Set value=" + value); this.selectedIndex = 0; if (value != null) { setSelectedItem(value); _value = value; } else { _value = ""; } if (this.selectedIndex > -1) { if (this.selectedItem != null) if (this.selectedItem.hasOwnProperty(dataField)) { _value = this.selectedItem[dataField]; } } } private function checkData(event:Event):void { this.removeEventListener(Event.CHANGE,_eventHandler) if (this.selectedItem) { this. _value = this.selectedItem[dataField]; validateData(); } this.addEventListener(Event.CHANGE,_eventHandler,false,0,true) } /** * setDefault - Method that will replace the value of the value property with the * value of the defaultValue property. */ public function setDefault():void { _value = _defaultValue; setSelectedItem(_value); } /** * getDefault - Method that will return the value of the defaultValue property. * * This method returns the defaultValue it exists to provide a consistenet api * between all "validated" components. */ public function getDefault():String { return _defaultValue; } /** * validateData - Method that will validate the data that has been entered. */ public function validateData():Boolean { if (_badIndexes.length == 0 && _badData.length == 0) { this.dispatchEvent(new DataEvent("postValidate")); return true; } var aryIndexesTemp:Array = _badIndexes.split(","); if (_doValidateData) { _isValid = ((aryIndexesTemp.indexOf(this.selectedIndex.toString()) == -1) && (this.selectedIndex > -1)); if (_isValid && _badData.length > 0) { var aryDataTemp:Array = _badData.split(","); if (this.selectedIndex > -1) _isValid = (aryDataTemp.indexOf(this.dataProvider[this.selectedIndex][dataField].toString()) == -1); } if (!_isValid) { this.errorString = '"' + this.selectedLabel + '" is an invalid choice. ' + this.selectedLabel.toString(); } else { this.errorString = ""; } } this.dispatchEvent(new DataEvent("postValidate")); return _isValid; } private function setSelectedItem(strFindItem:Object):void { var strDataItem:String = ""; if (this.dataProvider != null) { this.selectedIndex = -1; if (_dataType == "number") { strFindItem = String(parseInt(strFindItem.toString(),10)); } for (var i:int=0;i<this.dataProvider.length;i++) { if (this.dataProvider[i] != null) { if (_dataType == "number") { strDataItem = String(parseInt(this.dataProvider[i][dataField].toString(),10)); } else { strDataItem = this.dataProvider[i][dataField].toString(); } if (strDataItem == strFindItem && strDataItem.length == strFindItem.toString().length) { this.selectedIndex = i; if (_toolTipField.length > 0) { this.toolTip = this.dataProvider[i][_toolTipField] } // dispatchEvent( new ListEvent( ListEvent.CHANGE ) ); break; } } } } } override protected function focusOutHandler(event:FocusEvent):void { super.focusOutHandler(event); _typedText = ""; validateData() } override protected function focusInHandler(event:FocusEvent):void { this.removeEventListener(KeyboardEvent.KEY_DOWN,this.keyDownHandler); super.focusInHandler(event); _typedText = ""; _focusOutTab = false; this.addEventListener(KeyboardEvent.KEY_DOWN,this.keyDownHandler,false,0,true); } override protected function textInput_changeHandler(event:Event):void { _typedText += this.textInput.text; // trace('_typedText=' + _typedText); if (!findFirstItem(_typedText)) { _typedText = _typedText.substr(0,_typedText.length -1); findFirstItem(_typedText); } // this.dispatchEvent(new Event(Event.CHANGE,true)); } override protected function keyDownHandler(event:KeyboardEvent):void { // trace("event="+event.toString()); this.removeEventListener(KeyboardEvent.KEY_DOWN,this.keyDownHandler); event.preventDefault(); if (event.keyCode == Keyboard.TAB)_focusOutTab = true; if ((event.keyCode == Keyboard.TAB && _tabOut == null) || event.keyCode == Keyboard.ENTER) return; if (event.keyCode == Keyboard.TAB) { _tabOut(); } if(!event.ctrlKey) { if (event.keyCode == Keyboard.BACKSPACE || event.keyCode == Keyboard.DELETE) { _typedText = _typedText.substr(0,_typedText.length -1); findFirstItem(_typedText); } if (event.keyCode == Keyboard.DOWN || event.keyCode == Keyboard.RIGHT) { _typedText = ""; if (this.dropdown.selectedIndex < this.dataProvider.length - 1) { this.selectedIndex++; this.dropdown.selectedIndex++; this.dropdown.scrollToIndex(this.dropdown.selectedIndex); _value = this.selectedItem[dataField]; } } if (event.keyCode == Keyboard.UP || event.keyCode == Keyboard.LEFT) { _typedText = ""; if (this.dropdown.selectedIndex > 0) { this.selectedIndex--; this.dropdown.selectedIndex--; this.dropdown.scrollToIndex(this.dropdown.selectedIndex); _value = this.selectedItem[dataField]; } } if ((event.charCode > 31) && (event.charCode < 128)) { _typedText += String.fromCharCode(event.charCode); if (!findFirstItem(_typedText)) { _typedText = _typedText.substr(0,_typedText.length -1); } } } // trace("_typedText=" + _typedText); this.addEventListener(KeyboardEvent.KEY_DOWN,this.keyDownHandler,false,0,true); // super.keyDownHandler(event); } private function findFirstItem(strFindItem:String):Boolean { if (this.dataProvider != null) { if (strFindItem.length == 0) { this.selectedIndex = 0; this.dropdown.selectedIndex = 0; this.dropdown.scrollToIndex(0); _value = this.selectedItem[dataField]; dispatchEvent( new ListEvent( ListEvent.CHANGE ) ); return true; } this.dispatchEvent(new MouseEvent("mouseOut")); for (var i:int=0;i<this.dataProvider.length;i++) { if (this.dataProvider[i][this.labelField].toString().substr(0,strFindItem.length).toUpperCase() == strFindItem.toUpperCase()) { this.selectedIndex = i; this.dropdown.selectedIndex = i; this.dropdown.scrollToIndex(i); _value = this.selectedItem[dataField]; if (_toolTipField.length > 0) { this.toolTip = this.dataProvider[i][_toolTipField] this.dispatchEvent(new MouseEvent("mouseOver")); } dispatchEvent( new ListEvent( ListEvent.CHANGE ) ); return true; } } } return false; } public function killMe():void { trace('unloading='+this.id); this.dataProvider = null; this.dropdown.dataProvider = null; this.selectedIndex = -1; this.selectedItem = null; this.value = null; callLater(clearEvents); } protected var dctListeners:Dictionary = new Dictionary(); // added 05/22/2018 protected var bolInClearEvents:Boolean = false; override public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = true):void { if (bolInClearEvents == false) { var key:Object = {type: type, useCapture: useCapture}; if (dctListeners[key]) { removeEventListener(type, dctListeners[key], useCapture); dctListeners[key] = null; } dctListeners[key] = listener; super.addEventListener(type, listener, useCapture, priority, useWeakReference); } } public function clearEvents():void { bolInClearEvents = true; try { for (var key:Object in dctListeners) { removeEventListener(key.type, dctListeners[key], key.useCapture); dctListeners[key] = null; } } catch (e:Error) { trace('TestBig-clearEvents error='+e.message); } dctListeners = null; } } } -- Sent from: http://apache-flex-users.2333346.n4.nabble.com/