What is the best way to determine if a component in Flex/Flash is showing on the user\'s screen? I\'m looking for an analog to Java\'s Component.isShowing() method.
The
You want to check if the component property visible is true and this is for all the parents of your component in the DisplayList, am I correct?
public static function isVisible(c : UIComponent) : Boolean {
if (c == null) return false;
if (c is Application) return c.visible;
return c.visible && isVisible(c.parent);
}
UIComponent.visible is not necessarily valid for children of an object where visible=false. From the docs:
"In either case the children of the object will not emit a show or hide event unless the object has specifically written an implementation to do so."
I wrote a sample application that confirms this to be true. What you can do is walk up the display list checking for visible to be false on a parent. Basically "visible" gives false positives but shouldn't give false negatives. Here is a quick utility I put together:
package
{
import flash.display.DisplayObject;
import mx.core.Application;
public class VisibilityUtils
{
public static function isDisplayObjectVisible(obj : DisplayObject) : Boolean {
if (!obj.visible) return false;
return checkDisplayObjectVisible(obj);
}
private static function checkDisplayObjectVisible(obj : DisplayObject) : Boolean {
if (!obj.parent.visible) return false;
if (obj.parent != null && !(obj.parent is Application))
return checkDisplayObjectVisible(obj.parent);
else
return true;
}
}
}
I haven't done anything more than trivial tests on this but it should get you started.
I was trying to obtain the same in a reusable manner.. I almost found out a way using getObjectsUnderPoint() - this returns the object under a particolar point, z-ordered (even if they are not siblings, e.g. ViewStack, Popups, ecc.).
Basically, I get the topmost display object on under a particular point of the stage, then go up un the display object hierarchy to find the tested object. If I find it, the object is visible (not visible objects in the hierarchy should be already filtered out by the getObjectsUnderPoint call).
The problem here is that you must use a non-transparent point of your object (in my case, I used an offset of 5 pixel due to rounder borders), otherwise it will not be picked up by this function.
Any ideas to improve it?
Cosma
public static function isVisible(object:DisplayObject):Boolean {
var point:Point = object.localToGlobal(new Point(5, 5));
var objects:Array = object.stage.getObjectsUnderPoint(point);
if (objects.length > 0) {
if (isDescendantOf(object, objects[objects.length - 1] as DisplayObject)) {
return true;
}
}
return false;
}
public static function isDescendantOf(parent:DisplayObject, child:DisplayObject):Boolean {
while (child.parent != null) {
if (child.parent === parent) {
return true;
} else {
child = child.parent;
}
}
return false;
}
This is all you really need. The "Application.application" check is futile.
/**
* Returns `true` if this component is actually shown on screen currently. This could be false even with
* "visible" set to `true`, because one or more parents could have "visible" set to `false`.
*/
public static function isShowing (c : DisplayObject) : Boolean {
while (c && c.visible && c.parent) {
c = c.parent;
}
return c.visible;
}
Strange as it seems, now that you mention it, I don't believe there is a simple test to determine whether a component is actually visible onscreen in the sense Component.isShowing() implies.
It's also true the show and hide events don't bubble by default, so if you want to be notified of visibility changes in a descendant of a ViewStack container, you'll need to listen for them explicitly. The implementation details would vary depending on what sort of behavior you were after, but to take the simple example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:VBox>
<mx:HBox>
<mx:Button id="btn1" click="vs.selectedIndex = 0" label="Show 1" />
<mx:Button id="btn2" click="vs.selectedIndex = 1" label="Show 2" />
</mx:HBox>
<mx:ViewStack id="vs" selectedIndex="0">
<mx:Panel id="panel1">
<mx:Label id="label1" text="Label 1" show="trace('showing label 1')" hide="trace('hiding label 1')" visible="{panel1.visible}" />
</mx:Panel>
<mx:Panel id="panel2">
<mx:Label id="label2" text="Label 2" show="trace('showing label 2')" hide="trace('hiding label 2')" visible="{panel2.visible}" />
</mx:Panel>
</mx:ViewStack>
</mx:VBox>
</mx:Application>
... you'll see the show and hide events for each label fire once their visible properties have been bound to their parent panels'. Hopefully that illustrates the point; you can extend it however best suits your application. Good luck!
... or avoiding recursion:
public static function isVisible(obj:DisplayObject):Boolean
{
while (obj && obj.visible && obj !== Application.application)
{
obj = obj.parent;
}
return obj && obj.visible;
}