Flex Slider: How to differentiate between programmatic versus user change - apache-flex

I am using HSlider to implement a one-thumb slider control. The thumb position is updated by a timer. However, the user can also update the thumb position by either clicking on a position in the slider track, or by dragging the thumb.
Judging by the Flex documentation, I figured all I needed to do was write a handler for the change event. The docs say:
change Event Event Object Type:
mx.events.SliderEvent property
SliderEvent.type =
mx.events.SliderEvent.CHANGE
Dispatched when the slider changes
value due to mouse or keyboard
interaction.
I wrote my code as follows:
<mx:HSlider thumbPress="handleThumbPress(event)" change="handleUserChange(event)"
showTrackHighlight="true" buttonMode="true" useHandCursor="true" minimum="0"
maximum="{_max}" snapInterval="1" enabled="true" width="100%" id="mySlider"
liveDragging="false" showDataTip="true" verticalCenter="0" horizontalCenter="0"
trackSkin="{SliderTrack}" trackHighlightSkin="{TrackHighlightConfigurable}"
sliderThumbClass="{MySliderThumb}" thumbSkin="#Embed('../imgempty.png')"
dataTipFormatFunction="dataToolTipFormat"/>
What I observe is that my change handler, which is only supposed to be invoked in response to user interaction keeps getting invoked whenever my timer sets the value of the slider. The timer code sets the values quite simply as:
private function updateValue( newValue : Number ) : void
{
mySlider.value = newValue;
}
Am I making an obvious mistake? Is there a better way to distinguish between user versus programmatic change of Flex Slider?
Thanks.
-Raj

Change event is not fired if you change value programmatically. When you change value of thumb beyond minimum or maximum value of slider the event is fired. Event also gets fired if you change Minimum or maximum of slider. Is this happening in your code ?

You could just put a boolean wrapping when you set the value programmatically:
private var inputByUser:Boolean = false;
private function updateValue( newValue : Number ) : void
{
if (!inputByUser)
mySlider.value = newValue;
}
I don't know your exact code so I couldn't solve it explicitly, but something along those lines is what I'd do. You'd set that inputByUser in your change handler, like so:
private function changeHandler() : void
{
inputByUser = true;
updateValue(mySlider.value + 10); // however you're doing it
inputByUser = false;
}
Hope that helps,
Lance

Related

How to detect SizeChanged event after using .HeightRequest

I noticed that after using the .HeightRequest property on a View in Xamarin.Forms that a method I have assigned to the SizeChanged event is no longer called.
The content within the View that had called .HeightRequest is a Label that contains text which can be zoomed in. When zooming in, the text now exceeds the "bounds" of the View's height, and the height is no longer adjusting as it once was.
public ExpandableEntryView(...)
{
InitializeComponent();
// my beautiful code
SizeChanged += ExpandableEntryView_SizeChanged;
UpdateUI();
}
private void ExpandableEntryView_SizeChanged(object sender, EventArgs e)
{
// this is never called once .HeightRequest is assigned a value
}
private void SomeFunction() {
sectionsStackLayout.HeightRequest = 0; // this causes SizeChanged to no longer work
}
I essentially need this View to minimize its height to 0, and then return to its original height when I tell it to. I am also using IsVisible, but the Height property is required for an animation I am trying to do.
Thank you in advance.
Nevermind, I was able to figure it out. The reason why the SizeChanged event is no longer called is because the view is no longer responsible for keeping track of its size after assigning a value to HeightRequest. So, if you would like for your View to return to being able to determine its own size once again, (i.e. if a child element in that View happens to change size/scale) you need to assign the HeightRequest to -1. This will then reenable the SizeChanged event.
Hope this helps somebody!

how to find height of dynamic flex component used as a popup

