问题
I have prepared a very simple test case to demo my problem.
Please just place the 2 files below into a Flash Builder 4.6 project and they will run instantly.
My problem is that the menu attached to my (slightly modified) custom PopUpButton never changes - even though the underlying Array data provider is changed when you click one of the 3 buttons on the right side of it:
AuxButtonTest.mxml:
<?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/mx"
xmlns:comps="*"
creationComplete="init()">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.FlexEvent;
private const XML1:XML =
<pref>
<aux event="1">One</aux>
<aux event="2">Two</aux>
<aux event="3">Three</aux>
<aux event="4">Four</aux>
<aux event="5">Five</aux>
</pref>;
private const XML2:XML =
<pref>
<aux event="1">One</aux>
<aux event="2">Two</aux>
</pref>;
private const XML3:XML =
<pref>
<aux event="3">Three</aux>
</pref>;
public function init():void {
_auxBtn.update(XML1.aux);
}
//private function handleAuxChosen(event:PrefEvent):void {
//Alert.show(event.toString());
//}
]]>
</fx:Script>
<s:controlBarContent>
<!-- commented: aux_chosen="handleAuxChosen(event)" -->
<comps:AuxButton id="_auxBtn" />
<s:Button id="_btn1" label="XML 1" click="_auxBtn.update(XML1.aux);" />
<s:Button id="_btn2" label="XML 2" click="_auxBtn.update(XML2.aux);" />
<s:Button id="_btn3" label="XML 3" click="_auxBtn.update(XML3.aux);" />
</s:controlBarContent>
</s:Application>
AuxButton.mxml (my custom component based on PopUpButton):
<?xml version="1.0" encoding="utf-8"?>
<mx:PopUpButton
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
popUp="{_menu}"
creationComplete="init(event)">
<fx:Metadata>
<!-- [Event(name="aux_chosen", type="PrefEvent")] -->
</fx:Metadata>
<fx:Script>
<![CDATA[
import mx.controls.Menu;
import mx.events.MenuEvent;
import mx.events.FlexEvent;
private var _str:String;
[Bindable]
private var _data:Array = new Array();
[Bindable]
private var _menu:Menu = new Menu();
private function init(event:FlexEvent):void {
_menu.dataProvider = _data;
_menu.addEventListener('itemClick', handleMenu);
addEventListener('click', handleClick);
}
public function update(xlist:XMLList):void {
_data.length = 0;
for each (var xml:XML in xlist) {
_data.push({label: xml, event: xml.@event});
}
label = _data[0].label;
_str = _data[0].event;
enabled = true;
}
private function handleMenu(event:MenuEvent):void {
label = event.label;
_str = event.item.event;
}
private function handleClick(event:MouseEvent):void {
enabled = false;
//dispatchEvent(new PrefEvent(PrefEvent.AUX_CHOSEN, _str));
}
]]>
</fx:Script>
</mx:PopUpButton>
I've commented my custom event out - it doesn't matter here.
Please just click at the buttons few times and then look at the menu - it always has 5 items and that is wrong.
UPDATE: Thank you for the answers - the following code works now for me. I still wonder why isn't ArrayList supported, but ArrayCollection works fine.
AuxButtonText.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:PopUpButton
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
popUp="{_menu}"
creationComplete="init(event)">
<fx:Script>
<![CDATA[
import mx.controls.*;
import mx.events.*;
import mx.collections.*;
private var _str:String;
[Bindable]
private var _data:ArrayCollection = new ArrayCollection();
//private var _data:ArrayList = new ArrayList();
[Bindable]
private var _menu:Menu = new Menu();
private function init(event:FlexEvent):void {
_menu.dataProvider = _data;
_menu.addEventListener('itemClick', handleMenu);
addEventListener('click', handleClick);
}
public function update(xlist:XMLList):void {
_data.removeAll();
if (xlist == null || xlist.length() == 0) {
enabled = false;
return;
}
for each (var xml:XML in xlist)
_data.addItem({label: xml, event: xml.@event});
label = _data.getItemAt(0).label;
_str = _data.getItemAt(0).event;
enabled = true;
}
private function handleMenu(event:MenuEvent):void {
label = event.label;
_str = event.item.event;
}
private function handleClick(event:MouseEvent):void {
enabled = false;
//dispatchEvent(new PrefEvent(PrefEvent.AUX_CHOSEN, _str));
}
]]>
</fx:Script>
</mx:PopUpButton>
AuxButton.mxml:
<?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/mx"
xmlns:comps="*"
initialize="init()">
<fx:Script>
<![CDATA[
private const XML1:XML =
<pref>
<aux event="1">One</aux>
<aux event="2">Two</aux>
<aux event="3">Three</aux>
<aux event="4">Four</aux>
<aux event="5">Five</aux>
</pref>;
private const XML2:XML =
<pref>
<aux event="1">One</aux>
<aux event="2">Two</aux>
</pref>;
private const XML3:XML =
<pref>
<aux event="3">Three</aux>
</pref>;
private const XML4:XML =
<pref>
</pref>;
public function init():void {
_auxBtn.update(XML1.aux);
}
]]>
</fx:Script>
<s:controlBarContent>
<comps:AuxButton id="_auxBtn" />
<s:Button id="_btn1" label="XML 1" click="_auxBtn.update(XML1.aux);" />
<s:Button id="_btn2" label="XML 2" click="_auxBtn.update(XML2.aux);" />
<s:Button id="_btn3" label="XML 3" click="_auxBtn.update(XML3.aux);" />
<s:Button id="_btn4" label="LEN=0" click="_auxBtn.update(XML4.aux);" />
<s:Button id="_btn5" label="NULL" click="_auxBtn.update(null);" />
</s:controlBarContent>
</s:Application>
回答1:
Arrays do not dispatch any events when their content changes. As a rule of thumb: Using [Bindable]
on an Array and using it as some kind of dataProvider is usually a bad idea since adding/removing doesn't dispatch any events and therefore won't be handled by any component.
Instead of Array
use an ArrayCollection
which dispatches events of type CollectionEvent.COLLECTION_CHANGE
whenever its content changes. Most components in the Flex SDK handle those events and update itself accordingly. So, the following code works since assigning a new source
to the ArrayCollection
dispatches a CollectionEvent
which causes the Menu
to update its menu items.
// you don't need [Bindable] on _data
private var _data:ArrayCollection = new ArrayCollection();
public function update(xlist:XMLList):void
{
var items:Array = [];
for each (var xml:XML in xlist)
{
items.push({label: xml, event: xml.@event});
}
_data.source = items;
if (items.length > 0)
{
label = items[0].label;
_str = items[0].event;
}
enabled = true;
}
回答2:
My problem is that the menu attached to my (slightly modified) custom PopUpButton never changes - even though the underlying Array data provider is changed
This herein lies your problem. You have no code to change the menu's dataProvider or sync it with your private _data variable. The _data array does indeed change, but you have not coded any relation between _data and _menu.dataProvider.
The initial value is set in your init method:
private function init(event:FlexEvent):void {
_menu.dataProvider = _data;
_menu.addEventListener('itemClick', handleMenu);
addEventListener('click', handleClick);
}
But, it is never changed as data changes.
I'll do one little side note here to add that your init() method is called on creationComplete. Which means the component goes through it's full lifecycle once; then you change the dataProvider of the _menu, forcing it go through it's lifecycle again to redraw with the new dataProvider. This is usually undesirable. You should read up on the Flex Component Lifecycle to understand what events are fired and when. I would probably recommend setting the dataProvider on initialize, which is caleld after createChildren() is run, but before the children will be sized and measured.
Anyway, a quick fix is to just change the _menu.dataProvider on the fly in your update:
public function update(xlist:XMLList):void {
_data.length = 0;
for each (var xml:XML in xlist) {
_data.push({label: xml, event: xml.@event});
}
label = _data[0].label;
_str = _data[0].event;
enabled = true;
// new code
_menu.dataProvider = _data;
}
来源:https://stackoverflow.com/questions/8694276/menu-attached-to-mx-controls-popupbutton-never-changes