I need to be able to show a toolTip on disabled checkboxes. A solution I've seen here on stackoverflow and other places is to just wrap the checkbox in a Group and give the Group the toollTip. This works, but I'm trying to do this generically.
I want to be able to set a property on a custom Checkbox component and at that point wrap the Chexbox in a Group that has the toolTip set.
My problem is, I can't figure out how to add the Checkbox to a Group component at run time in the Checkbox ActionScript code. I've tried adding a showDisabledToolTip property to the Checkbox Class and when that is set do something like this:
var parent = this.parent;
var gp:Group = new Group();
gp.toolTip = this.toolTip;
gp.addElement(this);
if(parent is Group) {
parent.addElement(gp);
} else {
parent.addChild(gp);
}
My main problem at that point is this.parent is null. Besides that though, I don't even know if this will really work.
Help is appreciated. Thanks!
i came up with the solution to extend the CheckBox class and create a new CheckBoxSkin with 2 new SkinStates inside (disabledWithTooltip and disabledWithTooltipSelected)
The extended Checkbox class adds a new disabledWithTooltip property and overrides the getCurrentSkinState method and the mouseEventHandler from ButtonBase
Custom CheckBox class
package components
{
import flash.events.Event;
import flash.events.MouseEvent;
import mx.events.FlexEvent;
import spark.components.CheckBox;
[SkinState (disabledWithToolTip)]
[SkinState (disabledWithToolTipSelected)]
public class CustomCheckBox extends CheckBox
{
private var _disabledKeepToolTip:Boolean = false;
public function CustomCheckBox()
{
super();
this.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete, false, 0, true);
}
protected function onCreationComplete(ev:FlexEvent):void {
//_storedState = this.currentState;
}
protected override function getCurrentSkinState():String {
if(!_disabledKeepToolTip)
return super.getCurrentSkinState();
else {
if(!selected)
return "disabledWithToolTip";
else
return "disabledWithToolTipSelected";
}
}
protected override function mouseEventHandler(event:Event):void {
var skinState:String = getCurrentSkinState();
if(skinState != "disabledWithToolTip" && skinState != "disabledWithToolTipSelected") {
super.mouseEventHandler(event);
}
}
[Bindable]
[Inspectable(category="General", enumeration="true,false", defaultValue="true")]
public function get disabledKeepToolTip():Boolean {
return _disabledKeepToolTip;
}
public function set disabledKeepToolTip(value:Boolean):void {
_disabledKeepToolTip = value;
this.invalidateSkinState();
}
}
}
Create a new Skin based on (spark) CheckBoxSkin and change the hostcomponent in the metadata
[HostComponent("components.CustomCheckBox")]
and add two new skinStates
<s:State name="disabledWithToolTip" stateGroups="disabledStates" />
<s:State name="disabledWithToolTipSelected" stateGroups="disabledStates, selectedStates" />
Usage e.g.
<s:HGroup>
<components:CustomCheckBox id="custom_chk" label="KeepTooltipCheckbox" skinClass="skins.CustomCheckBoxSkin" toolTip="See this tooltip"/>
<s:CheckBox id="enable_chk" label="enable/disable" change="{custom_chk.disabledKeepToolTip = enable_chk.selected}"/>
</s:HGroup>
You have to adapt your own package structure if it's different...
Related
I have a class in Flex that crates a custom IconItemRenderer by extending the IconItemRenderer base class. I'm using this custom item within a list and listen to mouse press. Depending on the location of the mouse press, I have different options. One of which is to navigate to a different view. I know how to use the change listener of the list to push to a new view but don't want to implement it. The idea for the mouse click is that depending on the location, I can remove elements from the list or open up the current element.
For the life of me I cannot find a method to navigate to a new view from within the IconItemRenderer. This is the code I'm using, both the class and the list where I implement it.
package components
{
import spark.components.Button;
import spark.components.IconItemRenderer;
import spark.utils.MultiDPIBitmapSource;
public class DeleteItemRenderer extends IconItemRenderer
{
private var btn:Button;
private var xIcon:MultiDPIBitmapSource;
public function DeleteItemRenderer()
{
super();
super.iconWidth = super.iconHeight = 40;
super.labelField = 'title';
super.decorator = "assets/delete.png";
}
override public function set data(value:Object):void{
super.data = value;
}
override protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void{
setElementPosition(decoratorDisplay, unscaledWidth-40, 5);
setElementSize(decoratorDisplay, 40, 40);
}
override protected function measure():void{
measuredHeight = 50;
}
override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void{
graphics.beginFill(0xffffff);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.endFill();
decoratorDisplay.smooth = true;
graphics.lineStyle(1,0xcccccc,1);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.endFill();
}
}
}
List
<s:List id="survey_list" visible="true" width="98%" height="70%" contentBackgroundColor="#FFFFFF" horizontalCenter="0">
<s:itemRenderer>
<fx:Component>
<components:DeleteItemRenderer width="99.9%" height="98%" verticalAlign="top" click="detectActionPress();">
<fx:Script>
<![CDATA[
import spark.components.ViewNavigator;
import spark.components.View;
import mx.events.FlexEvent;
import mx.core.FlexGlobals;
import mx.core.UIComponent;
import spark.components.List;
private var application:UIComponent = FlexGlobals.topLevelApplication as UIComponent;
private var pressOpen:Number = application.width - 40;
//private var _navigator:ViewNavigator = FlexGlobals.topLevelApplication.navigation; //navigation is not defined uhhhh why????
private function detectActionPress():void{
var localX:Number = this.mouseX;
if(localX <= pressOpen){
engangeElement();
}
else{
deleteElement();
}
}
private function deleteElement():void{
var parentList:List = owner as List;
parentList.dataProvider.removeItemAt(parentList.dataProvider.getItemIndex(data));
trace('element removed');
}
private function engangeElement():void{
var parentList:List = owner as List;
var _test:ViewNavigator = this.parentDocument as ViewNavigator;
//this.parentApplication.navigator.pushView(views.UnfinishedSurvey, parentList.selectedItem.shortcode)
_test.pushView(views.UnfinishedSurvey, parentList.selectedItem.shortcode);
}
]]>
</fx:Script>
</components:DeleteItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:List>
Any ideas how I could push a new view from engageElement();
Why don't I have access to the navigator?
Thanks
Generally; I would recommend dispatching an event from inside the itemRenderer. Be sure that the event bubbles.
You can listen to the event on a class which is a hierarchical parent of the itemRenderer / List.
In the event handler you can access the navigator.
I wrote about the approaches here; which may provide more details.
There are alternate ways to do this. You could use a class that has an instance to the navigator and inject that into your renderer using some type of dependency injenction (DI) framework. Robotlegs and Swiz are two ActionScript based frameworks that support this.
I'm pretty new to Flex development in Spark and wanted to clarify the best way to build components.
I had previously tried to use a binding expression to set the selected index of a ViewStack:
public class MyComponentView extends SkinnableComponent
{
public var selectedIndex:int = 0;
}
<s:Skin ...>
<mx:ViewStack selectedIndex="{hostComponent.selectedIndex}">
....
</mx:ViewStack>
</s:Skin ...>
However, this binding expression doesn't appear to work, although it does show the correct number if I move that binding expression to a s:Label.
In order to get this to work I changed the code thus:
public class MyComponentView extends SkinnableComponent
{
[SkinPart(required = "true")]
public var myStack:ViewStack;
private var _selectedIndex:int = 0;
private var _indexChanged:Boolean;
public function set selectedHistoryIndex(value:int):void
{
_selectedIndex = value;
_indexChanged = true;
invalidateProperties();
}
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
switch (instance)
{
case myStack:
_indexChanged = true;
invalidateProperties();
break;
}
}
override protected function commitProperties():void
{
super.commitProperties();
if (_indexChanged && myStack)
{
_indexChanged = false;
myStack.selectedIndex = _selectedIndex;
}
}
}
<s:Skin ...>
<mx:ViewStack id="myStack">
....
</mx:ViewStack>
</s:Skin ...>
Is this the way I'm meant to do it?
As for me, your second way is more preferable. I'd rather change some code to make it better:
public function set selectedHistoryIndex(value:int):void
{
if (_selectedIndex == value)
return;
_selectedIndex = value;
_indexChanged = true;
invalidateProperties();
}
Yes, you can bind to component's properties from skin but this way View (skins in Spark architecture is for View in MVC and host component is for M and C) has knowledge about M which isn't good. The first implementation requires this knowledge from skin.
The second implementation makes View true View (managed by M). And it is good.
i want a dropdownlist that is wide enough to accommodate the larger item in the dropdown in display area of the main control.(.i.e. dropdownlist with the functionality of MX Combobox)
please guide me on this .
This will adjust the width of the drop down to the width of the data.
I forget where the example is where i got the code from. The height can be set by rowCount I believe.
package valueObjects.comboBox{
import flash.events.Event;
import mx.controls.ComboBox;
import mx.core.ClassFactory;
import mx.core.IFactory;
import mx.events.FlexEvent;
public class ExtendedComboBox extends ComboBox{
private var _ddFactory:IFactory = new ClassFactory(ExtendedList);
public function ExtendedComboBox(){
super();
}
override public function get dropdownFactory():IFactory{
return _ddFactory;
}
override public function set dropdownFactory(factory:IFactory):void{
_ddFactory = factory;
}
public function adjustDropDownWidth(event:Event=null):void{
this.removeEventListener(FlexEvent.VALUE_COMMIT,adjustDropDownWidth);
if (this.dropdown == null){
callLater(adjustDropDownWidth);
}else{
var ddWidth:int = this.dropdown.measureWidthOfItems(-1,this.dataProvider.length);
if (this.dropdown.maxVerticalScrollPosition > 0){
ddWidth += ExtendedList(dropdown).getScrollbarWidth();
}
this.dropdownWidth = Math.max(ddWidth,this.width);
}
}
override protected function collectionChangeHandler(event:Event):void{
super.collectionChangeHandler(event);
this.addEventListener(FlexEvent.VALUE_COMMIT,adjustDropDownWidth);
}
}
}
package valueObjects.comboBox{
import mx.controls.List;
public class ExtendedList extends List{
public function ExtendedList(){
super();
}
public function getScrollbarWidth():int{
var scrollbarWidth:int = 0;
if (this.verticalScrollBar != null){
scrollbarWidth = this.verticalScrollBar.width;
}
return scrollbarWidth;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" mlns:comboBox="valueObjects.comboBox.*" >
<comboBox:ExtendedComboBox labelField="data.totext()" itemRenderer="mx.controls.Label" />
</mx:Canvas>
You should create custom DropDownList (extending original) and override dataProvider setter and measure() method. In dataProvider setter you should invoke invalidateSize() and in measure you should iterate your data provider and find the largest label (you can assign new text to the label and compare their width).
To increase the height of a combo box's drop-down list, use "rowCount" property. This is for Flex 3.6.0
Check out this example from Flex Examples. You just have to create a skin for the DropDownList and set the popUpWidthMatchesAnchorWidth="false" property.
it is achieved by following code :
public override function set dataProvider(value:IList):void
{
var length:Number = 0;
var array:Array = value.toArray();
super.dataProvider = value;
var labelName:String = this.labelField;
var typicalObject:Object =new Object();
for each(var obj:Object in array)
{
var temp:Number = obj[labelName] .length;
if( length < temp )
{
length = temp;
Alert.show(" length "+length.toString());
typicalObject = obj;
}
//length = length < Number(obj[labelName].length) ? Number(obj[labelName].length) : length
//Alert.show(obj[labelName].length);
this.typicalItem = typicalObject;
}
}
Sorry, better take off both the width and height from the field.
I was wondering if anyone had any luck with the following senario in flex.
I'd like to be able to have a custom item renderer which delegates to another renderer inside.
The reason for this would be in a datagrid for instance displaying a checkbox if the dataprovider for the row had a boolean value. Using the default item renderer when the value was a non boolean.
Basically I was hoping to use a proxy object (though not necessarily the proxy class) so that I could a renderer which delegated all of its responsibilties to a sub renderer.
Hard to explain.
Edit 1
I think the following gives a clearer idea of what I had in mind. This is only knocked up quickly for the purpose of showing the idea.
SwitchingRenderer.as
package com.example
{
import mx.controls.CheckBox;
import mx.controls.dataGridClasses.DataGridItemRenderer;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.core.IDataRenderer;
import mx.core.UIComponent;
public class SwitchingRenderer extends UIComponent implements IDataRenderer, IDropInListItemRenderer
{
private var checkboxRenderer:CheckBox;
private var defaultRenderer:DataGridItemRenderer;
private var currentRenderer:IDataRenderer;
public function SwitchingRenderer()
{
this.checkboxRenderer = new CheckBox();
this.defaultRenderer = new DataGridItemRenderer();
this.currentRenderer = defaultRenderer();
super();
}
public function get data():Object
{
//If the data for this cell is a boolean
// currentRender = checkBoxRenderer
// otherwise
// currentRenderer = defaultRenderer
}
public function set data(value:Object):void
{
currentRenderer.data = value;
}
public function get listData():BaseListData
{
return currentRenderer.listData;
}
public function set listData(value:BaseListData):void
{
currentRenderer.listData = value;
}
}
}
If you're using Flex 4 spark components look into the itemRendererFunction,
Here is a good sample from the interwebs.
Unfortunately, Flex 3 components, such as the DataGrid do not support that.
You're a bit vague on what you'd be displaying if the data sent into the itemRenderer was not a Boolean value. But, you can easily modify the visual appearance of a component based on the data change event, including swapping visible properties of a component's children, changing states or change the selectedIndex of a ViewStack. All these things can be done within an itemRenderer w/o issues.
Edit:
Based on the user's additional posting, I'd add that what he is after can be done like this:
public function get data():Object
{
if(this.data is Boolean){
checkBoxRenderer.visible = true;
defaultRenderer.visible = false;
} else {
checkBoxRenderer.visible = false;
defaultRenderer.visible = true;
}
}
I am working on a problem since a week soon, but I still couldn't make it work as expected. I have a DataGrid which has HBox with a CheckBox an a Label as itemRenderer (see Code below). When I tap in to the Cell the standard itemEditor pops up and lets you enter the content of the label. Thats the standard behavior. I works fine except for 2 problems:
If I enter to much text, the horizontal srollbar pops up, and the cell is filled with that scrollbar. As you see I tried to set the horizontalScrollPolicy to off, but that doesnt work at all... I tried to do that for all the different elements, but the failure is still existent.
When I have filled more than one row, there is an other mistake happening. If I tap on a row, the datagrid selects the one below that row. That's only if one line is already selected. If I tap outside the datagrid and then, tap at any row the itemEditor of the right row will show up... Is there anything now wright in the setup of my set data method?
__
package components
{
import mx.containers.HBox;
import mx.controls.CheckBox;
import mx.controls.Label;
public class ChoiceRenderer extends HBox
{
private var correctAnswer:CheckBox;
private var choiceLabel:Label;
public function ChoiceRenderer()
{
super();
paint();
}
private function paint():void{
percentHeight = 100;
percentWidth = 100;
setStyle("horizontalScrollPolicy", "off");
super.setStyle("horizontalScrollPolicy", "off");
correctAnswer = new CheckBox;
correctAnswer.setStyle("horizontalScrollPolicy", "off");
addChild(correctAnswer);
choiceLabel = new Label;
choiceLabel.setStyle("horizontalScrollPolicy", "off");
addChild(choiceLabel);
}
override public function set data(xmldata:Object):void{
if(xmldata.name() == "BackSide"){
var xmlText:Object = xmldata.TextElements.TextElement.(#position == position)[0];
super.data = xmlText;
choiceLabel.text = xmlText.toString();
correctAnswer.selected = xmlText.#correct_answer;
}
}
}
Thanks in advance!
Markus
I am not sure if this is the reason behind your issues, but the standard way of creating children is to override the createChildren method.
Also, you are missing an else statement - you are not calling super.data when if condition fails. That doesn't look good either.
Try:
package components
{
public class ChoiceRenderer extends HBox {
private var correctAnswer:CheckBox;
private var choiceLabel:Label;
public function ChoiceRenderer() {
super();
percentHeight = 100;
percentWidth = 100;
setStyle("horizontalScrollPolicy", "off");
}
override protected function createChildren():void {
super.createChildren();
correctAnswer = new CheckBox();
addChild(correctAnswer);
choiceLabel = new Label();
choiceLabel.setStyle("horizontalScrollPolicy", "off");
addChild(choiceLabel);
}
override public function set data(xmldata:Object):void {
if(xmldata.name() == "BackSide") {
var xmlText:Object = xmldata.TextElements.TextElement.(#position == position)[0];
super.data = xmlText;
choiceLabel.text = xmlText.toString();
correctAnswer.selected = xmlText.#correct_answer;
}
else {
//what if xmldata.name() is not "BackSide"?
//you are not calling super.data in that case
}
}
}
in order to avoid scroll bar you have to let datagrid have variable height
<mx:DataGrid id="dg"
dataProvider="{dp}"
variableRowHeight="true"
creationComplete="dg.height=dg.measureHeightOfItems(0,dp.length)+dg.headerHeight+2"/>