I've created a custom component with several inline item renderers that I use as a tooltip. The height of the component is unknown, as the data contents of the component are not known until runtime.
However, when displaying the tooltip, occasionally it extends beyond the boundaries of the flash application, thus, I'd like to be able to detect this occurrence and reposition the tip.
The problem is that the height and width of the component are, apparently, not available until after being rendered by the popup manager. (i.e. they are always 0)
But, I do not know any way of finding out when the popup is actually rendered and, therefore, the height/width values available.
I tried adding a resize event listener to the component, but it doesn't appear to work, though I most certainly could be doing something wrong since it seems to me that the resize event only gives you the "oldWidth" and "oldHeight" of the object, which, at first display, would be 0...and useless to me.
Any ideas about how to proceed?
-----Edit-----
I have a base class like this:
public class TTComponent extends Canvas
{
var _parentC:UIComponent;
var popped:Boolean = false;
var timer:Timer;
var _comp:UIComponent;
public function set parentComponent(pC:UIComponent):void
{
_parentC = pc;
_parentC.addEventListener(MouseEvent.MOUSE_OUT, mouseOut);
_parentC.addEventListener(MouseEvent.MOUSE_OVER, mouseOver);
}
public function mouseOver(evt:MouseEvent):void
{
if (_parentC != null)
{
timer = new Timer(150,1);
_comp = this;
timer.addEventListener(TimerEvent.TIMER_COMPLETE, function( tevt:TimerEvent ):void
{
this.move( somex, somey);
if (popped != true)
{
PopUpManager.addPopUp(_comp, parentComponent );
popped = true;
});
timer.start();
}
}
public function mouseOut(evt:MouseEvent ):void
{
if ( timer )
{
timer.stop();
timer = null;
}
//If we popped up, remove the popup
if ( popped )
{
PopUpManager.removePopUp( _comp );
popped = false;
parentC .removeEventListener(MouseEvent.MOUSE_OUT, mouseOut);
parentC .removeEventListener(MouseEvent.MOUSE_OVER, mouseOver);
}
}
}
Then, an extended renderer like this:
<c:TTComponent name="T" 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:c="components.*">
<s:BorderContainer>
...about 30 labels grouped in various manners
...2 lists with inline item renderers
</s:BorderContainer>
</c:TTComponent>
Now, the code is called like this:
var w = new TTComponent();
w.data = data;
win.parentComponent = this;
This will add listeners to the mouse over and mouse out events on the parent, whatever it is, and then show or hide the tooltip accordingly.
------Edit------
Using a portion of what a commenter below suggested, this is the solution I came up with:
Inside the TTComponent class:
import flash.events.Event;
import mx.binding.utils.ChangeWatcher;
private var heightWatcher:ChangeWatcher;
public function set parentComponent
{
...
heightWatcher = ChangeWatcher.watch(this,'height',onSizeChange);
}
public function onSizeChange(evt:Event):void
{
if (this.height != 0)
{
....calculate the new component coords
this.move(newx, newy);
}
}
Note that this additional code doesn't bind to any component variable, it just adds a watcher on the component property.
You could also try binding your width and height. If these are made bindable in your class, flex will automatically adjust your popup's width and height.
When using mxml for your binding, you can just do something like this
<mx:YourComponent height="{HeightOfYourTooltip}" width="{WidthOfYourTooltip}"></mx:YourComponent>
You can also add a eventListener that listens to the change event if you want to reposition you component, like so
<mx:YourComponent height="{HeightOfYourTooltip}" width="{WidthOfYourTooltip}" change="yourComponentResizeHandler()"></mx:YourComponent>
If you are using a programmed approach, you should should use the changewatcher. Below is shown how you can use that.
ChangeWatcher.watch(YourComponent, "width", repositionHandler);
ChangeWatcher.watch(YourComponent, "height", repositionHandler);
If you want to watch for other variables or properties to change, be sure to add the [Bindable]-tag above your variables in your class, like this
[Bindable]
var myVariable:SomeVariable;
I hope this helps.
For displaying toolTip which controls you are using in itemRenderer? Text or Label?
Try to Listen update complete Event of that component. May this Help you. :)
This might be messy, but on the pop up component, you could add an event listener after complete is fired, if the height or width == 0 then you setTimeout() to a function after say 100ms until you get valid data.
Yes, I know it is a bit of a hack, but those will eventually report correctly measured values so it's not going to call that many times.
Just an idea if you are against a deadline or something like this isn't critical. :)

