I have a VBox that I am populating programatically, After a particular event (dragDrop) I do some calculations, reorder some variables, then re-build the VBox. This all works great, but I want the VBox to scroll back to the correct verticalScrollPosition. I tried even the simplest thing:
myVBox.verticalScrollPosition = 200
But I just can't get it to set the scroll position after it's rebuilt. Any ideas?
Edit: per Franky's response I realized that my dragDrop function was calling the rebuilder function then the position setter function back to back, which means it wasn't done being built when it was trying to set. Now I'm passing the position I want the box set to to the rebuilder function which sets the scroll position at the end of building the VBox and everything works out great.
Try adding this code, I'm at work so I can't verify if it works, hope so:
//Initialize the Vbox
public var myVbox:VBox = new VBox();
//Define the function which rebuilds the Vbox
public function rebuildVbox():VBox{
myVbox.verticalScrollPosition=200;
return myVbox
}
//Define your event.complete function which sets the verticalScrollPosition
//after the drag drop
public function setVerticalScrollPosition():void{
myVbox.addEventListener(Event.COMPLETE,function(event:Event):void{
rebuildVbox()
});
}
Related
I have a simple list and a background refresh protocol.
When the list is scrolled down, the refresh scrolls it back to the top. I want to stop this.
I have tried catching the COLLECTION_CHANGE event and
validateNow(); // try to get the component to reset to the new data
list.ensureIndexIsVisible(previousIndex); // actually, I search for the previous data id in the IList, but that's not important
This fails because the list resets itself after the change (in DataGroup.commitProperties).
I hate to use a Timer, ENTER_FRAME, or callLater(), but I cannot seem to figure out a way.
The only other alternatives I can see is sub-classing the List so it can catch the dataProviderChanged event the DataGroup in the skin is throwing.
Any ideas?
Actually MUCH better solution to this is to extend DataGroup. You need to override this.
All the solutions here create a flicker as the scrollbar gets resetted to 0 and the it's set back to the previous value. That looks wrong. This solution works without any flicker and the best of all, you just change DataGroup to FixedDataGroup in your code and it works, no other changes in code are needed ;).
Enjoy guys.
public class FixedDataGroup extends spark.components.DataGroup
{
private var _dataProviderChanged:Boolean;
private var _lastScrollPosition:Number = 0;
public function FixedDataGroup()
{
super();
}
override public function set dataProvider(value:IList):void
{
if ( this.dataProvider != null && value != this.dataProvider )
{
dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
}
super.dataProvider = value;
if ( value != null )
{
value.addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
}
}
override protected function commitProperties():void
{
var lastScrollPosition:Number = _lastScrollPosition;
super.commitProperties();
if ( _dataProviderChanged )
{
verticalScrollPosition = lastScrollPosition;
}
}
private function onDataProviderChanged(e:CollectionEvent):void
{
_dataProviderChanged = true;
invalidateProperties();
}
override public function set verticalScrollPosition(value:Number):void
{
super.verticalScrollPosition = value;
_lastScrollPosition = value;
}
}
I ll try to explain my approach...If you are still unsure let me know and I ll give you the source code as well.
1) Create a variable to store the current scroll position of the viewport.
2) Add Event listener for Event.CHANGE and MouseEvent.MOUSE_WHEEL on the scroller and update the variable created in step 1 with the current scroll position;
3) Add a event listener on your viewport for FlexEvent.UpdateComplete and set the scroll position to the variable stored.
In a nutshell, what we are doing is to have the scroll position stored in variable every time user interacts with it and when our viewport is updated (due to dataprovider change) we just set the scroll position we have stored previously in the variable.
I have faced this problem before and solved it by using a data proxy pattern with a matcher. Write a matcher for your collection that supports your list by updating only changed objects and by updating only attributes for existing objects. The goal is to avoid creation of new objects when your data source refreshes.
When you have new data for the list (after a refresh), loop through your list of new data objects, copying attributes from these objects into the objects in the collection supporting your list. Typically you will match the objects based on id. Any objects in the new list that did not exist in the old one get added. Your scroll position will normally not change and any selections are usually kept.
Here is an example.
for each(newObject:Object in newArrayValues){
var found:Boolean = false;
for each(oldObject:Object in oldArrayValues){
if(oldObject.id == newObject.id){
found = true;
oldObject.myAttribute = newObject.myAttribute;
oldObject.myAttribute2 = newObject.myAttribute2;
}
}
if(!found){
oldArrayValues.addItem(newObject);
}
}
My solution for this problem was targeting a specific situation, but it has the advantage of being very simple so perhaps you can draw something that fits your needs from it. Since I don't know exactly what issue you're trying to solve I'll give you a description of mine:
I had a List that was progressively loading data from the server. When the user scrolled down and the next batch of items would be added to the dataprovider, the scrollposition would jump back to the start.
The solution for this was as simple as stopping the propagation of the COLLECTION_CHANGE event so that the List wouldn't catch it.
myDataProvider.addEventListener(
CollectionEvent.COLLECTION_CHANGE, preventRefresh
);
private function preventRefresh(event:CollectionEvent):void {
event.stopImmediatePropagation();
}
You have to know that this effectively prevents a redraw of the List component, hence any added items would not be shown. This was not an issue for me since the items would be added at the end of the List (outside the viewport) and when the user would scroll, the List would automatically be redrawn and the new items would be displayed. Perhaps in your situation you can force the redraw if need be.
When all items had been loaded I could then remove the event listener and return to the normal behavior of the List component.
I am trying to drag and drop an object across the SkinnableContainer- am coming across a very strange issue
The drop occurs only at a few places- elsewhere it just shows the "X" sign and on dropping there, reverts to original position. I have used very standard commands... from function 2 to function 3, the call occurs very rarely as seen in trace statements- any guidance on why this happens?
I added the following code to SkinnableContainer: dragEnter="dragEnterHandler(event);" dragDrop="dragDropHandler(event);
(1):
private function mouseMoveHandler(event:MouseEvent):void
{
var dragInitiator:Image = Image(event.currentTarget);
var ds:DragSource = new DragSource();
ds.addData(dragInitiator,"img"); //made change here
DragManager.doDrag(dragInitiator, ds, event);
}
(2):
private function dragEnterHandler(event:DragEvent):void {
if (event.dragSource.hasFormat("img"))
{
trace("came here"); //comes here for each mouse move
DragManager.acceptDragDrop(SkinnableContainer(event.currentTarget));
}
(3):
private function dragDropHandler(event:DragEvent):void {
trace("in drag drop handler"); //doesn't come here for most places
According to the Using Flex 4 reference:
To use a container as a drop target, you must use the backgroundColor property of the container to set a color. Otherwise, the background color of the container is transparent, and the Drag and Drop Manager is unable to detect that the mouse pointer is on a possible drop target.
In the subsequent example, they use an mx container (Canvas), but I checked the AS3 reference, and spark.components.SkinnableContainer does have a style backgroundColor.
I haven't tried this myself, so please confirm whether it is the issue. From your description that only certain parts of the container are registering the dragEnter event, this seems like a consideration that would lead to such effects.
I have a Spark List (spark.components.List) backed by an ArrayCollection for its dataProvider. The List has a vertical scrollbar when there's too many rows to display. What I want is when a new row is added to the List for it to scroll to the bottom to show that new row.
I've tried calling List's ensureIndexIsVisible from a listener on the ArrayCollection. This doesn't work because the List hasn't yet fully rendered the new row. It will either scroll to the second from the last row, or throw the exception:
Error: invalidIndex
at spark.layouts.supportClasses::LinearLayoutVector/start()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\supportClasses\LinearLayoutVector.as:893]
at spark.layouts.supportClasses::LinearLayoutVector/getBounds()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\supportClasses\LinearLayoutVector.as:1117]
at spark.layouts::VerticalLayout/getElementBounds()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\VerticalLayout.as:852]
at spark.layouts.supportClasses::LayoutBase/http://www.adobe.com/2006/flex/mx/internal::getScrollPositionDeltaToElementHelper()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\supportClasses\LayoutBase.as:1367]
at spark.layouts.supportClasses::LayoutBase/getScrollPositionDeltaToElement()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\supportClasses\LayoutBase.as:1348]
at spark.components::List/ensureIndexIsVisible()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\components\List.as:2105]
I've made sure that my listener is added to the ArrayCollection after setting List's dataProvider. The hope is that I'd be calling ensureIndexIsVisible after the List got a chance to process the new row. My guess is that the List doesn't render the row until some redraw event that occurs later (after I've called ensureIndexIsVisible).
I've also tried specifying a VerticalLayout and setting its verticalScrollPosition to an overly large number (like 99999). This has the same problem - it scrolls to the second from the last row.
So, is there a way to scroll to a newly added row in a Spark List? The only I could find on the Internet is this poorly formatted flexcoders thread.
Instead of extending List in order to override updateDisplayList, I added a listener to the FlexEvent.UPDATE_COMPLETE on my List component and it works just fine for me.
My solution was to extend List and override updateDisplayList, which works:
protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
// call ensureIndexIsVisible here
}
See discussion on Adobe's forums.
I found that when ensureIndexIsVisible is run directly after an insert to a list dataProvider it does not accurately pull the position of the last item in the list. This looks to be because the binding has not had a chance to update the list. The fastest solution for me was to run a callLater on ensureIndexIsVisible.
_acGuestBookMessages.addItemAt(obj, _acGuestBookMessages.length);
var arr:Array = [_acGuestBookMessages.length-1];
callLater(lstGuestBookMessages.ensureIndexIsVisible, arr);
Want to scroll to bottom of an updating spark list and
ensureIndexIsVisible is too much undocumented trouble?
I got this to work by dealing with the physical height of dataGroup property on List...
function newMessage(...)
{
messages.addItemAt(msg, messages.length);
messages.refresh();
scrollToBottom();
}
function scrollToBottom():void
{
messagesList.addEventListener("updateComplete", scrollUpdate);
}
private function scrollUpdate(e:Event):void
{
messagesList.removeEventListener("updateComplete", scrollUpdate);
messagesList.dataGroup.verticalScrollPosition = messagesList.dataGroup.contentHeight - messagesList.dataGroup.height;
}
if(itemsList && itemsList.scroller &&
itemsList.scroller.verticalScrollBar &&
itemsList.scroller.verticalScrollBar.visible){
itemsList.scroller.verticalScrollBar.value =
itemsList.scroller.verticalScrollBar.maximum;
}
Was having the same problem, this works for me -
if(chatCollection.length > 0)
{
chatList.validateNow();
chatList.ensureIndexIsVisible(chatCollection.length - 1);
}
I have a component which inherrits Group. I made a property called dataSource:ArrayList. I wish to draw a Line for each of the entries.
When the 'function set dataSource' -method is invoked I do the following (simplified):
var newLine:Line = new Line();
newLine.stroke = new SolidColorStroke();
newLine.xFrom = 0;
newLine.yFrom = 0;
newLine.xTo = 0;
newLine.yTo = height;
this.addElement(newLine);
The line doesn't stretch itself to the very bottom of the parent. I'm guessing I'm messing up life cycle, but I'm not finding flex life cycle particular easy to understand, so I'm not sure how to go about this.
If you don't want to interact with the line as an object on the display list, I'd simply draw it in updateDisplayList() using the Graphics api, and call invalidateDisplayList() from set dataSource()
The "right" way is slightly more verbose ;-)
private var dataSourceValid = true;
public function set dataSource(value:FooData):void {
_dataSource = foo;
dataSourceValid = false;
invalidateProperties();
}
override protected function commitProperties():void {
if (!dataSourceValid)
commitDataSource();
// Do it later in case we've invalidated something
// belonging to Flex while validating our stuff
super.commitProperties();
}
protected function commitDataSource():void {
// Do whatever we need to with our datasource,
// including adding or removing child elements.
// ...
// If we also need to re-draw something, then
// invalidateDisplayList();
dataSourceValid = true;
}
(All code typed in TextMate, so it's probably full of spelling errors and doesn't compile, but you get the idea)
I agree with you, it probably has to do with the component not being properly measured yet when you create your lines. You could try overriding updateDisplayList and setting the height of the lines you created to be the height parameter supplied to the updateDisplayList method. Don't create the lines in updateDisplayList since it can get called multiple times during the component life cycle. Regarding the life cycle in general, here's a link to a chart I've found helpful in the past: http://danorlando.com/?p=122 Hope that helps.
It isn't entirely clear what you're looking to do, but by putting a dataSource property on Group it appears as if you're trying to re-invent DataGroup. Perhaps you should consider just using the latter instead, along with a custom ItemRenderer within which you could draw a line?
I'm a beginner in Flex so there must be more elegant way of doing this.
//move effect
private var m:Move = new Move();
//this function creates labels with some text and starts move effect on them
public function moveText(i:int):void {
var myLabel:Label = new Label();
myLabel.text = "some text";
m.target = myLabel;
...
m.play();
}
Method moveText is called in a loop so I guess that labels don't get "garbage collected".
What I want to do is to remove Labels created in moveText method after play animation ends.
Another way of doing this is maybe creating some kind of "pool" of labels which I would use to move arround text. I don't know how would I return labels in to "pool".
The question is how to do something after effect animation ends?
You can listen to the EffectEnd event.
Check out here
Look at the effectEnd event in the Effect class. You can put a handler in there that does your garbage collection.