I have a radio button group within a data grid, whenever I attempt to change the selected radio button the web page crashes. Below is how I put together the radio button in the data grid. Anyone see what could be the cause of the issue?
MXML
<mx:Accordion>
<fx:Declarations>
<s:RadioButtonGroup id="cover"/>
</fx:Declarations>
<fx:Script>
<![CDATA[[
[Bindable] public var arrColl = new ArrayCollection();
arrColl.addItem(new ObjectProxy({name:"Jon Doe", position:1, cover:true, group:cover}));
arrColl.addItem(new ObjectProxy({name:"John Smith", position:2, cover:true, group:cover}));
]]>
</fx:Script>
<fx:Binding source="{dg.dataProvider as ArrayCollection}" destination="{arrColl}"/>
<mx:DataGrid id="dg" dataProvider="{arrColl}" ..>
...
</mx:DataGrid>
Renderer
<?xml version="1.0" encoding="utf-8"?>
<s:MXDataGridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
focusEnabled="true">
<fx:Script>
<![CDATA[
import mx.events.DynamicEvent;
public function selection(event:MouseEvent):void
{
var selectionEvt:DynamicEvent = new DynamicEvent("selectionEvt", true);
selectionEvt.data = this.data;
dispatchEvent(selectionEvt);
}
]]>
</fx:Script>
<s:RadioButton label="Cover" group="{data.group}" selected="{data.cover}" click="selection(event)"/>
</s:MXDataGridItemRenderer>
Radio Button Selection Event Handler
private function updateDp(event:DynamicEvent):void
{
if (event.type == "selectionEvt")
{
for (var i:int = 0; i < editGrid.dataProvider.length; i++)
{
editGrid.dataProvider.getItemAt(i).cover = false; // Unselect all members of radio button group
}
var index:int = editGrid.dataProvider.getItemIndex(event.data);
editGrid.dataProvider.getItemAt(index).cover = true; // Select target radio button
}
I got the code to work by using the radio button group's change event, rather than a custom/dynamic event within the radio button item renderer. However, I still don't quite understand (if/why) using the dynamic events caused the problem.
I had a similar issue once but can't seem to recall how I resolved as it was quite some time ago.
Related
I have prepared a simplified test case for my question. It will run instantly in your Flash Builder if you put the 2 files below into a project.
I'm trying to display a List of strings and a confirmation checkbox in a popup:
In the real application I dispatch a custom event with the string selected in the list, but in the test code below I just call trace(str);
My problem: if I use click event, then the window closes, even if I click at a scrollbar (the !str check below doesn't help, when an item had been selected in previous use). And if I use change event, then the window doesn't close, when I click on the same item as the last time. And the itemClick event seems not to be present in spark.components.List anymore.
Any suggestions please on how to handle this probably frequent problem?
Writing a custom item renderer and having a click event handler for each item seems to be overkill for this case, because I have strings in the list.
Test.mxml: (please click myBtn few times - to see my problems with click and change)
<?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"
minWidth="400" minHeight="300">
<fx:Script>
<![CDATA[
import mx.managers.PopUpManager;
private var _popup:Popup = new Popup();
private function showPopup(event:MouseEvent):void {
PopUpManager.addPopUp(_popup, this, true);
PopUpManager.centerPopUp(_popup);
}
]]>
</fx:Script>
<s:Button id="myBtn" right="5" bottom="5"
label="Open window" click="showPopup(event)" />
</s:Application>
Popup.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="240" height="240"
creationComplete="init(event)"
close="close()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayList;
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.events.CloseEvent;
import mx.events.ItemClickEvent;
import mx.managers.PopUpManager;
private var myData:ArrayList = new ArrayList();
private function init(event:FlexEvent):void {
// XXX in the real app data is updated from server
myData.removeAll();
for (var i:uint = 1; i <= 10; i++)
myData.addItem('Item #' + i);
}
public function close(event:TimerEvent=null):void {
PopUpManager.removePopUp(this);
}
private function handleClick(event:MouseEvent):void {
var str:String = myList.selectedItem as String;
if (!str)
return;
if (myBox.selected) {
Alert.show(
'Select ' + str + '?',
null,
mx.controls.Alert.YES | mx.controls.Alert.NO,
null,
handleConfirm,
null,
mx.controls.Alert.NO
);
} else {
sendEvent();
}
}
private function handleConfirm(event:CloseEvent):void {
if (event.detail == mx.controls.Alert.YES)
sendEvent();
}
private function sendEvent():void {
close();
// XXX in the real app dispatchEvent() is called
trace('selected: ' + (myList.selectedItem as String));
}
]]>
</fx:Script>
<s:VGroup paddingLeft="20" paddingTop="20"
paddingRight="20" paddingBottom="20" gap="20"
width="100%" height="100%">
<s:List id="myList" dataProvider="{myData}"
click="handleClick(event)"
width="100%" height="100%" fontSize="24" />
<s:CheckBox id="myBox" label="Confirm" />
</s:VGroup>
</s:TitleWindow>
Also I wonder, why do I get the warning above:
Data binding will not be able to detect assignments to "myData".
The Spark List dispatches an 'IndexChangeEvent.CHANGE'. You can listen for this event to know when the selection in the List has changed.
<s:List id="myList" dataProvider="{myData}"
change="handleIndexChange()"
width="100%" height="100%" fontSize="24" />
That event is only dispatched whenever the selected index actually changes, which means that when you reopen the window a second time an item might still be selected and when you click on that one, no CHANGE event will be fired. To fix this just deselect the selection before you close the window:
public function close():void {
myList.selectedIndex = -1;
PopUpManager.removePopUp(this);
}
Also make sure to dispatch your event with the selected item before you close the window (and deselect it).
As for your question about the binding warning: you get that message because you didn't mark 'myData' to be bindable. To fix this just use the [Bindable] tag:
[Bindable]
private var myData:ArrayList = new ArrayList();
or skip the binding altogether if you don't need it and just assign the dataprovider to the list in ActionScript:
myList.dataProvider = myData;
I'd recommend two solutions if you absolutely wnat to display what item was selected. Otherwise, the solution provided by RIAStar would do the trick.
Listen to rendererAdd and rendererRemove events within your PopUp
As explained here, you can easily access to your list's renderers without interfering with its virtualLayout business.
Use a custom renderer
I know. But as long as you keep your code clean, itemRenderers won't blow up your application's memory. They're made to render huge amount of items without memory leaks.
In your Test.mxml, modify the codes like:
<fx:Script>
<![CDATA[
import mx.managers.PopUpManager;
private var _popup:Popup;
private function showPopup(event:MouseEvent):void {
_popup = new Popup();
PopUpManager.addPopUp(_popup, this, true);
PopUpManager.centerPopUp(_popup);
}
]]>
</fx:Script>
And in your Popup.mxml, I am not sure why you have the TimerEvent in the close function.
Also the trace won't be shown, as you are calling the close() function immediately after the alert's YES button has been clicked..
My problem is i am trying to make a panel. My button in in Main.mxml whereas the panel functions are defined in panel_Create.mxml. the code works fine. In panel_Create their are functions to create panels at runtime. The problem i am facing is when i run the program it wont show the panels but it does increase the value of n and after 8 clicks it gives alert message. Please tell me why cant i see panels. The code works fine when i put all the code in Main.mxml
<fx:Script>
<![CDATA[
import Components.panel_Create;
import mx.controls.Alert;
import spark.components.Button
public var adminPanel:panel_Create = new panel_Create();
public var n:Number = 0;
public function panel(event:MouseEvent): void
{
if ( n < 8)
{
adminPanel.panel_Create(n);
n++;
}
else
Alert.show('More Panels Not Allowed', 'Alert Box', mx.controls.Alert.OK);
}
]]>
</fx:Script>
<s:Button id="add" includeIn="State1" x="398" y="10" label="Add Panel" click="panel(event)"/>
<Components2:panel_Create includeIn="State1" x="10" y="66" width="737" height="599">
</Components2:panel_Create>
</s:Application>
I believe that the 8 panels are created and based on the code in one of your comments they are added as child elements to the adminPanel.
The problem is that your adminPanel is never added to the stage so is not visible.
Try this instead:
<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">
<fx:Script>
<![CDATA[
private var panels:Array = [];
private function addPanel():void
{
if (panels.length < 8)
{
var panel:Panel = new Panel();
panel.title = "Panel "+(panels.length + 1);
panels.push(panel);
addElement(panel);
}else{
trace('More Panels Not Allowed');
}
}
]]>
</fx:Script>
<s:Button label="Add Panel" click="addPanel()"/>
</s:Application>
adminPanel is not being created. you have to do addElement(adminPanel) itself
if ( n < 8)
{
adminPanel.panel_Create(n);
addElement(adminPanel)
n++;
}
My data grid is displaying stale data, rather than the real time data available in it's data provider (array collection). I've tried refeshing the data in the collection, but that has no effect. Below is my code, does anyone see what could be the problem?
<mx:Accordion/>
<fx:Script>
<![CDATA[
private var _gridData:ArrayCollecton = new ArrayCollection;
[Bindable] public function get gridData():ArrayCollection { return _gridData; }
public function set gridData(value:ArrayCollection):void { _gridData = value; }
public function loadGridData():void {
// imgCollection is the data returned from the server
var tempCollection:ArrayCollection = new ArrayCollection();
for (var i:int = 0; i < imgCollection.length; i++)
{
var img:Object = new Object();
img.id = imgCollection.getItemAt(i).imgId;
img.url = "http://..." + imgCollection.getItemAt(i).imgId;
img.caption = (imgCollection.getItemAt(i).imgCaption == null) ? "": imgCollection.getItemAt(i).imgCaption;
img.group = images;
tempCollection.addItem(new ObjectProxy(img));
}
gridData = tempCollection;
<!-- Use http service to get data and save it in grid data array collection, this is run on accordion create completion and whenever data is added or removed from the array collection -->
}
]]>
</fx:Script>
<!-- NOTE: There is a cyclic binding between the data grid and the gridData array collection -->
<fx:Binding source="dg.dataProvider as ArrayCollection" destination="gridData"/>
...
...
<s:NavigatorContent>
<s:Panel>
<mx:DataGrid dataProvider="{gridData}" ...>
...
...
</mx:DataGrid>
</s:Panel>
</s:NavigatorContent>
UPDATE:
I tried the suggestions mentioned below, however, they do not resolve the issue. The data grid has custom item renderers, could that be the problem?
<?xml version="1.0" encoding="utf-8"?>
<s:MXDataGridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
focusEnabled="true">
<mx:Image id="image" source="{data.url}" height="65" maintainAspectRatio="true" scaleContent="true"/>
</s:MXDataGridItemRenderer>
You don't need "cyclical" binding, because in your datagrid you do not change the collection, but you change its items. The collection stays intact. the dataprovider of the DataGrid and your _gridData point to the same collection.
If I'm not mistaking you should have [Bindable] on the setter as well because the datagrid has not other way of knowing when your data has changed.
Regards, Alin
Looks to me like you are overthinking this. Since you aren't doing anything in your getter/setters you could get rid of them and just mark your ArrayCollection as Bindable, then set it as the dataProvider for the DataGrid and be done:
<fx:Script>
<![CDATA[
[Bindable]
public var gridData:ArrayCollecton = new ArrayCollection;
public function loadGridData():void {
// Whenever you change the gridData, the DataGrid will update appropriately.
}
]]>
</fx:Script>
<mx:DataGrid dataProvider="{gridData}"></mx:DataGrid>
The problem with your existing code is likely that you are not dispatching a change event in your setter. Getting rid of the getter/setters allows the ArrayCollection to handle dispatching that event for you. Hope that helps.
EDIT: Based on the updated question, you may want to try changing your renderer to look like this, which would help if your custom data object is not bindable.
<?xml version="1.0" encoding="utf-8"?>
<s:MXDataGridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
focusEnabled="true">
<fx:Script>
<![CDATA[
override public function set data(value:Object):void {
super.data = value;
image.source = value.url;
}
]]>
</fx:Script>
<mx:Image id="image" source="{data.url}" height="65" maintainAspectRatio="true" scaleContent="true"/>
I'm displaying a list of buttons, some of which might be disabled. I need to show a tooltip on the disabled buttons with an explanation of why it's disabled, but it seems I can't disable the button without disabling the tooltip. Is there a simple way around this?
Wrap the Button in a Group, and apply the toolTip to the group instead.
<s:Group toolTip="My toolTip">
<s:Button enabled="false"/>
</s:Group>
It's a bit ugly, but it works.
One way to do this is to override the enabled getter and setter to do what you want. So in my case, I still wanted most mouse events to fire, just not the click event.
<?xml version="1.0" encoding="utf-8"?>
<s:Button buttonMode="true" click="handleClick(event)" xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
public var data:Object;
private var _enabled:Boolean = true;
public override function get enabled():Boolean
{
return _enabled;
}
public override function set enabled(value:Boolean):void
{
_enabled = value;
invalidateDisplayList();
dispatchEvent(new Event("enabledChanged"));
invalidateSkinState();
}
protected function handleClick(event:MouseEvent):void
{
if (!_enabled)
{
event.stopPropagation();
}
}
]]>
</fx:Script>
</s:Button>
Since mouse events now fire, the tooltips work again.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.managers.ToolTipManager;
import mx.controls.ToolTip;
private var tooltip:ToolTip;
private var p:Point;
private function whyDisable():void
{
//calculate the button position , so that roll over shows the tooltip
p=new Point();
p=localToGlobal(new Point(btn.x,btn.y));
if(btn.enabled==false)
tooltip = ToolTipManager.createToolTip('Button is disabled',p.x+(btn.width/2),p.y-20,'errorTipAbove') as ToolTip;
else
tooltip=ToolTipManager.createToolTip('Button is enabled',p.x+(btn.width/2),p.y-20,'errorTipAbove') as ToolTip;
}
]]>
</mx:Script>
<mx:VBox height="100%" width="100%" horizontalAlign="center" verticalAlign="middle">
<mx:Button id="btn" label="Show Tooltip" buttonDown="trace('ankur')" autoRepeat="true" enabled="true" rollOver="whyDisable();" rollOut="{ToolTipManager.destroyToolTip(tooltip);}"/>
</mx:VBox>
</mx:Application>
Hi, this application works on the disabled button,I used ToolTipManager to do this,
i hope this works for you
have a gr8 time
Ankur Sharma
The best choice for me was to put a void label around and in front of the element. Then, if necessary, I set the element to disable and the tooltip works in the label. If not, I put sent the label to back. It works pretty well.
if (new MainListsAdmin(this.mainApp).temInvestimentoComAqueleTipo(t)) {
deletarGroupInto.setTooltip(new Tooltip("Há investimentos vinculados a Tipo de Investimento.\nDeleção bloqueada."));
this.deletarButton.setDisable(true);
}else{
deletarGroupInto.toBack();
}
You will need to use the ToolTipManager class to create and destroy the tool tips manually.
This article should give you all the info you need to accomplish this:
http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf60d65-7ff6.html
I wahnt to change button appearance when it was clicked.
<?xml version="1.0" encoding="utf-8"?>
<s:Button xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="init()">
<fx:Script>
<![CDATA[
public var _clicked:Boolean = false;
public function init():void{
addEventListener(MouseEvent.CLICK, changeButtonClickStatus);
}
public function changeButtonClickStatus(event:MouseEvent):void{
var that:TopMenuButton = event.currentTarget as TopMenuButton;
that._clicked = !(that._clicked);
if(that._clicked == true){
//change button appearance
}else{
//change button appearance
}
}
]]>
</fx:Script>
</s:Button>
Is there a method using states? I could then use the skin convention.
Thanks in advance for Your help.
If you are looking for a ToggleButton that you can skin the different states of then it already exists in Flex 4.
Check out the source code for ToggleButtonSkin.mxml to see how to skin the different states.
Have a look on the following example:
Applying styles to different states on a Spark Button control in Flex 4