Help with containers

I am using view stack...so when view change like when we move from one page to another hide event is dispatched.So i am saving the information of last page in hide event before i go to next page.but thing is that if i change nothing still change on view hide event is invoked nd call go to backend...i just want do call only if sumthing change in the view..like sum text value...So i have two options
use event listener on each component if sumthing change its make the flag true...nd hide event check, if flag is true send call to backend.
event listener at container level ..if sumthing change in child componenet through bubbling container knows if sum event is dispatched.nd makes the flag true.
I have doubt with container...
Can i use container, and how?
Reason why I can't use container?
What are the pros and cons either way?
I would recommend using a dataProvider with the ability to compare them. For instance, if you are changing things with textinputs, you could basically do something like this:
[Bindable]
private var myDataProvider:Object = new Object();
private function creationCompleteHandler():void {
myDataProvider.updated = false;
myDataProvider.defaultValue = 'default';
myDataProvider.defaultValueTwo = 'default';
}
etc.
Then, in your mxml, you can have something like this:
<mx:TextInput id="myText" text="{myDataProvider.defaultValue}" change="myDataProvider.defaultValue=myText.text; myDataProvider.updated=true;" />
Lastly, in your hide event, you can do the following:
private function hideEventHandler( event:Event ):void {
if( myDataProvider.updated ){
// Call your RemoteServices (or w/e) to update the information
}
}
This way, when anything changes, you can update your dataProvider and have access to the new information each time.
Hope this helps!
I've used an approach similar to your first option in a couple of my past projects. In the change event for each of my form's controls I make a call to a small function that just sets a changesMade flag to true in my model. When the user tries to navigate away from my form, I check the changesMade flag to see if I need to save the info.
Data models are your friend!
If you get in the habit of creating strongly typed data models out of your loaded data, questions like this become very basic.
I always have a key binding set to generate a code snipit similar to this...
private var _foo:String;
public function get foo():String
{
return _foo;
}
public function set foo(value:String):void
{
if(_foo == value)
return;
var oldVal:String = _foo;
_foo = value;
this.invalidateProperty("foo", oldVal, value);
}
If your data used getters/setters like this, it would be very easy to validate a change on the model level, cutting the view out of the process entirely.

FLEX button to trigger event continiously when pressed... not once!

I'm wondering if there's a way to configure a FLEX button so it behaves like a push button...
<mx:Button buttonDown="trace('ankur')" autoRepeat="true"/>
to make a flex button to receive contiuous event happening, use the autoRepeat property with buttonDown event, note the click property will no work,
put this tag in ur application, run it,
i hope , this is wht u were luking for
thanx
Ankur Sharma
If you just need it to toggle (which is how a pushbutton behaves), set its toggle property to true.
<mx:Button label="Button Test" toggle="true"/>
If this is not what you mean, be more specific in your question.
EDIT: Since you refined your question, I would suggest you make a handler for the mouseDown event of the button, which starts a method running, and make a mouseUp handler that stops the method from running. Or better yet, have it set or unset a variable, which is tested in the updateDisplayList() method. Like so:
private var _runButtonStuff:Boolean = false;
override protected function updateDisplayList(width:Number, height:number) : void {
super.updateDisplayList(width,height);
if (_runButtonStuff) {
doStuff();
}
}
private function doStuff() : void {
// do some stuff
}
private function buttonIsDown() : void {
_runButtonStuff = true;
}
private function buttonIsUp() : void {
_runButtonStuff = false;
}
and the button looks like this:
<mx:Button text="Run Something" mouseDown="buttonIsDown()" mouseUp="buttonIsUp()"/>
<mx:Button label="Button Test" toggle="true" click="yourMethodName()"/>
public function yourMethodName():void {
var evt:someEventName = new someEventName(someEventName.TYPE);
dispatchEvent(evt);
}
Now when you listen to this event again, call the same method name again through addEventListner. It will keep on firing the same event for ever.
I have a question, on what scenario you want to apply this. You can either go by EventDispatcher or some ActionScript code which will keep on firing the same method name until the equation is solved.
Depending upon your requirement you can go. I would suggest event driven, as it would be easier to manage.

