开发者

AS3: Loading swf as Custom Class that extends MovieClip - getting null object reference

开发者 https://www.devze.com 2023-03-28 13:48 出处:网络
I followed the example from a previous question and I am loading an external swf using a loader and inside the loader event handler I am trying to cast the loader.content as my custom class PanelRefer

I followed the example from a previous question and I am loading an external swf using a loader and inside the loader event handler I am trying to cast the loader.content as my custom class PanelReferenceClip which extends MovieClip

When I publish I receive a this error:

TypeError: Error #1009: Cannot access a property or method of a null object reference.

Just to make sure and test that the swf location was correct and the swf was actually being loaded, I changed the type of the content to as MovieClip and it worked fine.

EDIT: I also wanted to add that these swfs are being stored locally and not being pulled across the internet, multiple networks or servers.

I am not sure if I did something quirky in my class so I am providing the source to my custom class PanelReferenceClip

package com.components 
{
    import com.UI.DevicePanel;
    import flash.display.MovieClip;

    /**
     * ...
     * 
     * used to store the loaded swf inside the panel
     * 
     * *parentPanel is set so that it is able to access it's parent Panel when needing
     * to set parameters.
     */

    public class PanelReferenceClip extends MovieClip 
    {
        private var _parentPanel:DevicePanel;
        private var _bg_mc:MovieClip;
        private var _oldY:Number = 0;
        private var _oldX:Number = 0;
        private var _IsDragging:Boolean = false;

        public function PanelReferenceClip() {
            super();
        }

        /*--------------------------------------------------------------------------
         * GETTERS AND SETTERS
         * -----------------------------------------------------------------------*/

        public function set parentPanel(p:DevicePanel):void {
            _parentPanel = p;
        }

        public function get parentPanel():DevicePanel {
            return _parentPanel;
        }

        public function get bg_mc():MovieClip {
            try {
                return getChildByName("bg_mc") as MovieClip;
            } catch (e:Error) {
                trace("could not find bg_mc in " + _parentPanel.DeviceName + " panel");
            }

            return null;
        }

        public function set oldY(n:Number):void {
            _oldY = n;
        }

        public function get oldY():Number {
            return _oldY;
        }

        public function set oldX(n:Number):void {
            _oldX = n;
        }

        public function get oldX():Number {
            return _oldX;
        }

        public function set IsDragging(b:Boolean):void {
            _IsDragging = b;
        }

        public function get IsDragging():Boolean {
            return _IsDragging;
        }
    }

}

Here is the part of another class that is loading the swfs and then trying to assign them as the class prop _reference which is of type PanelReferenceClip . I am doing this so I am able to get ahold of the swf and it's children because when you import a swf you do not get to set the instance name of the imported swf. So I am assigning it a custom class that extends MovieClip so I can store have some custom properties.

        private function handleLoad(e:Event):void
        {
            e.target.removeEventListener(Event.COMPLETE, handleLoad, false);
            // keep reference to开发者_运维问答 the content
            _reference = e.target.content as PanelReferenceClip;
                    //  ** BREAKS ON THE NEXT LINE **/
            trace(_reference.numChildren);

            // add loader to the display list so we can see the external SWF.
            addChild(e.target.loader);

            // signal the sim engine that the swf has loaded 
            // and to go ahead and wire up the components
            dispatchEvent(new DataEvent(DataEvent.COMPLETE));

            initPanel();
        }

Here is the method used to load the swf. I added in the application context part to try it out but I am still not getting anywhere.

    public function loadSWF(theSWF:String):void
    {
        var url:String = theSWF;
        var urlReq:URLRequest = new URLRequest(url);
        _urlError = url;
        _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoad);
        _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
        _loader.load(urlReq,context);
        _loader.mouseEnabled = false;
    }


This might be caused by converting between types that can't be typecast. Instead of an error occurring when performing someVariable as OtherClass, the variable will just become null. (Example Shown)

I would store a reference to the original movieclip as one of the properties in PanelReferenceClip, and just use that reference when you need to access things like .numChildren

//MovieClips are subclasses of Sprites... this conversion works
var originalMC:MovieClip = new MovieClip();
var sprite:Sprite = originalMC as Sprite; 
trace("sprite: " + sprite); //defined

//Sprites are not subclasses of MovieClips... this conversion wont work
var originalSprite:Sprite = new Sprite();
var mc:MovieClip = originalSprite as MovieClip; 
trace("mc: " + mc); //null

//MovieClips are not subclasses of PanelReferenceClips (quite the opposite)
//this conversion wont work, just as the one before
var panelRef:PanelReferenceClip = originalMC as PanelReferenceClip;
trace("panelRef: " + panelRef); //null


As mentioned by @nox-noctis, the problem may be due to the applicationDomain. I answered a similar question here.

The underlying cause of the problem is that there are actually two different classes defined for PanelReferenceClip. Even though they have they may have same name, and contain the same code, Flash sees them as two different objects.

If you give the classes two different names, this will more clearly convey how the classes appear to Flash. In essence you are telling Flash to cast one object type to a different type, eg:

var foo:Foo = new Foo();
var bar:Bar = foo as Bar(); // where Bar does not inherit Foo

What would work though, is to case the panel as a MovieClip, since the panel does extend MovieClip. That would let you add it to the display list, but won't provide an API to the panel's implementation.

One solution is to specify an interface for the classes. This tells Flash that even though the code may be different, the two classes can be used in the same way. This also has the advantage that you only need to compile the interface into the parent SWF, and not the whole class, reducing the file size of the parent.

An interface for the panel might look like this:

public interface IPanelReference
{
    function set parentPanel(p:IDevicePanel):void;
    function get parentPanel():IDevicePanel;
    function get bg_mc():MovieClip;
    function set oldY(n:Number):void;
    function get oldY():Number;
    function set oldX(n:Number):void;
    function get oldX():Number;
    function set IsDragging(b:Boolean):void;
    function get IsDragging():Boolean;
}

Apply the interface to the panel like this:

public class PanelReferenceClip extends MovieClip implements IPanelReference
{
    ...
}

The parent would then reference the loaded class by the interface and known ancestors:

private function handleLoad(e:Event):void
{
    ...
    _reference = e.target.content as IPanelReference;
    trace((_reference as DisplayObjectContainer).numChildren);
    trace(_reference.oldX);
    addChild(_reference as DisplayObject);
    ....
}


The facts provided are not conclusive, as it is very important where is the loader SWF and from where does it load external SWFs.

My guess: your loaded content resides in a different ApplicationDomain. See LoaderContext.applicationDomain and second parameter to Loader.load() method.

Some details on the mechanics. Apparently, you compile PanelReferenceClip class into two different SWF files. When you load some external SWF file with code in it, VM decides whether to mix or not any incoming declarations with those of loader SWF depending on loader context you provide. If the context you specified allows mixing, then incoming SWF uses the same declarations with coinciding qualified names that parent SWF has. If not -- the classes being initialized are different even if their fully qualified names are identical. In the latter case you will not be able to cast loaded content to what you like.

Try the following tests in handleLoad() method:

trace(getQualifiedClassName(e.target.content)); // This is the incoming declaration 
trace(getQualifiedClassName(PanelReferenceClip)); // This is the parent declaration 

Even if the output is identical, that doesn't necessarily mean, that the classes are the same. If you are using some kind of debugger, you may look at the memory addresses in the following test lines.

var incomingClass:Class = e.target.content["constructor"];
var residentClass:Class = PanelReferenceClip;
trace(incomingClass == residentClass); // toggle breakpoint here and compare memory addresses
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号