Problems with itemRollOver and itemRollOut events on List component

I have set the itemRollOver and itemRollOut event listeners on a List component, but whenever I roll the mouse over a list item, both the over and out events of the same list item fire in succession right after each other. My list uses a custom itemRenderer.
Any ideas why this might be? The Adobe documentation doesn't provide much insight into this (not surprisingly...).
In my opinion this is a bug. The ListBase.mouseOverHandler now sets a variable called lastHighlightItemRendererAtIndices when it dispatches an ITEM_ROLL_OVER event, which is then used (together with lastHighlightItemIndices) when dispatching an ITEM_ROLL_OUT event in ListBase.clearHighlight (called by the mouseOutHandler).
The problem is that when you mouse from row-to-row the mouseOverHandler is called first, setting the lastHightlight... variables, and then when the mouseOutHandler gets called subsequently, it uses the lastHighlight... values that were just set with the result that you get consecutive 'roll over' and 'roll out' events for the same renderer.
Frankly I don't know why ListBase.clearHighlight just doesn't use the passed in renderer when dispatching the ITEM_ROLL_OUT event (which is how it used to work in SDK 2) as this is the actual renderer that is being 'rolled out of'.
Are they coming from the same object?
If not you will it is likely so that you will get an itemRollOut from the "item" you just left and a itemRollOver from the new one you entered, depending on their spacing and such these may fire very close to each other.
Make sure you are setting super.data in your item renderer if you are overriding set data().
ListBase listens for MOUSE_OVER and then figures out the item underneath it based on coordinates of mouse and the position of the item renderer. You could check ListEvent.itemRenderer to see which renderer's roll over and roll out are firing and in what order.
Worst case, you could listen for rollOver and rollOut inside your item renderer.
Had the same problem. super.data was already being set, and the item is the same for the rollOut and rollOver event. I ended up opting for anirudhsasikumar's worst case scenario, and listened for rollOver and rollOut inside the item renderer. Seems to work fine.
I was having this same issue. I ended up subclassing the mx.controls.List class and overriding the clearHighlight function. As far as I can tell, the lastHighlightItemIndices variable is only ever read in that function. So doing something like the following fixed this issue:
import mx.core.mx_internal;
use namespace mx_internal;
public class List extends mx.controls.List
{
public function List()
{
super();
}
override mx_internal function clearHighlight( item:IListItemRenderer ):void
{
var uid:String = itemToUID( item.data );
drawItem( UIDToItemRenderer( uid ), isItemSelected( item.data ), false, uid == caretUID );
var pt:Point = itemRendererToIndices( item );
if( pt )
{
var listEvent:ListEvent = new ListEvent( ListEvent.ITEM_ROLL_OUT );
listEvent.columnIndex = item.x;
listEvent.rowIndex = item.y;
listEvent.itemRenderer = item;
dispatchEvent( listEvent );
}
}
}
Then just use this List class instead of the Adobe one and you'll have the behavior you expect. I tested this against Flex SDK 3.2 and it works.
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:controls="com.example.controls.*">
[ other code ... ]
<controls:List itemRollOver="onItemRollOver( event )" itemRollOut="onItemRollOut( event )" />
</mx:Canvas>
Thanks to Gino Basso for the idea in the post above. Hope that helps.
Thanks for the solution. That really solved the problem! Small correction, though:
listEvent.columnIndex = item.x;
listEvent.rowIndex = item.y;
should be
listEvent.columnIndex = pt.x;
listEvent.rowIndex = pt.y;
item.x and y hold the coordinate of the renderer in pixels.

Resources